/* Copyright (c) 2020-2023, Arm Limited and Contributors * * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 the "License"; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "animation.h" #include "scene_graph/node.h" namespace vkb { namespace sg { Animation::Animation(const std::string &name) : Script{name} { } Animation::Animation(const Animation &other) : channels{other.channels} { } void Animation::add_channel(Node &node, const AnimationTarget &target, const AnimationSampler &sampler) { channels.push_back({node, target, sampler}); } void Animation::update(float delta_time) { current_time += delta_time; if (current_time > end_time) { current_time -= end_time; } for (auto &channel : channels) { for (size_t i = 0; i < channel.sampler.inputs.size() - 1; ++i) { if ((current_time >= channel.sampler.inputs[i]) && (current_time <= channel.sampler.inputs[i + 1])) { float time = (current_time - channel.sampler.inputs[i]) / (channel.sampler.inputs[i + 1] - channel.sampler.inputs[i]); auto &transform = channel.node.get_transform(); if (channel.sampler.type == AnimationType::Linear) { switch (channel.target) { case Translation: { transform.set_translation(glm::vec3(glm::mix(channel.sampler.outputs[i], channel.sampler.outputs[i + 1], time))); break; } case Rotation: { glm::quat q1; q1.x = channel.sampler.outputs[i].x; q1.y = channel.sampler.outputs[i].y; q1.z = channel.sampler.outputs[i].z; q1.w = channel.sampler.outputs[i].w; glm::quat q2; q2.x = channel.sampler.outputs[i + 1].x; q2.y = channel.sampler.outputs[i + 1].y; q2.z = channel.sampler.outputs[i + 1].z; q2.w = channel.sampler.outputs[i + 1].w; transform.set_rotation(glm::normalize(glm::slerp(q1, q2, time))); break; } case Scale: { transform.set_scale(glm::vec3(glm::mix(channel.sampler.outputs[i], channel.sampler.outputs[i + 1], time))); } } } else if (channel.sampler.type == AnimationType::Step) { switch (channel.target) { case Translation: { transform.set_translation(glm::vec3(channel.sampler.outputs[i])); break; } case Rotation: { glm::quat q1; q1.x = channel.sampler.outputs[i].x; q1.y = channel.sampler.outputs[i].y; q1.z = channel.sampler.outputs[i].z; q1.w = channel.sampler.outputs[i].w; transform.set_rotation(glm::normalize(q1)); break; } case Scale: { transform.set_scale(glm::vec3(channel.sampler.outputs[i])); } } } else if (channel.sampler.type == AnimationType::CubicSpline) { float delta = channel.sampler.inputs[i + 1] - channel.sampler.inputs[i]; glm::vec4 p0 = channel.sampler.outputs[i * 3 + 1]; // Starting point glm::vec4 p1 = channel.sampler.outputs[(i + 1) * 3 + 1]; // Ending point glm::vec4 m0 = delta * channel.sampler.outputs[i * 3 + 2]; // Delta time * out tangent glm::vec4 m1 = delta * channel.sampler.outputs[(i + 1) * 3 + 0]; // Delta time * in tangent of next point // This equation is taken from the GLTF 2.0 specification Appendix C (https://github.com/KhronosGroup/glTF/tree/main/specification/2.0#appendix-c-spline-interpolation) glm::vec4 result = (2.0f * glm::pow(time, 3.0f) - 3.0f * glm::pow(time, 2.0f) + 1.0f) * p0 + (glm::pow(time, 3.0f) - 2.0f * glm::pow(time, 2.0f) + time) * m0 + (-2.0f * glm::pow(time, 3.0f) + 3.0f * glm::pow(time, 2.0f)) * p1 + (glm::pow(time, 3.0f) - glm::pow(time, 2.0f)) * m1; auto &transform = channel.node.get_transform(); switch (channel.target) { case Translation: { transform.set_translation(glm::vec3(result)); break; } case Rotation: { glm::quat q1; q1.x = result.x; q1.y = result.y; q1.z = result.z; q1.w = result.w; transform.set_rotation(glm::normalize(q1)); break; } case Scale: { transform.set_scale(glm::vec3(result)); } } } } } } } void Animation::update_times(float new_start_time, float new_end_time) { if (new_start_time < start_time) { start_time = new_start_time; } if (new_end_time > end_time) { end_time = new_end_time; } } } // namespace sg } // namespace vkb