Commit 0864007f authored by wjf's avatar wjf

l

parent e3424da3
......@@ -16168,7 +16168,7 @@ var D3Renderer = (function (_super) {
var mesh = this.meshes[i];
var mat = mesh.material;
var geo = mesh.geometry;
var shader = getCusShader_1.getCusShader(this.renderer, mat, this.lightsConfig);
var shader = getCusShader_1.getCusShader(this.renderer, mat, this.lightsConfig, mesh);
if (curShader !== shader) {
curShader = shader;
this.renderer.bindShader(curShader, false);
......@@ -16249,7 +16249,7 @@ var D3Renderer = (function (_super) {
this.renderer.bindVao(vao);
if (uploadBufferDatas)
uploadBufferDatas.forEach(function (e) {
e.buffer.upload(e.data, 0, true);
e.buffer.upload(e.data, 0, false);
});
}
if (mat.side == BaseMaterial_1.RenderSideType.DoubleSide) {
......@@ -17445,6 +17445,437 @@ var Scene3D = (function (_super) {
exports.Scene3D = Scene3D;
/***/ }),
/***/ "./src/3d/animation/AnimationClip3D.ts":
/*!*********************************************!*\
!*** ./src/3d/animation/AnimationClip3D.ts ***!
\*********************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var events_1 = __webpack_require__(/*! ../../2d/events */ "./src/2d/events/index.ts");
var AnimationClip3D = (function (_super) {
__extends(AnimationClip3D, _super);
function AnimationClip3D(tracks) {
var _this = _super.call(this) || this;
_this._isPlaying = true;
_this._isFront = true;
_this.lastTime = null;
_this.curTime = 0;
_this._instanceType = "AnimationClip";
_this.tracks = tracks;
_this.calculateTotalTime();
return _this;
}
Object.defineProperty(AnimationClip3D.prototype, "totalTime", {
get: function () {
return this._totalTime;
},
enumerable: true,
configurable: true
});
AnimationClip3D.prototype.calculateTotalTime = function () {
var tracks = this.tracks, duration = 0;
for (var i = 0, n = tracks.length; i !== n; ++i) {
var track = this.tracks[i];
duration = Math.max(duration, track.times[track.times.length - 1]);
}
this._totalTime = duration;
return this;
};
Object.defineProperty(AnimationClip3D.prototype, "isPlaying", {
get: function () {
return this._isPlaying;
},
enumerable: true,
configurable: true
});
Object.defineProperty(AnimationClip3D.prototype, "isFront", {
get: function () {
return this._isFront;
},
enumerable: true,
configurable: true
});
AnimationClip3D.prototype.update = function (time) {
if (!this.tracks || !this.tracks.length)
return;
if (this.curTime !== this.lastTime) {
this.rectify();
return;
}
if (time <= 0 || !this._isPlaying)
return;
if (this._isFront) {
this.curTime += time;
if (this.curTime > this._totalTime)
this.curTime = 0;
}
else {
this.curTime -= time;
if (this.curTime < 0)
this.curTime = this._totalTime;
}
if (this.curTime !== this.lastTime) {
this.rectify();
this.dispatchEvent(events_1.Event.ENTER_FRAME);
}
};
AnimationClip3D.prototype.play = function (isFront) {
if (isFront === void 0) { isFront = true; }
this._isFront = isFront;
this._isPlaying = true;
};
AnimationClip3D.prototype.stop = function () {
this._isPlaying = false;
};
AnimationClip3D.prototype.gotoAndPlay = function (time, isFront) {
if (isFront === void 0) { isFront = true; }
var s = this;
s._isFront = isFront;
s._isPlaying = true;
if (time > s._totalTime)
time = s._totalTime;
if (time < 1)
time = 0;
s.curTime = time;
};
AnimationClip3D.prototype.gotoAndStop = function (time) {
this._isPlaying = false;
if (time > this.totalTime)
time = this.totalTime;
if (time < 0)
time = 0;
this.curTime = time;
;
};
AnimationClip3D.prototype.startAniRange = function (beginTime, endTime, loops, callback) {
var _this = this;
if (beginTime === void 0) { beginTime = 0; }
if (endTime === void 0) { endTime = this._totalTime; }
if (loops === void 0) { loops = 1; }
if (beginTime < 0)
beginTime = 0;
if (beginTime > this._totalTime)
beginTime = this._totalTime;
if (endTime < 0)
endTime = 0;
if (endTime > this._totalTime)
endTime = this._totalTime;
if (beginTime === endTime) {
this.gotoAndStop(beginTime);
return;
}
else if (beginTime < endTime) {
this._isFront = true;
}
else {
this._isFront = false;
var temp = beginTime;
beginTime = endTime;
endTime = temp;
}
if (this.startAniRangeFun)
this.removeEventListener(events_1.Event.ENTER_FRAME, this.startAniRangeFun, this);
this.curTime = beginTime;
this._isPlaying = true;
var loopCount = loops ? (loops + 0.5 >> 0) : Infinity;
this.addEventListener(events_1.Event.ENTER_FRAME, this.startAniRangeFun = function (e) {
var s = e.target;
if (s._isFront) {
if (s.curTime >= endTime) {
loopCount--;
if (loopCount <= 0) {
s.gotoAndStop(endTime);
s.removeEventListener(events_1.Event.ENTER_FRAME, _this.startAniRangeFun, _this);
_this.startAniRangeFun = null;
callback && callback();
}
else {
s.gotoAndPlay(beginTime);
}
}
}
else {
if (s.curTime <= beginTime) {
loopCount--;
if (loopCount <= 0) {
s.gotoAndStop(beginTime);
s.removeEventListener(events_1.Event.ENTER_FRAME, _this.startAniRangeFun, _this);
_this.startAniRangeFun = null;
callback && callback();
}
else {
s.gotoAndPlay(endTime, false);
}
}
}
}, this);
};
AnimationClip3D.prototype.rectify = function () {
if (!this.tracks || !this.tracks.length)
return;
for (var i = 0; i < this.tracks.length; i++) {
this.tracks[i].setValue(this.curTime);
}
this.lastTime = this.curTime;
};
return AnimationClip3D;
}(events_1.EventDispatcher));
exports.AnimationClip3D = AnimationClip3D;
/***/ }),
/***/ "./src/3d/animation/AnimationTrack3D.ts":
/*!**********************************************!*\
!*** ./src/3d/animation/AnimationTrack3D.ts ***!
\**********************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var HashObject_1 = __webpack_require__(/*! ../../2d/HashObject */ "./src/2d/HashObject.ts");
var Quaternion_1 = __webpack_require__(/*! ../math/Quaternion */ "./src/3d/math/Quaternion.ts");
var AnimationType;
(function (AnimationType) {
AnimationType["quaternion"] = "quaternion";
AnimationType["scale"] = "scale";
AnimationType["morphTargetInfluences"] = "morphTargetInfluences";
AnimationType["position"] = "position";
})(AnimationType = exports.AnimationType || (exports.AnimationType = {}));
var AnimationTrack3D = (function (_super) {
__extends(AnimationTrack3D, _super);
function AnimationTrack3D(node, animationType, times, values) {
var _this = _super.call(this) || this;
_this.node = node;
_this.animationType = animationType;
_this.times = times;
_this.values = values;
_this._instanceType = "AnimationTrack";
return _this;
}
AnimationTrack3D.prototype.setValue = function (time) {
var value = this.getValue(time);
switch (this.animationType) {
case AnimationType.position:
case AnimationType.scale:
case AnimationType.quaternion:
this.node[this.animationType].fromArray(value);
break;
case AnimationType.morphTargetInfluences:
if (this.node.instanceType == "Mesh3D") {
var arr = this.node["morphTargetInfluences"];
for (var i = 0; i < arr.length; i++) {
arr[i] = value[i] || 0;
}
}
break;
}
};
AnimationTrack3D.prototype.getValue = function (time) {
var size = this.getValueSize();
if (time <= 0)
return this.values.slice(0, size);
if (time >= this.times[this.times.length - 1])
return this.values.slice(-size);
var t1 = this.findPreIndex(time);
var t0 = t1 - 1;
if (t0 < 0)
return this.values.slice(0, size);
var scale = (time - this.times[t0]) / (this.times[t1] - this.times[t0]);
var v0 = this.values.slice(t0 * size, t0 * size + size);
var v1 = this.values.slice(t1 * size, t1 * size + size);
if (this.animationType == AnimationType.quaternion) {
return quaternionLinearInterpolate(scale, v0, v1);
}
return linearInterpolate(scale, v0, v1);
};
AnimationTrack3D.prototype.getValueSize = function () {
return this.values.length / this.times.length;
};
AnimationTrack3D.prototype.findPreIndex = function (time) {
var lastIndex = 0;
for (var i = 0; i < this.times.length; i++) {
if (this.times[i] > time) {
lastIndex = i;
break;
}
}
return lastIndex;
};
AnimationTrack3D.prototype.destroy = function () {
this.node = null;
this.values = null;
this.times = null;
};
return AnimationTrack3D;
}(HashObject_1.HashObject));
exports.AnimationTrack3D = AnimationTrack3D;
function linearInterpolate(s, v0, v1) {
var result = [];
for (var i = 0; i !== v0.length; ++i) {
result[i] = v0[i] * (1 - s) + v1[i] * s;
}
return result;
}
function quaternionLinearInterpolate(s, v0, v1) {
var result = [];
Quaternion_1.Quaternion.slerpFlat(result, 0, v0, 0, v1, 0, s);
return result;
}
/***/ }),
/***/ "./src/3d/animation/index.ts":
/*!***********************************!*\
!*** ./src/3d/animation/index.ts ***!
\***********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(__webpack_require__(/*! ./AnimationClip3D */ "./src/3d/animation/AnimationClip3D.ts"));
__export(__webpack_require__(/*! ./AnimationTrack3D */ "./src/3d/animation/AnimationTrack3D.ts"));
/***/ }),
/***/ "./src/3d/animation/utils.ts":
/*!***********************************!*\
!*** ./src/3d/animation/utils.ts ***!
\***********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function isTypedArray(object) {
return ArrayBuffer.isView(object) &&
!(object instanceof DataView);
}
exports.isTypedArray = isTypedArray;
function arraySlice(array, from, to) {
if (isTypedArray(array)) {
return new array.constructor(array.subarray(from, to !== undefined ? to : array.length));
}
return array.slice(from, to);
}
exports.arraySlice = arraySlice;
function convertArray(array, type, forceClone) {
if (!array ||
!forceClone && array.constructor === type)
return array;
if (typeof type.BYTES_PER_ELEMENT === 'number') {
return new type(array);
}
return Array.prototype.slice.call(array);
}
exports.convertArray = convertArray;
function flattenJSON(jsonKeys, times, values, valuePropertyName) {
var i = 1, key = jsonKeys[0];
while (key !== undefined && key[valuePropertyName] === undefined) {
key = jsonKeys[i++];
}
if (key === undefined)
return;
var value = key[valuePropertyName];
if (value === undefined)
return;
if (Array.isArray(value)) {
do {
value = key[valuePropertyName];
if (value !== undefined) {
times.push(key.time);
values.push.apply(values, value);
}
key = jsonKeys[i++];
} while (key !== undefined);
}
else if (value.toArray !== undefined) {
do {
value = key[valuePropertyName];
if (value !== undefined) {
times.push(key.time);
value.toArray(values, values.length);
}
key = jsonKeys[i++];
} while (key !== undefined);
}
else {
do {
value = key[valuePropertyName];
if (value !== undefined) {
times.push(key.time);
values.push(value);
}
key = jsonKeys[i++];
} while (key !== undefined);
}
}
exports.flattenJSON = flattenJSON;
function getKeyframeOrder(times) {
function compareTime(i, j) {
return times[i] - times[j];
}
var n = times.length;
var result = new Array(n);
for (var i = 0; i !== n; ++i)
result[i] = i;
result.sort(compareTime);
return result;
}
exports.getKeyframeOrder = getKeyframeOrder;
function sortedArray(values, stride, order) {
var nValues = values.length;
var result = new values.constructor(nValues);
for (var i = 0, dstOffset = 0; dstOffset !== nValues; ++i) {
var srcOffset = order[i] * stride;
for (var j = 0; j !== stride; ++j) {
result[dstOffset++] = values[srcOffset + j];
}
}
return result;
}
exports.sortedArray = sortedArray;
exports.InterpolateDiscrete = 2300;
exports.InterpolateLinear = 2301;
exports.InterpolateSmooth = 2302;
/***/ }),
/***/ "./src/3d/cameras/Camera.ts":
......@@ -17894,7 +18325,10 @@ var Mesh3D_1 = __webpack_require__(/*! ../Mesh3D */ "./src/3d/Mesh3D.ts");
var PerspectiveCamera_1 = __webpack_require__(/*! ../cameras/PerspectiveCamera */ "./src/3d/cameras/PerspectiveCamera.ts");
var const_1 = __webpack_require__(/*! ../../2d/const */ "./src/2d/const.ts");
var __1 = __webpack_require__(/*! .. */ "./src/3d/index.ts");
var THREE = {};
var utils_2 = __webpack_require__(/*! ../animation/utils */ "./src/3d/animation/utils.ts");
var utils_3 = __webpack_require__(/*! ../animation/utils */ "./src/3d/animation/utils.ts");
var AnimationTrack3D_1 = __webpack_require__(/*! ../animation/AnimationTrack3D */ "./src/3d/animation/AnimationTrack3D.ts");
var AnimationClip3D_1 = __webpack_require__(/*! ../animation/AnimationClip3D */ "./src/3d/animation/AnimationClip3D.ts");
var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF';
var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
var BINARY_EXTENSION_HEADER_LENGTH = 12;
......@@ -17967,9 +18401,9 @@ var PATH_PROPERTIES = {
position: "position"
};
var INTERPOLATION = {
CUBICSPLINE: THREE.InterpolateSmooth,
LINEAR: THREE.InterpolateLinear,
STEP: THREE.InterpolateDiscrete
CUBICSPLINE: utils_3.InterpolateSmooth,
LINEAR: utils_3.InterpolateLinear,
STEP: utils_3.InterpolateDiscrete
};
var GLTFLoader = (function (_super) {
__extends(GLTFLoader, _super);
......@@ -18258,7 +18692,6 @@ var GLTFParser = (function () {
console.warn('THREE.GLTFLoader: Joint "%s" could not be found.', jointId);
}
}
mesh.bind(new THREE.Skeleton(bones, boneInverses), mesh.matrixWorld);
}
}
parentObject.addChild(node);
......@@ -18417,25 +18850,22 @@ var GLTFParser = (function () {
return Promise.resolve(this.extensions[EXTENSIONS.KHR_BINARY_GLTF].body);
}
return new Promise(function (resolve, reject) {
if (/^data:.*,.*$/i.test(bufferDef.uri)) {
var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
var dataUriRegexResult = bufferDef.uri.match(dataUriRegex);
if (dataUriRegexResult) {
var mimeType = dataUriRegexResult[1];
var isBase64 = !!dataUriRegexResult[2];
var data = dataUriRegexResult[3];
data = decodeURIComponent(data);
if (isBase64)
data = atob(data);
var view = new Uint8Array(data.length);
for (var i = 0; i < data.length; i++) {
view[i] = data.charCodeAt(i);
}
resolve(view.buffer);
return;
var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
var dataUriRegexResult = bufferDef.uri.match(dataUriRegex);
if (dataUriRegexResult) {
var mimeType = dataUriRegexResult[1];
var isBase64 = !!dataUriRegexResult[2];
var data = dataUriRegexResult[3];
data = decodeURIComponent(data);
if (isBase64)
data = atob(data);
var view = new Uint8Array(data.length);
for (var i = 0; i < data.length; i++) {
view[i] = data.charCodeAt(i);
}
resolve(view.buffer);
return;
}
;
reject("阿三的");
});
};
......@@ -18682,7 +19112,8 @@ var GLTFParser = (function () {
var material = mesh.material;
var useMorphTargets = !!geometry._morphPositions;
var useMorphNormals = useMorphTargets && !!geometry._morphNormals;
material.morphTargets = useMorphTargets;
if (useMorphTargets)
material.morphTargets = useMorphTargets;
material.morphNormals = useMorphNormals;
}
if (meshes.length === 1) {
......@@ -18743,29 +19174,29 @@ var GLTFParser = (function () {
var output = animationDef.parameters !== undefined ? animationDef.parameters[sampler.output] : sampler.output;
var inputAccessor = dependencies.accessors[input];
var outputAccessor = dependencies.accessors[output];
console.log(2222222);
console.log(inputAccessor);
console.log(outputAccessor);
var node = dependencies.nodes[name];
console.log("node:", node);
if (node) {
node.updateLocalMatrix();
var TypedKeyframeTrack;
switch (PATH_PROPERTIES[target.path]) {
case PATH_PROPERTIES.weights:
console.log("weights");
TypedKeyframeTrack = AnimationTrack3D_1.AnimationType.morphTargetInfluences;
break;
case PATH_PROPERTIES.rotation:
console.log("rotation");
TypedKeyframeTrack = AnimationTrack3D_1.AnimationType.quaternion;
break;
case PATH_PROPERTIES.position:
TypedKeyframeTrack = AnimationTrack3D_1.AnimationType.position;
break;
case PATH_PROPERTIES.scale:
TypedKeyframeTrack = AnimationTrack3D_1.AnimationType.scale;
break;
default:
console.log("position,scale");
TypedKeyframeTrack = AnimationTrack3D_1.AnimationType.position;
break;
}
var targetName = node.name ? node.name : node.instanceId;
var interpolation = sampler.interpolation !== undefined ? 1 : 0;
var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[sampler.interpolation] : utils_3.InterpolateLinear;
var targetNames = [];
if (PATH_PROPERTIES[target.path] === PATH_PROPERTIES.weights) {
traverse(node, function (object) {
......@@ -18779,12 +19210,13 @@ var GLTFParser = (function () {
}
console.log("targetNames:", targetNames);
for (var j = 0, jl = targetNames.length; j < jl; j++) {
var track = new AnimationTrack3D_1.AnimationTrack3D(node, TypedKeyframeTrack, utils_2.arraySlice(inputAccessor.array, 0), utils_2.arraySlice(outputAccessor.array, 0));
tracks.push(track);
}
}
}
}
var name = animationDef.name !== undefined ? animationDef.name : 'animation_' + animationIndex;
return 1;
return new AnimationClip3D_1.AnimationClip3D(tracks);
});
};
;
......@@ -19164,16 +19596,6 @@ function sanitizeNodeName(name) {
return name.replace(/\s/g, '_').replace(reservedRe, '');
}
;
function arraySlice(array, from, to) {
if (isTypedArray(array)) {
return new array.constructor(array.subarray(from, to !== undefined ? to : array.length));
}
return array.slice(from, to);
}
function isTypedArray(object) {
return ArrayBuffer.isView(object) &&
!(object instanceof DataView);
}
function traverse(object, callback) {
callback(object);
var children = object.children;
......@@ -19363,6 +19785,7 @@ __export(__webpack_require__(/*! ./helpers */ "./src/3d/helpers/index.ts"));
__export(__webpack_require__(/*! ./cameras/PerspectiveCamera */ "./src/3d/cameras/PerspectiveCamera.ts"));
__export(__webpack_require__(/*! ./D3Renderer */ "./src/3d/D3Renderer.ts"));
__export(__webpack_require__(/*! ./gltf */ "./src/3d/gltf/index.ts"));
__export(__webpack_require__(/*! ./animation */ "./src/3d/animation/index.ts"));
/***/ }),
......@@ -22121,12 +22544,12 @@ var __extends = (this && this.__extends) || (function () {
Object.defineProperty(exports, "__esModule", { value: true });
var glCore_1 = __webpack_require__(/*! ../../glCore */ "./src/glCore/index.ts");
var shader_1 = __webpack_require__(/*! ../../glCore/shader */ "./src/glCore/shader/index.ts");
function getCusShader(render, material, lights) {
function getCusShader(render, material, lights, mesh) {
var parameters = {
pointLightsNum: lights.pointLights.length,
dirLightsNum: lights.directionalLights.length,
morphTargets: material.morphTargets,
morphNormals: material.morphNormals,
morphTargets: mesh.morphTargetInfluences && material.morphTargets,
morphNormals: mesh.morphTargetInfluences && material.morphTargets && material.morphNormals,
lightAffect: material._lightAffect,
};
var shaderKey = getShaderKey(parameters);
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -86,7 +86,7 @@ export class D3Renderer extends ObjectRenderer {
let mat: BaseMaterial = mesh.material;
let geo = mesh.geometry;
let shader = getCusShader(this.renderer, mat, this.lightsConfig);
let shader = getCusShader(this.renderer, mat, this.lightsConfig,mesh);
if (curShader !== shader) {//同一着色器只需要传一次相机参数和光照参数(因为都是场景全局)
curShader = shader;
//先绑定着色器,project不设置
......@@ -226,7 +226,7 @@ export class D3Renderer extends ObjectRenderer {
//变形的数据可能传入的会改变,所以需要upload,还要重新修改数据,有removeAttribute的情况,TODO
//还得在这里做数据的变形,不绑定
if (uploadBufferDatas) uploadBufferDatas.forEach((e) => {
e.buffer.upload(e.data, 0, true);
e.buffer.upload(e.data, 0, false/*true*/);//貌似里面有修改的Attribute队列,所以还是绑定下buffer
})
}
//根据材质切换渲染面
......
import { WrapAroundEnding, ZeroCurvatureEnding, ZeroSlopeEnding, LoopPingPong, LoopOnce, LoopRepeat } from '../constants.js';
/**
*
* Action provided by AnimationMixer for scheduling clip playback on specific
* objects.
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*
*/
function AnimationAction( mixer, clip, localRoot ) {
this._mixer = mixer;
this._clip = clip;
this._localRoot = localRoot || null;
var tracks = clip.tracks,
nTracks = tracks.length,
interpolants = new Array( nTracks );
var interpolantSettings = {
endingStart: ZeroCurvatureEnding,
endingEnd: ZeroCurvatureEnding
};
for ( var i = 0; i !== nTracks; ++ i ) {
var interpolant = tracks[ i ].createInterpolant( null );
interpolants[ i ] = interpolant;
interpolant.settings = interpolantSettings;
}
this._interpolantSettings = interpolantSettings;
this._interpolants = interpolants; // bound by the mixer
// inside: PropertyMixer (managed by the mixer)
this._propertyBindings = new Array( nTracks );
this._cacheIndex = null; // for the memory manager
this._byClipCacheIndex = null; // for the memory manager
this._timeScaleInterpolant = null;
this._weightInterpolant = null;
this.loop = LoopRepeat;
this._loopCount = - 1;
// global mixer time when the action is to be started
// it's set back to 'null' upon start of the action
this._startTime = null;
// scaled local time of the action
// gets clamped or wrapped to 0..clip.duration according to loop
this.time = 0;
this.timeScale = 1;
this._effectiveTimeScale = 1;
this.weight = 1;
this._effectiveWeight = 1;
this.repetitions = Infinity; // no. of repetitions when looping
this.paused = false; // true -> zero effective time scale
this.enabled = true; // false -> zero effective weight
this.clampWhenFinished = false; // keep feeding the last frame?
this.zeroSlopeAtStart = true; // for smooth interpolation w/o separate
this.zeroSlopeAtEnd = true; // clips for start, loop and end
}
Object.assign( AnimationAction.prototype, {
// State & Scheduling
play: function () {
this._mixer._activateAction( this );
return this;
},
stop: function () {
this._mixer._deactivateAction( this );
return this.reset();
},
reset: function () {
this.paused = false;
this.enabled = true;
this.time = 0; // restart clip
this._loopCount = - 1; // forget previous loops
this._startTime = null; // forget scheduling
return this.stopFading().stopWarping();
},
isRunning: function () {
return this.enabled && ! this.paused && this.timeScale !== 0 &&
this._startTime === null && this._mixer._isActiveAction( this );
},
// return true when play has been called
isScheduled: function () {
return this._mixer._isActiveAction( this );
},
startAt: function ( time ) {
this._startTime = time;
return this;
},
setLoop: function ( mode, repetitions ) {
this.loop = mode;
this.repetitions = repetitions;
return this;
},
// Weight
// set the weight stopping any scheduled fading
// although .enabled = false yields an effective weight of zero, this
// method does *not* change .enabled, because it would be confusing
setEffectiveWeight: function ( weight ) {
this.weight = weight;
// note: same logic as when updated at runtime
this._effectiveWeight = this.enabled ? weight : 0;
return this.stopFading();
},
// return the weight considering fading and .enabled
getEffectiveWeight: function () {
return this._effectiveWeight;
},
fadeIn: function ( duration ) {
return this._scheduleFading( duration, 0, 1 );
},
fadeOut: function ( duration ) {
return this._scheduleFading( duration, 1, 0 );
},
crossFadeFrom: function ( fadeOutAction, duration, warp ) {
fadeOutAction.fadeOut( duration );
this.fadeIn( duration );
if ( warp ) {
var fadeInDuration = this._clip.duration,
fadeOutDuration = fadeOutAction._clip.duration,
startEndRatio = fadeOutDuration / fadeInDuration,
endStartRatio = fadeInDuration / fadeOutDuration;
fadeOutAction.warp( 1.0, startEndRatio, duration );
this.warp( endStartRatio, 1.0, duration );
}
return this;
},
crossFadeTo: function ( fadeInAction, duration, warp ) {
return fadeInAction.crossFadeFrom( this, duration, warp );
},
stopFading: function () {
var weightInterpolant = this._weightInterpolant;
if ( weightInterpolant !== null ) {
this._weightInterpolant = null;
this._mixer._takeBackControlInterpolant( weightInterpolant );
}
return this;
},
// Time Scale Control
// set the time scale stopping any scheduled warping
// although .paused = true yields an effective time scale of zero, this
// method does *not* change .paused, because it would be confusing
setEffectiveTimeScale: function ( timeScale ) {
this.timeScale = timeScale;
this._effectiveTimeScale = this.paused ? 0 : timeScale;
return this.stopWarping();
},
// return the time scale considering warping and .paused
getEffectiveTimeScale: function () {
return this._effectiveTimeScale;
},
setDuration: function ( duration ) {
this.timeScale = this._clip.duration / duration;
return this.stopWarping();
},
syncWith: function ( action ) {
this.time = action.time;
this.timeScale = action.timeScale;
return this.stopWarping();
},
halt: function ( duration ) {
return this.warp( this._effectiveTimeScale, 0, duration );
},
warp: function ( startTimeScale, endTimeScale, duration ) {
var mixer = this._mixer, now = mixer.time,
interpolant = this._timeScaleInterpolant,
timeScale = this.timeScale;
if ( interpolant === null ) {
interpolant = mixer._lendControlInterpolant();
this._timeScaleInterpolant = interpolant;
}
var times = interpolant.parameterPositions,
values = interpolant.sampleValues;
times[ 0 ] = now;
times[ 1 ] = now + duration;
values[ 0 ] = startTimeScale / timeScale;
values[ 1 ] = endTimeScale / timeScale;
return this;
},
stopWarping: function () {
var timeScaleInterpolant = this._timeScaleInterpolant;
if ( timeScaleInterpolant !== null ) {
this._timeScaleInterpolant = null;
this._mixer._takeBackControlInterpolant( timeScaleInterpolant );
}
return this;
},
// Object Accessors
getMixer: function () {
return this._mixer;
},
getClip: function () {
return this._clip;
},
getRoot: function () {
return this._localRoot || this._mixer._root;
},
// Interna
_update: function ( time, deltaTime, timeDirection, accuIndex ) {
// called by the mixer
if ( ! this.enabled ) {
// call ._updateWeight() to update ._effectiveWeight
this._updateWeight( time );
return;
}
var startTime = this._startTime;
if ( startTime !== null ) {
// check for scheduled start of action
var timeRunning = ( time - startTime ) * timeDirection;
if ( timeRunning < 0 || timeDirection === 0 ) {
return; // yet to come / don't decide when delta = 0
}
// start
this._startTime = null; // unschedule
deltaTime = timeDirection * timeRunning;
}
// apply time scale and advance time
deltaTime *= this._updateTimeScale( time );
var clipTime = this._updateTime( deltaTime );
// note: _updateTime may disable the action resulting in
// an effective weight of 0
var weight = this._updateWeight( time );
if ( weight > 0 ) {
var interpolants = this._interpolants;
var propertyMixers = this._propertyBindings;
for ( var j = 0, m = interpolants.length; j !== m; ++ j ) {
interpolants[ j ].evaluate( clipTime );
propertyMixers[ j ].accumulate( accuIndex, weight );
}
}
},
_updateWeight: function ( time ) {
var weight = 0;
if ( this.enabled ) {
weight = this.weight;
var interpolant = this._weightInterpolant;
if ( interpolant !== null ) {
var interpolantValue = interpolant.evaluate( time )[ 0 ];
weight *= interpolantValue;
if ( time > interpolant.parameterPositions[ 1 ] ) {
this.stopFading();
if ( interpolantValue === 0 ) {
// faded out, disable
this.enabled = false;
}
}
}
}
this._effectiveWeight = weight;
return weight;
},
_updateTimeScale: function ( time ) {
var timeScale = 0;
if ( ! this.paused ) {
timeScale = this.timeScale;
var interpolant = this._timeScaleInterpolant;
if ( interpolant !== null ) {
var interpolantValue = interpolant.evaluate( time )[ 0 ];
timeScale *= interpolantValue;
if ( time > interpolant.parameterPositions[ 1 ] ) {
this.stopWarping();
if ( timeScale === 0 ) {
// motion has halted, pause
this.paused = true;
} else {
// warp done - apply final time scale
this.timeScale = timeScale;
}
}
}
}
this._effectiveTimeScale = timeScale;
return timeScale;
},
_updateTime: function ( deltaTime ) {
var time = this.time + deltaTime;
var duration = this._clip.duration;
var loop = this.loop;
var loopCount = this._loopCount;
var pingPong = ( loop === LoopPingPong );
if ( deltaTime === 0 ) {
if ( loopCount === - 1 ) return time;
return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time;
}
if ( loop === LoopOnce ) {
if ( loopCount === - 1 ) {
// just started
this._loopCount = 0;
this._setEndings( true, true, false );
}
handle_stop: {
if ( time >= duration ) {
time = duration;
} else if ( time < 0 ) {
time = 0;
} else break handle_stop;
if ( this.clampWhenFinished ) this.paused = true;
else this.enabled = false;
this._mixer.dispatchEvent( {
type: 'finished', action: this,
direction: deltaTime < 0 ? - 1 : 1
} );
}
} else { // repetitive Repeat or PingPong
if ( loopCount === - 1 ) {
// just started
if ( deltaTime >= 0 ) {
loopCount = 0;
this._setEndings( true, this.repetitions === 0, pingPong );
} else {
// when looping in reverse direction, the initial
// transition through zero counts as a repetition,
// so leave loopCount at -1
this._setEndings( this.repetitions === 0, true, pingPong );
}
}
if ( time >= duration || time < 0 ) {
// wrap around
var loopDelta = Math.floor( time / duration ); // signed
time -= duration * loopDelta;
loopCount += Math.abs( loopDelta );
var pending = this.repetitions - loopCount;
if ( pending <= 0 ) {
// have to stop (switch state, clamp time, fire event)
if ( this.clampWhenFinished ) this.paused = true;
else this.enabled = false;
time = deltaTime > 0 ? duration : 0;
this._mixer.dispatchEvent( {
type: 'finished', action: this,
direction: deltaTime > 0 ? 1 : - 1
} );
} else {
// keep running
if ( pending === 1 ) {
// entering the last round
var atStart = deltaTime < 0;
this._setEndings( atStart, ! atStart, pingPong );
} else {
this._setEndings( false, false, pingPong );
}
this._loopCount = loopCount;
this._mixer.dispatchEvent( {
type: 'loop', action: this, loopDelta: loopDelta
} );
}
}
if ( pingPong && ( loopCount & 1 ) === 1 ) {
// invert time for the "pong round"
this.time = time;
return duration - time;
}
}
this.time = time;
return time;
},
_setEndings: function ( atStart, atEnd, pingPong ) {
var settings = this._interpolantSettings;
if ( pingPong ) {
settings.endingStart = ZeroSlopeEnding;
settings.endingEnd = ZeroSlopeEnding;
} else {
// assuming for LoopOnce atStart == atEnd == true
if ( atStart ) {
settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;
} else {
settings.endingStart = WrapAroundEnding;
}
if ( atEnd ) {
settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;
} else {
settings.endingEnd = WrapAroundEnding;
}
}
},
_scheduleFading: function ( duration, weightNow, weightThen ) {
var mixer = this._mixer, now = mixer.time,
interpolant = this._weightInterpolant;
if ( interpolant === null ) {
interpolant = mixer._lendControlInterpolant();
this._weightInterpolant = interpolant;
}
var times = interpolant.parameterPositions,
values = interpolant.sampleValues;
times[ 0 ] = now; values[ 0 ] = weightNow;
times[ 1 ] = now + duration; values[ 1 ] = weightThen;
return this;
}
} );
export { AnimationAction };
import { AnimationUtils } from './AnimationUtils.js';
import { KeyframeTrack } from './KeyframeTrack.js';
import { BooleanKeyframeTrack } from './tracks/BooleanKeyframeTrack.js';
import { ColorKeyframeTrack } from './tracks/ColorKeyframeTrack.js';
import { NumberKeyframeTrack } from './tracks/NumberKeyframeTrack.js';
import { QuaternionKeyframeTrack } from './tracks/QuaternionKeyframeTrack.js';
import { StringKeyframeTrack } from './tracks/StringKeyframeTrack.js';
import { VectorKeyframeTrack } from './tracks/VectorKeyframeTrack.js';
import { _Math } from '../math/Math.js';
/**
*
* Reusable set of Tracks that represent an animation.
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
*/
function AnimationClip( name, duration, tracks ) {
this.name = name;
this.tracks = tracks;
this.duration = ( duration !== undefined ) ? duration : - 1;
this.uuid = _Math.generateUUID();
// this means it should figure out its duration by scanning the tracks
if ( this.duration < 0 ) {
this.resetDuration();
}
}
function getTrackTypeForValueTypeName( typeName ) {
switch ( typeName.toLowerCase() ) {
case 'scalar':
case 'double':
case 'float':
case 'number':
case 'integer':
return NumberKeyframeTrack;
case 'vector':
case 'vector2':
case 'vector3':
case 'vector4':
return VectorKeyframeTrack;
case 'color':
return ColorKeyframeTrack;
case 'quaternion':
return QuaternionKeyframeTrack;
case 'bool':
case 'boolean':
return BooleanKeyframeTrack;
case 'string':
return StringKeyframeTrack;
}
throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );
}
function parseKeyframeTrack( json ) {
if ( json.type === undefined ) {
throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );
}
var trackType = getTrackTypeForValueTypeName( json.type );
if ( json.times === undefined ) {
var times = [], values = [];
AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
json.times = times;
json.values = values;
}
// derived classes can define a static parse method
if ( trackType.parse !== undefined ) {
return trackType.parse( json );
} else {
// by default, we assume a constructor compatible with the base
return new trackType( json.name, json.times, json.values, json.interpolation );
}
}
Object.assign( AnimationClip, {
parse: function ( json ) {
var tracks = [],
jsonTracks = json.tracks,
frameTime = 1.0 / ( json.fps || 1.0 );
for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) {
tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) );
}
return new AnimationClip( json.name, json.duration, tracks );
},
toJSON: function ( clip ) {
var tracks = [],
clipTracks = clip.tracks;
var json = {
'name': clip.name,
'duration': clip.duration,
'tracks': tracks,
'uuid': clip.uuid
};
for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) {
tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );
}
return json;
},
CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) {
var numMorphTargets = morphTargetSequence.length;
var tracks = [];
for ( var i = 0; i < numMorphTargets; i ++ ) {
var times = [];
var values = [];
times.push(
( i + numMorphTargets - 1 ) % numMorphTargets,
i,
( i + 1 ) % numMorphTargets );
values.push( 0, 1, 0 );
var order = AnimationUtils.getKeyframeOrder( times );
times = AnimationUtils.sortedArray( times, 1, order );
values = AnimationUtils.sortedArray( values, 1, order );
// if there is a key at the first frame, duplicate it as the
// last frame as well for perfect loop.
if ( ! noLoop && times[ 0 ] === 0 ) {
times.push( numMorphTargets );
values.push( values[ 0 ] );
}
tracks.push(
new NumberKeyframeTrack(
'.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
times, values
).scale( 1.0 / fps ) );
}
return new AnimationClip( name, - 1, tracks );
},
findByName: function ( objectOrClipArray, name ) {
var clipArray = objectOrClipArray;
if ( ! Array.isArray( objectOrClipArray ) ) {
var o = objectOrClipArray;
clipArray = o.geometry && o.geometry.animations || o.animations;
}
for ( var i = 0; i < clipArray.length; i ++ ) {
if ( clipArray[ i ].name === name ) {
return clipArray[ i ];
}
}
return null;
},
CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) {
var animationToMorphTargets = {};
// tested with https://regex101.com/ on trick sequences
// such flamingo_flyA_003, flamingo_run1_003, crdeath0059
var pattern = /^([\w-]*?)([\d]+)$/;
// sort morph target names into animation groups based
// patterns like Walk_001, Walk_002, Run_001, Run_002
for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {
var morphTarget = morphTargets[ i ];
var parts = morphTarget.name.match( pattern );
if ( parts && parts.length > 1 ) {
var name = parts[ 1 ];
var animationMorphTargets = animationToMorphTargets[ name ];
if ( ! animationMorphTargets ) {
animationToMorphTargets[ name ] = animationMorphTargets = [];
}
animationMorphTargets.push( morphTarget );
}
}
var clips = [];
for ( var name in animationToMorphTargets ) {
clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );
}
return clips;
},
// parse the animation.hierarchy format
parseAnimation: function ( animation, bones ) {
if ( ! animation ) {
console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );
return null;
}
var addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {
// only return track if there are actually keys.
if ( animationKeys.length !== 0 ) {
var times = [];
var values = [];
AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
// empty keys are filtered out, so check again
if ( times.length !== 0 ) {
destTracks.push( new trackType( trackName, times, values ) );
}
}
};
var tracks = [];
var clipName = animation.name || 'default';
// automatic length determination in AnimationClip.
var duration = animation.length || - 1;
var fps = animation.fps || 30;
var hierarchyTracks = animation.hierarchy || [];
for ( var h = 0; h < hierarchyTracks.length; h ++ ) {
var animationKeys = hierarchyTracks[ h ].keys;
// skip empty tracks
if ( ! animationKeys || animationKeys.length === 0 ) continue;
// process morph targets
if ( animationKeys[ 0 ].morphTargets ) {
// figure out all morph targets used in this track
var morphTargetNames = {};
for ( var k = 0; k < animationKeys.length; k ++ ) {
if ( animationKeys[ k ].morphTargets ) {
for ( var m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {
morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;
}
}
}
// create a track for each morph target with all zero
// morphTargetInfluences except for the keys in which
// the morphTarget is named.
for ( var morphTargetName in morphTargetNames ) {
var times = [];
var values = [];
for ( var m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {
var animationKey = animationKeys[ k ];
times.push( animationKey.time );
values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );
}
tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
}
duration = morphTargetNames.length * ( fps || 1.0 );
} else {
// ...assume skeletal animation
var boneName = '.bones[' + bones[ h ].name + ']';
addNonemptyTrack(
VectorKeyframeTrack, boneName + '.position',
animationKeys, 'pos', tracks );
addNonemptyTrack(
QuaternionKeyframeTrack, boneName + '.quaternion',
animationKeys, 'rot', tracks );
addNonemptyTrack(
VectorKeyframeTrack, boneName + '.scale',
animationKeys, 'scl', tracks );
}
}
if ( tracks.length === 0 ) {
return null;
}
var clip = new AnimationClip( clipName, duration, tracks );
return clip;
}
} );
Object.assign( AnimationClip.prototype, {
resetDuration: function () {
var tracks = this.tracks, duration = 0;
for ( var i = 0, n = tracks.length; i !== n; ++ i ) {
var track = this.tracks[ i ];
duration = Math.max( duration, track.times[ track.times.length - 1 ] );
}
this.duration = duration;
return this;
},
trim: function () {
for ( var i = 0; i < this.tracks.length; i ++ ) {
this.tracks[ i ].trim( 0, this.duration );
}
return this;
},
validate: function () {
var valid = true;
for ( var i = 0; i < this.tracks.length; i ++ ) {
valid = valid && this.tracks[ i ].validate();
}
return valid;
},
optimize: function () {
for ( var i = 0; i < this.tracks.length; i ++ ) {
this.tracks[ i ].optimize();
}
return this;
}
} );
export { AnimationClip };
import { AnimationTrack3D } from "./AnimationTrack3D";
import { HashObject } from "../../2d/HashObject";
import { EventDispatcher, Event } from "../../2d/events";
/**
* 不需要挂到节点上
* 暂时没有帧,只有时间,以后再说
* 用update更新
*/
export class AnimationClip3D extends EventDispatcher {
/**
* 所有的动画数据
*/
private tracks: AnimationTrack3D[];
private _totalTime: number;
get totalTime() {
return this._totalTime;
}
constructor(tracks: AnimationTrack3D[]) {
super()
this._instanceType = "AnimationClip"
this.tracks = tracks;
this.calculateTotalTime();
}
private calculateTotalTime() {
var tracks = this.tracks, duration = 0;
for (var i = 0, n = tracks.length; i !== n; ++i) {
var track = this.tracks[i];
duration = Math.max(duration, track.times[track.times.length - 1]);
}
this._totalTime = duration;
return this;
}
private _isPlaying: boolean = true;
public get isPlaying(): boolean {
return this._isPlaying;
}
private _isFront: boolean = true;
get isFront(): boolean {
return this._isFront;
}
/**
* 上个时间,用来确定是否更新
*/
private lastTime: number = null;
/**
* 记录时间
*/
private curTime: number = 0;
/**
* 需要挂在循环里的方法,传时间间隔
* @param time
*/
update(time: number) {
if (!this.tracks || !this.tracks.length) return;
//时间不等,直接播放
if (this.curTime !== this.lastTime) {
this.rectify()
return;
}
//时间没有,或没在播放
if (time <= 0 || !this._isPlaying) return;
if (this._isFront) {
this.curTime += time;
if (this.curTime > this._totalTime) this.curTime = 0;
} else {
this.curTime -= time;
if (this.curTime < 0) this.curTime = this._totalTime;
}
if (this.curTime !== this.lastTime) {
//矫正
this.rectify();
//派发事件
this.dispatchEvent(Event.ENTER_FRAME);
}
}
/**
* 从当前时间点播放
* @param isFront 默认true正向
*/
play(isFront: boolean = true) {
this._isFront = isFront;
this._isPlaying = true;
}
/**
* 停在当前时间
*/
stop() {
this._isPlaying = false;
}
/**
*
* @param time
* @param isFront 默认true,正向播放
*/
public gotoAndPlay(time: number, isFront: boolean = true): void {
let s = this;
s._isFront = isFront;
s._isPlaying = true;
if (time > s._totalTime) time = s._totalTime;
if (time < 1) time = 0;
s.curTime = time;
}
/**
* 停在指定时间
* @param time
*/
public gotoAndStop(time: number): void {
this._isPlaying = false;
if (time > this.totalTime) time = this.totalTime;
if (time < 0) time = 0;
this.curTime = time;;
}
private startAniRangeFun;
public startAniRange(
beginTime: number = 0,
endTime: number = this._totalTime,
loops: number = 1,
callback?: Function
) {
if (beginTime < 0) beginTime = 0;
if (beginTime > this._totalTime) beginTime = this._totalTime;
if (endTime < 0) endTime = 0;
if (endTime > this._totalTime) endTime = this._totalTime;
if (beginTime === endTime) {
this.gotoAndStop(beginTime)
//如果相等
return
} else if (beginTime < endTime) {
this._isFront = true;
} else {
this._isFront = false;
var temp = beginTime;
beginTime = endTime;
endTime = temp;
}
//移除原先的绑定吧
if (this.startAniRangeFun) this.removeEventListener(Event.ENTER_FRAME, this.startAniRangeFun, this)
this.curTime = beginTime;
this._isPlaying = true;
let loopCount = loops ? (loops + 0.5 >> 0) : Infinity;
this.addEventListener(Event.ENTER_FRAME, this.startAniRangeFun = (e: Event) => {
let s: AnimationClip3D = e.target;
if (s._isFront) {
if (s.curTime >= endTime) {
loopCount--;
if (loopCount <= 0) {
s.gotoAndStop(endTime);
s.removeEventListener(Event.ENTER_FRAME, this.startAniRangeFun, this);
this.startAniRangeFun = null;
callback && callback();
} else {
s.gotoAndPlay(beginTime);
}
}
} else {
if (s.curTime <= beginTime) {
loopCount--
if (loopCount <= 0) {
s.gotoAndStop(beginTime);
s.removeEventListener(Event.ENTER_FRAME, this.startAniRangeFun, this);
this.startAniRangeFun = null;
callback && callback();
} else {
s.gotoAndPlay(endTime, false);
}
}
}
}, this)
}
/**
* 矫正
*/
private rectify() {
if (!this.tracks || !this.tracks.length) return;
for (var i = 0; i < this.tracks.length; i++) {
this.tracks[i].setValue(this.curTime)
}
//设置相等
this.lastTime = this.curTime;
}
}
\ No newline at end of file
import { AnimationAction } from './AnimationAction.js';
import { EventDispatcher } from '../core/EventDispatcher.js';
import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
import { PropertyBinding } from './PropertyBinding.js';
import { PropertyMixer } from './PropertyMixer.js';
import { AnimationClip } from './AnimationClip.js';
/**
*
* Player for AnimationClips.
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function AnimationMixer( root ) {
this._root = root;
this._initMemoryManager();
this._accuIndex = 0;
this.time = 0;
this.timeScale = 1.0;
}
AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
constructor: AnimationMixer,
_bindAction: function ( action, prototypeAction ) {
var root = action._localRoot || this._root,
tracks = action._clip.tracks,
nTracks = tracks.length,
bindings = action._propertyBindings,
interpolants = action._interpolants,
rootUuid = root.uuid,
bindingsByRoot = this._bindingsByRootAndName,
bindingsByName = bindingsByRoot[ rootUuid ];
if ( bindingsByName === undefined ) {
bindingsByName = {};
bindingsByRoot[ rootUuid ] = bindingsByName;
}
for ( var i = 0; i !== nTracks; ++ i ) {
var track = tracks[ i ],
trackName = track.name,
binding = bindingsByName[ trackName ];
if ( binding !== undefined ) {
bindings[ i ] = binding;
} else {
binding = bindings[ i ];
if ( binding !== undefined ) {
// existing binding, make sure the cache knows
if ( binding._cacheIndex === null ) {
++ binding.referenceCount;
this._addInactiveBinding( binding, rootUuid, trackName );
}
continue;
}
var path = prototypeAction && prototypeAction.
_propertyBindings[ i ].binding.parsedPath;
binding = new PropertyMixer(
PropertyBinding.create( root, trackName, path ),
track.ValueTypeName, track.getValueSize() );
++ binding.referenceCount;
this._addInactiveBinding( binding, rootUuid, trackName );
bindings[ i ] = binding;
}
interpolants[ i ].resultBuffer = binding.buffer;
}
},
_activateAction: function ( action ) {
if ( ! this._isActiveAction( action ) ) {
if ( action._cacheIndex === null ) {
// this action has been forgotten by the cache, but the user
// appears to be still using it -> rebind
var rootUuid = ( action._localRoot || this._root ).uuid,
clipUuid = action._clip.uuid,
actionsForClip = this._actionsByClip[ clipUuid ];
this._bindAction( action,
actionsForClip && actionsForClip.knownActions[ 0 ] );
this._addInactiveAction( action, clipUuid, rootUuid );
}
var bindings = action._propertyBindings;
// increment reference counts / sort out state
for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
var binding = bindings[ i ];
if ( binding.useCount ++ === 0 ) {
this._lendBinding( binding );
binding.saveOriginalState();
}
}
this._lendAction( action );
}
},
_deactivateAction: function ( action ) {
if ( this._isActiveAction( action ) ) {
var bindings = action._propertyBindings;
// decrement reference counts / sort out state
for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
var binding = bindings[ i ];
if ( -- binding.useCount === 0 ) {
binding.restoreOriginalState();
this._takeBackBinding( binding );
}
}
this._takeBackAction( action );
}
},
// Memory manager
_initMemoryManager: function () {
this._actions = []; // 'nActiveActions' followed by inactive ones
this._nActiveActions = 0;
this._actionsByClip = {};
// inside:
// {
// knownActions: Array< AnimationAction > - used as prototypes
// actionByRoot: AnimationAction - lookup
// }
this._bindings = []; // 'nActiveBindings' followed by inactive ones
this._nActiveBindings = 0;
this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
this._controlInterpolants = []; // same game as above
this._nActiveControlInterpolants = 0;
var scope = this;
this.stats = {
actions: {
get total() {
return scope._actions.length;
},
get inUse() {
return scope._nActiveActions;
}
},
bindings: {
get total() {
return scope._bindings.length;
},
get inUse() {
return scope._nActiveBindings;
}
},
controlInterpolants: {
get total() {
return scope._controlInterpolants.length;
},
get inUse() {
return scope._nActiveControlInterpolants;
}
}
};
},
// Memory management for AnimationAction objects
_isActiveAction: function ( action ) {
var index = action._cacheIndex;
return index !== null && index < this._nActiveActions;
},
_addInactiveAction: function ( action, clipUuid, rootUuid ) {
var actions = this._actions,
actionsByClip = this._actionsByClip,
actionsForClip = actionsByClip[ clipUuid ];
if ( actionsForClip === undefined ) {
actionsForClip = {
knownActions: [ action ],
actionByRoot: {}
};
action._byClipCacheIndex = 0;
actionsByClip[ clipUuid ] = actionsForClip;
} else {
var knownActions = actionsForClip.knownActions;
action._byClipCacheIndex = knownActions.length;
knownActions.push( action );
}
action._cacheIndex = actions.length;
actions.push( action );
actionsForClip.actionByRoot[ rootUuid ] = action;
},
_removeInactiveAction: function ( action ) {
var actions = this._actions,
lastInactiveAction = actions[ actions.length - 1 ],
cacheIndex = action._cacheIndex;
lastInactiveAction._cacheIndex = cacheIndex;
actions[ cacheIndex ] = lastInactiveAction;
actions.pop();
action._cacheIndex = null;
var clipUuid = action._clip.uuid,
actionsByClip = this._actionsByClip,
actionsForClip = actionsByClip[ clipUuid ],
knownActionsForClip = actionsForClip.knownActions,
lastKnownAction =
knownActionsForClip[ knownActionsForClip.length - 1 ],
byClipCacheIndex = action._byClipCacheIndex;
lastKnownAction._byClipCacheIndex = byClipCacheIndex;
knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
knownActionsForClip.pop();
action._byClipCacheIndex = null;
var actionByRoot = actionsForClip.actionByRoot,
rootUuid = ( action._localRoot || this._root ).uuid;
delete actionByRoot[ rootUuid ];
if ( knownActionsForClip.length === 0 ) {
delete actionsByClip[ clipUuid ];
}
this._removeInactiveBindingsForAction( action );
},
_removeInactiveBindingsForAction: function ( action ) {
var bindings = action._propertyBindings;
for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
var binding = bindings[ i ];
if ( -- binding.referenceCount === 0 ) {
this._removeInactiveBinding( binding );
}
}
},
_lendAction: function ( action ) {
// [ active actions | inactive actions ]
// [ active actions >| inactive actions ]
// s a
// <-swap->
// a s
var actions = this._actions,
prevIndex = action._cacheIndex,
lastActiveIndex = this._nActiveActions ++,
firstInactiveAction = actions[ lastActiveIndex ];
action._cacheIndex = lastActiveIndex;
actions[ lastActiveIndex ] = action;
firstInactiveAction._cacheIndex = prevIndex;
actions[ prevIndex ] = firstInactiveAction;
},
_takeBackAction: function ( action ) {
// [ active actions | inactive actions ]
// [ active actions |< inactive actions ]
// a s
// <-swap->
// s a
var actions = this._actions,
prevIndex = action._cacheIndex,
firstInactiveIndex = -- this._nActiveActions,
lastActiveAction = actions[ firstInactiveIndex ];
action._cacheIndex = firstInactiveIndex;
actions[ firstInactiveIndex ] = action;
lastActiveAction._cacheIndex = prevIndex;
actions[ prevIndex ] = lastActiveAction;
},
// Memory management for PropertyMixer objects
_addInactiveBinding: function ( binding, rootUuid, trackName ) {
var bindingsByRoot = this._bindingsByRootAndName,
bindingByName = bindingsByRoot[ rootUuid ],
bindings = this._bindings;
if ( bindingByName === undefined ) {
bindingByName = {};
bindingsByRoot[ rootUuid ] = bindingByName;
}
bindingByName[ trackName ] = binding;
binding._cacheIndex = bindings.length;
bindings.push( binding );
},
_removeInactiveBinding: function ( binding ) {
var bindings = this._bindings,
propBinding = binding.binding,
rootUuid = propBinding.rootNode.uuid,
trackName = propBinding.path,
bindingsByRoot = this._bindingsByRootAndName,
bindingByName = bindingsByRoot[ rootUuid ],
lastInactiveBinding = bindings[ bindings.length - 1 ],
cacheIndex = binding._cacheIndex;
lastInactiveBinding._cacheIndex = cacheIndex;
bindings[ cacheIndex ] = lastInactiveBinding;
bindings.pop();
delete bindingByName[ trackName ];
remove_empty_map: {
for ( var _ in bindingByName ) break remove_empty_map; // eslint-disable-line no-unused-vars
delete bindingsByRoot[ rootUuid ];
}
},
_lendBinding: function ( binding ) {
var bindings = this._bindings,
prevIndex = binding._cacheIndex,
lastActiveIndex = this._nActiveBindings ++,
firstInactiveBinding = bindings[ lastActiveIndex ];
binding._cacheIndex = lastActiveIndex;
bindings[ lastActiveIndex ] = binding;
firstInactiveBinding._cacheIndex = prevIndex;
bindings[ prevIndex ] = firstInactiveBinding;
},
_takeBackBinding: function ( binding ) {
var bindings = this._bindings,
prevIndex = binding._cacheIndex,
firstInactiveIndex = -- this._nActiveBindings,
lastActiveBinding = bindings[ firstInactiveIndex ];
binding._cacheIndex = firstInactiveIndex;
bindings[ firstInactiveIndex ] = binding;
lastActiveBinding._cacheIndex = prevIndex;
bindings[ prevIndex ] = lastActiveBinding;
},
// Memory management of Interpolants for weight and time scale
_lendControlInterpolant: function () {
var interpolants = this._controlInterpolants,
lastActiveIndex = this._nActiveControlInterpolants ++,
interpolant = interpolants[ lastActiveIndex ];
if ( interpolant === undefined ) {
interpolant = new LinearInterpolant(
new Float32Array( 2 ), new Float32Array( 2 ),
1, this._controlInterpolantsResultBuffer );
interpolant.__cacheIndex = lastActiveIndex;
interpolants[ lastActiveIndex ] = interpolant;
}
return interpolant;
},
_takeBackControlInterpolant: function ( interpolant ) {
var interpolants = this._controlInterpolants,
prevIndex = interpolant.__cacheIndex,
firstInactiveIndex = -- this._nActiveControlInterpolants,
lastActiveInterpolant = interpolants[ firstInactiveIndex ];
interpolant.__cacheIndex = firstInactiveIndex;
interpolants[ firstInactiveIndex ] = interpolant;
lastActiveInterpolant.__cacheIndex = prevIndex;
interpolants[ prevIndex ] = lastActiveInterpolant;
},
_controlInterpolantsResultBuffer: new Float32Array( 1 ),
// return an action for a clip optionally using a custom root target
// object (this method allocates a lot of dynamic memory in case a
// previously unknown clip/root combination is specified)
clipAction: function ( clip, optionalRoot ) {
var root = optionalRoot || this._root,
rootUuid = root.uuid,
clipObject = typeof clip === 'string' ?
AnimationClip.findByName( root, clip ) : clip,
clipUuid = clipObject !== null ? clipObject.uuid : clip,
actionsForClip = this._actionsByClip[ clipUuid ],
prototypeAction = null;
if ( actionsForClip !== undefined ) {
var existingAction =
actionsForClip.actionByRoot[ rootUuid ];
if ( existingAction !== undefined ) {
return existingAction;
}
// we know the clip, so we don't have to parse all
// the bindings again but can just copy
prototypeAction = actionsForClip.knownActions[ 0 ];
// also, take the clip from the prototype action
if ( clipObject === null )
clipObject = prototypeAction._clip;
}
// clip must be known when specified via string
if ( clipObject === null ) return null;
// allocate all resources required to run it
var newAction = new AnimationAction( this, clipObject, optionalRoot );
this._bindAction( newAction, prototypeAction );
// and make the action known to the memory manager
this._addInactiveAction( newAction, clipUuid, rootUuid );
return newAction;
},
// get an existing action
existingAction: function ( clip, optionalRoot ) {
var root = optionalRoot || this._root,
rootUuid = root.uuid,
clipObject = typeof clip === 'string' ?
AnimationClip.findByName( root, clip ) : clip,
clipUuid = clipObject ? clipObject.uuid : clip,
actionsForClip = this._actionsByClip[ clipUuid ];
if ( actionsForClip !== undefined ) {
return actionsForClip.actionByRoot[ rootUuid ] || null;
}
return null;
},
// deactivates all previously scheduled actions
stopAllAction: function () {
var actions = this._actions,
nActions = this._nActiveActions,
bindings = this._bindings,
nBindings = this._nActiveBindings;
this._nActiveActions = 0;
this._nActiveBindings = 0;
for ( var i = 0; i !== nActions; ++ i ) {
actions[ i ].reset();
}
for ( var i = 0; i !== nBindings; ++ i ) {
bindings[ i ].useCount = 0;
}
return this;
},
// advance the time and update apply the animation
update: function ( deltaTime ) {
deltaTime *= this.timeScale;
var actions = this._actions,
nActions = this._nActiveActions,
time = this.time += deltaTime,
timeDirection = Math.sign( deltaTime ),
accuIndex = this._accuIndex ^= 1;
// run active actions
for ( var i = 0; i !== nActions; ++ i ) {
var action = actions[ i ];
action._update( time, deltaTime, timeDirection, accuIndex );
}
// update scene graph
var bindings = this._bindings,
nBindings = this._nActiveBindings;
for ( var i = 0; i !== nBindings; ++ i ) {
bindings[ i ].apply( accuIndex );
}
return this;
},
// return this mixer's root target object
getRoot: function () {
return this._root;
},
// free all resources specific to a particular clip
uncacheClip: function ( clip ) {
var actions = this._actions,
clipUuid = clip.uuid,
actionsByClip = this._actionsByClip,
actionsForClip = actionsByClip[ clipUuid ];
if ( actionsForClip !== undefined ) {
// note: just calling _removeInactiveAction would mess up the
// iteration state and also require updating the state we can
// just throw away
var actionsToRemove = actionsForClip.knownActions;
for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
var action = actionsToRemove[ i ];
this._deactivateAction( action );
var cacheIndex = action._cacheIndex,
lastInactiveAction = actions[ actions.length - 1 ];
action._cacheIndex = null;
action._byClipCacheIndex = null;
lastInactiveAction._cacheIndex = cacheIndex;
actions[ cacheIndex ] = lastInactiveAction;
actions.pop();
this._removeInactiveBindingsForAction( action );
}
delete actionsByClip[ clipUuid ];
}
},
// free all resources specific to a particular root target object
uncacheRoot: function ( root ) {
var rootUuid = root.uuid,
actionsByClip = this._actionsByClip;
for ( var clipUuid in actionsByClip ) {
var actionByRoot = actionsByClip[ clipUuid ].actionByRoot,
action = actionByRoot[ rootUuid ];
if ( action !== undefined ) {
this._deactivateAction( action );
this._removeInactiveAction( action );
}
}
var bindingsByRoot = this._bindingsByRootAndName,
bindingByName = bindingsByRoot[ rootUuid ];
if ( bindingByName !== undefined ) {
for ( var trackName in bindingByName ) {
var binding = bindingByName[ trackName ];
binding.restoreOriginalState();
this._removeInactiveBinding( binding );
}
}
},
// remove a targeted clip from the cache
uncacheAction: function ( clip, optionalRoot ) {
var action = this.existingAction( clip, optionalRoot );
if ( action !== null ) {
this._deactivateAction( action );
this._removeInactiveAction( action );
}
}
} );
export { AnimationMixer };
import { PropertyBinding } from './PropertyBinding.js';
import { _Math } from '../math/Math.js';
/**
*
* A group of objects that receives a shared animation state.
*
* Usage:
*
* - Add objects you would otherwise pass as 'root' to the
* constructor or the .clipAction method of AnimationMixer.
*
* - Instead pass this object as 'root'.
*
* - You can also add and remove objects later when the mixer
* is running.
*
* Note:
*
* Objects of this class appear as one object to the mixer,
* so cache control of the individual objects must be done
* on the group.
*
* Limitation:
*
* - The animated properties must be compatible among the
* all objects in the group.
*
* - A single property can either be controlled through a
* target group or directly, but not both.
*
* @author tschw
*/
function AnimationObjectGroup() {
this.uuid = _Math.generateUUID();
// cached objects followed by the active ones
this._objects = Array.prototype.slice.call( arguments );
this.nCachedObjects_ = 0; // threshold
// note: read by PropertyBinding.Composite
var indices = {};
this._indicesByUUID = indices; // for bookkeeping
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
indices[ arguments[ i ].uuid ] = i;
}
this._paths = []; // inside: string
this._parsedPaths = []; // inside: { we don't care, here }
this._bindings = []; // inside: Array< PropertyBinding >
this._bindingsIndicesByPath = {}; // inside: indices in these arrays
var scope = this;
this.stats = {
objects: {
get total() {
return scope._objects.length;
},
get inUse() {
return this.total - scope.nCachedObjects_;
}
},
get bindingsPerObject() {
return scope._bindings.length;
}
};
}
Object.assign( AnimationObjectGroup.prototype, {
isAnimationObjectGroup: true,
add: function () {
var objects = this._objects,
nObjects = objects.length,
nCachedObjects = this.nCachedObjects_,
indicesByUUID = this._indicesByUUID,
paths = this._paths,
parsedPaths = this._parsedPaths,
bindings = this._bindings,
nBindings = bindings.length,
knownObject = undefined;
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
var object = arguments[ i ],
uuid = object.uuid,
index = indicesByUUID[ uuid ];
if ( index === undefined ) {
// unknown object -> add it to the ACTIVE region
index = nObjects ++;
indicesByUUID[ uuid ] = index;
objects.push( object );
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) );
}
} else if ( index < nCachedObjects ) {
knownObject = objects[ index ];
// move existing object to the ACTIVE region
var firstActiveIndex = -- nCachedObjects,
lastCachedObject = objects[ firstActiveIndex ];
indicesByUUID[ lastCachedObject.uuid ] = index;
objects[ index ] = lastCachedObject;
indicesByUUID[ uuid ] = firstActiveIndex;
objects[ firstActiveIndex ] = object;
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
var bindingsForPath = bindings[ j ],
lastCached = bindingsForPath[ firstActiveIndex ],
binding = bindingsForPath[ index ];
bindingsForPath[ index ] = lastCached;
if ( binding === undefined ) {
// since we do not bother to create new bindings
// for objects that are cached, the binding may
// or may not exist
binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] );
}
bindingsForPath[ firstActiveIndex ] = binding;
}
} else if ( objects[ index ] !== knownObject ) {
console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' +
'detected. Clean the caches or recreate your infrastructure when reloading scenes.' );
} // else the object is already where we want it to be
} // for arguments
this.nCachedObjects_ = nCachedObjects;
},
remove: function () {
var objects = this._objects,
nCachedObjects = this.nCachedObjects_,
indicesByUUID = this._indicesByUUID,
bindings = this._bindings,
nBindings = bindings.length;
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
var object = arguments[ i ],
uuid = object.uuid,
index = indicesByUUID[ uuid ];
if ( index !== undefined && index >= nCachedObjects ) {
// move existing object into the CACHED region
var lastCachedIndex = nCachedObjects ++,
firstActiveObject = objects[ lastCachedIndex ];
indicesByUUID[ firstActiveObject.uuid ] = index;
objects[ index ] = firstActiveObject;
indicesByUUID[ uuid ] = lastCachedIndex;
objects[ lastCachedIndex ] = object;
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
var bindingsForPath = bindings[ j ],
firstActive = bindingsForPath[ lastCachedIndex ],
binding = bindingsForPath[ index ];
bindingsForPath[ index ] = firstActive;
bindingsForPath[ lastCachedIndex ] = binding;
}
}
} // for arguments
this.nCachedObjects_ = nCachedObjects;
},
// remove & forget
uncache: function () {
var objects = this._objects,
nObjects = objects.length,
nCachedObjects = this.nCachedObjects_,
indicesByUUID = this._indicesByUUID,
bindings = this._bindings,
nBindings = bindings.length;
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
var object = arguments[ i ],
uuid = object.uuid,
index = indicesByUUID[ uuid ];
if ( index !== undefined ) {
delete indicesByUUID[ uuid ];
if ( index < nCachedObjects ) {
// object is cached, shrink the CACHED region
var firstActiveIndex = -- nCachedObjects,
lastCachedObject = objects[ firstActiveIndex ],
lastIndex = -- nObjects,
lastObject = objects[ lastIndex ];
// last cached object takes this object's place
indicesByUUID[ lastCachedObject.uuid ] = index;
objects[ index ] = lastCachedObject;
// last object goes to the activated slot and pop
indicesByUUID[ lastObject.uuid ] = firstActiveIndex;
objects[ firstActiveIndex ] = lastObject;
objects.pop();
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
var bindingsForPath = bindings[ j ],
lastCached = bindingsForPath[ firstActiveIndex ],
last = bindingsForPath[ lastIndex ];
bindingsForPath[ index ] = lastCached;
bindingsForPath[ firstActiveIndex ] = last;
bindingsForPath.pop();
}
} else {
// object is active, just swap with the last and pop
var lastIndex = -- nObjects,
lastObject = objects[ lastIndex ];
indicesByUUID[ lastObject.uuid ] = index;
objects[ index ] = lastObject;
objects.pop();
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
var bindingsForPath = bindings[ j ];
bindingsForPath[ index ] = bindingsForPath[ lastIndex ];
bindingsForPath.pop();
}
} // cached or active
} // if object is known
} // for arguments
this.nCachedObjects_ = nCachedObjects;
},
// Internal interface used by befriended PropertyBinding.Composite:
subscribe_: function ( path, parsedPath ) {
// returns an array of bindings for the given path that is changed
// according to the contained objects in the group
var indicesByPath = this._bindingsIndicesByPath,
index = indicesByPath[ path ],
bindings = this._bindings;
if ( index !== undefined ) return bindings[ index ];
var paths = this._paths,
parsedPaths = this._parsedPaths,
objects = this._objects,
nObjects = objects.length,
nCachedObjects = this.nCachedObjects_,
bindingsForPath = new Array( nObjects );
index = bindings.length;
indicesByPath[ path ] = index;
paths.push( path );
parsedPaths.push( parsedPath );
bindings.push( bindingsForPath );
for ( var i = nCachedObjects, n = objects.length; i !== n; ++ i ) {
var object = objects[ i ];
bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath );
}
return bindingsForPath;
},
unsubscribe_: function ( path ) {
// tells the group to forget about a property path and no longer
// update the array previously obtained with 'subscribe_'
var indicesByPath = this._bindingsIndicesByPath,
index = indicesByPath[ path ];
if ( index !== undefined ) {
var paths = this._paths,
parsedPaths = this._parsedPaths,
bindings = this._bindings,
lastBindingsIndex = bindings.length - 1,
lastBindings = bindings[ lastBindingsIndex ],
lastBindingsPath = path[ lastBindingsIndex ];
indicesByPath[ lastBindingsPath ] = index;
bindings[ index ] = lastBindings;
bindings.pop();
parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];
parsedPaths.pop();
paths[ index ] = paths[ lastBindingsIndex ];
paths.pop();
}
}
} );
export { AnimationObjectGroup };
import { HashObject } from "../../2d/HashObject";
import { Object3D } from "../Object3D";
import { Mesh3D } from "../Mesh3D";
import { clamp } from "../../2d/utils";
import { Quaternion } from "../math/Quaternion";
/**
* 动画类型
*/
export enum AnimationType {
/**
* 旋转
*/
quaternion = "quaternion",
/**
* 缩放
*/
scale = "scale",
/**
* 顶点权重
*/
morphTargetInfluences = 'morphTargetInfluences',
/**
* 位移
*/
position = "position"
}
/**
* 记录动画的轨迹,实时计算,还是用每帧的,暂时按时间间隔来搞,实时计算吧
* 统统用线性的吧,貌似时间都打过点
*/
export class AnimationTrack3D extends HashObject {
constructor(
/**
* 需要改变属性的节点对象
*/
public node: Object3D | Mesh3D,
/**
* 动画类型
*/
public animationType: AnimationType,
/**
* 记录所有时间的数组,按顺序递增,没有的按最后一个时间的数据
*/
public times: Float32Array | number[],
/**
* 上述时间要对应的数值,values.length/times.length就是步长
*/
public values: Float32Array | number[]
) {
super()
this._instanceType = "AnimationTrack";
}
/**
* 传入时间设置对应的属性值,time大于所有动画总时间,外部处理
* @param time
*/
setValue(time: number) {
var value: Float32Array | number[] = this.getValue(time);
switch (this.animationType) {
case AnimationType.position:
case AnimationType.scale:
case AnimationType.quaternion:
this.node[this.animationType].fromArray(value);
break;
case AnimationType.morphTargetInfluences:
if (this.node.instanceType == "Mesh3D") {
var arr = this.node["morphTargetInfluences"]
for (var i = 0; i < arr.length; i++) {
arr[i] = value[i] || 0;
}
}
break;
}
}
private getValue(time: number) {
var size = this.getValueSize();
//为0直接用第一帧数据
if (time <= 0) return this.values.slice(0, size);
//如果超过最后时间,给最后一帧数据
if (time >= this.times[this.times.length - 1]) return this.values.slice(-size);
//取最近的两个时间索引,
var t1 = this.findPreIndex(time);
var t0 = t1 - 1;
if (t0 < 0) return this.values.slice(0, size);
//计算比例插值
var scale = (time - this.times[t0]) / (this.times[t1] - this.times[t0]);
//获取数组,
var v0 = this.values.slice(t0 * size, t0 * size + size);
var v1 = this.values.slice(t1 * size, t1 * size + size);
// time = clamp(time, 0, this.times[this.times.length - 1]);
//暂时都是线性的,到时再说
if (this.animationType == AnimationType.quaternion) {
return quaternionLinearInterpolate(scale, v0, v1);
}
return linearInterpolate(scale, v0, v1);
}
//每项尺寸
private getValueSize() {
return this.values.length / this.times.length;
}
/**
* 外部已经剔除了第一帧和最后一帧
* @param time
*/
private findPreIndex(time: number) {
var lastIndex = 0;
for (var i = 0; i < this.times.length; i++) {
if (this.times[i] > time) {
lastIndex = i;
break
}
}
return lastIndex;
}
destroy() {
this.node = null;
this.values = null;
this.times = null;
}
}
//线性插值,除了四元数
function linearInterpolate(s: number, v0: Float32Array | number[], v1: Float32Array | number[]) {
var result = [];
// values = this.sampleValues,
// stride = this.valueSize,
// offset1 = i1 * stride,
// offset0 = offset1 - stride,
// weight1 = (t - t0) / (t1 - t0),
// weight0 = 1 - weight1;
for (var i = 0; i !== v0.length; ++i) {
// result[i] =
// values[offset0 + i] * weight0 +
// values[offset1 + i] * weight1;
result[i] = v0[i] * (1 - s) + v1[i] * s;
}
return result;
}
function quaternionLinearInterpolate(s: number, v0: Float32Array | number[], v1: Float32Array | number[]) {
var result = [];
Quaternion.slerpFlat(result, 0, v0, 0, v1, 0, s);
return result;
}
\ No newline at end of file
/**
* @author tschw
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
*/
var AnimationUtils = {
// same as Array.prototype.slice, but also works on typed arrays
arraySlice: function ( array, from, to ) {
if ( AnimationUtils.isTypedArray( array ) ) {
// in ios9 array.subarray(from, undefined) will return empty array
// but array.subarray(from) or array.subarray(from, len) is correct
return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) );
}
return array.slice( from, to );
},
// converts an array to a specific type
convertArray: function ( array, type, forceClone ) {
if ( ! array || // let 'undefined' and 'null' pass
! forceClone && array.constructor === type ) return array;
if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
return new type( array ); // create typed array
}
return Array.prototype.slice.call( array ); // create Array
},
isTypedArray: function ( object ) {
return ArrayBuffer.isView( object ) &&
! ( object instanceof DataView );
},
// returns an array by which times and values can be sorted
getKeyframeOrder: function ( times ) {
function compareTime( i, j ) {
return times[ i ] - times[ j ];
}
var n = times.length;
var result = new Array( n );
for ( var i = 0; i !== n; ++ i ) result[ i ] = i;
result.sort( compareTime );
return result;
},
// uses the array previously returned by 'getKeyframeOrder' to sort data
sortedArray: function ( values, stride, order ) {
var nValues = values.length;
var result = new values.constructor( nValues );
for ( var i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
var srcOffset = order[ i ] * stride;
for ( var j = 0; j !== stride; ++ j ) {
result[ dstOffset ++ ] = values[ srcOffset + j ];
}
}
return result;
},
// function for parsing AOS keyframe formats
flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
var i = 1, key = jsonKeys[ 0 ];
while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
key = jsonKeys[ i ++ ];
}
if ( key === undefined ) return; // no data
var value = key[ valuePropertyName ];
if ( value === undefined ) return; // no data
if ( Array.isArray( value ) ) {
do {
value = key[ valuePropertyName ];
if ( value !== undefined ) {
times.push( key.time );
values.push.apply( values, value ); // push all elements
}
key = jsonKeys[ i ++ ];
} while ( key !== undefined );
} else if ( value.toArray !== undefined ) {
// ...assume THREE.Math-ish
do {
value = key[ valuePropertyName ];
if ( value !== undefined ) {
times.push( key.time );
value.toArray( values, values.length );
}
key = jsonKeys[ i ++ ];
} while ( key !== undefined );
} else {
// otherwise push as-is
do {
value = key[ valuePropertyName ];
if ( value !== undefined ) {
times.push( key.time );
values.push( value );
}
key = jsonKeys[ i ++ ];
} while ( key !== undefined );
}
}
};
export { AnimationUtils };
import {
InterpolateLinear,
InterpolateSmooth,
InterpolateDiscrete
} from '../constants.js';
import { CubicInterpolant } from '../math/interpolants/CubicInterpolant.js';
import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
import { DiscreteInterpolant } from '../math/interpolants/DiscreteInterpolant.js';
import { AnimationUtils } from './AnimationUtils.js';
/**
*
* A timed sequence of keyframes for a specific property.
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function KeyframeTrack( name, times, values, interpolation ) {
if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );
if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );
this.name = name;
this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
this.setInterpolation( interpolation || this.DefaultInterpolation );
}
// Static methods
Object.assign( KeyframeTrack, {
// Serialization (in static context, because of constructor invocation
// and automatic invocation of .toJSON):
toJSON: function ( track ) {
var trackType = track.constructor;
var json;
// derived classes can define a static toJSON method
if ( trackType.toJSON !== undefined ) {
json = trackType.toJSON( track );
} else {
// by default, we assume the data can be serialized as-is
json = {
'name': track.name,
'times': AnimationUtils.convertArray( track.times, Array ),
'values': AnimationUtils.convertArray( track.values, Array )
};
var interpolation = track.getInterpolation();
if ( interpolation !== track.DefaultInterpolation ) {
json.interpolation = interpolation;
}
}
json.type = track.ValueTypeName; // mandatory
return json;
}
} );
Object.assign( KeyframeTrack.prototype, {
constructor: KeyframeTrack,
TimeBufferType: Float32Array,
ValueBufferType: Float32Array,
DefaultInterpolation: InterpolateLinear,
InterpolantFactoryMethodDiscrete: function ( result ) {
return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
},
InterpolantFactoryMethodLinear: function ( result ) {
return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
},
InterpolantFactoryMethodSmooth: function ( result ) {
return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
},
setInterpolation: function ( interpolation ) {
var factoryMethod;
switch ( interpolation ) {
case InterpolateDiscrete:
factoryMethod = this.InterpolantFactoryMethodDiscrete;
break;
case InterpolateLinear:
factoryMethod = this.InterpolantFactoryMethodLinear;
break;
case InterpolateSmooth:
factoryMethod = this.InterpolantFactoryMethodSmooth;
break;
}
if ( factoryMethod === undefined ) {
var message = "unsupported interpolation for " +
this.ValueTypeName + " keyframe track named " + this.name;
if ( this.createInterpolant === undefined ) {
// fall back to default, unless the default itself is messed up
if ( interpolation !== this.DefaultInterpolation ) {
this.setInterpolation( this.DefaultInterpolation );
} else {
throw new Error( message ); // fatal, in this case
}
}
console.warn( 'THREE.KeyframeTrack:', message );
return this;
}
this.createInterpolant = factoryMethod;
return this;
},
getInterpolation: function () {
switch ( this.createInterpolant ) {
case this.InterpolantFactoryMethodDiscrete:
return InterpolateDiscrete;
case this.InterpolantFactoryMethodLinear:
return InterpolateLinear;
case this.InterpolantFactoryMethodSmooth:
return InterpolateSmooth;
}
},
getValueSize: function () {
return this.values.length / this.times.length;
},
// move all keyframes either forwards or backwards in time
shift: function ( timeOffset ) {
if ( timeOffset !== 0.0 ) {
var times = this.times;
for ( var i = 0, n = times.length; i !== n; ++ i ) {
times[ i ] += timeOffset;
}
}
return this;
},
// scale all keyframe times by a factor (useful for frame <-> seconds conversions)
scale: function ( timeScale ) {
if ( timeScale !== 1.0 ) {
var times = this.times;
for ( var i = 0, n = times.length; i !== n; ++ i ) {
times[ i ] *= timeScale;
}
}
return this;
},
// removes keyframes before and after animation without changing any values within the range [startTime, endTime].
// IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
trim: function ( startTime, endTime ) {
var times = this.times,
nKeys = times.length,
from = 0,
to = nKeys - 1;
while ( from !== nKeys && times[ from ] < startTime ) {
++ from;
}
while ( to !== - 1 && times[ to ] > endTime ) {
-- to;
}
++ to; // inclusive -> exclusive bound
if ( from !== 0 || to !== nKeys ) {
// empty tracks are forbidden, so keep at least one keyframe
if ( from >= to ) to = Math.max( to, 1 ), from = to - 1;
var stride = this.getValueSize();
this.times = AnimationUtils.arraySlice( times, from, to );
this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
}
return this;
},
// ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
validate: function () {
var valid = true;
var valueSize = this.getValueSize();
if ( valueSize - Math.floor( valueSize ) !== 0 ) {
console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
valid = false;
}
var times = this.times,
values = this.values,
nKeys = times.length;
if ( nKeys === 0 ) {
console.error( 'THREE.KeyframeTrack: Track is empty.', this );
valid = false;
}
var prevTime = null;
for ( var i = 0; i !== nKeys; i ++ ) {
var currTime = times[ i ];
if ( typeof currTime === 'number' && isNaN( currTime ) ) {
console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );
valid = false;
break;
}
if ( prevTime !== null && prevTime > currTime ) {
console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );
valid = false;
break;
}
prevTime = currTime;
}
if ( values !== undefined ) {
if ( AnimationUtils.isTypedArray( values ) ) {
for ( var i = 0, n = values.length; i !== n; ++ i ) {
var value = values[ i ];
if ( isNaN( value ) ) {
console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
valid = false;
break;
}
}
}
}
return valid;
},
// removes equivalent sequential keys as common in morph target sequences
// (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
optimize: function () {
var times = this.times,
values = this.values,
stride = this.getValueSize(),
smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
writeIndex = 1,
lastIndex = times.length - 1;
for ( var i = 1; i < lastIndex; ++ i ) {
var keep = false;
var time = times[ i ];
var timeNext = times[ i + 1 ];
// remove adjacent keyframes scheduled at the same time
if ( time !== timeNext && ( i !== 1 || time !== time[ 0 ] ) ) {
if ( ! smoothInterpolation ) {
// remove unnecessary keyframes same as their neighbors
var offset = i * stride,
offsetP = offset - stride,
offsetN = offset + stride;
for ( var j = 0; j !== stride; ++ j ) {
var value = values[ offset + j ];
if ( value !== values[ offsetP + j ] ||
value !== values[ offsetN + j ] ) {
keep = true;
break;
}
}
} else {
keep = true;
}
}
// in-place compaction
if ( keep ) {
if ( i !== writeIndex ) {
times[ writeIndex ] = times[ i ];
var readOffset = i * stride,
writeOffset = writeIndex * stride;
for ( var j = 0; j !== stride; ++ j ) {
values[ writeOffset + j ] = values[ readOffset + j ];
}
}
++ writeIndex;
}
}
// flush last keyframe (compaction looks ahead)
if ( lastIndex > 0 ) {
times[ writeIndex ] = times[ lastIndex ];
for ( var readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
values[ writeOffset + j ] = values[ readOffset + j ];
}
++ writeIndex;
}
if ( writeIndex !== times.length ) {
this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
}
return this;
}
} );
export { KeyframeTrack };
/**
*
* A reference to a real property in the scene graph.
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
// Characters [].:/ are reserved for track binding syntax.
var RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
function Composite( targetGroup, path, optionalParsedPath ) {
var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
this._targetGroup = targetGroup;
this._bindings = targetGroup.subscribe_( path, parsedPath );
}
Object.assign( Composite.prototype, {
getValue: function ( array, offset ) {
this.bind(); // bind all binding
var firstValidIndex = this._targetGroup.nCachedObjects_,
binding = this._bindings[ firstValidIndex ];
// and only call .getValue on the first
if ( binding !== undefined ) binding.getValue( array, offset );
},
setValue: function ( array, offset ) {
var bindings = this._bindings;
for ( var i = this._targetGroup.nCachedObjects_,
n = bindings.length; i !== n; ++ i ) {
bindings[ i ].setValue( array, offset );
}
},
bind: function () {
var bindings = this._bindings;
for ( var i = this._targetGroup.nCachedObjects_,
n = bindings.length; i !== n; ++ i ) {
bindings[ i ].bind();
}
},
unbind: function () {
var bindings = this._bindings;
for ( var i = this._targetGroup.nCachedObjects_,
n = bindings.length; i !== n; ++ i ) {
bindings[ i ].unbind();
}
}
} );
function PropertyBinding( rootNode, path, parsedPath ) {
this.path = path;
this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
this.rootNode = rootNode;
}
Object.assign( PropertyBinding, {
Composite: Composite,
create: function ( root, path, parsedPath ) {
if ( ! ( root && root.isAnimationObjectGroup ) ) {
return new PropertyBinding( root, path, parsedPath );
} else {
return new PropertyBinding.Composite( root, path, parsedPath );
}
},
/**
* Replaces spaces with underscores and removes unsupported characters from
* node names, to ensure compatibility with parseTrackName().
*
* @param {string} name Node name to be sanitized.
* @return {string}
*/
sanitizeNodeName: ( function () {
var reservedRe = new RegExp( '[' + RESERVED_CHARS_RE + ']', 'g' );
return function sanitizeNodeName( name ) {
return name.replace( /\s/g, '_' ).replace( reservedRe, '' );
};
}() ),
parseTrackName: function () {
// Attempts to allow node names from any language. ES5's `\w` regexp matches
// only latin characters, and the unicode \p{L} is not yet supported. So
// instead, we exclude reserved characters and match everything else.
var wordChar = '[^' + RESERVED_CHARS_RE + ']';
var wordCharOrDot = '[^' + RESERVED_CHARS_RE.replace( '\\.', '' ) + ']';
// Parent directories, delimited by '/' or ':'. Currently unused, but must
// be matched to parse the rest of the track name.
var directoryRe = /((?:WC+[\/:])*)/.source.replace( 'WC', wordChar );
// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.
var nodeRe = /(WCOD+)?/.source.replace( 'WCOD', wordCharOrDot );
// Object on target node, and accessor. May not contain reserved
// characters. Accessor may contain any character except closing bracket.
var objectRe = /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', wordChar );
// Property and accessor. May not contain reserved characters. Accessor may
// contain any non-bracket characters.
var propertyRe = /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', wordChar );
var trackRe = new RegExp( ''
+ '^'
+ directoryRe
+ nodeRe
+ objectRe
+ propertyRe
+ '$'
);
var supportedObjectNames = [ 'material', 'materials', 'bones' ];
return function parseTrackName( trackName ) {
var matches = trackRe.exec( trackName );
if ( ! matches ) {
throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );
}
var results = {
// directoryName: matches[ 1 ], // (tschw) currently unused
nodeName: matches[ 2 ],
objectName: matches[ 3 ],
objectIndex: matches[ 4 ],
propertyName: matches[ 5 ], // required
propertyIndex: matches[ 6 ]
};
var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );
if ( lastDot !== undefined && lastDot !== - 1 ) {
var objectName = results.nodeName.substring( lastDot + 1 );
// Object names must be checked against a whitelist. Otherwise, there
// is no way to parse 'foo.bar.baz': 'baz' must be a property, but
// 'bar' could be the objectName, or part of a nodeName (which can
// include '.' characters).
if ( supportedObjectNames.indexOf( objectName ) !== - 1 ) {
results.nodeName = results.nodeName.substring( 0, lastDot );
results.objectName = objectName;
}
}
if ( results.propertyName === null || results.propertyName.length === 0 ) {
throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );
}
return results;
};
}(),
findNode: function ( root, nodeName ) {
if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {
return root;
}
// search into skeleton bones.
if ( root.skeleton ) {
var bone = root.skeleton.getBoneByName( nodeName );
if ( bone !== undefined ) {
return bone;
}
}
// search into node subtree.
if ( root.children ) {
var searchNodeSubtree = function ( children ) {
for ( var i = 0; i < children.length; i ++ ) {
var childNode = children[ i ];
if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
return childNode;
}
var result = searchNodeSubtree( childNode.children );
if ( result ) return result;
}
return null;
};
var subTreeNode = searchNodeSubtree( root.children );
if ( subTreeNode ) {
return subTreeNode;
}
}
return null;
}
} );
Object.assign( PropertyBinding.prototype, { // prototype, continued
// these are used to "bind" a nonexistent property
_getValue_unavailable: function () {},
_setValue_unavailable: function () {},
BindingType: {
Direct: 0,
EntireArray: 1,
ArrayElement: 2,
HasFromToArray: 3
},
Versioning: {
None: 0,
NeedsUpdate: 1,
MatrixWorldNeedsUpdate: 2
},
GetterByBindingType: [
function getValue_direct( buffer, offset ) {
buffer[ offset ] = this.node[ this.propertyName ];
},
function getValue_array( buffer, offset ) {
var source = this.resolvedProperty;
for ( var i = 0, n = source.length; i !== n; ++ i ) {
buffer[ offset ++ ] = source[ i ];
}
},
function getValue_arrayElement( buffer, offset ) {
buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
},
function getValue_toArray( buffer, offset ) {
this.resolvedProperty.toArray( buffer, offset );
}
],
SetterByBindingTypeAndVersioning: [
[
// Direct
function setValue_direct( buffer, offset ) {
this.targetObject[ this.propertyName ] = buffer[ offset ];
},
function setValue_direct_setNeedsUpdate( buffer, offset ) {
this.targetObject[ this.propertyName ] = buffer[ offset ];
this.targetObject.needsUpdate = true;
},
function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
this.targetObject[ this.propertyName ] = buffer[ offset ];
this.targetObject.matrixWorldNeedsUpdate = true;
}
], [
// EntireArray
function setValue_array( buffer, offset ) {
var dest = this.resolvedProperty;
for ( var i = 0, n = dest.length; i !== n; ++ i ) {
dest[ i ] = buffer[ offset ++ ];
}
},
function setValue_array_setNeedsUpdate( buffer, offset ) {
var dest = this.resolvedProperty;
for ( var i = 0, n = dest.length; i !== n; ++ i ) {
dest[ i ] = buffer[ offset ++ ];
}
this.targetObject.needsUpdate = true;
},
function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {
var dest = this.resolvedProperty;
for ( var i = 0, n = dest.length; i !== n; ++ i ) {
dest[ i ] = buffer[ offset ++ ];
}
this.targetObject.matrixWorldNeedsUpdate = true;
}
], [
// ArrayElement
function setValue_arrayElement( buffer, offset ) {
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
},
function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
this.targetObject.needsUpdate = true;
},
function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
this.targetObject.matrixWorldNeedsUpdate = true;
}
], [
// HasToFromArray
function setValue_fromArray( buffer, offset ) {
this.resolvedProperty.fromArray( buffer, offset );
},
function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
this.resolvedProperty.fromArray( buffer, offset );
this.targetObject.needsUpdate = true;
},
function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
this.resolvedProperty.fromArray( buffer, offset );
this.targetObject.matrixWorldNeedsUpdate = true;
}
]
],
getValue: function getValue_unbound( targetArray, offset ) {
this.bind();
this.getValue( targetArray, offset );
// Note: This class uses a State pattern on a per-method basis:
// 'bind' sets 'this.getValue' / 'setValue' and shadows the
// prototype version of these methods with one that represents
// the bound state. When the property is not found, the methods
// become no-ops.
},
setValue: function getValue_unbound( sourceArray, offset ) {
this.bind();
this.setValue( sourceArray, offset );
},
// create getter / setter pair for a property in the scene graph
bind: function () {
var targetObject = this.node,
parsedPath = this.parsedPath,
objectName = parsedPath.objectName,
propertyName = parsedPath.propertyName,
propertyIndex = parsedPath.propertyIndex;
if ( ! targetObject ) {
targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode;
this.node = targetObject;
}
// set fail state so we can just 'return' on error
this.getValue = this._getValue_unavailable;
this.setValue = this._setValue_unavailable;
// ensure there is a value node
if ( ! targetObject ) {
console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' );
return;
}
if ( objectName ) {
var objectIndex = parsedPath.objectIndex;
// special cases were we need to reach deeper into the hierarchy to get the face materials....
switch ( objectName ) {
case 'materials':
if ( ! targetObject.material ) {
console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this );
return;
}
if ( ! targetObject.material.materials ) {
console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this );
return;
}
targetObject = targetObject.material.materials;
break;
case 'bones':
if ( ! targetObject.skeleton ) {
console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this );
return;
}
// potential future optimization: skip this if propertyIndex is already an integer
// and convert the integer string to a true integer.
targetObject = targetObject.skeleton.bones;
// support resolving morphTarget names into indices.
for ( var i = 0; i < targetObject.length; i ++ ) {
if ( targetObject[ i ].name === objectIndex ) {
objectIndex = i;
break;
}
}
break;
default:
if ( targetObject[ objectName ] === undefined ) {
console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this );
return;
}
targetObject = targetObject[ objectName ];
}
if ( objectIndex !== undefined ) {
if ( targetObject[ objectIndex ] === undefined ) {
console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject );
return;
}
targetObject = targetObject[ objectIndex ];
}
}
// resolve property
var nodeProperty = targetObject[ propertyName ];
if ( nodeProperty === undefined ) {
var nodeName = parsedPath.nodeName;
console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName +
'.' + propertyName + ' but it wasn\'t found.', targetObject );
return;
}
// determine versioning scheme
var versioning = this.Versioning.None;
this.targetObject = targetObject;
if ( targetObject.needsUpdate !== undefined ) { // material
versioning = this.Versioning.NeedsUpdate;
} else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
versioning = this.Versioning.MatrixWorldNeedsUpdate;
}
// determine how the property gets bound
var bindingType = this.BindingType.Direct;
if ( propertyIndex !== undefined ) {
// access a sub element of the property array (only primitives are supported right now)
if ( propertyName === "morphTargetInfluences" ) {
// potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
// support resolving morphTarget names into indices.
if ( ! targetObject.geometry ) {
console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this );
return;
}
if ( targetObject.geometry.isBufferGeometry ) {
if ( ! targetObject.geometry.morphAttributes ) {
console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this );
return;
}
for ( var i = 0; i < this.node.geometry.morphAttributes.position.length; i ++ ) {
if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) {
propertyIndex = i;
break;
}
}
} else {
if ( ! targetObject.geometry.morphTargets ) {
console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this );
return;
}
for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) {
propertyIndex = i;
break;
}
}
}
}
bindingType = this.BindingType.ArrayElement;
this.resolvedProperty = nodeProperty;
this.propertyIndex = propertyIndex;
} else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
// must use copy for Object3D.Euler/Quaternion
bindingType = this.BindingType.HasFromToArray;
this.resolvedProperty = nodeProperty;
} else if ( Array.isArray( nodeProperty ) ) {
bindingType = this.BindingType.EntireArray;
this.resolvedProperty = nodeProperty;
} else {
this.propertyName = propertyName;
}
// select getter / setter
this.getValue = this.GetterByBindingType[ bindingType ];
this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
},
unbind: function () {
this.node = null;
// back to the prototype version of getValue / setValue
// note: avoiding to mutate the shape of 'this' via 'delete'
this.getValue = this._getValue_unbound;
this.setValue = this._setValue_unbound;
}
} );
//!\ DECLARE ALIAS AFTER assign prototype !
Object.assign( PropertyBinding.prototype, {
// initial state of these methods that calls 'bind'
_getValue_unbound: PropertyBinding.prototype.getValue,
_setValue_unbound: PropertyBinding.prototype.setValue,
} );
export { PropertyBinding };
import { Quaternion } from '../math/Quaternion.js';
/**
*
* Buffered scene graph property that allows weighted accumulation.
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function PropertyMixer( binding, typeName, valueSize ) {
this.binding = binding;
this.valueSize = valueSize;
var bufferType = Float64Array,
mixFunction;
switch ( typeName ) {
case 'quaternion':
mixFunction = this._slerp;
break;
case 'string':
case 'bool':
bufferType = Array;
mixFunction = this._select;
break;
default:
mixFunction = this._lerp;
}
this.buffer = new bufferType( valueSize * 4 );
// layout: [ incoming | accu0 | accu1 | orig ]
//
// interpolators can use .buffer as their .result
// the data then goes to 'incoming'
//
// 'accu0' and 'accu1' are used frame-interleaved for
// the cumulative result and are compared to detect
// changes
//
// 'orig' stores the original state of the property
this._mixBufferRegion = mixFunction;
this.cumulativeWeight = 0;
this.useCount = 0;
this.referenceCount = 0;
}
Object.assign( PropertyMixer.prototype, {
// accumulate data in the 'incoming' region into 'accu<i>'
accumulate: function ( accuIndex, weight ) {
// note: happily accumulating nothing when weight = 0, the caller knows
// the weight and shouldn't have made the call in the first place
var buffer = this.buffer,
stride = this.valueSize,
offset = accuIndex * stride + stride,
currentWeight = this.cumulativeWeight;
if ( currentWeight === 0 ) {
// accuN := incoming * weight
for ( var i = 0; i !== stride; ++ i ) {
buffer[ offset + i ] = buffer[ i ];
}
currentWeight = weight;
} else {
// accuN := accuN + incoming * weight
currentWeight += weight;
var mix = weight / currentWeight;
this._mixBufferRegion( buffer, offset, 0, mix, stride );
}
this.cumulativeWeight = currentWeight;
},
// apply the state of 'accu<i>' to the binding when accus differ
apply: function ( accuIndex ) {
var stride = this.valueSize,
buffer = this.buffer,
offset = accuIndex * stride + stride,
weight = this.cumulativeWeight,
binding = this.binding;
this.cumulativeWeight = 0;
if ( weight < 1 ) {
// accuN := accuN + original * ( 1 - cumulativeWeight )
var originalValueOffset = stride * 3;
this._mixBufferRegion(
buffer, offset, originalValueOffset, 1 - weight, stride );
}
for ( var i = stride, e = stride + stride; i !== e; ++ i ) {
if ( buffer[ i ] !== buffer[ i + stride ] ) {
// value has changed -> update scene graph
binding.setValue( buffer, offset );
break;
}
}
},
// remember the state of the bound property and copy it to both accus
saveOriginalState: function () {
var binding = this.binding;
var buffer = this.buffer,
stride = this.valueSize,
originalValueOffset = stride * 3;
binding.getValue( buffer, originalValueOffset );
// accu[0..1] := orig -- initially detect changes against the original
for ( var i = stride, e = originalValueOffset; i !== e; ++ i ) {
buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ];
}
this.cumulativeWeight = 0;
},
// apply the state previously taken via 'saveOriginalState' to the binding
restoreOriginalState: function () {
var originalValueOffset = this.valueSize * 3;
this.binding.setValue( this.buffer, originalValueOffset );
},
// mix functions
_select: function ( buffer, dstOffset, srcOffset, t, stride ) {
if ( t >= 0.5 ) {
for ( var i = 0; i !== stride; ++ i ) {
buffer[ dstOffset + i ] = buffer[ srcOffset + i ];
}
}
},
_slerp: function ( buffer, dstOffset, srcOffset, t ) {
Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t );
},
_lerp: function ( buffer, dstOffset, srcOffset, t, stride ) {
var s = 1 - t;
for ( var i = 0; i !== stride; ++ i ) {
var j = dstOffset + i;
buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t;
}
}
} );
export { PropertyMixer };
export var ZeroCurvatureEnding = 2400;
export var ZeroSlopeEnding = 2401;
export var WrapAroundEnding = 2402;
export var LoopOnce = 2200;
export var LoopRepeat = 2201;
export var LoopPingPong = 2202;
export var InterpolateDiscrete = 2300;
export var InterpolateLinear = 2301;
export var InterpolateSmooth = 2302;
\ No newline at end of file
export * from "./AnimationClip3D"
export * from "./AnimationTrack3D"
\ No newline at end of file
import { ZeroCurvatureEnding } from '../constants';
import { Interpolant } from '../../math/Interpolant';
import { WrapAroundEnding, ZeroSlopeEnding } from '../constants';
/**
* Fast and simple cubic spline interpolant.
*
* It was derived from a Hermitian construction setting the first derivative
* at each sample position to the linear slope between neighboring positions
* over their parameter interval.
*
* @author tschw
*/
export class CubicInterpolant extends Interpolant {
_weightPrev: number;
_offsetPrev: number;
_weightNext: number;
_offsetNext: number;
DefaultSettings_ = {
endingStart: ZeroCurvatureEnding,
endingEnd: ZeroCurvatureEnding
}
constructor(parameterPositions, sampleValues, sampleSize, resultBuffer) {
super(parameterPositions, sampleValues, sampleSize, resultBuffer)
this._weightPrev = - 0;
this._offsetPrev = - 0;
this._weightNext = - 0;
this._offsetNext = - 0;
}
intervalChanged_(i1, t0, t1) {
var pp = this.parameterPositions,
iPrev = i1 - 2,
iNext = i1 + 1,
tPrev = pp[iPrev],
tNext = pp[iNext];
if (tPrev === undefined) {
switch (this.getSettings_().endingStart) {
case ZeroSlopeEnding:
// f'(t0) = 0
iPrev = i1;
tPrev = 2 * t0 - t1;
break;
case WrapAroundEnding:
// use the other end of the curve
iPrev = pp.length - 2;
tPrev = t0 + pp[iPrev] - pp[iPrev + 1];
break;
default: // ZeroCurvatureEnding
// f''(t0) = 0 a.k.a. Natural Spline
iPrev = i1;
tPrev = t1;
}
}
if (tNext === undefined) {
switch (this.getSettings_().endingEnd) {
case ZeroSlopeEnding:
// f'(tN) = 0
iNext = i1;
tNext = 2 * t1 - t0;
break;
case WrapAroundEnding:
// use the other end of the curve
iNext = 1;
tNext = t1 + pp[1] - pp[0];
break;
default: // ZeroCurvatureEnding
// f''(tN) = 0, a.k.a. Natural Spline
iNext = i1 - 1;
tNext = t0;
}
}
var halfDt = (t1 - t0) * 0.5,
stride = this.valueSize;
this._weightPrev = halfDt / (t0 - tPrev);
this._weightNext = halfDt / (tNext - t1);
this._offsetPrev = iPrev * stride;
this._offsetNext = iNext * stride;
};
interpolate_(i1, t0, t, t1) {
var result = this.resultBuffer,
values = this.sampleValues,
stride = this.valueSize,
o1 = i1 * stride, o0 = o1 - stride,
oP = this._offsetPrev, oN = this._offsetNext,
wP = this._weightPrev, wN = this._weightNext,
p = (t - t0) / (t1 - t0),
pp = p * p,
ppp = pp * p;
// evaluate polynomials
var sP = - wP * ppp + 2 * wP * pp - wP * p;
var s0 = (1 + wP) * ppp + (- 1.5 - 2 * wP) * pp + (- 0.5 + wP) * p + 1;
var s1 = (- 1 - wN) * ppp + (1.5 + wN) * pp + 0.5 * p;
var sN = wN * ppp - wN * pp;
// combine data linearly
for (var i = 0; i !== stride; ++i) {
result[i] =
sP * values[oP + i] +
s0 * values[o0 + i] +
s1 * values[o1 + i] +
sN * values[oN + i];
}
return result;
}
}
import { Interpolant } from '../../math/Interpolant';
/**
*
* Interpolant that evaluates to the sample value at the position preceeding
* the parameter.
*
* @author tschw
*/
export class DiscreteInterpolant extends Interpolant {
constructor(parameterPositions, sampleValues, sampleSize, resultBuffer) {
super(parameterPositions, sampleValues, sampleSize, resultBuffer);
}
interpolate_(i1 /*, t0, t, t1 */) {
return this.copySampleValue_(i1 - 1);
}
}
import { Interpolant } from '../Interpolant.js';
/**
* @author tschw
*/
function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
}
LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
constructor: LinearInterpolant,
interpolate_: function ( i1, t0, t, t1 ) {
var result = this.resultBuffer,
values = this.sampleValues,
stride = this.valueSize,
offset1 = i1 * stride,
offset0 = offset1 - stride,
weight1 = ( t - t0 ) / ( t1 - t0 ),
weight0 = 1 - weight1;
for ( var i = 0; i !== stride; ++ i ) {
result[ i ] =
values[ offset0 + i ] * weight0 +
values[ offset1 + i ] * weight1;
}
return result;
}
} );
export { LinearInterpolant };
import { Interpolant } from '../Interpolant.js';
import { Quaternion } from '../Quaternion.js';
/**
* Spherical linear unit quaternion interpolant.
*
* @author tschw
*/
function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
}
QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
constructor: QuaternionLinearInterpolant,
interpolate_: function ( i1, t0, t, t1 ) {
var result = this.resultBuffer,
values = this.sampleValues,
stride = this.valueSize,
offset = i1 * stride,
alpha = ( t - t0 ) / ( t1 - t0 );
for ( var end = offset + stride; offset !== end; offset += 4 ) {
Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );
}
return result;
}
} );
export { QuaternionLinearInterpolant };
import { InterpolateDiscrete } from '../../constants.js';
import { KeyframeTrack } from '../KeyframeTrack.js';
/**
*
* A Track of Boolean keyframe values.
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function BooleanKeyframeTrack( name, times, values ) {
KeyframeTrack.call( this, name, times, values );
}
BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
constructor: BooleanKeyframeTrack,
ValueTypeName: 'bool',
ValueBufferType: Array,
DefaultInterpolation: InterpolateDiscrete,
InterpolantFactoryMethodLinear: undefined,
InterpolantFactoryMethodSmooth: undefined
// Note: Actually this track could have a optimized / compressed
// representation of a single value and a custom interpolant that
// computes "firstValue ^ isOdd( index )".
} );
export { BooleanKeyframeTrack };
import { KeyframeTrack } from '../KeyframeTrack.js';
/**
*
* A Track of keyframe values that represent color.
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function ColorKeyframeTrack( name, times, values, interpolation ) {
KeyframeTrack.call( this, name, times, values, interpolation );
}
ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
constructor: ColorKeyframeTrack,
ValueTypeName: 'color'
// ValueBufferType is inherited
// DefaultInterpolation is inherited
// Note: Very basic implementation and nothing special yet.
// However, this is the place for color space parameterization.
} );
export { ColorKeyframeTrack };
import { KeyframeTrack } from '../KeyframeTrack.js';
/**
*
* A Track of numeric keyframe values.
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function NumberKeyframeTrack( name, times, values, interpolation ) {
KeyframeTrack.call( this, name, times, values, interpolation );
}
NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
constructor: NumberKeyframeTrack,
ValueTypeName: 'number'
// ValueBufferType is inherited
// DefaultInterpolation is inherited
} );
export { NumberKeyframeTrack };
import { InterpolateLinear } from '../../constants.js';
import { KeyframeTrack } from '../KeyframeTrack.js';
import { QuaternionLinearInterpolant } from '../../math/interpolants/QuaternionLinearInterpolant.js';
/**
*
* A Track of quaternion keyframe values.
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function QuaternionKeyframeTrack( name, times, values, interpolation ) {
KeyframeTrack.call( this, name, times, values, interpolation );
}
QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
constructor: QuaternionKeyframeTrack,
ValueTypeName: 'quaternion',
// ValueBufferType is inherited
DefaultInterpolation: InterpolateLinear,
InterpolantFactoryMethodLinear: function ( result ) {
return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );
},
InterpolantFactoryMethodSmooth: undefined // not yet implemented
} );
export { QuaternionKeyframeTrack };
import { InterpolateDiscrete } from '../../constants.js';
import { KeyframeTrack } from '../KeyframeTrack.js';
/**
*
* A Track that interpolates Strings
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function StringKeyframeTrack( name, times, values, interpolation ) {
KeyframeTrack.call( this, name, times, values, interpolation );
}
StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
constructor: StringKeyframeTrack,
ValueTypeName: 'string',
ValueBufferType: Array,
DefaultInterpolation: InterpolateDiscrete,
InterpolantFactoryMethodLinear: undefined,
InterpolantFactoryMethodSmooth: undefined
} );
export { StringKeyframeTrack };
import { KeyframeTrack } from '../KeyframeTrack.js';
/**
*
* A Track of vectored keyframe values.
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
* @author tschw
*/
function VectorKeyframeTrack( name, times, values, interpolation ) {
KeyframeTrack.call( this, name, times, values, interpolation );
}
VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
constructor: VectorKeyframeTrack,
ValueTypeName: 'vector'
// ValueBufferType is inherited
// DefaultInterpolation is inherited
} );
export { VectorKeyframeTrack };
/**
* 判断是否是类型化数组
* @param {*} object
*/
export function isTypedArray(object: any): boolean {
return ArrayBuffer.isView(object) &&
!(object instanceof DataView);
}
/**
* 截取数组
* @param array
* @param from
* @param to
*/
export function arraySlice(array, from: number, to?: number) {
if (isTypedArray(array)) {
// in ios9 array.subarray(from, undefined) will return empty array
// but array.subarray(from) or array.subarray(from, len) is correct
return new array.constructor(array.subarray(from, to !== undefined ? to : array.length));
}
return array.slice(from, to);
}
// converts an array to a specific type
export function convertArray(array, type, forceClone?: boolean) {
if (!array || // let 'undefined' and 'null' pass
!forceClone && array.constructor === type) return array;
if (typeof type.BYTES_PER_ELEMENT === 'number') {
return new type(array); // create typed array
}
return Array.prototype.slice.call(array); // create Array
}
// function for parsing AOS keyframe formats
export function flattenJSON(jsonKeys, times, values, valuePropertyName) {
var i = 1, key = jsonKeys[0];
while (key !== undefined && key[valuePropertyName] === undefined) {
key = jsonKeys[i++];
}
if (key === undefined) return; // no data
var value = key[valuePropertyName];
if (value === undefined) return; // no data
if (Array.isArray(value)) {
do {
value = key[valuePropertyName];
if (value !== undefined) {
times.push(key.time);
values.push.apply(values, value); // push all elements
}
key = jsonKeys[i++];
} while (key !== undefined);
} else if (value.toArray !== undefined) {
// ...assume THREE.Math-ish
do {
value = key[valuePropertyName];
if (value !== undefined) {
times.push(key.time);
value.toArray(values, values.length);
}
key = jsonKeys[i++];
} while (key !== undefined);
} else {
// otherwise push as-is
do {
value = key[valuePropertyName];
if (value !== undefined) {
times.push(key.time);
values.push(value);
}
key = jsonKeys[i++];
} while (key !== undefined);
}
}
// returns an array by which times and values can be sorted
export function getKeyframeOrder(times) {
function compareTime(i, j) {
return times[i] - times[j];
}
var n = times.length;
var result = new Array(n);
for (var i = 0; i !== n; ++i) result[i] = i;
result.sort(compareTime);
return result;
}
// uses the array previously returned by 'getKeyframeOrder' to sort data
export function sortedArray(values, stride, order) {
var nValues = values.length;
var result = new values.constructor(nValues);
for (var i = 0, dstOffset = 0; dstOffset !== nValues; ++i) {
var srcOffset = order[i] * stride;
for (var j = 0; j !== stride; ++j) {
result[dstOffset++] = values[srcOffset + j];
}
}
return result;
}
export var InterpolateDiscrete = 2300;
export var InterpolateLinear = 2301;
export var InterpolateSmooth = 2302;
\ No newline at end of file
......@@ -3,6 +3,11 @@ import { Interpolant } from "../math/Interpolant";
// Spline Interpolation
// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
/**
* 暂时还没用到,以后再说
*/
export class GLTFCubicSplineInterpolant extends Interpolant {
copySampleValue_(index: number) {
......
......@@ -12,8 +12,13 @@ import { Mesh3D } from "../Mesh3D";
import { PerspectiveCamera } from "../cameras/PerspectiveCamera";
import { RAD_TO_DEG } from "../../2d/const";
import { Matrix4 } from "..";
import { arraySlice } from "../animation/utils";
import { InterpolateSmooth, InterpolateLinear, InterpolateDiscrete } from "../animation/utils";
var THREE: any = {}
import { AnimationType, AnimationTrack3D } from "../animation/AnimationTrack3D";
import { AnimationClip3D } from "../animation/AnimationClip3D";
// var THREE: any = {}
/* BINARY EXTENSION */
var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF';
......@@ -108,12 +113,12 @@ var PATH_PROPERTIES = {
};
var INTERPOLATION = {
CUBICSPLINE: THREE.InterpolateSmooth, // We use custom interpolation GLTFCubicSplineInterpolation for CUBICSPLINE.
CUBICSPLINE: InterpolateSmooth, // We use custom interpolation GLTFCubicSplineInterpolation for CUBICSPLINE.
// KeyframeTrack.optimize() can't handle glTF Cubic Spline output values layout,
// using THREE.InterpolateSmooth for KeyframeTrack instantiation to prevent optimization.
// See KeyframeTrack.optimize() for the detail.
LINEAR: THREE.InterpolateLinear,
STEP: THREE.InterpolateDiscrete
LINEAR: InterpolateLinear,
STEP: InterpolateDiscrete
};
export class GLTFLoader extends EventDispatcher {
......@@ -1573,7 +1578,8 @@ class GLTFParser {
// var useSkinning = mesh.isSkinnedMesh === true;
var useMorphTargets = !!geometry._morphPositions;
var useMorphNormals = useMorphTargets && !!geometry._morphNormals;
material.morphTargets = useMorphTargets;
//用相同的材质有覆盖的风险,所以有才设置吧,反正mesh也会判断一次
if (useMorphTargets) material.morphTargets = useMorphTargets;
material.morphNormals = useMorphNormals;
// for (var j = 0, jl = materials.length; j < jl; j++) {
......@@ -1783,7 +1789,7 @@ class GLTFParser {
]).then(function (dependencies) {
var tracks = [];
var tracks: AnimationTrack3D[] = [];
for (var i = 0, il = animationDef.channels.length; i < il; i++) {
......@@ -1799,11 +1805,11 @@ class GLTFParser {
var inputAccessor = dependencies.accessors[input];
var outputAccessor = dependencies.accessors[output];
console.log(2222222)
console.log(inputAccessor)
console.log(outputAccessor)
// console.log(2222222)
// console.log(inputAccessor)
// console.log(outputAccessor)
var node: Object3D = dependencies.nodes[name];
console.log("node:", node)
// console.log("node:", node)
if (node) {
node.updateLocalMatrix();
......@@ -1814,28 +1820,32 @@ class GLTFParser {
case PATH_PROPERTIES.weights:
// TypedKeyframeTrack = THREE.NumberKeyframeTrack;
console.log("weights")
TypedKeyframeTrack = AnimationType.morphTargetInfluences;
// console.log("weights")
break;
case PATH_PROPERTIES.rotation:
// TypedKeyframeTrack = THREE.QuaternionKeyframeTrack;
console.log("rotation")
TypedKeyframeTrack = AnimationType.quaternion;
// console.log("rotation")
break;
case PATH_PROPERTIES.position:
TypedKeyframeTrack = AnimationType.position;
break;
case PATH_PROPERTIES.scale:
TypedKeyframeTrack = AnimationType.scale;
break;
default:
// TypedKeyframeTrack = THREE.VectorKeyframeTrack;
console.log("position,scale")
TypedKeyframeTrack = AnimationType.position;
// console.log("position,scale")
break;
}
var targetName = node.name ? node.name : node.instanceId;
//TODO
var interpolation = sampler.interpolation !== undefined ? 1 : 0//INTERPOLATION[sampler.interpolation] : THREE.InterpolateLinear;
//TODO,暂时不用,以后用到再说
var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[sampler.interpolation] : InterpolateLinear;
//处理顶点变形的权重
var targetNames = [];
......@@ -1864,6 +1874,12 @@ class GLTFParser {
// arraySlice(outputAccessor.array, 0),
// interpolation
// );
var track = new AnimationTrack3D(
node,
TypedKeyframeTrack,
arraySlice(inputAccessor.array, 0),
arraySlice(outputAccessor.array, 0),
)
// Here is the trick to enable custom interpolation.
// Overrides .createInterpolant in a factory method which creates custom interpolation.
......@@ -1885,7 +1901,7 @@ class GLTFParser {
// }
// tracks.push(track);
tracks.push(track);
}
......@@ -1895,10 +1911,10 @@ class GLTFParser {
}
var name = animationDef.name !== undefined ? animationDef.name : 'animation_' + animationIndex;
// var name = animationDef.name !== undefined ? animationDef.name : 'animation_' + animationIndex;
// return new THREE.AnimationClip(name, undefined, tracks);
return 1
return new AnimationClip3D(tracks);
});
};
......@@ -2075,7 +2091,7 @@ class GLTFParser {
}
mesh.bind(new THREE.Skeleton(bones, boneInverses), mesh.matrixWorld);
// mesh.bind(new THREE.Skeleton(bones, boneInverses), mesh.matrixWorld);
}
......@@ -2595,29 +2611,6 @@ function sanitizeNodeName(name) {
};
/**
* 截取数组
* @param array
* @param from
* @param to
*/
function arraySlice(array, from: number, to?: number) {
if (isTypedArray(array)) {
// in ios9 array.subarray(from, undefined) will return empty array
// but array.subarray(from) or array.subarray(from, len) is correct
return new array.constructor(array.subarray(from, to !== undefined ? to : array.length));
}
return array.slice(from, to);
}
/**
* 判断是否是类型化数组
* @param {*} object
*/
function isTypedArray(object: any): boolean {
return ArrayBuffer.isView(object) &&
!(object instanceof DataView);
}
/**
* 深度递归处理Object3D
* @param object
......
......@@ -30,3 +30,6 @@ export * from "./D3Renderer";
//gltf
export * from "./gltf";
//动画
export * from "./animation"
......@@ -519,7 +519,7 @@ export class Quaternion {
}
fromArray(array, offset: number = 0) {
fromArray(array: number[] | Float32Array, offset: number = 0) {
this._x = array[offset];
this._y = array[offset + 1];
this._z = array[offset + 2];
......
......@@ -3,6 +3,7 @@ import { WebglRenderer } from "../../2d/renderers/WebglRenderer";
import { BaseMaterial } from "../materials/BaseMaterial";
import { LightsConfig } from "../Scene3D"
import { generateUniformAccessObject, mapType } from "../../glCore/shader";
import { Mesh3D } from "..";
/**
......@@ -11,13 +12,13 @@ import { generateUniformAccessObject, mapType } from "../../glCore/shader";
* @param material
* @param lights
*/
export function getCusShader(render: WebglRenderer, material: BaseMaterial, lights: LightsConfig) {
export function getCusShader(render: WebglRenderer, material: BaseMaterial, lights: LightsConfig, mesh: Mesh3D) {
//所有参数
var parameters: ShaderParametersInt = {
pointLightsNum: lights.pointLights.length,
dirLightsNum: lights.directionalLights.length,
morphTargets: material.morphTargets,
morphNormals: material.morphNormals,
morphTargets: mesh.morphTargetInfluences && material.morphTargets,
morphNormals: mesh.morphTargetInfluences && material.morphTargets && material.morphNormals,
lightAffect: material._lightAffect,
}
//计算code,
......
......@@ -48,6 +48,8 @@
<script src="//yun.duiba.com.cn/db_games/libs0924/stats.js"></script>
</body>
<script>
window.addEventListener("load", async function () {
//获取canvas
var canvas = document.getElementById("canvas");
......@@ -209,26 +211,41 @@
m.visible = false;//可设置隐藏
//加载模型
console.time("a")
new FYGE.GLTFLoader().load(
"./res/b.glb",
// "./res/c.gltf",
(gltf) => {
console.timeEnd("a")
console.log(gltf)
var con = new FYGE.Object3D();
scene.addChild(con)
con.scale.set(0.02,0.02,0.02)
con.scale.set(0.02, 0.02, 0.02)
for (let i = gltf.scene.children.length - 1; i >= 0; i--) {
let o = gltf.scene.children[i]
con.addChild(o)
// o.scale.set(0.02, 0.02, 0.02)
// console.log(o)
}
var aaa = scene.getChildByName("圆盘", true, true)
// scene.children[scene.children.length-1].children[0].visible=false
// scene.children[scene.children.length-1].children[0].geometry = aaa.geometry
// scene.children[scene.children.length-1].children[0].material = aaa.material
console.log(aaa)
// scene.addChild(aaa)
con.addEventListener(FYGE.MouseEvent.MOUSE_DOWN,()=>{
console.log(1111)
},this)
// console.log(gltf.animations)
// con.getChildByName("双眼皮左",false,true)[1] .name = "asd"
// gltf.animations[0].tracks[10].name = "asd.quaternion"
// //
// console.log("asdasd:", con.getChildByName("耳朵1",false,true))
// console.log("asdasd:", con.getChildByName("耳朵2",false,true))
// con.getChildByName("耳朵2",false,true).material.morphTargets=true
console.log(gltf.animations)
var lastTime=Date.now()
con.addEventListener(FYGE.Event.ENTER_FRAME, () => {
let now = Date.now();
var dt = now - lastTime
lastTime = now
gltf.animations[0].update(dt/1000)
// if (mixer) mixer.update(dt/1000);
}, this)
},
(err) => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment