1// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "fcurve_p.h"
5#include <private/bezierevaluator_p.h>
6
7#include <QtCore/qjsonarray.h>
8#include <QtCore/qjsonobject.h>
9#include <QtCore/QLatin1String>
10
11QT_BEGIN_NAMESPACE
12
13namespace Qt3DAnimation {
14namespace Animation {
15
16FCurve::FCurve()
17 : m_rangeFinder(&m_localTimes)
18{
19}
20
21float FCurve::evaluateAtTime(float localTime) const
22{
23 return evaluateAtTime(localTime, lowerBound: lowerKeyframeBound(localTime));
24}
25
26
27float FCurve::evaluateAtTime(float localTime, int lowerBound) const
28{
29 // TODO: Implement extrapolation beyond first/last keyframes
30 if (localTime < m_localTimes.first()) {
31 return m_keyframes.first().value;
32 } else if (localTime > m_localTimes.last()) {
33 return m_keyframes.last().value;
34 } else {
35 // Find keyframes that sandwich the requested localTime
36 if (lowerBound < 0) // only one keyframe
37 return m_keyframes.first().value;
38
39 const float t0 = m_localTimes[lowerBound];
40 const float t1 = m_localTimes[lowerBound + 1];
41 const Keyframe &keyframe0(m_keyframes[lowerBound]);
42 const Keyframe &keyframe1(m_keyframes[lowerBound + 1]);
43
44 switch (keyframe0.interpolation) {
45 case QKeyFrame::ConstantInterpolation:
46 return keyframe0.value;
47 case QKeyFrame::LinearInterpolation:
48 if (localTime >= t0 && localTime <= t1 && t1 > t0) {
49 float t = (localTime - t0) / (t1 - t0);
50 return (1 - t) * keyframe0.value + t * keyframe1.value;
51 }
52 break;
53 case QKeyFrame::BezierInterpolation:
54 {
55 BezierEvaluator evaluator(t0, keyframe0, t1, keyframe1);
56 return evaluator.valueForTime(time: localTime);
57 }
58 default:
59 qWarning(msg: "Unknown interpolation type %d", keyframe0.interpolation);
60 break;
61 }
62 }
63
64 return m_keyframes.first().value;
65}
66
67float FCurve::evaluateAtTimeAsSlerp(float localTime, int lowerBound, float halfTheta, float sinHalfTheta, float reverseQ1) const
68{
69 // TODO: Implement extrapolation beyond first/last keyframes
70 if (localTime < m_localTimes.first())
71 return m_keyframes.first().value;
72
73 if (localTime > m_localTimes.last())
74 return m_keyframes.last().value;
75 // Find keyframes that sandwich the requested localTime
76 if (lowerBound < 0) // only one keyframe
77 return m_keyframes.first().value;
78
79 const float t0 = m_localTimes[lowerBound];
80 const float t1 = m_localTimes[lowerBound + 1];
81 const Keyframe &keyframe0(m_keyframes[lowerBound]);
82 const Keyframe &keyframe1(m_keyframes[lowerBound + 1]);
83
84 switch (keyframe0.interpolation) {
85 case QKeyFrame::ConstantInterpolation:
86 return keyframe0.value;
87 case QKeyFrame::LinearInterpolation:
88 if (localTime >= t0 && localTime <= t1 && t1 > t0) {
89 const auto t = (localTime - t0) / (t1 - t0);
90
91 const auto A = std::sin(x: (1.0f-t) * halfTheta) / sinHalfTheta;
92 const auto B = std::sin(x: t * halfTheta) / sinHalfTheta;
93 return A * keyframe0.value + reverseQ1 * B * keyframe1.value;
94 }
95 break;
96 case QKeyFrame::BezierInterpolation:
97 // TODO implement a proper slerp bezier interpolation
98 BezierEvaluator evaluator(t0, keyframe0, t1, keyframe1);
99 return evaluator.valueForTime(time: localTime);
100 }
101
102 return m_keyframes.first().value;
103}
104
105int FCurve::lowerKeyframeBound(float localTime) const
106{
107 if (localTime < m_localTimes.first())
108 return 0;
109 if (localTime > m_localTimes.last())
110 return 0;
111 return m_rangeFinder.findLowerBound(x: localTime);
112}
113
114float FCurve::startTime() const
115{
116 if (!m_localTimes.isEmpty())
117 return m_localTimes.first();
118 return 0.0f;
119}
120
121float FCurve::endTime() const
122{
123 if (!m_localTimes.isEmpty())
124 return m_localTimes.last();
125 return 0.0f;
126}
127
128void FCurve::appendKeyframe(float localTime, const Keyframe &keyframe)
129{
130 m_localTimes.append(t: localTime);
131 m_keyframes.append(t: keyframe);
132}
133
134void FCurve::read(const QJsonObject &json)
135{
136 clearKeyframes();
137
138 const QJsonArray keyframeArray = json[QLatin1String("keyFrames")].toArray();
139 const qsizetype keyframeCount = keyframeArray.size();
140
141 for (int i = 0; i < keyframeCount; ++i) {
142 const QJsonObject keyframeData = keyframeArray.at(i).toObject();
143
144 // Extract the keyframe local time and value
145 const QJsonArray keyframeCoords = keyframeData[QLatin1String("coords")].toArray();
146 float localTime = keyframeCoords.at(i: 0).toDouble();
147
148 Keyframe keyframe;
149 keyframe.value = keyframeCoords.at(i: 1).toDouble();
150
151 if (keyframeData.contains(key: QLatin1String("leftHandle"))) {
152 keyframe.interpolation = QKeyFrame::BezierInterpolation;
153
154 const QJsonArray leftHandle = keyframeData[QLatin1String("leftHandle")].toArray();
155 keyframe.leftControlPoint[0] = leftHandle.at(i: 0).toDouble();
156 keyframe.leftControlPoint[1] = leftHandle.at(i: 1).toDouble();
157
158 const QJsonArray rightHandle = keyframeData[QLatin1String("rightHandle")].toArray();
159 keyframe.rightControlPoint[0] = rightHandle.at(i: 0).toDouble();
160 keyframe.rightControlPoint[1] = rightHandle.at(i: 1).toDouble();
161 } else {
162 keyframe.interpolation = QKeyFrame::LinearInterpolation;
163 }
164
165 appendKeyframe(localTime, keyframe);
166 }
167
168 // TODO: Ensure beziers have no loops or cusps by scaling the control points
169 // back so they do not interset.
170}
171
172void FCurve::setFromQChannelComponent(const QChannelComponent &qcc)
173{
174 clearKeyframes();
175
176 for (const auto &frontendKeyFrame : qcc) {
177 // Extract the keyframe local time and value
178 const float localTime = frontendKeyFrame.coordinates()[0];
179
180 Keyframe keyFrame;
181 keyFrame.interpolation = frontendKeyFrame.interpolationType();
182 keyFrame.value = frontendKeyFrame.coordinates()[1];
183 keyFrame.leftControlPoint = frontendKeyFrame.leftControlPoint();
184 keyFrame.rightControlPoint = frontendKeyFrame.rightControlPoint();
185 appendKeyframe(localTime, keyframe: keyFrame);
186 }
187
188 // TODO: Ensure beziers have no loops or cusps by scaling the control points
189 // back so they do not interset.
190}
191
192void ChannelComponent::read(const QJsonObject &json)
193{
194 name = json[QLatin1String("channelComponentName")].toString();
195 fcurve.read(json);
196}
197
198void ChannelComponent::setFromQChannelComponent(const QChannelComponent &qcc)
199{
200 name = qcc.name();
201 fcurve.setFromQChannelComponent(qcc);
202}
203
204void Channel::read(const QJsonObject &json)
205{
206 name = json[QLatin1String("channelName")].toString();
207 const auto jointIndexValue = json[QLatin1String("jointIndex")];
208 if (!jointIndexValue.isUndefined())
209 jointIndex = jointIndexValue.toInt();
210 const QJsonArray channelComponentsArray = json[QLatin1String("channelComponents")].toArray();
211 const qsizetype channelCount = channelComponentsArray.size();
212 channelComponents.resize(size: channelCount);
213
214 for (qsizetype i = 0; i < channelCount; ++i) {
215 const QJsonObject channel = channelComponentsArray.at(i).toObject();
216 channelComponents[i].read(json: channel);
217 }
218}
219
220void Channel::setFromQChannel(const QChannel &qch)
221{
222 name = qch.name();
223 jointIndex = qch.jointIndex();
224 channelComponents.resize(size: qch.channelComponentCount());
225 qsizetype i = 0;
226 for (const auto &frontendChannelComponent : qch)
227 channelComponents[i++].setFromQChannelComponent(frontendChannelComponent);
228}
229
230} // namespace Animation
231} // namespace Qt3DAnimation
232
233QT_END_NAMESPACE
234

source code of qt3d/src/animation/backend/fcurve.cpp