babylon.actionManager.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. module BABYLON {
  2. /**
  3. * ActionEvent is the event beint sent when an action is triggered.
  4. */
  5. export class ActionEvent {
  6. /**
  7. * @param source The mesh or sprite that triggered the action.
  8. * @param pointerX The X mouse cursor position at the time of the event
  9. * @param pointerY The Y mouse cursor position at the time of the event
  10. * @param meshUnderPointer The mesh that is currently pointed at (can be null)
  11. * @param sourceEvent the original (browser) event that triggered the ActionEvent
  12. */
  13. constructor(public source: any, public pointerX: number, public pointerY: number, public meshUnderPointer: Nullable<AbstractMesh>, public sourceEvent?: any, public additionalData?: any) {
  14. }
  15. /**
  16. * Helper function to auto-create an ActionEvent from a source mesh.
  17. * @param source The source mesh that triggered the event
  18. * @param evt {Event} The original (browser) event
  19. */
  20. public static CreateNew(source: AbstractMesh, evt?: Event, additionalData?: any): ActionEvent {
  21. var scene = source.getScene();
  22. return new ActionEvent(source, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt, additionalData);
  23. }
  24. /**
  25. * Helper function to auto-create an ActionEvent from a source mesh.
  26. * @param source The source sprite that triggered the event
  27. * @param scene Scene associated with the sprite
  28. * @param evt {Event} The original (browser) event
  29. */
  30. public static CreateNewFromSprite(source: Sprite, scene: Scene, evt?: Event, additionalData?: any): ActionEvent {
  31. return new ActionEvent(source, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt, additionalData);
  32. }
  33. /**
  34. * Helper function to auto-create an ActionEvent from a scene. If triggered by a mesh use ActionEvent.CreateNew
  35. * @param scene the scene where the event occurred
  36. * @param evt {Event} The original (browser) event
  37. */
  38. public static CreateNewFromScene(scene: Scene, evt: Event): ActionEvent {
  39. return new ActionEvent(null, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt);
  40. }
  41. public static CreateNewFromPrimitive(prim: any, pointerPos: Vector2, evt?: Event, additionalData?: any): ActionEvent {
  42. return new ActionEvent(prim, pointerPos.x, pointerPos.y, null, evt, additionalData);
  43. }
  44. }
  45. /**
  46. * Action Manager manages all events to be triggered on a given mesh or the global scene.
  47. * A single scene can have many Action Managers to handle predefined actions on specific meshes.
  48. */
  49. export class ActionManager {
  50. // Statics
  51. private static _NothingTrigger = 0;
  52. private static _OnPickTrigger = 1;
  53. private static _OnLeftPickTrigger = 2;
  54. private static _OnRightPickTrigger = 3;
  55. private static _OnCenterPickTrigger = 4;
  56. private static _OnPickDownTrigger = 5;
  57. private static _OnDoublePickTrigger = 6;
  58. private static _OnPickUpTrigger = 7;
  59. private static _OnLongPressTrigger = 8;
  60. private static _OnPointerOverTrigger = 9;
  61. private static _OnPointerOutTrigger = 10;
  62. private static _OnEveryFrameTrigger = 11;
  63. private static _OnIntersectionEnterTrigger = 12;
  64. private static _OnIntersectionExitTrigger = 13;
  65. private static _OnKeyDownTrigger = 14;
  66. private static _OnKeyUpTrigger = 15;
  67. private static _OnPickOutTrigger = 16;
  68. public static get NothingTrigger(): number {
  69. return ActionManager._NothingTrigger;
  70. }
  71. public static get OnPickTrigger(): number {
  72. return ActionManager._OnPickTrigger;
  73. }
  74. public static get OnLeftPickTrigger(): number {
  75. return ActionManager._OnLeftPickTrigger;
  76. }
  77. public static get OnRightPickTrigger(): number {
  78. return ActionManager._OnRightPickTrigger;
  79. }
  80. public static get OnCenterPickTrigger(): number {
  81. return ActionManager._OnCenterPickTrigger;
  82. }
  83. public static get OnPickDownTrigger(): number {
  84. return ActionManager._OnPickDownTrigger;
  85. }
  86. public static get OnDoublePickTrigger(): number {
  87. return ActionManager._OnDoublePickTrigger;
  88. }
  89. public static get OnPickUpTrigger(): number {
  90. return ActionManager._OnPickUpTrigger;
  91. }
  92. /// This trigger will only be raised if you also declared a OnPickDown
  93. public static get OnPickOutTrigger(): number {
  94. return ActionManager._OnPickOutTrigger;
  95. }
  96. public static get OnLongPressTrigger(): number {
  97. return ActionManager._OnLongPressTrigger;
  98. }
  99. public static get OnPointerOverTrigger(): number {
  100. return ActionManager._OnPointerOverTrigger;
  101. }
  102. public static get OnPointerOutTrigger(): number {
  103. return ActionManager._OnPointerOutTrigger;
  104. }
  105. public static get OnEveryFrameTrigger(): number {
  106. return ActionManager._OnEveryFrameTrigger;
  107. }
  108. public static get OnIntersectionEnterTrigger(): number {
  109. return ActionManager._OnIntersectionEnterTrigger;
  110. }
  111. public static get OnIntersectionExitTrigger(): number {
  112. return ActionManager._OnIntersectionExitTrigger;
  113. }
  114. public static get OnKeyDownTrigger(): number {
  115. return ActionManager._OnKeyDownTrigger;
  116. }
  117. public static get OnKeyUpTrigger(): number {
  118. return ActionManager._OnKeyUpTrigger;
  119. }
  120. public static Triggers: { [key: string]: number } = {};
  121. // Members
  122. public actions = new Array<Action>();
  123. public hoverCursor: string = '';
  124. private _scene: Scene;
  125. constructor(scene: Scene) {
  126. this._scene = scene;
  127. scene._actionManagers.push(this);
  128. }
  129. // Methods
  130. public dispose(): void {
  131. var index = this._scene._actionManagers.indexOf(this);
  132. for (var i = 0; i < this.actions.length; i++) {
  133. var action = this.actions[i];
  134. ActionManager.Triggers[action.trigger]--;
  135. if (ActionManager.Triggers[action.trigger] === 0) {
  136. delete ActionManager.Triggers[action.trigger]
  137. }
  138. }
  139. if (index > -1) {
  140. this._scene._actionManagers.splice(index, 1);
  141. }
  142. }
  143. public getScene(): Scene {
  144. return this._scene;
  145. }
  146. /**
  147. * Does this action manager handles actions of any of the given triggers
  148. * @param {number[]} triggers - the triggers to be tested
  149. * @return {boolean} whether one (or more) of the triggers is handeled
  150. */
  151. public hasSpecificTriggers(triggers: number[]): boolean {
  152. for (var index = 0; index < this.actions.length; index++) {
  153. var action = this.actions[index];
  154. if (triggers.indexOf(action.trigger) > -1) {
  155. return true;
  156. }
  157. }
  158. return false;
  159. }
  160. /**
  161. * Does this action manager handles actions of a given trigger
  162. * @param {number} trigger - the trigger to be tested
  163. * @return {boolean} whether the trigger is handeled
  164. */
  165. public hasSpecificTrigger(trigger: number): boolean {
  166. for (var index = 0; index < this.actions.length; index++) {
  167. var action = this.actions[index];
  168. if (action.trigger === trigger) {
  169. return true;
  170. }
  171. }
  172. return false;
  173. }
  174. /**
  175. * Does this action manager has pointer triggers
  176. * @return {boolean} whether or not it has pointer triggers
  177. */
  178. public get hasPointerTriggers(): boolean {
  179. for (var index = 0; index < this.actions.length; index++) {
  180. var action = this.actions[index];
  181. if (action.trigger >= ActionManager._OnPickTrigger && action.trigger <= ActionManager._OnPointerOutTrigger) {
  182. return true;
  183. }
  184. }
  185. return false;
  186. }
  187. /**
  188. * Does this action manager has pick triggers
  189. * @return {boolean} whether or not it has pick triggers
  190. */
  191. public get hasPickTriggers(): boolean {
  192. for (var index = 0; index < this.actions.length; index++) {
  193. var action = this.actions[index];
  194. if (action.trigger >= ActionManager._OnPickTrigger && action.trigger <= ActionManager._OnPickUpTrigger) {
  195. return true;
  196. }
  197. }
  198. return false;
  199. }
  200. /**
  201. * Does exist one action manager with at least one trigger
  202. * @return {boolean} whether or not it exists one action manager with one trigger
  203. **/
  204. public static get HasTriggers(): boolean {
  205. for (var t in ActionManager.Triggers) {
  206. if (ActionManager.Triggers.hasOwnProperty(t)) {
  207. return true;
  208. }
  209. }
  210. return false;
  211. }
  212. /**
  213. * Does exist one action manager with at least one pick trigger
  214. * @return {boolean} whether or not it exists one action manager with one pick trigger
  215. **/
  216. public static get HasPickTriggers(): boolean {
  217. for (var t in ActionManager.Triggers) {
  218. if (ActionManager.Triggers.hasOwnProperty(t)) {
  219. let t_int = parseInt(t);
  220. if (t_int >= ActionManager._OnPickTrigger && t_int <= ActionManager._OnPickUpTrigger) {
  221. return true;
  222. }
  223. }
  224. }
  225. return false;
  226. }
  227. /**
  228. * Does exist one action manager that handles actions of a given trigger
  229. * @param {number} trigger - the trigger to be tested
  230. * @return {boolean} whether the trigger is handeled by at least one action manager
  231. **/
  232. public static HasSpecificTrigger(trigger: number): boolean {
  233. for (var t in ActionManager.Triggers) {
  234. if (ActionManager.Triggers.hasOwnProperty(t)) {
  235. let t_int = parseInt(t);
  236. if (t_int === trigger) {
  237. return true;
  238. }
  239. }
  240. }
  241. return false;
  242. }
  243. /**
  244. * Registers an action to this action manager
  245. * @param {BABYLON.Action} action - the action to be registered
  246. * @return {BABYLON.Action} the action amended (prepared) after registration
  247. */
  248. public registerAction(action: Action): Nullable<Action> {
  249. if (action.trigger === ActionManager.OnEveryFrameTrigger) {
  250. if (this.getScene().actionManager !== this) {
  251. Tools.Warn("OnEveryFrameTrigger can only be used with scene.actionManager");
  252. return null;
  253. }
  254. }
  255. this.actions.push(action);
  256. if (ActionManager.Triggers[action.trigger]) {
  257. ActionManager.Triggers[action.trigger]++;
  258. }
  259. else {
  260. ActionManager.Triggers[action.trigger] = 1;
  261. }
  262. action._actionManager = this;
  263. action._prepare();
  264. return action;
  265. }
  266. /**
  267. * Unregisters an action to this action manager
  268. * @param action The action to be unregistered
  269. * @return whether the action has been unregistered
  270. */
  271. public unregisterAction(action: Action): Boolean {
  272. var index = this.actions.indexOf(action);
  273. if (index !== -1) {
  274. this.actions.splice(index, 1);
  275. ActionManager.Triggers[action.trigger] -= 1;
  276. if (ActionManager.Triggers[action.trigger] === 0) {
  277. delete ActionManager.Triggers[action.trigger]
  278. }
  279. delete action._actionManager;
  280. return true;
  281. }
  282. return false;
  283. }
  284. /**
  285. * Process a specific trigger
  286. * @param {number} trigger - the trigger to process
  287. * @param evt {BABYLON.ActionEvent} the event details to be processed
  288. */
  289. public processTrigger(trigger: number, evt?: ActionEvent): void {
  290. for (var index = 0; index < this.actions.length; index++) {
  291. var action = this.actions[index];
  292. if (action.trigger === trigger) {
  293. if (evt) {
  294. if (trigger === ActionManager.OnKeyUpTrigger
  295. || trigger === ActionManager.OnKeyDownTrigger) {
  296. var parameter = action.getTriggerParameter();
  297. if (parameter && parameter !== evt.sourceEvent.keyCode) {
  298. if (!parameter.toLowerCase) {
  299. continue;
  300. }
  301. var lowerCase = parameter.toLowerCase();
  302. if (lowerCase !== evt.sourceEvent.key) {
  303. var unicode = evt.sourceEvent.charCode ? evt.sourceEvent.charCode : evt.sourceEvent.keyCode;
  304. var actualkey = String.fromCharCode(unicode).toLowerCase();
  305. if (actualkey !== lowerCase) {
  306. continue;
  307. }
  308. }
  309. }
  310. }
  311. }
  312. action._executeCurrent(evt);
  313. }
  314. }
  315. }
  316. public _getEffectiveTarget(target: any, propertyPath: string): any {
  317. var properties = propertyPath.split(".");
  318. for (var index = 0; index < properties.length - 1; index++) {
  319. target = target[properties[index]];
  320. }
  321. return target;
  322. }
  323. public _getProperty(propertyPath: string): string {
  324. var properties = propertyPath.split(".");
  325. return properties[properties.length - 1];
  326. }
  327. public serialize(name: string): any {
  328. var root = {
  329. children: new Array(),
  330. name: name,
  331. type: 3, // Root node
  332. properties: new Array() // Empty for root but required
  333. };
  334. for (var i = 0; i < this.actions.length; i++) {
  335. var triggerObject = {
  336. type: 0, // Trigger
  337. children: new Array(),
  338. name: ActionManager.GetTriggerName(this.actions[i].trigger),
  339. properties: new Array()
  340. };
  341. var triggerOptions = this.actions[i].triggerOptions;
  342. if (triggerOptions && typeof triggerOptions !== "number") {
  343. if (triggerOptions.parameter instanceof Node) {
  344. triggerObject.properties.push(Action._GetTargetProperty(triggerOptions.parameter));
  345. }
  346. else {
  347. var parameter = <any>{};
  348. Tools.DeepCopy(triggerOptions.parameter, parameter, ["mesh"]);
  349. if (triggerOptions.parameter.mesh) {
  350. parameter._meshId = triggerOptions.parameter.mesh.id;
  351. }
  352. triggerObject.properties.push({ name: "parameter", targetType: null, value: parameter });
  353. }
  354. }
  355. // Serialize child action, recursively
  356. this.actions[i].serialize(triggerObject);
  357. // Add serialized trigger
  358. root.children.push(triggerObject);
  359. }
  360. return root;
  361. }
  362. public static Parse(parsedActions: any, object: Nullable<AbstractMesh>, scene: Scene) {
  363. var actionManager = new ActionManager(scene);
  364. if (object === null)
  365. scene.actionManager = actionManager;
  366. else
  367. object.actionManager = actionManager;
  368. // instanciate a new object
  369. var instanciate = (name: string, params: Array<any>): any => {
  370. // TODO: We will need to find a solution for the next line when using commonjs / es6 .
  371. var newInstance: Object = Object.create(Tools.Instantiate("BABYLON." + name).prototype);
  372. newInstance.constructor.apply(newInstance, params);
  373. return newInstance;
  374. };
  375. var parseParameter = (name: string, value: string, target: any, propertyPath: Nullable<string>): any => {
  376. if (propertyPath === null) {
  377. // String, boolean or float
  378. var floatValue = parseFloat(value);
  379. if (value === "true" || value === "false")
  380. return value === "true";
  381. else
  382. return isNaN(floatValue) ? value : floatValue;
  383. }
  384. var effectiveTarget = propertyPath.split(".");
  385. var values = value.split(",");
  386. // Get effective Target
  387. for (var i = 0; i < effectiveTarget.length; i++) {
  388. target = target[effectiveTarget[i]];
  389. }
  390. // Return appropriate value with its type
  391. if (typeof (target) === "boolean")
  392. return values[0] === "true";
  393. if (typeof (target) === "string")
  394. return values[0];
  395. // Parameters with multiple values such as Vector3 etc.
  396. var split = new Array<number>();
  397. for (var i = 0; i < values.length; i++)
  398. split.push(parseFloat(values[i]));
  399. if (target instanceof Vector3)
  400. return Vector3.FromArray(split);
  401. if (target instanceof Vector4)
  402. return Vector4.FromArray(split);
  403. if (target instanceof Color3)
  404. return Color3.FromArray(split);
  405. if (target instanceof Color4)
  406. return Color4.FromArray(split);
  407. return parseFloat(values[0]);
  408. };
  409. // traverse graph per trigger
  410. var traverse = (parsedAction: any, trigger: any, condition: Nullable<Condition>, action: Nullable<Action>, combineArray: Nullable<Array<Action>> = null) => {
  411. if (parsedAction.detached)
  412. return;
  413. var parameters = new Array<any>();
  414. var target: any = null;
  415. var propertyPath: Nullable<string> = null;
  416. var combine = parsedAction.combine && parsedAction.combine.length > 0;
  417. // Parameters
  418. if (parsedAction.type === 2)
  419. parameters.push(actionManager);
  420. else
  421. parameters.push(trigger);
  422. if (combine) {
  423. var actions = new Array<Action>();
  424. for (var j = 0; j < parsedAction.combine.length; j++) {
  425. traverse(parsedAction.combine[j], ActionManager.NothingTrigger, condition, action, actions);
  426. }
  427. parameters.push(actions);
  428. }
  429. else {
  430. for (var i = 0; i < parsedAction.properties.length; i++) {
  431. var value = parsedAction.properties[i].value;
  432. var name = parsedAction.properties[i].name;
  433. var targetType = parsedAction.properties[i].targetType;
  434. if (name === "target")
  435. if (targetType !== null && targetType === "SceneProperties")
  436. value = target = scene;
  437. else
  438. value = target = scene.getNodeByName(value);
  439. else if (name === "parent")
  440. value = scene.getNodeByName(value);
  441. else if (name === "sound")
  442. value = scene.getSoundByName(value);
  443. else if (name !== "propertyPath") {
  444. if (parsedAction.type === 2 && name === "operator")
  445. value = (<any>ValueCondition)[value];
  446. else
  447. value = parseParameter(name, value, target, name === "value" ? propertyPath : null);
  448. } else {
  449. propertyPath = value;
  450. }
  451. parameters.push(value);
  452. }
  453. }
  454. if (combineArray === null) {
  455. parameters.push(condition);
  456. }
  457. else {
  458. parameters.push(null);
  459. }
  460. // If interpolate value action
  461. if (parsedAction.name === "InterpolateValueAction") {
  462. var param = parameters[parameters.length - 2];
  463. parameters[parameters.length - 1] = param;
  464. parameters[parameters.length - 2] = condition;
  465. }
  466. // Action or condition(s) and not CombineAction
  467. var newAction = instanciate(parsedAction.name, parameters);
  468. if (newAction instanceof Condition && condition !== null) {
  469. var nothing = new DoNothingAction(trigger, condition);
  470. if (action)
  471. action.then(nothing);
  472. else
  473. actionManager.registerAction(nothing);
  474. action = nothing;
  475. }
  476. if (combineArray === null) {
  477. if (newAction instanceof Condition) {
  478. condition = newAction;
  479. newAction = action;
  480. } else {
  481. condition = null;
  482. if (action)
  483. action.then(newAction);
  484. else
  485. actionManager.registerAction(newAction);
  486. }
  487. }
  488. else {
  489. combineArray.push(newAction);
  490. }
  491. for (var i = 0; i < parsedAction.children.length; i++)
  492. traverse(parsedAction.children[i], trigger, condition, newAction, null);
  493. };
  494. // triggers
  495. for (var i = 0; i < parsedActions.children.length; i++) {
  496. var triggerParams: any;
  497. var trigger = parsedActions.children[i];
  498. if (trigger.properties.length > 0) {
  499. var param = trigger.properties[0].value;
  500. var value = trigger.properties[0].targetType === null ? param : scene.getMeshByName(param);
  501. if (value._meshId) {
  502. value.mesh = scene.getMeshByID(value._meshId);
  503. }
  504. triggerParams = { trigger: (<any>ActionManager)[trigger.name], parameter: value };
  505. }
  506. else
  507. triggerParams = (<any>ActionManager)[trigger.name];
  508. for (var j = 0; j < trigger.children.length; j++) {
  509. if (!trigger.detached)
  510. traverse(trigger.children[j], triggerParams, null, null);
  511. }
  512. }
  513. }
  514. public static GetTriggerName(trigger: number): string {
  515. switch (trigger) {
  516. case 0: return "NothingTrigger";
  517. case 1: return "OnPickTrigger";
  518. case 2: return "OnLeftPickTrigger";
  519. case 3: return "OnRightPickTrigger";
  520. case 4: return "OnCenterPickTrigger";
  521. case 5: return "OnPickDownTrigger";
  522. case 6: return "OnPickUpTrigger";
  523. case 7: return "OnLongPressTrigger";
  524. case 8: return "OnPointerOverTrigger";
  525. case 9: return "OnPointerOutTrigger";
  526. case 10: return "OnEveryFrameTrigger";
  527. case 11: return "OnIntersectionEnterTrigger";
  528. case 12: return "OnIntersectionExitTrigger";
  529. case 13: return "OnKeyDownTrigger";
  530. case 14: return "OnKeyUpTrigger";
  531. case 15: return "OnPickOutTrigger";
  532. default: return "";
  533. }
  534. }
  535. }
  536. }