123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- module BABYLON {
- export class ShapeKeyGroup {
- // position elements converted to typed array
- private _affectedPositionElements : Uint16Array;
- private _nPosElements : number;
-
- // arrays for the storage of each state
- private _states = new Array<Float32Array>();
- private _normals = new Array<Float32Array>();
- private _stateNames = new Array<string>();
- // event series queue & reference vars for current seris & step within
- private _queue = new Array<AutomatonEventSeries>();
- private _currentSeries : AutomatonEventSeries = null;
- private _currentStepInSeries : ReferenceDeformation = null;
- private _endOfLastFrameTs = -1;
- // affected vertices are used for normals, since all the entire vertex is involved, even if only the x of a position is affected
- private _affectedVertices : Uint16Array;
- private _nVertices;
-
- // reference vars for the current & prior deformation; assigned either an item of (_states / _normals) or one of the reusables
- private _currFinalPositionVals : Float32Array;
- private _priorFinalPositionVals : Float32Array;
- private _currFinalNormalVals : Float32Array;
- private _priorFinalNormalVals : Float32Array;
-
- // typed arrays are more expense to create, pre-allocate pairs for reuse
- private _reusablePositionFinals = new Array<Float32Array>();
- private _reusableNormalFinals = new Array<Float32Array>();
- private _lastReusablePosUsed = 0;
- private _lastReusableNormUsed = 0;
-
- // rotation control members
- private _doingRotation = false;
- private _rotationStartVec : Vector3;
- private _rotationEndVec : Vector3;
-
- // position control members
- private _doingMovePOV = false;
- private _positionStartVec : Vector3; // for lerp(ing) when NOT also rotating too
- private _positionEndVec : Vector3; // for lerp(ing) when NOT also rotating too
- private _fullAmtRight : number; // for when also rotating
- private _fullAmtUp : number; // for when also rotating
- private _fullAmtForward : number; // for when also rotating
- private _amtRightSoFar : number; // for when also rotating
- private _amtUpSoFar : number; // for when also rotating
- private _amtForwardSoFar : number; // for when also rotating
-
- // misc
- private _activeLockedCamera : any = null; // any, or would require casting to FreeCamera & no point in JavaScript
- private _mirrorAxis = -1; // when in use x = 1, y = 2, z = 3
-
- /**
- * @param {Automaton} _automaton - reference of Automaton this ShapeKeyGroup is a part of
- * @param {String} _name - Name of the Key Group, upper case only
- * @param {Array} affectedPositionElements - index of either an x, y, or z of positions. Not all 3 of a vertex need be present. Ascending order.
- * @param {Array} basisState - original state of the affectedPositionElements of positions
- */
- constructor(private _automaton : Automaton, private _name : string, affectedPositionElements : Array<number>, basisState : Array<number>){
- if (!(affectedPositionElements instanceof Array) || affectedPositionElements.length === 0 ) throw "ShapeKeyGroup: invalid affectedPositionElements arg";
- if (!(basisState instanceof Array) || basisState.length !== affectedPositionElements.length) throw "ShapeKeyGroup: invalid basisState arg";
- // validation that position elements are in ascending order; normals relies on this being true
- this._affectedPositionElements = new Uint16Array(affectedPositionElements);
- this._nPosElements = affectedPositionElements.length;
- for (var i = 0; i + 1 < this._nPosElements; i++)
- if (!(this._affectedPositionElements[i] < this._affectedPositionElements[i + 1])) throw "ShapeKeyGroup: affectedPositionElements must be in ascending order";
-
- // initialize 2 position reusables, the size needed
- this._reusablePositionFinals.push(new Float32Array(this._nPosElements));
- this._reusablePositionFinals.push(new Float32Array(this._nPosElements));
-
- // determine affectedVertices for updating cooresponding normals
- var affectedVert = new Array<number>(); // final size unknown, so use a push-able array & convert to Uint16Array at end
- var vertIdx = -1;
- var nextVertIdx : number;
-
- // go through each position element
- for (var i = 0; i < this._nPosElements; i++){
- // the vertex index is 1/3 the position element index
- nextVertIdx = Math.floor(this._affectedPositionElements[i] / 3);
-
- // since position element indexes in ascending order, check if vertex not already added by the x, or y elements
- if (vertIdx !== nextVertIdx){
- vertIdx = nextVertIdx;
- affectedVert.push(vertIdx);
- }
- }
- this._affectedVertices = new Uint16Array(affectedVert);
- this._nVertices = this._affectedVertices.length;
-
- // initialize 2 normal reusables, the size needed
- this._reusableNormalFinals.push(new Float32Array(this._nVertices * 3));
- this._reusableNormalFinals.push(new Float32Array(this._nVertices * 3));
-
- // push 'BASIS' to _states & _stateNames, then initialize _currFinalVals to 'BASIS' state
- this.addShapeKey("BASIS", basisState);
- this._currFinalPositionVals = this._states [0];
- this._currFinalNormalVals = this._normals[0];
- }
- // =============================== Shape-Key adding & deriving ===============================
- private getDerivedName(referenceIdx : number, endStateIdx : number, endStateRatio : number) : string{
- return referenceIdx + "-" + endStateIdx + "@" + endStateRatio;
- }
- /**
- * add a derived key from the data contained in a deformation; wrapper for addDerivedKey()
- * @param {ReferenceDeformation} deformation - mined for its reference & end state names, and end state ratio
- */
- public addDerivedKeyFromDeformation(deformation : ReferenceDeformation) : void{
- this.addDerivedKey(deformation.getReferenceStateName(), deformation.getEndStateName(), deformation.getEndStateRatio());
- }
-
- /**
- * add a derived key from the arguments
- * @param {string} referenceStateName - Name of the reference state to be based on
- * @param {string} endStateName - Name of the end state to be based on
- * @param {number} endStateRatio - Unvalidated, but if -1 < or > 1, then can never be called, since Deformation validates
- */
- public addDerivedKey(referenceStateName : string, endStateName : string, endStateRatio : number) : void{
- var referenceIdx = this.getIdxForState(referenceStateName.toUpperCase());
- var endStateIdx = this.getIdxForState(endStateName .toUpperCase());
- if (referenceIdx === -1 || endStateIdx === -1) throw "ShapeKeyGroup: invalid source state name(s)";
- if (endStateRatio === 1) throw "ShapeKeyGroup: deriving a shape key where the endStateRatio is 1 is pointless";
-
- var stateName = this.getDerivedName(referenceIdx, endStateIdx, endStateRatio);
- var stateKey = new Float32Array(this._nPosElements);
- this.buildPosEndPoint(stateKey, referenceIdx, endStateIdx, endStateRatio);
- this.addShapeKeyInternal(stateName, stateKey);
- }
-
- /** called in construction code from TOB, but outside the constructor, except for 'BASIS'. Unlikely to be called by application code. */
- public addShapeKey(stateName : string, stateKey : Array<number>) : void {
- if (!(stateKey instanceof Array) || stateKey.length !== this._nPosElements) throw "ShapeKeyGroup: invalid stateKey arg";
- this.addShapeKeyInternal(stateName, new Float32Array(stateKey) );
- }
- /** worker method for both the addShapeKey() & addDerivedKey() methods */
- private addShapeKeyInternal(stateName : string, stateKey : Float32Array) : void {
- if (typeof stateName !== 'string' || stateName.length === 0) throw "ShapeKeyGroup: invalid stateName arg";
- if (this.getIdxForState(stateName) !== -1) throw "ShapeKeyGroup: stateName " + stateName + " is a duplicate";
- this._states.push(stateKey);
- this._stateNames.push(stateName);
-
- var coorespondingNormals = new Float32Array(this._nVertices * 3);
- this.buildNormEndPoint(coorespondingNormals, stateKey);
- this._normals.push(coorespondingNormals);
- if (this._automaton.debug) console.log("Shape key: " + stateName + " added to group: " + this._name + " on Automaton: " + this._automaton.name);
- }
- // =================================== inside before render ==================================
- /**
- * Called by the beforeRender() registered by this._automaton
- * @param {Float32Array} positions - Array of the positions for the entire mesh, portion updated based on _affectedIndices
- * @param {Float32Array } normals - Array of the normals for the entire mesh, if not null, portion updated based on _affectedVertices
- */
- public incrementallyDeform(positions : Float32Array, normals :Float32Array) : boolean {
- // series level of processing; get another series from the queue when none or last is done
- if (this._currentSeries === null || !this._currentSeries.hasMoreEvents() ){
- if (! this._nextEventSeries()) return false;
- }
-
- // ok, have an active event series, now get the next deformation in series if required
- while (this._currentStepInSeries === null || this._currentStepInSeries.isComplete() ){
- var next : any = this._currentSeries.nextEvent(this._name);
-
- if (next === null) return false; // being blocked, this must be a multi-group series, not ready for us
- if (next instanceof Action){
- (<Action> next).execute(ActionEvent.CreateNew(this._automaton));
- }
- else if (typeof next === "function"){
- next.call();
- }
- else{
- this._nextDeformation(<ReferenceDeformation> next); // must be a new deformation. _currentStepInSeries assigned if valid
- }
- }
-
- // have a deformation to process
- var ratioComplete = this._currentStepInSeries.getCompletionMilestone();
- if (ratioComplete < 0) return false; // Deformation.BLOCKED or Deformation.WAITING
-
- // update the positions
- for (var i = 0; i < this._nPosElements; i++){
- positions[this._affectedPositionElements[i]] = this._priorFinalPositionVals[i] + ((this._currFinalPositionVals[i] - this._priorFinalPositionVals[i]) * ratioComplete);
- }
-
- // update the normals
- var mIdx : number, kIdx : number;
- for (var i = 0; i < this._nVertices; i++){
- mIdx = 3 * this._affectedVertices[i] // offset for this vertex in the entire mesh
- kIdx = 3 * i; // offset for this vertex in the shape key group
- normals[mIdx ] = this._priorFinalNormalVals[kIdx ] + ((this._currFinalNormalVals[kIdx ] - this._priorFinalNormalVals[kIdx ]) * ratioComplete);
- normals[mIdx + 1] = this._priorFinalNormalVals[kIdx + 1] + ((this._currFinalNormalVals[kIdx + 1] - this._priorFinalNormalVals[kIdx + 1]) * ratioComplete);
- normals[mIdx + 2] = this._priorFinalNormalVals[kIdx + 2] + ((this._currFinalNormalVals[kIdx + 2] - this._priorFinalNormalVals[kIdx + 2]) * ratioComplete);
- }
-
- if (this._doingRotation){
- this._automaton.rotation = BABYLON.Vector3.Lerp(this._rotationStartVec, this._rotationEndVec, ratioComplete);
- }
-
- if (this._doingMovePOV === true){
- if (this._doingRotation){
- // some of these amounts, could be negative, if has a Pace with a hiccup
- var amtRight = (this._fullAmtRight * ratioComplete) - this._amtRightSoFar;
- var amtUp = (this._fullAmtUp * ratioComplete) - this._amtUpSoFar;
- var amtForward = (this._fullAmtForward * ratioComplete) - this._amtForwardSoFar;
-
- this._automaton.movePOV(amtRight, amtUp, amtForward);
-
- this._amtRightSoFar += amtRight;
- this._amtUpSoFar += amtUp;
- this._amtForwardSoFar += amtForward;
- }else{
- this._automaton.position = BABYLON.Vector3.Lerp(this._positionStartVec, this._positionEndVec, ratioComplete);
- }
-
- if (this._activeLockedCamera !== null) this._activeLockedCamera._getViewMatrix();
- }
- this._endOfLastFrameTs = Automaton.now();
- return true;
- }
-
- public resumePlay() : void {
- if (this._currentStepInSeries !== null) this._currentStepInSeries.resumePlay();
- }
- // ============================ Event Series Queueing & retrieval ============================
- public queueEventSeries(eSeries : AutomatonEventSeries) :void {
- this._queue.push(eSeries);
- }
-
- private _nextEventSeries() : boolean {
- var ret = this._queue.length > 0;
- if (ret){
- this._currentSeries = this._queue.shift();
- this._currentSeries.activate(this._name);
- }
- return ret;
- }
- // ===================================== deformation prep ====================================
- private _nextDeformation(deformation : ReferenceDeformation) : void {
- // do this as soon as possible to get the clock started, retroactively, when sole group in the series, and within 50 millis of last deform
- var lateStart = Automaton.now() - this._endOfLastFrameTs;
- deformation.activate((this._currentSeries.nGroups === 1 && lateStart - this._endOfLastFrameTs < 50) ? lateStart : 0);
-
- this._currentStepInSeries = deformation;
- this._priorFinalPositionVals = this._currFinalPositionVals;
- this._priorFinalNormalVals = this._currFinalNormalVals ;
-
- var referenceIdx = this.getIdxForState(deformation.getReferenceStateName() );
- var endStateIdx = this.getIdxForState(deformation.getEndStateName () );
- if (referenceIdx === -1 || endStateIdx === -1) throw "ShapeKeyGroup " + this._name + ": invalid deformation, source state name(s) not found";
- var endStateRatio = deformation.getEndStateRatio();
- if (endStateRatio < 0 && this._mirrorAxis === -1) throw "ShapeKeyGroup " + this._name + ": invalid deformation, negative end state ratios when not mirroring";
-
- // when endStateRatio is 1 or 0, just assign _currFinalVals directly from _states
- if (endStateRatio === 1 || endStateRatio === 0){
- if (endStateRatio === 0) endStateIdx = referenceIdx; // really just the reference when 0
- this._currFinalPositionVals = this._states [endStateIdx];
- this._currFinalNormalVals = this._normals[endStateIdx];
- }else{
- // check there was not a pre-built derived key to assign
- var derivedIdx = this.getIdxForState(this.getDerivedName(referenceIdx, endStateIdx, endStateRatio) );
- if (derivedIdx !== -1){
- this._currFinalPositionVals = this._states [derivedIdx];
- this._currFinalNormalVals = this._normals[derivedIdx];
- } else{
- // need to build _currFinalVals, toggling the _lastReusableUsed
- this._lastReusablePosUsed = (this._lastReusablePosUsed === 1) ? 0 : 1;
- this.buildPosEndPoint(this._reusablePositionFinals[this._lastReusablePosUsed], referenceIdx, endStateIdx, endStateRatio, this._automaton.debug);
- this._currFinalPositionVals = this._reusablePositionFinals[this._lastReusablePosUsed];
-
- // need to build _currFinalNormalVals, toggling the _lastReusableUsed
- this._lastReusableNormUsed = (this._lastReusableNormUsed === 1) ? 0 : 1;
- this.buildNormEndPoint(this._reusableNormalFinals[this._lastReusableNormUsed], this._currFinalPositionVals);
- this._currFinalNormalVals = this._reusableNormalFinals[this._lastReusableNormUsed];
- }
- }
- // prepare for rotation, if deformation calls for
- this._doingRotation = deformation.rotatePOV !== null;
- if (this._doingRotation){
- this._rotationStartVec = this._automaton.rotation; // no clone required, since Lerp() returns a new Vec3 written over .rotation
- this._rotationEndVec = this._rotationStartVec.add(this._automaton.calcRotatePOV(deformation.rotatePOV.x, deformation.rotatePOV.y, deformation.rotatePOV.z));
- }
-
- // prepare for POV move, if deformation calls for
- this._doingMovePOV = deformation.movePOV !== null;
- if (this._doingMovePOV){
- this._fullAmtRight = deformation.movePOV.x; this._amtRightSoFar = 0;
- this._fullAmtUp = deformation.movePOV.y; this._amtUpSoFar = 0;
- this._fullAmtForward = deformation.movePOV.z; this._amtForwardSoFar = 0;
-
- // less resources to calcMovePOV() once then Lerp(), but calcMovePOV() uses rotation, so can only go fast when not rotating too
- if (!this._doingRotation){
- this._positionStartVec = this._automaton.position; // no clone required, since Lerp() returns a new Vec3 written over .position
- this._positionEndVec = this._positionStartVec.add(this._automaton.calcMovePOV(this._fullAmtRight, this._fullAmtUp, this._fullAmtForward));
- }
- }
-
- // determine if camera needs to be woke up for tracking
- this._activeLockedCamera = null; // assigned for failure
-
- if (this._doingRotation || this._doingMovePOV){
- var activeCamera = <any> this._automaton.getScene().activeCamera;
- if(activeCamera.lockedTarget && activeCamera.lockedTarget === this._automaton)
- this._activeLockedCamera = activeCamera;
- }
- }
- /**
- * Called by addShapeKeyInternal() & _nextDeformation() to build the positions for an end point
- * @param {Float32Array} targetArray - location of output. One of the _reusablePositionFinals for _nextDeformation(). Bound for: _states[], if addShapeKeyInternal().
- * @param {number} referenceIdx - the index into _states[] to use as a reference
- * @param {number} endStateIdx - the index into _states[] to use as a target
- * @param {number} endStateRatio - the ratio of the target state to achive, relative to the reference state
- * @param {boolean} log - write console message of action, when true (Default false)
- *
- */
- private buildPosEndPoint(targetArray : Float32Array, referenceIdx: number, endStateIdx : number, endStateRatio : number, log = false) : void {
- var refEndState = this._states[referenceIdx];
- var newEndState = this._states[endStateIdx];
-
- // compute each of the new final values of positions
- var deltaToRefState : number;
- for (var i = 0; i < this._nPosElements; i++){
- deltaToRefState = (newEndState[i] - refEndState[i]) * endStateRatio;
-
- // reverse sign on appropriate elements of referenceDelta when ratio neg & mirroring
- if (endStateRatio < 0 && this._mirrorAxis !== (i + 1) % 3){
- deltaToRefState *= -1;
- }
- targetArray[i] = refEndState[i] + deltaToRefState;
- }
- if (log) console.log(this._name + " end Point built for referenceIdx: " + referenceIdx + ", endStateIdx: " + endStateIdx + ", endStateRatio: " + endStateRatio);
- }
-
- /**
- * Called by addShapeKeyInternal() & _nextDeformation() to build the normals for an end point
- * @param {Float32Array} targetArray - location of output. One of the _reusableNormalFinals for _nextDeformation(). Bound for: _normals[], if addShapeKeyInternal().
- * @param {Float32Array} endStatePos - postion data to build the normals for. Output from buildPosEndPoint, or data passed in from addShapeKey()
- */
- private buildNormEndPoint(targetArray : Float32Array, endStatePos : Float32Array) : void {
- // build a full, mesh sized, set of positions & populate with the left-over initial data
- var futurePos = new Float32Array(this._automaton.getVerticesData(VertexBuffer.PositionKind));
-
- // populate the changes that this state has
- for (var i = 0; i < this._nPosElements; i++){
- futurePos[this._affectedPositionElements[i]] = endStatePos[i];
- }
-
- // compute using method in _automaton
- this._automaton.normalsforVerticesInPlace(this._affectedVertices, targetArray, futurePos);
- }
- // ==================================== Getters & setters ====================================
- private getIdxForState(stateName : string) : number{
- for (var i = this._stateNames.length - 1; i >= 0; i--){
- if (this._stateNames[i] === stateName){
- return i;
- }
- }
- return -1;
- }
- public getName() : string { return this._name; }
- public getNPosElements() : number { return this._nPosElements; }
- public getNStates() : number { return this._stateNames.length; }
- public toString() : string { return 'ShapeKeyGroup: ' + this._name + ', n position elements: ' + this._nPosElements + ',\nStates: ' + this._stateNames; }
-
- public mirrorAxisOnX() : void {this._mirrorAxis = 1;}
- public mirrorAxisOnY() : void {this._mirrorAxis = 2;}
- public mirrorAxisOnZ() : void {this._mirrorAxis = 3;}
- }
- }
|