babylon.prim2dBase.ts 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161
  1. module BABYLON {
  2. export class PrepareRender2DContext {
  3. constructor() {
  4. this.forceRefreshPrimitive = false;
  5. }
  6. /**
  7. * True if the primitive must be refreshed no matter what
  8. * This mode is needed because sometimes the primitive doesn't change by itself, but external changes make a refresh of its InstanceData necessary
  9. */
  10. forceRefreshPrimitive: boolean;
  11. }
  12. export class Render2DContext {
  13. constructor(renderMode: number) {
  14. this._renderMode = renderMode;
  15. this.useInstancing = false;
  16. this.groupInfoPartData = null;
  17. this.partDataStartIndex = this.partDataEndIndex = null;
  18. }
  19. /**
  20. * Define which render Mode should be used to render the primitive: one of Render2DContext.RenderModeXxxx property
  21. */
  22. get renderMode(): number {
  23. return this._renderMode;
  24. }
  25. /**
  26. * If true hardware instancing is supported and must be used for the rendering. The groupInfoPartData._partBuffer must be used.
  27. * If false rendering on a per primitive basis must be made. The following properties must be used
  28. * - groupInfoPartData._partData: contains the primitive instances data to render
  29. * - partDataStartIndex: the index into instanceArrayData of the first instance to render.
  30. * - partDataCount: the number of primitive to render
  31. */
  32. useInstancing: boolean;
  33. /**
  34. * Contains the data related to the primitives instances to render
  35. */
  36. groupInfoPartData: GroupInfoPartData[];
  37. /**
  38. * The index into groupInfoPartData._partData of the first primitive to render. This is an index, not an offset: it represent the nth primitive which is the first to render.
  39. */
  40. partDataStartIndex: number;
  41. /**
  42. * The exclusive end index, you have to render the primitive instances until you reach this one, but don't render this one!
  43. */
  44. partDataEndIndex: number;
  45. /**
  46. * The set of primitives to render is opaque.
  47. * This is the first rendering pass. All Opaque primitives are rendered. Depth Compare and Write are both enabled.
  48. */
  49. public static get RenderModeOpaque(): number {
  50. return Render2DContext._renderModeOpaque;
  51. }
  52. /**
  53. * The set of primitives to render is using Alpha Test (aka masking).
  54. * Alpha Blend is enabled, the AlphaMode must be manually set, the render occurs after the RenderModeOpaque and is depth independent (i.e. primitives are not sorted by depth). Depth Compare and Write are both enabled.
  55. */
  56. public static get RenderModeAlphaTest(): number {
  57. return Render2DContext._renderModeAlphaTest;
  58. }
  59. /**
  60. * The set of primitives to render is transparent.
  61. * Alpha Blend is enabled, the AlphaMode must be manually set, the render occurs after the RenderModeAlphaTest and is depth dependent (i.e. primitives are stored by depth and rendered back to front). Depth Compare is on, but Depth write is Off.
  62. */
  63. public static get RenderModeTransparent(): number {
  64. return Render2DContext._renderModeTransparent;
  65. }
  66. private static _renderModeOpaque: number = 1;
  67. private static _renderModeAlphaTest: number = 2;
  68. private static _renderModeTransparent: number = 3;
  69. private _renderMode: number;
  70. }
  71. /**
  72. * This class store information for the pointerEventObservable Observable.
  73. * The Observable is divided into many sub events (using the Mask feature of the Observable pattern): PointerOver, PointerEnter, PointerDown, PointerMouseWheel, PointerMove, PointerUp, PointerDown, PointerLeave, PointerGotCapture and PointerLostCapture.
  74. */
  75. export class PrimitivePointerInfo {
  76. private static _pointerOver = 0x0001;
  77. private static _pointerEnter = 0x0002;
  78. private static _pointerDown = 0x0004;
  79. private static _pointerMouseWheel = 0x0008;
  80. private static _pointerMove = 0x0010;
  81. private static _pointerUp = 0x0020;
  82. private static _pointerOut = 0x0040;
  83. private static _pointerLeave = 0x0080;
  84. private static _pointerGotCapture = 0x0100;
  85. private static _pointerLostCapture = 0x0200;
  86. private static _mouseWheelPrecision = 3.0;
  87. // The behavior is based on the HTML specifications of the Pointer Events (https://www.w3.org/TR/pointerevents/#list-of-pointer-events). This is not 100% compliant and not meant to be, but still, it's based on these specs for most use cases to be programmed the same way (as closest as possible) as it would have been in HTML.
  88. /**
  89. * This event type is raised when a pointing device is moved into the hit test boundaries of a primitive.
  90. * Bubbles: yes
  91. */
  92. public static get PointerOver(): number {
  93. return PrimitivePointerInfo._pointerOver;
  94. }
  95. /**
  96. * This event type is raised when a pointing device is moved into the hit test boundaries of a primitive or one of its descendants.
  97. * Bubbles: no
  98. */
  99. public static get PointerEnter(): number {
  100. return PrimitivePointerInfo._pointerEnter;
  101. }
  102. /**
  103. * This event type is raised when a pointer enters the active button state (non-zero value in the buttons property). For mouse it's when the device transitions from no buttons depressed to at least one button depressed. For touch/pen this is when a physical contact is made.
  104. * Bubbles: yes
  105. */
  106. public static get PointerDown(): number {
  107. return PrimitivePointerInfo._pointerDown;
  108. }
  109. /**
  110. * This event type is raised when the pointer is a mouse and it's wheel is rolling
  111. * Bubbles: yes
  112. */
  113. public static get PointerMouseWheel(): number {
  114. return PrimitivePointerInfo._pointerMouseWheel;
  115. }
  116. /**
  117. * This event type is raised when a pointer change coordinates or when a pointer changes button state, pressure, tilt, or contact geometry and the circumstances produce no other pointers events.
  118. * Bubbles: yes
  119. */
  120. public static get PointerMove(): number {
  121. return PrimitivePointerInfo._pointerMove;
  122. }
  123. /**
  124. * This event type is raised when the pointer leaves the active buttons states (zero value in the buttons property). For mouse, this is when the device transitions from at least one button depressed to no buttons depressed. For touch/pen, this is when physical contact is removed.
  125. * Bubbles: yes
  126. */
  127. public static get PointerUp(): number {
  128. return PrimitivePointerInfo._pointerUp;
  129. }
  130. /**
  131. * This event type is raised when a pointing device is moved out of the hit test the boundaries of a primitive.
  132. * Bubbles: yes
  133. */
  134. public static get PointerOut(): number {
  135. return PrimitivePointerInfo._pointerOut;
  136. }
  137. /**
  138. * This event type is raised when a pointing device is moved out of the hit test boundaries of a primitive and all its descendants.
  139. * Bubbles: no
  140. */
  141. public static get PointerLeave(): number {
  142. return PrimitivePointerInfo._pointerLeave;
  143. }
  144. /**
  145. * This event type is raised when a primitive receives the pointer capture. This event is fired at the element that is receiving pointer capture. Subsequent events for that pointer will be fired at this element.
  146. * Bubbles: yes
  147. */
  148. public static get PointerGotCapture(): number {
  149. return PrimitivePointerInfo._pointerGotCapture;
  150. }
  151. /**
  152. * This event type is raised after pointer capture is released for a pointer.
  153. * Bubbles: yes
  154. */
  155. public static get PointerLostCapture(): number {
  156. return PrimitivePointerInfo._pointerLostCapture;
  157. }
  158. public static get MouseWheelPrecision(): number {
  159. return PrimitivePointerInfo._mouseWheelPrecision;
  160. }
  161. /**
  162. * Event Type, one of the static PointerXXXX property defined above (PrimitivePointerInfo.PointerOver to PrimitivePointerInfo.PointerLostCapture)
  163. */
  164. eventType: number;
  165. /**
  166. * Position of the pointer relative to the bottom/left of the Canvas
  167. */
  168. canvasPointerPos: Vector2;
  169. /**
  170. * Position of the pointer relative to the bottom/left of the primitive that registered the Observer
  171. */
  172. primitivePointerPos: Vector2;
  173. /**
  174. * The primitive where the event was initiated first (in case of bubbling)
  175. */
  176. relatedTarget: Prim2DBase;
  177. /**
  178. * Position of the pointer relative to the bottom/left of the relatedTarget
  179. */
  180. relatedTargetPointerPos: Vector2;
  181. /**
  182. * An observable can set this property to true to stop bubbling on the upper levels
  183. */
  184. cancelBubble: boolean;
  185. /**
  186. * True if the Control keyboard key is down
  187. */
  188. ctrlKey: boolean;
  189. /**
  190. * true if the Shift keyboard key is down
  191. */
  192. shiftKey: boolean;
  193. /**
  194. * true if the Alt keyboard key is down
  195. */
  196. altKey: boolean;
  197. /**
  198. * true if the Meta keyboard key is down
  199. */
  200. metaKey: boolean;
  201. /**
  202. * For button, buttons, refer to https://www.w3.org/TR/pointerevents/#button-states
  203. */
  204. button: number;
  205. /**
  206. * For button, buttons, refer to https://www.w3.org/TR/pointerevents/#button-states
  207. */
  208. buttons: number;
  209. /**
  210. * The amount of mouse wheel rolled
  211. */
  212. mouseWheelDelta: number;
  213. /**
  214. * Id of the Pointer involved in the event
  215. */
  216. pointerId: number;
  217. width: number;
  218. height: number;
  219. presssure: number;
  220. tilt: Vector2;
  221. /**
  222. * true if the involved pointer is captured for a particular primitive, false otherwise.
  223. */
  224. isCaptured: boolean;
  225. constructor() {
  226. this.primitivePointerPos = Vector2.Zero();
  227. this.tilt = Vector2.Zero();
  228. this.cancelBubble = false;
  229. }
  230. updateRelatedTarget(prim: Prim2DBase, primPointerPos: Vector2) {
  231. this.relatedTarget = prim;
  232. this.relatedTargetPointerPos = primPointerPos;
  233. }
  234. public static getEventTypeName(mask: number): string {
  235. switch (mask) {
  236. case PrimitivePointerInfo.PointerOver: return "PointerOver";
  237. case PrimitivePointerInfo.PointerEnter: return "PointerEnter";
  238. case PrimitivePointerInfo.PointerDown: return "PointerDown";
  239. case PrimitivePointerInfo.PointerMouseWheel: return "PointerMouseWheel";
  240. case PrimitivePointerInfo.PointerMove: return "PointerMove";
  241. case PrimitivePointerInfo.PointerUp: return "PointerUp";
  242. case PrimitivePointerInfo.PointerOut: return "PointerOut";
  243. case PrimitivePointerInfo.PointerLeave: return "PointerLeave";
  244. case PrimitivePointerInfo.PointerGotCapture: return "PointerGotCapture";
  245. case PrimitivePointerInfo.PointerLostCapture: return "PointerLostCapture";
  246. }
  247. }
  248. }
  249. /**
  250. * Stores information about a Primitive that was intersected
  251. */
  252. export class PrimitiveIntersectedInfo {
  253. constructor(public prim: Prim2DBase, public intersectionLocation: Vector2) {
  254. }
  255. }
  256. export class PrimitiveMargin {
  257. constructor(owner: Prim2DBase) {
  258. this._owner = owner;
  259. this._left = this._top = this._bottom = this.right = 0;
  260. }
  261. public get top(): number {
  262. return this._top;
  263. }
  264. public set top(value: number) {
  265. if (value === this._top) {
  266. return;
  267. }
  268. this._top = value;
  269. this._owner._marginChanged();
  270. }
  271. public get left(): number {
  272. return this._left;
  273. }
  274. public set left(value: number) {
  275. if (value === this._left) {
  276. return;
  277. }
  278. this._left = value;
  279. this._owner._marginChanged();
  280. }
  281. public get right(): number {
  282. return this._right;
  283. }
  284. public set right(value: number) {
  285. if (value === this._right) {
  286. return;
  287. }
  288. this._right = value;
  289. this._owner._marginChanged();
  290. }
  291. public get bottom(): number {
  292. return this._bottom;
  293. }
  294. public set bottom(value: number) {
  295. if (value === this._bottom) {
  296. return;
  297. }
  298. this._bottom = value;
  299. this._owner._marginChanged();
  300. }
  301. private _owner: Prim2DBase;
  302. private _top: number;
  303. private _left: number;
  304. private _right: number;
  305. private _bottom: number;
  306. static Zero(owner: Prim2DBase): PrimitiveMargin {
  307. return new PrimitiveMargin(owner);
  308. }
  309. }
  310. /**
  311. * Main class used for the Primitive Intersection API
  312. */
  313. export class IntersectInfo2D {
  314. constructor() {
  315. this.findFirstOnly = false;
  316. this.intersectHidden = false;
  317. this.pickPosition = Vector2.Zero();
  318. }
  319. // Input settings, to setup before calling an intersection related method
  320. /**
  321. * Set the pick position, relative to the primitive where the intersection test is made
  322. */
  323. public pickPosition: Vector2;
  324. /**
  325. * If true the intersection will stop at the first hit, if false all primitives will be tested and the intersectedPrimitives array will be filled accordingly (false default)
  326. */
  327. public findFirstOnly: boolean;
  328. /**
  329. * If true the intersection test will also be made on hidden primitive (false default)
  330. */
  331. public intersectHidden: boolean;
  332. // Intermediate data, don't use!
  333. public _globalPickPosition: Vector2;
  334. public _localPickPosition: Vector2;
  335. // Output settings, up to date in return of a call to an intersection related method
  336. /**
  337. * The topmost intersected primitive
  338. */
  339. public topMostIntersectedPrimitive: PrimitiveIntersectedInfo;
  340. /**
  341. * The array containing all intersected primitive, in no particular order.
  342. */
  343. public intersectedPrimitives: Array<PrimitiveIntersectedInfo>;
  344. /**
  345. * true if at least one primitive intersected during the test
  346. */
  347. public get isIntersected(): boolean {
  348. return this.intersectedPrimitives && this.intersectedPrimitives.length > 0;
  349. }
  350. public isPrimIntersected(prim: Prim2DBase): Vector2 {
  351. for (let cur of this.intersectedPrimitives) {
  352. if (cur.prim === prim) {
  353. return cur.intersectionLocation;
  354. }
  355. }
  356. return null;
  357. }
  358. // Internals, don't use
  359. public _exit(firstLevel: boolean) {
  360. if (firstLevel) {
  361. this._globalPickPosition = null;
  362. }
  363. }
  364. }
  365. @className("Prim2DBase")
  366. /**
  367. * Base class for a Primitive of the Canvas2D feature
  368. */
  369. export class Prim2DBase extends SmartPropertyPrim {
  370. static PRIM2DBASE_PROPCOUNT: number = 12;
  371. public static get HAlignLeft(): number { return Prim2DBase._hAlignLeft; }
  372. public static get HAlignCenter(): number { return Prim2DBase._hAlignCenter; }
  373. public static get HAlignRight(): number { return Prim2DBase._hAlignRight; }
  374. public static get HAlignStretch(): number { return Prim2DBase._hAlignStretch;}
  375. public static get VAlignTop(): number { return Prim2DBase._vAlignTop; }
  376. public static get VAlignCenter(): number { return Prim2DBase._vAlignCenter; }
  377. public static get VAlignBottom(): number { return Prim2DBase._vAlignBottom; }
  378. public static get VAlignStretch(): number { return Prim2DBase._vAlignStretch;}
  379. protected setupPrim2DBase(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number) {
  380. if (!(this instanceof Group2D) && !(this instanceof Sprite2D && id !== null && id.indexOf("__cachedSpriteOfGroup__") === 0) && (owner.cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) && (parent === owner)) {
  381. throw new Error("Can't create a primitive with the canvas as direct parent when the caching strategy is TOPLEVELGROUPS. You need to create a Group below the canvas and use it as the parent for the primitive");
  382. }
  383. let m: PrimitiveMargin = null;
  384. if (marginTop || marginLeft || marginRight || marginBottom) {
  385. m = new PrimitiveMargin(this);
  386. m.top = marginTop || 0;
  387. m.left = marginLeft || 0;
  388. m.right = marginRight || 0;
  389. m.bottom = marginBottom || 0;
  390. }
  391. this.setupSmartPropertyPrim();
  392. this._pointerEventObservable = new Observable<PrimitivePointerInfo>();
  393. this._isPickable = true;
  394. this._siblingDepthOffset = this._hierarchyDepthOffset = 0;
  395. this._boundingInfoDirty = true;
  396. this._boundingInfo = new BoundingInfo2D();
  397. this._owner = owner;
  398. this._parent = parent;
  399. this._id = id;
  400. if (parent != null) {
  401. this._hierarchyDepth = parent._hierarchyDepth + 1;
  402. this._renderGroup = <Group2D>this.parent.traverseUp(p => p instanceof Group2D && p.isRenderableGroup);
  403. parent.addChild(this);
  404. } else {
  405. this._hierarchyDepth = 0;
  406. this._renderGroup = null;
  407. }
  408. this.propertyChanged = new Observable<PropertyChangedInfo>();
  409. this._children = new Array<Prim2DBase>();
  410. this._globalTransformProcessStep = 0;
  411. this._globalTransformStep = 0;
  412. if (this instanceof Group2D) {
  413. var group: any = this;
  414. group.detectGroupStates();
  415. }
  416. this.position = position;
  417. this.rotation = 0;
  418. this.scale = 1;
  419. this.levelVisible = isVisible;
  420. this.origin = origin || new Vector2(0.5, 0.5);
  421. this.margin = m;
  422. this.hAlignment = hAlignment;
  423. this.vAlignment = vAlignment;
  424. }
  425. public get actionManager(): ActionManager {
  426. if (!this._actionManager) {
  427. this._actionManager = new ActionManager(this.owner.scene);
  428. }
  429. return this._actionManager;
  430. }
  431. /**
  432. * From 'this' primitive, traverse up (from parent to parent) until the given predicate is true
  433. * @param predicate the predicate to test on each parent
  434. * @return the first primitive where the predicate was successful
  435. */
  436. public traverseUp(predicate: (p: Prim2DBase) => boolean): Prim2DBase {
  437. let p: Prim2DBase = this;
  438. while (p != null) {
  439. if (predicate(p)) {
  440. return p;
  441. }
  442. p = p._parent;
  443. }
  444. return null;
  445. }
  446. /**
  447. * Retrieve the owner Canvas2D
  448. */
  449. public get owner(): Canvas2D {
  450. return this._owner;
  451. }
  452. /**
  453. * Get the parent primitive (can be the Canvas, only the Canvas has no parent)
  454. */
  455. public get parent(): Prim2DBase {
  456. return this._parent;
  457. }
  458. /**
  459. * The array of direct children primitives
  460. */
  461. public get children(): Prim2DBase[] {
  462. return this._children;
  463. }
  464. /**
  465. * The identifier of this primitive, may not be unique, it's for information purpose only
  466. */
  467. public get id(): string {
  468. return this._id;
  469. }
  470. /**
  471. * Metadata of the position property
  472. */
  473. public static positionProperty: Prim2DPropInfo;
  474. /**
  475. * Metadata of the rotation property
  476. */
  477. public static rotationProperty: Prim2DPropInfo;
  478. /**
  479. * Metadata of the scale property
  480. */
  481. public static scaleProperty: Prim2DPropInfo;
  482. /**
  483. * Metadata of the origin property
  484. */
  485. public static originProperty: Prim2DPropInfo;
  486. /**
  487. * Metadata of the levelVisible property
  488. */
  489. public static levelVisibleProperty: Prim2DPropInfo;
  490. /**
  491. * Metadata of the isVisible property
  492. */
  493. public static isVisibleProperty: Prim2DPropInfo;
  494. /**
  495. * Metadata of the zOrder property
  496. */
  497. public static zOrderProperty: Prim2DPropInfo;
  498. /**
  499. * Metadata of the margin property
  500. */
  501. public static marginProperty: Prim2DPropInfo;
  502. /**
  503. * Metadata of the vAlignment property
  504. */
  505. public static vAlignmentProperty: Prim2DPropInfo;
  506. /**
  507. * Metadata of the hAlignment property
  508. */
  509. public static hAlignmentProperty: Prim2DPropInfo;
  510. @instanceLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, true)
  511. /**
  512. * Position of the primitive, relative to its parent.
  513. */
  514. public get position(): Vector2 {
  515. return this._position;
  516. }
  517. public set position(value: Vector2) {
  518. this._position = value;
  519. }
  520. @instanceLevelProperty(2, pi => Prim2DBase.rotationProperty = pi, false, true)
  521. /**
  522. * Rotation of the primitive, in radian, along the Z axis
  523. * @returns {}
  524. */
  525. public get rotation(): number {
  526. return this._rotation;
  527. }
  528. public set rotation(value: number) {
  529. this._rotation = value;
  530. }
  531. @instanceLevelProperty(3, pi => Prim2DBase.scaleProperty = pi, false, true)
  532. /**
  533. * Uniform scale applied on the primitive
  534. */
  535. public set scale(value: number) {
  536. this._scale = value;
  537. }
  538. public get scale(): number {
  539. return this._scale;
  540. }
  541. /**
  542. * this method must be implemented by the primitive type to return its size
  543. * @returns The size of the primitive
  544. */
  545. public get actualSize(): Size {
  546. return undefined;
  547. }
  548. @instanceLevelProperty(4, pi => Prim2DBase.originProperty = pi, false, true)
  549. public set origin(value: Vector2) {
  550. this._origin = value;
  551. }
  552. /**
  553. * The origin defines the normalized coordinate of the center of the primitive, from the top/left corner.
  554. * The origin is used only to compute transformation of the primitive, it has no meaning in the primitive local frame of reference
  555. * For instance:
  556. * 0,0 means the center is bottom/left. Which is the default for Canvas2D instances
  557. * 0.5,0.5 means the center is at the center of the primitive, which is default of all types of Primitives
  558. * 0,1 means the center is top/left
  559. * @returns The normalized center.
  560. */
  561. public get origin(): Vector2 {
  562. return this._origin;
  563. }
  564. @dynamicLevelProperty(5, pi => Prim2DBase.levelVisibleProperty = pi)
  565. /**
  566. * Let the user defines if the Primitive is hidden or not at its level. As Primitives inherit the hidden status from their parent, only the isVisible property give properly the real visible state.
  567. * Default is true, setting to false will hide this primitive and its children.
  568. */
  569. public get levelVisible(): boolean {
  570. return this._levelVisible;
  571. }
  572. public set levelVisible(value: boolean) {
  573. this._levelVisible = value;
  574. }
  575. @instanceLevelProperty(6, pi => Prim2DBase.isVisibleProperty = pi)
  576. /**
  577. * Use ONLY THE GETTER to determine if the primitive is visible or not.
  578. * The Setter is for internal purpose only!
  579. */
  580. public get isVisible(): boolean {
  581. return this._isVisible;
  582. }
  583. public set isVisible(value: boolean) {
  584. this._isVisible = value;
  585. }
  586. @instanceLevelProperty(7, pi => Prim2DBase.zOrderProperty = pi)
  587. /**
  588. * You can override the default Z Order through this property, but most of the time the default behavior is acceptable
  589. * @returns {}
  590. */
  591. public get zOrder(): number {
  592. return this._zOrder;
  593. }
  594. public set zOrder(value: number) {
  595. this._zOrder = value;
  596. this.onZOrderChanged();
  597. }
  598. @dynamicLevelProperty(8, pi => Prim2DBase.marginProperty = pi)
  599. /**
  600. * You can get/set a margin on the primitive through this property
  601. * @returns the margin object, if there was none, a default one is created and returned
  602. */
  603. public get margin(): PrimitiveMargin {
  604. if (!this._margin) {
  605. this._margin = new PrimitiveMargin(this);
  606. }
  607. return this._margin;
  608. }
  609. public set margin(value: PrimitiveMargin) {
  610. this._margin = value;
  611. }
  612. @dynamicLevelProperty(9, pi => Prim2DBase.hAlignmentProperty = pi)
  613. /**
  614. * You can get/set the horizontal alignment through this property
  615. */
  616. public get hAlignment(): number {
  617. return this._hAlignment;
  618. }
  619. public set hAlignment(value: number) {
  620. this._hAlignment = value;
  621. }
  622. @dynamicLevelProperty(10, pi => Prim2DBase.vAlignmentProperty = pi)
  623. /**
  624. * You can get/set the vertical alignment through this property
  625. */
  626. public get vAlignment(): number {
  627. return this._vAlignment;
  628. }
  629. public set vAlignment(value: number) {
  630. this._vAlignment = value;
  631. }
  632. /**
  633. * Define if the Primitive can be subject to intersection test or not (default is true)
  634. */
  635. public get isPickable(): boolean {
  636. return this._isPickable;
  637. }
  638. public set isPickable(value: boolean) {
  639. this._isPickable = value;
  640. }
  641. /**
  642. * Return the depth level of the Primitive into the Canvas' Graph. A Canvas will be 0, its direct children 1, and so on.
  643. * @returns {}
  644. */
  645. public get hierarchyDepth(): number {
  646. return this._hierarchyDepth;
  647. }
  648. /**
  649. * Retrieve the Group that is responsible to render this primitive
  650. * @returns {}
  651. */
  652. public get renderGroup(): Group2D {
  653. return this._renderGroup;
  654. }
  655. /**
  656. * Get the global transformation matrix of the primitive
  657. */
  658. public get globalTransform(): Matrix {
  659. return this._globalTransform;
  660. }
  661. /**
  662. * Get invert of the global transformation matrix of the primitive
  663. * @returns {}
  664. */
  665. public get invGlobalTransform(): Matrix {
  666. return this._invGlobalTransform;
  667. }
  668. /**
  669. * Get the local transformation of the primitive
  670. */
  671. public get localTransform(): Matrix {
  672. this._updateLocalTransform();
  673. return this._localTransform;
  674. }
  675. /**
  676. * Get the boundingInfo associated to the primitive and its children.
  677. * The value is supposed to be always up to date
  678. */
  679. public get boundingInfo(): BoundingInfo2D {
  680. if (this._boundingInfoDirty) {
  681. this._boundingInfo = this.levelBoundingInfo.clone();
  682. let bi = this._boundingInfo;
  683. var tps = new BoundingInfo2D();
  684. for (let curChild of this._children) {
  685. curChild.boundingInfo.transformToRef(curChild.localTransform, tps);
  686. bi.unionToRef(tps, bi);
  687. }
  688. this._boundingInfoDirty = false;
  689. }
  690. return this._boundingInfo;
  691. }
  692. /**
  693. * Interaction with the primitive can be create using this Observable. See the PrimitivePointerInfo class for more information
  694. */
  695. public get pointerEventObservable(): Observable<PrimitivePointerInfo> {
  696. return this._pointerEventObservable;
  697. }
  698. protected onZOrderChanged() {
  699. }
  700. protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
  701. return false;
  702. }
  703. /**
  704. * Capture all the Events of the given PointerId for this primitive.
  705. * Don't forget to call releasePointerEventsCapture when done.
  706. * @param pointerId the Id of the pointer to capture the events from.
  707. */
  708. public setPointerEventCapture(pointerId: number): boolean {
  709. return this.owner._setPointerCapture(pointerId, this);
  710. }
  711. /**
  712. * Release a captured pointer made with setPointerEventCapture.
  713. * @param pointerId the Id of the pointer to release the capture from.
  714. */
  715. public releasePointerEventsCapture(pointerId: number): boolean {
  716. return this.owner._releasePointerCapture(pointerId, this);
  717. }
  718. /**
  719. * Make an intersection test with the primitive, all inputs/outputs are stored in the IntersectInfo2D class, see its documentation for more information.
  720. * @param intersectInfo contains the settings of the intersection to perform, to setup before calling this method as well as the result, available after a call to this method.
  721. */
  722. public intersect(intersectInfo: IntersectInfo2D): boolean {
  723. if (!intersectInfo) {
  724. return false;
  725. }
  726. // If this is null it means this method is call for the first level, initialize stuffs
  727. let firstLevel = !intersectInfo._globalPickPosition;
  728. if (firstLevel) {
  729. // Compute the pickPosition in global space and use it to find the local position for each level down, always relative from the world to get the maximum accuracy (and speed). The other way would have been to compute in local every level down relative to its parent's local, which wouldn't be as accurate (even if javascript number is 80bits accurate).
  730. intersectInfo._globalPickPosition = Vector2.Zero();
  731. Vector2.TransformToRef(intersectInfo.pickPosition, this.globalTransform, intersectInfo._globalPickPosition);
  732. intersectInfo._localPickPosition = intersectInfo.pickPosition.clone();
  733. intersectInfo.intersectedPrimitives = new Array<PrimitiveIntersectedInfo>();
  734. intersectInfo.topMostIntersectedPrimitive = null;
  735. }
  736. if (!intersectInfo.intersectHidden && !this.isVisible) {
  737. return false;
  738. }
  739. // Fast rejection test with boundingInfo
  740. if (this.isPickable && !this.boundingInfo.doesIntersect(intersectInfo._localPickPosition)) {
  741. // Important to call this before each return to allow a good recursion next time this intersectInfo is reused
  742. intersectInfo._exit(firstLevel);
  743. return false;
  744. }
  745. // We hit the boundingInfo that bounds this primitive and its children, now we have to test on the primitive of this level
  746. let levelIntersectRes = false;
  747. if (this.isPickable) {
  748. levelIntersectRes = this.levelIntersect(intersectInfo);
  749. if (levelIntersectRes) {
  750. let pii = new PrimitiveIntersectedInfo(this, intersectInfo._localPickPosition.clone());
  751. intersectInfo.intersectedPrimitives.push(pii);
  752. if (!intersectInfo.topMostIntersectedPrimitive || (intersectInfo.topMostIntersectedPrimitive.prim.getActualZOffset() > pii.prim.getActualZOffset())) {
  753. intersectInfo.topMostIntersectedPrimitive = pii;
  754. }
  755. // If we must stop at the first intersection, we're done, quit!
  756. if (intersectInfo.findFirstOnly) {
  757. intersectInfo._exit(firstLevel);
  758. return true;
  759. }
  760. }
  761. }
  762. // Recurse to children if needed
  763. if (!levelIntersectRes || !intersectInfo.findFirstOnly) {
  764. for (let curChild of this._children) {
  765. // Don't test primitive not pick able or if it's hidden and we don't test hidden ones
  766. if (!curChild.isPickable || (!intersectInfo.intersectHidden && !curChild.isVisible)) {
  767. continue;
  768. }
  769. // Must compute the localPickLocation for the children level
  770. Vector2.TransformToRef(intersectInfo._globalPickPosition, curChild.invGlobalTransform, intersectInfo._localPickPosition);
  771. // If we got an intersection with the child and we only need to find the first one, quit!
  772. if (curChild.intersect(intersectInfo) && intersectInfo.findFirstOnly) {
  773. intersectInfo._exit(firstLevel);
  774. return true;
  775. }
  776. }
  777. }
  778. intersectInfo._exit(firstLevel);
  779. return intersectInfo.isIntersected;
  780. }
  781. public moveChild(child: Prim2DBase, previous: Prim2DBase): boolean {
  782. if (child.parent !== this) {
  783. return false;
  784. }
  785. let prevOffset: number, nextOffset: number;
  786. let childIndex = this._children.indexOf(child);
  787. let prevIndex = previous ? this._children.indexOf(previous) : -1;
  788. // Move to first position
  789. if (!previous) {
  790. prevOffset = 1;
  791. nextOffset = this._children[1]._siblingDepthOffset;
  792. } else {
  793. prevOffset = this._children[prevIndex]._siblingDepthOffset;
  794. nextOffset = this._children[prevIndex + 1]._siblingDepthOffset;
  795. }
  796. child._siblingDepthOffset = (nextOffset - prevOffset) / 2;
  797. this._children.splice(prevIndex + 1, 0, this._children.splice(childIndex, 1)[0]);
  798. }
  799. private addChild(child: Prim2DBase) {
  800. child._hierarchyDepthOffset = this._hierarchyDepthOffset + ((this._children.length + 1) * this._siblingDepthOffset);
  801. // console.log(`Node: ${child.id} has depth: ${child._hierarchyDepthOffset}`);
  802. child._siblingDepthOffset = this._siblingDepthOffset / this.owner.hierarchyLevelMaxSiblingCount;
  803. this._children.push(child);
  804. }
  805. public dispose(): boolean {
  806. if (!super.dispose()) {
  807. return false;
  808. }
  809. if (this._actionManager) {
  810. this._actionManager.dispose();
  811. this._actionManager = null;
  812. }
  813. // If there's a parent, remove this object from its parent list
  814. if (this._parent) {
  815. let i = this._parent._children.indexOf(this);
  816. if (i !== undefined) {
  817. this._parent._children.splice(i, 1);
  818. }
  819. this._parent = null;
  820. }
  821. // Recurse dispose to children
  822. if (this._children) {
  823. while (this._children.length > 0) {
  824. this._children[this._children.length - 1].dispose();
  825. }
  826. }
  827. return true;
  828. }
  829. public getActualZOffset(): number {
  830. return this._zOrder || (1 - this._hierarchyDepthOffset);
  831. }
  832. protected onPrimBecomesDirty() {
  833. if (this._renderGroup) {
  834. this._renderGroup._addPrimToDirtyList(this);
  835. }
  836. }
  837. public _marginChanged() {
  838. }
  839. public _needPrepare(): boolean {
  840. return this._visibilityChanged || this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep);
  841. }
  842. public _prepareRender(context: PrepareRender2DContext) {
  843. this._prepareRenderPre(context);
  844. this._prepareRenderPost(context);
  845. }
  846. public _prepareRenderPre(context: PrepareRender2DContext) {
  847. }
  848. public _prepareRenderPost(context: PrepareRender2DContext) {
  849. // Don't recurse if it's a renderable group, the content will be processed by the group itself
  850. if (this instanceof Group2D) {
  851. var self: any = this;
  852. if (self.isRenderableGroup) {
  853. return;
  854. }
  855. }
  856. // Check if we need to recurse the prepare to children primitives
  857. // - must have children
  858. // - the global transform of this level have changed, or
  859. // - the visible state of primitive has changed
  860. if (this._children.length > 0 && ((this._globalTransformProcessStep !== this._globalTransformStep) ||
  861. this.checkPropertiesDirty(Prim2DBase.isVisibleProperty.flagId))) {
  862. this._children.forEach(c => {
  863. // As usual stop the recursion if we meet a renderable group
  864. if (!(c instanceof Group2D && c.isRenderableGroup)) {
  865. c._prepareRender(context);
  866. }
  867. });
  868. }
  869. // Finally reset the dirty flags as we've processed everything
  870. this._modelDirty = false;
  871. this._instanceDirtyFlags = 0;
  872. }
  873. protected static CheckParent(parent: Prim2DBase) {
  874. if (!parent) {
  875. throw new Error("A Primitive needs a valid Parent, it can be any kind of Primitives based types, even the Canvas (with the exception that only Group2D can be direct child of a Canvas if the cache strategy used is TOPLEVELGROUPS)");
  876. }
  877. }
  878. protected updateGlobalTransVisOf(list: Prim2DBase[], recurse: boolean) {
  879. for (let cur of list) {
  880. cur.updateGlobalTransVis(recurse);
  881. }
  882. }
  883. private _updateLocalTransform(): boolean {
  884. let tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
  885. if (this.checkPropertiesDirty(tflags)) {
  886. var rot = Quaternion.RotationAxis(new Vector3(0, 0, 1), this._rotation);
  887. var local = Matrix.Compose(new Vector3(this._scale, this._scale, this._scale), rot, new Vector3(this._position.x, this._position.y, 0));
  888. this._localTransform = local;
  889. this.clearPropertiesDirty(tflags);
  890. // this is important to access actualSize AFTER fetching a first version of the local transform and reset the dirty flag, because accessing actualSize on a Group2D which actualSize is built from its content will trigger a call to this very method on this very object. We won't mind about the origin offset not being computed, as long as we return a local transform based on the position/rotation/scale
  891. //var actualSize = this.actualSize;
  892. //if (!actualSize) {
  893. // throw new Error(`The primitive type: ${Tools.getClassName(this)} must implement the actualSize get property!`);
  894. //}
  895. //local.m[12] -= (actualSize.width * this.origin.x) * local.m[0] + (actualSize.height * this.origin.y) * local.m[4];
  896. //local.m[13] -= (actualSize.width * this.origin.x) * local.m[1] + (actualSize.height * this.origin.y) * local.m[5];
  897. return true;
  898. }
  899. return false;
  900. }
  901. protected updateGlobalTransVis(recurse: boolean) {
  902. if (this.isDisposed) {
  903. return;
  904. }
  905. // Check if the parent is synced
  906. if (this._parent && this._parent._globalTransformProcessStep !== this.owner._globalTransformProcessStep) {
  907. this._parent.updateGlobalTransVis(false);
  908. }
  909. // Check if we must update this prim
  910. if (this === <any>this.owner || this._globalTransformProcessStep !== this.owner._globalTransformProcessStep) {
  911. let curVisibleState = this.isVisible;
  912. this.isVisible = (!this._parent || this._parent.isVisible) && this.levelVisible;
  913. // Detect a change of visibility
  914. this._visibilityChanged = curVisibleState !== this.isVisible;
  915. // Get/compute the localTransform
  916. let localDirty = this._updateLocalTransform();
  917. // Check if we have to update the globalTransform
  918. if (!this._globalTransform || localDirty || (this._parent && this._parent._globalTransformStep !== this._parentTransformStep)) {
  919. this._globalTransform = this._parent ? this._localTransform.multiply(this._parent._globalTransform) : this._localTransform;
  920. this._invGlobalTransform = Matrix.Invert(this._globalTransform);
  921. this._globalTransformStep = this.owner._globalTransformProcessStep + 1;
  922. this._parentTransformStep = this._parent ? this._parent._globalTransformStep : 0;
  923. }
  924. this._globalTransformProcessStep = this.owner._globalTransformProcessStep;
  925. }
  926. if (recurse) {
  927. for (let child of this._children) {
  928. // Stop the recursion if we meet a renderable group
  929. child.updateGlobalTransVis(!(child instanceof Group2D && child.isRenderableGroup));
  930. }
  931. }
  932. }
  933. private static _hAlignLeft = 1;
  934. private static _hAlignCenter = 2;
  935. private static _hAlignRight = 3;
  936. private static _hAlignStretch = 4;
  937. private static _vAlignTop = 1;
  938. private static _vAlignCenter = 2;
  939. private static _vAlignBottom = 3;
  940. private static _vAlignStretch = 4;
  941. private _owner: Canvas2D;
  942. private _parent: Prim2DBase;
  943. private _actionManager: ActionManager;
  944. protected _children: Array<Prim2DBase>;
  945. private _renderGroup: Group2D;
  946. private _hierarchyDepth: number;
  947. protected _hierarchyDepthOffset: number;
  948. protected _siblingDepthOffset: number;
  949. private _zOrder: number;
  950. private _margin: PrimitiveMargin;
  951. private _hAlignment: number;
  952. private _vAlignment: number;
  953. private _levelVisible: boolean;
  954. public _pointerEventObservable: Observable<PrimitivePointerInfo>;
  955. public _boundingInfoDirty: boolean;
  956. protected _visibilityChanged;
  957. private _isPickable;
  958. private _isVisible: boolean;
  959. private _id: string;
  960. private _position: Vector2;
  961. private _rotation: number;
  962. private _scale: number;
  963. private _origin: Vector2;
  964. // Stores the step of the parent for which the current global transform was computed
  965. // If the parent has a new step, it means this prim's global transform must be updated
  966. protected _parentTransformStep: number;
  967. // Stores the step corresponding of the global transform for this prim
  968. // If a child prim has an older _parentTransformStep it means the child's transform should be updated
  969. protected _globalTransformStep: number;
  970. // Stores the previous
  971. protected _globalTransformProcessStep: number;
  972. protected _localTransform: Matrix;
  973. protected _globalTransform: Matrix;
  974. protected _invGlobalTransform: Matrix;
  975. }
  976. }