diff --git a/CHANGES.md b/CHANGES.md index ceba1d0f8f94..baf54c04ab59 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ - Added basic underground rendering support. When the camera is underground the globe will be rendered as a solid surface and underground entities will not be culled. [#8572](https://github.com/AnalyticalGraphicsInc/cesium/pull/8572) - The `CesiumUnminified` build now includes sourcemaps. [#8572](https://github.com/CesiumGS/cesium/pull/8659) +- Added glTF `STEP` animation interpolation. [#8786](https://github.com/CesiumGS/cesium/pull/8786) - Added the ability to edit CesiumJS shaders on-the-fly using the [SpectorJS](https://spector.babylonjs.com/) Shader Editor. [#8608](https://github.com/CesiumGS/cesium/pull/8608) ##### Fixes :wrench: diff --git a/Source/Scene/ModelAnimationCache.js b/Source/Scene/ModelAnimationCache.js index c78192da6f7e..2262b9c03760 100644 --- a/Source/Scene/ModelAnimationCache.js +++ b/Source/Scene/ModelAnimationCache.js @@ -6,6 +6,7 @@ import LinearSpline from "../Core/LinearSpline.js"; import Matrix4 from "../Core/Matrix4.js"; import Quaternion from "../Core/Quaternion.js"; import QuaternionSpline from "../Core/QuaternionSpline.js"; +import Spline from "../Core/Spline.js"; import WebGLConstants from "../Core/WebGLConstants.js"; import WeightSpline from "../Core/WeightSpline.js"; import getAccessorByteStride from "../ThirdParty/GltfPipeline/getAccessorByteStride.js"; @@ -107,6 +108,34 @@ ConstantSpline.prototype.clampTime = function (time) { return 0.0; }; +function SteppedSpline(backingSpline) { + this._spline = backingSpline; + this._lastTimeIndex = 0; +} +SteppedSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval; +SteppedSpline.prototype.evaluate = function (time, result) { + var i = (this._lastTimeIndex = this.findTimeInterval( + time, + this._lastTimeIndex + )); + var times = this._spline.times; + var steppedTime = time >= times[i + 1] ? times[i + 1] : times[i]; + return this._spline.evaluate(steppedTime, result); +}; +Object.defineProperties(SteppedSpline.prototype, { + times: { + get: function () { + return this._spline.times; + }, + }, +}); +SteppedSpline.prototype.wrapTime = function (time) { + return this._spline.wrapTime(time); +}; +SteppedSpline.prototype.clampTime = function (time) { + return this._spline.clampTime(time); +}; + ModelAnimationCache.getAnimationSpline = function ( model, animationName, @@ -126,7 +155,10 @@ ModelAnimationCache.getAnimationSpline = function ( if (times.length === 1 && controlPoints.length === 1) { spline = new ConstantSpline(controlPoints[0]); - } else if (sampler.interpolation === "LINEAR") { + } else if ( + sampler.interpolation === "LINEAR" || + sampler.interpolation === "STEP" + ) { if (path === "translation" || path === "scale") { spline = new LinearSpline({ times: times, @@ -143,6 +175,10 @@ ModelAnimationCache.getAnimationSpline = function ( weights: controlPoints, }); } + + if (defined(spline) && sampler.interpolation === "STEP") { + spline = new SteppedSpline(spline); + } } if (defined(model.cacheKey)) { diff --git a/Specs/Data/Models/InterpolationTest/InterpolationTest.glb b/Specs/Data/Models/InterpolationTest/InterpolationTest.glb new file mode 100644 index 000000000000..f8907f3251db Binary files /dev/null and b/Specs/Data/Models/InterpolationTest/InterpolationTest.glb differ diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index b3500adbedf3..5fb5e6bd56af 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -111,6 +111,8 @@ describe( var riggedSimpleQuantizedUrl = "./Data/Models/WEB3DQuantizedAttributes/RiggedSimple-Quantized.gltf"; var CesiumManUrl = "./Data/Models/MaterialsCommon/Cesium_Man.gltf"; + var interpolationTestUrl = + "./Data/Models/InterpolationTest/InterpolationTest.glb"; var boomBoxUrl = "./Data/Models/PBR/BoomBox/BoomBox.gltf"; var boomBoxPbrSpecularGlossinessUrl = @@ -2232,6 +2234,106 @@ describe( }); }); + it("animates with STEP interpolation", function () { + return loadModel(interpolationTestUrl).then(function (model) { + var time = JulianDate.fromDate( + new Date("January 1, 2014 12:00:00 UTC") + ); + + model.show = true; + var animations = model.activeAnimations; + var a = animations.add({ + name: "Step Translation", + startTime: time, + }); + + var animatedNode = model.getNode("Cube.006"); + + scene.renderForSpecs(time); + expect(animatedNode.matrix[13]).toEqualEpsilon( + 6.665, + CesiumMath.EPSILON3 + ); + + scene.renderForSpecs( + JulianDate.addSeconds(time, 0.5, new JulianDate()) + ); + expect(animatedNode.matrix[13]).toEqualEpsilon( + 10.0, + CesiumMath.EPSILON14 + ); + + scene.renderForSpecs( + JulianDate.addSeconds(time, 1.0, new JulianDate()) + ); + expect(animatedNode.matrix[13]).toEqualEpsilon( + 6.0, + CesiumMath.EPSILON14 + ); + + scene.renderForSpecs( + JulianDate.addSeconds(time, 2.0, new JulianDate()) + ); + expect(animatedNode.matrix[13]).toEqualEpsilon( + 6.0, + CesiumMath.EPSILON14 + ); + + expect(animations.remove(a)).toEqual(true); + primitives.remove(model); + }); + }); + + it("animates with LINEAR interpolation", function () { + return loadModel(interpolationTestUrl).then(function (model) { + var time = JulianDate.fromDate( + new Date("January 1, 2014 12:00:00 UTC") + ); + + model.show = true; + var animations = model.activeAnimations; + var a = animations.add({ + name: "Linear Translation", + startTime: time, + }); + + var animatedNode = model.getNode("Cube.009"); + + scene.renderForSpecs(time); + expect(animatedNode.matrix[13]).toEqualEpsilon( + 6.621, + CesiumMath.EPSILON3 + ); + + scene.renderForSpecs( + JulianDate.addSeconds(time, 0.5, new JulianDate()) + ); + expect(animatedNode.matrix[13]).toEqualEpsilon( + 9.2, + CesiumMath.EPSILON3 + ); + + scene.renderForSpecs( + JulianDate.addSeconds(time, 1.0, new JulianDate()) + ); + expect(animatedNode.matrix[13]).toEqualEpsilon( + 7.6, + CesiumMath.EPSILON3 + ); + + scene.renderForSpecs( + JulianDate.addSeconds(time, 2.0, new JulianDate()) + ); + expect(animatedNode.matrix[13]).toEqualEpsilon( + 6.0, + CesiumMath.EPSILON14 + ); + + expect(animations.remove(a)).toEqual(true); + primitives.remove(model); + }); + }); + /////////////////////////////////////////////////////////////////////////// it("renders riggedFigure without animation", function () {