babylon.shapeKeyGroup.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. module BABYLON {
  2. export class ShapeKeyGroup {
  3. // position elements converted to typed array
  4. private _affectedPositionElements : Uint16Array;
  5. private _nPosElements : number;
  6. // arrays for the storage of each state
  7. private _states = new Array<Float32Array>();
  8. private _normals = new Array<Float32Array>();
  9. private _stateNames = new Array<string>();
  10. // event series queue & reference vars for current seris & step within
  11. private _queue = new Array<AutomatonEventSeries>();
  12. private _currentSeries : AutomatonEventSeries = null;
  13. private _currentStepInSeries : ReferenceDeformation = null;
  14. private _endOfLastFrameTs = -1;
  15. // affected vertices are used for normals, since all the entire vertex is involved, even if only the x of a position is affected
  16. private _affectedVertices : Uint16Array;
  17. private _nVertices;
  18. // reference vars for the current & prior deformation; assigned either an item of (_states / _normals) or one of the reusables
  19. private _currFinalPositionVals : Float32Array;
  20. private _priorFinalPositionVals : Float32Array;
  21. private _currFinalNormalVals : Float32Array;
  22. private _priorFinalNormalVals : Float32Array;
  23. // typed arrays are more expense to create, pre-allocate pairs for reuse
  24. private _reusablePositionFinals = new Array<Float32Array>();
  25. private _reusableNormalFinals = new Array<Float32Array>();
  26. private _lastReusablePosUsed = 0;
  27. private _lastReusableNormUsed = 0;
  28. // rotation control members
  29. private _doingRotation = false;
  30. private _rotationStartVec : Vector3;
  31. private _rotationEndVec : Vector3;
  32. // position control members
  33. private _doingMovePOV = false;
  34. private _positionStartVec : Vector3; // for lerp(ing) when NOT also rotating too
  35. private _positionEndVec : Vector3; // for lerp(ing) when NOT also rotating too
  36. private _fullAmtRight : number; // for when also rotating
  37. private _fullAmtUp : number; // for when also rotating
  38. private _fullAmtForward : number; // for when also rotating
  39. private _amtRightSoFar : number; // for when also rotating
  40. private _amtUpSoFar : number; // for when also rotating
  41. private _amtForwardSoFar : number; // for when also rotating
  42. // misc
  43. private _activeLockedCamera : any = null; // any, or would require casting to FreeCamera & no point in JavaScript
  44. private _mirrorAxis = -1; // when in use x = 1, y = 2, z = 3
  45. /**
  46. * @param {Automaton} _automaton - reference of Automaton this ShapeKeyGroup is a part of
  47. * @param {String} _name - Name of the Key Group, upper case only
  48. * @param {Array} affectedPositionElements - index of either an x, y, or z of positions. Not all 3 of a vertex need be present. Ascending order.
  49. * @param {Array} basisState - original state of the affectedPositionElements of positions
  50. */
  51. constructor(private _automaton : Automaton, private _name : string, affectedPositionElements : Array<number>, basisState : Array<number>){
  52. if (!(affectedPositionElements instanceof Array) || affectedPositionElements.length === 0 ) throw "ShapeKeyGroup: invalid affectedPositionElements arg";
  53. if (!(basisState instanceof Array) || basisState.length !== affectedPositionElements.length) throw "ShapeKeyGroup: invalid basisState arg";
  54. // validation that position elements are in ascending order; normals relies on this being true
  55. this._affectedPositionElements = new Uint16Array(affectedPositionElements);
  56. this._nPosElements = affectedPositionElements.length;
  57. for (var i = 0; i + 1 < this._nPosElements; i++)
  58. if (!(this._affectedPositionElements[i] < this._affectedPositionElements[i + 1])) throw "ShapeKeyGroup: affectedPositionElements must be in ascending order";
  59. // initialize 2 position reusables, the size needed
  60. this._reusablePositionFinals.push(new Float32Array(this._nPosElements));
  61. this._reusablePositionFinals.push(new Float32Array(this._nPosElements));
  62. // determine affectedVertices for updating cooresponding normals
  63. var affectedVert = new Array<number>(); // final size unknown, so use a push-able array & convert to Uint16Array at end
  64. var vertIdx = -1;
  65. var nextVertIdx : number;
  66. // go through each position element
  67. for (var i = 0; i < this._nPosElements; i++){
  68. // the vertex index is 1/3 the position element index
  69. nextVertIdx = Math.floor(this._affectedPositionElements[i] / 3);
  70. // since position element indexes in ascending order, check if vertex not already added by the x, or y elements
  71. if (vertIdx !== nextVertIdx){
  72. vertIdx = nextVertIdx;
  73. affectedVert.push(vertIdx);
  74. }
  75. }
  76. this._affectedVertices = new Uint16Array(affectedVert);
  77. this._nVertices = this._affectedVertices.length;
  78. // initialize 2 normal reusables, the size needed
  79. this._reusableNormalFinals.push(new Float32Array(this._nVertices * 3));
  80. this._reusableNormalFinals.push(new Float32Array(this._nVertices * 3));
  81. // push 'BASIS' to _states & _stateNames, then initialize _currFinalVals to 'BASIS' state
  82. this.addShapeKey("BASIS", basisState);
  83. this._currFinalPositionVals = this._states [0];
  84. this._currFinalNormalVals = this._normals[0];
  85. }
  86. // =============================== Shape-Key adding & deriving ===============================
  87. private getDerivedName(referenceIdx : number, endStateIdx : number, endStateRatio : number) : string{
  88. return referenceIdx + "-" + endStateIdx + "@" + endStateRatio;
  89. }
  90. /**
  91. * add a derived key from the data contained in a deformation; wrapper for addDerivedKey()
  92. * @param {ReferenceDeformation} deformation - mined for its reference & end state names, and end state ratio
  93. */
  94. public addDerivedKeyFromDeformation(deformation : ReferenceDeformation) : void{
  95. this.addDerivedKey(deformation.getReferenceStateName(), deformation.getEndStateName(), deformation.getEndStateRatio());
  96. }
  97. /**
  98. * add a derived key from the arguments
  99. * @param {string} referenceStateName - Name of the reference state to be based on
  100. * @param {string} endStateName - Name of the end state to be based on
  101. * @param {number} endStateRatio - Unvalidated, but if -1 < or > 1, then can never be called, since Deformation validates
  102. */
  103. public addDerivedKey(referenceStateName : string, endStateName : string, endStateRatio : number) : void{
  104. var referenceIdx = this.getIdxForState(referenceStateName.toUpperCase());
  105. var endStateIdx = this.getIdxForState(endStateName .toUpperCase());
  106. if (referenceIdx === -1 || endStateIdx === -1) throw "ShapeKeyGroup: invalid source state name(s)";
  107. if (endStateRatio === 1) throw "ShapeKeyGroup: deriving a shape key where the endStateRatio is 1 is pointless";
  108. var stateName = this.getDerivedName(referenceIdx, endStateIdx, endStateRatio);
  109. var stateKey = new Float32Array(this._nPosElements);
  110. this.buildPosEndPoint(stateKey, referenceIdx, endStateIdx, endStateRatio);
  111. this.addShapeKeyInternal(stateName, stateKey);
  112. }
  113. /** called in construction code from TOB, but outside the constructor, except for 'BASIS'. Unlikely to be called by application code. */
  114. public addShapeKey(stateName : string, stateKey : Array<number>) : void {
  115. if (!(stateKey instanceof Array) || stateKey.length !== this._nPosElements) throw "ShapeKeyGroup: invalid stateKey arg";
  116. this.addShapeKeyInternal(stateName, new Float32Array(stateKey) );
  117. }
  118. /** worker method for both the addShapeKey() & addDerivedKey() methods */
  119. private addShapeKeyInternal(stateName : string, stateKey : Float32Array) : void {
  120. if (typeof stateName !== 'string' || stateName.length === 0) throw "ShapeKeyGroup: invalid stateName arg";
  121. if (this.getIdxForState(stateName) !== -1) throw "ShapeKeyGroup: stateName " + stateName + " is a duplicate";
  122. this._states.push(stateKey);
  123. this._stateNames.push(stateName);
  124. var coorespondingNormals = new Float32Array(this._nVertices * 3);
  125. this.buildNormEndPoint(coorespondingNormals, stateKey);
  126. this._normals.push(coorespondingNormals);
  127. if (this._automaton.debug) console.log("Shape key: " + stateName + " added to group: " + this._name + " on Automaton: " + this._automaton.name);
  128. }
  129. // =================================== inside before render ==================================
  130. /**
  131. * Called by the beforeRender() registered by this._automaton
  132. * @param {Float32Array} positions - Array of the positions for the entire mesh, portion updated based on _affectedIndices
  133. * @param {Float32Array } normals - Array of the normals for the entire mesh, if not null, portion updated based on _affectedVertices
  134. */
  135. public incrementallyDeform(positions : Float32Array, normals :Float32Array) : boolean {
  136. // series level of processing; get another series from the queue when none or last is done
  137. if (this._currentSeries === null || !this._currentSeries.hasMoreEvents() ){
  138. if (! this._nextEventSeries()) return false;
  139. }
  140. // ok, have an active event series, now get the next deformation in series if required
  141. while (this._currentStepInSeries === null || this._currentStepInSeries.isComplete() ){
  142. var next : any = this._currentSeries.nextEvent(this._name);
  143. if (next === null) return false; // being blocked, this must be a multi-group series, not ready for us
  144. if (next instanceof Action){
  145. (<Action> next).execute(ActionEvent.CreateNew(this._automaton));
  146. }
  147. else if (typeof next === "function"){
  148. next.call();
  149. }
  150. else{
  151. this._nextDeformation(<ReferenceDeformation> next); // must be a new deformation. _currentStepInSeries assigned if valid
  152. }
  153. }
  154. // have a deformation to process
  155. var ratioComplete = this._currentStepInSeries.getCompletionMilestone();
  156. if (ratioComplete < 0) return false; // Deformation.BLOCKED or Deformation.WAITING
  157. // update the positions
  158. for (var i = 0; i < this._nPosElements; i++){
  159. positions[this._affectedPositionElements[i]] = this._priorFinalPositionVals[i] + ((this._currFinalPositionVals[i] - this._priorFinalPositionVals[i]) * ratioComplete);
  160. }
  161. // update the normals
  162. var mIdx : number, kIdx : number;
  163. for (var i = 0; i < this._nVertices; i++){
  164. mIdx = 3 * this._affectedVertices[i] // offset for this vertex in the entire mesh
  165. kIdx = 3 * i; // offset for this vertex in the shape key group
  166. normals[mIdx ] = this._priorFinalNormalVals[kIdx ] + ((this._currFinalNormalVals[kIdx ] - this._priorFinalNormalVals[kIdx ]) * ratioComplete);
  167. normals[mIdx + 1] = this._priorFinalNormalVals[kIdx + 1] + ((this._currFinalNormalVals[kIdx + 1] - this._priorFinalNormalVals[kIdx + 1]) * ratioComplete);
  168. normals[mIdx + 2] = this._priorFinalNormalVals[kIdx + 2] + ((this._currFinalNormalVals[kIdx + 2] - this._priorFinalNormalVals[kIdx + 2]) * ratioComplete);
  169. }
  170. if (this._doingRotation){
  171. this._automaton.rotation = BABYLON.Vector3.Lerp(this._rotationStartVec, this._rotationEndVec, ratioComplete);
  172. }
  173. if (this._doingMovePOV === true){
  174. if (this._doingRotation){
  175. // some of these amounts, could be negative, if has a Pace with a hiccup
  176. var amtRight = (this._fullAmtRight * ratioComplete) - this._amtRightSoFar;
  177. var amtUp = (this._fullAmtUp * ratioComplete) - this._amtUpSoFar;
  178. var amtForward = (this._fullAmtForward * ratioComplete) - this._amtForwardSoFar;
  179. this._automaton.movePOV(amtRight, amtUp, amtForward);
  180. this._amtRightSoFar += amtRight;
  181. this._amtUpSoFar += amtUp;
  182. this._amtForwardSoFar += amtForward;
  183. }else{
  184. this._automaton.position = BABYLON.Vector3.Lerp(this._positionStartVec, this._positionEndVec, ratioComplete);
  185. }
  186. if (this._activeLockedCamera !== null) this._activeLockedCamera._getViewMatrix();
  187. }
  188. this._endOfLastFrameTs = Automaton.now();
  189. return true;
  190. }
  191. public resumePlay() : void {
  192. if (this._currentStepInSeries !== null) this._currentStepInSeries.resumePlay();
  193. }
  194. // ============================ Event Series Queueing & retrieval ============================
  195. public queueEventSeries(eSeries : AutomatonEventSeries) :void {
  196. this._queue.push(eSeries);
  197. }
  198. private _nextEventSeries() : boolean {
  199. var ret = this._queue.length > 0;
  200. if (ret){
  201. this._currentSeries = this._queue.shift();
  202. this._currentSeries.activate(this._name);
  203. }
  204. return ret;
  205. }
  206. // ===================================== deformation prep ====================================
  207. private _nextDeformation(deformation : ReferenceDeformation) : void {
  208. // 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
  209. var lateStart = Automaton.now() - this._endOfLastFrameTs;
  210. deformation.activate((this._currentSeries.nGroups === 1 && lateStart - this._endOfLastFrameTs < 50) ? lateStart : 0);
  211. this._currentStepInSeries = deformation;
  212. this._priorFinalPositionVals = this._currFinalPositionVals;
  213. this._priorFinalNormalVals = this._currFinalNormalVals ;
  214. var referenceIdx = this.getIdxForState(deformation.getReferenceStateName() );
  215. var endStateIdx = this.getIdxForState(deformation.getEndStateName () );
  216. if (referenceIdx === -1 || endStateIdx === -1) throw "ShapeKeyGroup " + this._name + ": invalid deformation, source state name(s) not found";
  217. var endStateRatio = deformation.getEndStateRatio();
  218. if (endStateRatio < 0 && this._mirrorAxis === -1) throw "ShapeKeyGroup " + this._name + ": invalid deformation, negative end state ratios when not mirroring";
  219. // when endStateRatio is 1 or 0, just assign _currFinalVals directly from _states
  220. if (endStateRatio === 1 || endStateRatio === 0){
  221. if (endStateRatio === 0) endStateIdx = referenceIdx; // really just the reference when 0
  222. this._currFinalPositionVals = this._states [endStateIdx];
  223. this._currFinalNormalVals = this._normals[endStateIdx];
  224. }else{
  225. // check there was not a pre-built derived key to assign
  226. var derivedIdx = this.getIdxForState(this.getDerivedName(referenceIdx, endStateIdx, endStateRatio) );
  227. if (derivedIdx !== -1){
  228. this._currFinalPositionVals = this._states [derivedIdx];
  229. this._currFinalNormalVals = this._normals[derivedIdx];
  230. } else{
  231. // need to build _currFinalVals, toggling the _lastReusableUsed
  232. this._lastReusablePosUsed = (this._lastReusablePosUsed === 1) ? 0 : 1;
  233. this.buildPosEndPoint(this._reusablePositionFinals[this._lastReusablePosUsed], referenceIdx, endStateIdx, endStateRatio, this._automaton.debug);
  234. this._currFinalPositionVals = this._reusablePositionFinals[this._lastReusablePosUsed];
  235. // need to build _currFinalNormalVals, toggling the _lastReusableUsed
  236. this._lastReusableNormUsed = (this._lastReusableNormUsed === 1) ? 0 : 1;
  237. this.buildNormEndPoint(this._reusableNormalFinals[this._lastReusableNormUsed], this._currFinalPositionVals);
  238. this._currFinalNormalVals = this._reusableNormalFinals[this._lastReusableNormUsed];
  239. }
  240. }
  241. // prepare for rotation, if deformation calls for
  242. this._doingRotation = deformation.rotatePOV !== null;
  243. if (this._doingRotation){
  244. this._rotationStartVec = this._automaton.rotation; // no clone required, since Lerp() returns a new Vec3 written over .rotation
  245. this._rotationEndVec = this._rotationStartVec.add(this._automaton.calcRotatePOV(deformation.rotatePOV.x, deformation.rotatePOV.y, deformation.rotatePOV.z));
  246. }
  247. // prepare for POV move, if deformation calls for
  248. this._doingMovePOV = deformation.movePOV !== null;
  249. if (this._doingMovePOV){
  250. this._fullAmtRight = deformation.movePOV.x; this._amtRightSoFar = 0;
  251. this._fullAmtUp = deformation.movePOV.y; this._amtUpSoFar = 0;
  252. this._fullAmtForward = deformation.movePOV.z; this._amtForwardSoFar = 0;
  253. // less resources to calcMovePOV() once then Lerp(), but calcMovePOV() uses rotation, so can only go fast when not rotating too
  254. if (!this._doingRotation){
  255. this._positionStartVec = this._automaton.position; // no clone required, since Lerp() returns a new Vec3 written over .position
  256. this._positionEndVec = this._positionStartVec.add(this._automaton.calcMovePOV(this._fullAmtRight, this._fullAmtUp, this._fullAmtForward));
  257. }
  258. }
  259. // determine if camera needs to be woke up for tracking
  260. this._activeLockedCamera = null; // assigned for failure
  261. if (this._doingRotation || this._doingMovePOV){
  262. var activeCamera = <any> this._automaton.getScene().activeCamera;
  263. if(activeCamera.lockedTarget && activeCamera.lockedTarget === this._automaton)
  264. this._activeLockedCamera = activeCamera;
  265. }
  266. }
  267. /**
  268. * Called by addShapeKeyInternal() & _nextDeformation() to build the positions for an end point
  269. * @param {Float32Array} targetArray - location of output. One of the _reusablePositionFinals for _nextDeformation(). Bound for: _states[], if addShapeKeyInternal().
  270. * @param {number} referenceIdx - the index into _states[] to use as a reference
  271. * @param {number} endStateIdx - the index into _states[] to use as a target
  272. * @param {number} endStateRatio - the ratio of the target state to achive, relative to the reference state
  273. * @param {boolean} log - write console message of action, when true (Default false)
  274. *
  275. */
  276. private buildPosEndPoint(targetArray : Float32Array, referenceIdx: number, endStateIdx : number, endStateRatio : number, log = false) : void {
  277. var refEndState = this._states[referenceIdx];
  278. var newEndState = this._states[endStateIdx];
  279. // compute each of the new final values of positions
  280. var deltaToRefState : number;
  281. for (var i = 0; i < this._nPosElements; i++){
  282. deltaToRefState = (newEndState[i] - refEndState[i]) * endStateRatio;
  283. // reverse sign on appropriate elements of referenceDelta when ratio neg & mirroring
  284. if (endStateRatio < 0 && this._mirrorAxis !== (i + 1) % 3){
  285. deltaToRefState *= -1;
  286. }
  287. targetArray[i] = refEndState[i] + deltaToRefState;
  288. }
  289. if (log) console.log(this._name + " end Point built for referenceIdx: " + referenceIdx + ", endStateIdx: " + endStateIdx + ", endStateRatio: " + endStateRatio);
  290. }
  291. /**
  292. * Called by addShapeKeyInternal() & _nextDeformation() to build the normals for an end point
  293. * @param {Float32Array} targetArray - location of output. One of the _reusableNormalFinals for _nextDeformation(). Bound for: _normals[], if addShapeKeyInternal().
  294. * @param {Float32Array} endStatePos - postion data to build the normals for. Output from buildPosEndPoint, or data passed in from addShapeKey()
  295. */
  296. private buildNormEndPoint(targetArray : Float32Array, endStatePos : Float32Array) : void {
  297. // build a full, mesh sized, set of positions & populate with the left-over initial data
  298. var futurePos = new Float32Array(this._automaton.getVerticesData(VertexBuffer.PositionKind));
  299. // populate the changes that this state has
  300. for (var i = 0; i < this._nPosElements; i++){
  301. futurePos[this._affectedPositionElements[i]] = endStatePos[i];
  302. }
  303. // compute using method in _automaton
  304. this._automaton.normalsforVerticesInPlace(this._affectedVertices, targetArray, futurePos);
  305. }
  306. // ==================================== Getters & setters ====================================
  307. private getIdxForState(stateName : string) : number{
  308. for (var i = this._stateNames.length - 1; i >= 0; i--){
  309. if (this._stateNames[i] === stateName){
  310. return i;
  311. }
  312. }
  313. return -1;
  314. }
  315. public getName() : string { return this._name; }
  316. public getNPosElements() : number { return this._nPosElements; }
  317. public getNStates() : number { return this._stateNames.length; }
  318. public toString() : string { return 'ShapeKeyGroup: ' + this._name + ', n position elements: ' + this._nPosElements + ',\nStates: ' + this._stateNames; }
  319. public mirrorAxisOnX() : void {this._mirrorAxis = 1;}
  320. public mirrorAxisOnY() : void {this._mirrorAxis = 2;}
  321. public mirrorAxisOnZ() : void {this._mirrorAxis = 3;}
  322. }
  323. }