/home/lindsay/xeolabs/xeogl-next/xeogl/examples/js/annotations/pin.js
API Docs for:

File: /home/lindsay/xeolabs/xeogl-next/xeogl/examples/js/annotations/pin.js

  1. /**
  2. A **Pin** is a pinned position on the surface of a {{#crossLink "Mesh"}}{{/crossLink}}.
  3.  
  4. ## Overview
  5.  
  6. #### Position
  7.  
  8. A Pin is positioned within one of the triangles of its {{#crossLink "Mesh"}}Mesh's{{/crossLink}} {{#crossLink "Geometry"}}{{/crossLink}}. Wherever that triangles goes within the 3D view, the Pin will automatically follow. An Pin specifies its position with two properties:
  9.  
  10. * {{#crossLink "Pin/primIndex:property"}}{{/crossLink}}, which indicates the index of the triangle within the {{#crossLink "Geometry"}}{{/crossLink}} {{#crossLink "Geometry/indices:property"}}{{/crossLink}}, and
  11. * {{#crossLink "Pin/bary:property"}}{{/crossLink}}, the barycentric coordinates of the position within the triangle.
  12.  
  13. From these, an Pin dynamically calculates its Cartesian coordinates, which it provides in each xeogl coordinate space:
  14.  
  15. * {{#crossLink "Pin/localPos:property"}}{{/crossLink}} - 3D position local to the coordinate space of the {{#crossLink "Geometry"}}{{/crossLink}},
  16. * {{#crossLink "Pin/worldPos:property"}}{{/crossLink}} - 3D World-space position,
  17. * {{#crossLink "Pin/viewPos:property"}}{{/crossLink}} - 3D View-space position, and
  18. * {{#crossLink "Pin/canvasPos:property"}}{{/crossLink}} - 2D Canvas-space position.
  19.  
  20. An Pin automatically recalculates these coordinates whenever its {{#crossLink "Mesh"}}{{/crossLink}} is replaced or transformed, the {{#crossLink "Geometry"}}{{/crossLink}} is replaced or modified, or the {{#crossLink "Camera"}}{{/crossLink}} is moved.
  21.  
  22. #### Visibility
  23.  
  24. * {{#crossLink "Pin/occludable:property"}}{{/crossLink}} specifies whether the Pin becomes invisible whenever its occluded by other objects in the 3D view, and
  25. * {{#crossLink "Pin/visible:property"}}{{/crossLink}} indicates if the Pins is currently visible.
  26.  
  27. * To determine if each Pin is occluded, xeogl renders the scene to a hidden framebuffer, along with a colored point for each Pin's position. Then xeogl determines each Pin to be occluded if the pixel at its position does not match the special pin color. The color is configured as some unusual color that is not used elsewhere in the scene.
  28.  
  29. ## Example
  30.  
  31. In the example below we'll create an {{#crossLink "Mesh"}}{{/crossLink}} with a {{#crossLink "TorusGeometry"}}{{/crossLink}}, then attach a Pin to it. Then we'll subscribe to changes to the Pin's position and visibility status.
  32. ````javascript
  33. var mesh = new xeogl.Mesh({
  34. geometry: new xeogl.TorusGeometry(),
  35. transform: new xeogl.Translate({
  36. xyz: [0,0,0]
  37. });
  38. });
  39.  
  40. var pin = new xeogl.Pin({
  41. mesh: mesh,
  42. primIndex: 12, // Triangle index in Geometry indices array
  43. bary: [0.11, 0.79, 0.08] // Barycentric coordinates in the triangle
  44. });
  45.  
  46. var localPos = pin.localPos; // 3D Local-space position
  47. var worldPos = pin.worldPos; // 3D World-space position
  48. var viewPos = pin.viewPos; // 3D View-space position
  49. var canvasPos = pin.canvasPos; // 2D Canvas-space position
  50.  
  51. // Listen for change of the Pin's Local-space cartesian position,
  52. // which is caused by modification of Pin's Geometry or update to
  53. // the Pin's 'primIndex' or 'bary' properties.
  54. pin.on("localPos", function() {
  55. //...
  56. });
  57.  
  58. // Listen for change of the Pin's World-space cartesian position,
  59. // which is caused by update of Pin's Local-space position or by
  60. // animation of the Mesh by its modelling Transform.
  61. pin.on("worldPos", function() {
  62. //...
  63. });
  64.  
  65. // Listen for change of Pin visibility. The Pin becomes invisible when it
  66. // falls outside the canvas, or when an object occludes the Pin's position
  67. // in the 3D view.
  68. pin.on("visible", function(visible) { // Pin visibility has changed
  69. if (visible) {
  70. this.log("Pin is not occluded and within canvas.");
  71. } else {
  72. this.log("Pin is either occluded or is off the canvas.");
  73. }
  74. });
  75. ````
  76. @class Pin
  77. @module xeogl
  78. @submodule annotations
  79. @constructor
  80. @param [scene] {Scene} Parent {{#crossLink "Scene"}}Scene{{/crossLink}} - creates this Pin in the default
  81. {{#crossLink "Scene"}}Scene{{/crossLink}} when omitted.
  82. @param [cfg] {*} Configs
  83. @param [cfg.id] {String} Optional ID, unique among all components in the parent {{#crossLink "Scene"}}Scene{{/crossLink}},
  84. generated automatically when omitted.
  85. @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this Pin.
  86. @param [cfg.mesh] {Number|String|Mesh} ID or instance of the {{#crossLink "Mesh"}}{{/crossLink}} the Pin is attached to.
  87. @param [cfg.primIndex=0] {Number} Index of the triangle containing the Pin. Within the {{#crossLink "Mesh"}}Mesh's{{/crossLink}} {{#crossLink "Geometry/indices:property"}}Geometry indices{{/crossLink}}, this is the index of the first vertex for the triangle.
  88. @param [cfg.bary=[0.3,0.3,0.3]] {Float32Array} Barycentric coordinates of the Pin within its triangle.
  89. @param [cfg.offset=0.2] {Number} How far the Pin is lifted out of its triangle, along the surface normal vector. This is used when occlusion culling, to ensure that the Pin is not lost inside the surface it's attached to.
  90. @param [cfg.occludable=false] {Boolean} Indicates whether occlusion testing is performed for the Pin, where it will be flagged invisible whenever it's hidden by something else in the 3D view.
  91. @extends Component
  92. */
  93. {
  94. const math = xeogl.math;
  95.  
  96. const tempVec3a = math.vec3();
  97. const tempVec3b = math.vec3();
  98. const tempVec3c = math.vec3();
  99. const tempVec3d = math.vec3();
  100. const tempVec4a = math.vec4([0, 0, 0, 1]);
  101. const tempVec4b = math.vec4();
  102.  
  103. const PIN_COLOR = math.vec3([1.0, 1.0, 0.0]);
  104.  
  105. // Do occlusion test per this number of ticks. Having a high-ish number
  106. // gives a nice hysteresis where, when occluded, the label remains visible
  107. // for a moment before disappearing, which feels smooth.
  108. const TEST_TICKS = 20;
  109.  
  110. // Each Scene gets a VisibilityTester, which periodically tests if registered
  111. // pins are visible, which is when they are within the canvas boundary and
  112. // are not obscured by any objects in the 3D view.
  113.  
  114. const VisibilityTester = function (scene) {
  115.  
  116. this._scene = scene;
  117. this._pins = {};
  118. this._markers = {};
  119. this._testablePins = {};
  120.  
  121. let countDown = TEST_TICKS;
  122.  
  123. scene.on("tick", function () {
  124. if (--countDown <= 0) {
  125. this._runTest();
  126. countDown = TEST_TICKS;
  127. }
  128. }, this);
  129. };
  130.  
  131. VisibilityTester.prototype._runTest = (function () {
  132.  
  133. const testList = [];
  134. const pixels = [];
  135. const colors = [];
  136.  
  137. return function () {
  138.  
  139. const canvas = this._scene.canvas;
  140. let pin;
  141. let canvasPos;
  142. let canvasX;
  143. let canvasY;
  144. let lenPixels = 0;
  145. let i;
  146. const boundary = canvas.boundary;
  147. const canvasWidth = boundary[2];
  148. const canvasHeight = boundary[3];
  149. let testListLen = 0;
  150.  
  151. // Hide pins that fall outside canvas
  152.  
  153. for (const id in this._testablePins) {
  154. if (this._testablePins.hasOwnProperty(id)) {
  155.  
  156. pin = this._testablePins[id];
  157.  
  158. canvasPos = pin.canvasPos;
  159.  
  160. canvasX = canvasPos[0];
  161. canvasY = canvasPos[1];
  162.  
  163. if ((canvasX + 10) < 0 ||
  164. (canvasY + 10) < 0 ||
  165. (canvasX - 10) > canvasWidth ||
  166. (canvasY - 10) > canvasHeight) {
  167.  
  168. pin._setVisible(false);
  169.  
  170. } else if (pin.occludable) {
  171.  
  172. testList[testListLen++] = pin;
  173.  
  174. pixels[lenPixels++] = canvasX;
  175. pixels[lenPixels++] = canvasY;
  176.  
  177. } else {
  178. pin._setVisible(true);
  179. }
  180. }
  181. }
  182.  
  183. // Hide pins that are occluded by 3D objects
  184.  
  185. const opaqueOnly = true;
  186.  
  187. canvas.readPixels(pixels, colors, testListLen, opaqueOnly);
  188.  
  189. const r = PIN_COLOR[0] * 255;
  190. const g = PIN_COLOR[1] * 255;
  191. const b = PIN_COLOR[2] * 255;
  192.  
  193. for (i = 0; i < testListLen; i++) {
  194. pin = testList[i];
  195. pin._setVisible((colors[i * 4] === r) && (colors[i * 4 + 1] === g) && (colors[i * 4 + 2] === b));
  196. }
  197. };
  198. })();
  199.  
  200. // Registers a Pin for visibility testing
  201. VisibilityTester.prototype.addPin = function (pin) {
  202. const pinId = pin.id;
  203. this._pins[pinId] = pin;
  204.  
  205. // Mesh which renders a 3D point that we'll test for occlusion
  206. this._markers[pinId] = new xeogl.Mesh(this._scene, {
  207. geometry: this._scene.components["_pinMarkerGeometry"] || new xeogl.Geometry(this._scene, {
  208. id: "_pinMarkerGeometry",
  209. primitive: "points",
  210. positions: [0, 0, 0],
  211. indices: [0]
  212. }),
  213. material: this._scene.components["_pinMarkerMaterial"] || new xeogl.PhongMaterial(this._scene, {
  214. id: "_pinMarkerMaterial",
  215. emissive: PIN_COLOR,
  216. diffuse: [0, 0, 0],
  217. specular: [0, 0, 0],
  218. pointSize: 5
  219. }),
  220. visible: true
  221. });
  222. this._testablePins[pinId] = pin;
  223. };
  224.  
  225. // Enables or disables occlusion testing for a Pin
  226. VisibilityTester.prototype.setPinTestable = function (pinId, testable) {
  227. const pin = this._pins[pinId];
  228. if (pin) {
  229. this._markers[pinId].visible = testable;
  230. testable ? this._testablePins[pinId] = pin : delete this._testablePins[pinId];
  231. }
  232. };
  233.  
  234. // Updates the World-space position of a Pin
  235. VisibilityTester.prototype.setPinWorldPos = function (pinId, worldPos) {
  236. this._markers[pinId].position = worldPos;
  237. };
  238.  
  239. // De-registers a Pin, so that it is not tested for visibility
  240. VisibilityTester.prototype.removePin = function (pinId) {
  241. const info = this._pins[pinId];
  242. if (info) {
  243. delete this._pins[pinId];
  244. this._markers[pinId].destroy();
  245. delete this._markers[pinId];
  246. delete this._testablePins[pinId];
  247. }
  248. };
  249.  
  250. const visibilityTesters = {};
  251.  
  252. function getVisibilityTester(scene) {
  253. let visibilityTester = visibilityTesters[scene.id];
  254. if (!visibilityTester) {
  255. visibilityTester = new VisibilityTester(scene);
  256. visibilityTesters[scene.id] = visibilityTester;
  257. scene.on("destroyed", function () {
  258. delete visibilityTesters[scene.id];
  259. })
  260. }
  261. return visibilityTester;
  262. }
  263.  
  264. xeogl.Pin = class xeoglPin extends xeogl.Component {
  265.  
  266. init(cfg) {
  267.  
  268. super.init(cfg);
  269.  
  270. this._visible = null;
  271. this._localPos = new Float32Array(3);
  272. this._worldPos = new Float32Array(3);
  273. this._viewPos = new Float32Array(3);
  274. this._canvasPos = new Float32Array(2);
  275. this._localNormal = new Float32Array(3);
  276. this._worldNormal = new Float32Array(3);
  277.  
  278. this._localPosDirty = true;
  279. this._worldPosDirty = false;
  280.  
  281. this.mesh = cfg.mesh;
  282. this.primIndex = cfg.primIndex;
  283. this.bary = cfg.bary;
  284. this.offset = cfg.offset;
  285. this.occludable = cfg.occludable;
  286. }
  287.  
  288. /**
  289. The {{#crossLink "Mesh"}}{{/crossLink}} this Pin is attached to.
  290.  
  291. You can attach a Pin to a different {{#crossLink "Mesh"}}{{/crossLink}} at any time.
  292.  
  293. Note that {{#crossLink "Pin/primIndex:property"}}{{/crossLink}} should always
  294. be within the {{#crossLink "Geometry/indices:property"}}{{/crossLink}} of the {{#crossLink "Geometry"}}{{/crossLink}} belonging to the {{#crossLink "Mesh"}}Mesh{{/crossLink}}.
  295.  
  296. Fires an {{#crossLink "Pin/mesh:event"}}{{/crossLink}} event on change.
  297.  
  298. @property mesh
  299. @type {Number | String | xeogl.Mesh}
  300. */
  301. set mesh(value) {
  302.  
  303. /**
  304. * Fired whenever this Pin's {{#crossLink "Pin/mesh:property"}}{{/crossLink}} property changes.
  305. * @event mesh
  306. * @param value The property's new value
  307. */
  308. this._attach({
  309. name: "mesh",
  310. type: "xeogl.Mesh",
  311. component: value,
  312. sceneDefault: false,
  313. onAttached: {callback: this._meshAttached, scope: this},
  314. onDetached: {callback: this._meshDetached, scope: this}
  315. });
  316. }
  317.  
  318. get mesh() {
  319. return this._attached.mesh;
  320. }
  321.  
  322. /**
  323. Index of the triangle containing this pin.
  324.  
  325. Within the {{#crossLink "Geometry/indices:property"}}{{/crossLink}} of the {{#crossLink "Geometry"}}{{/crossLink}} attached to the {{#crossLink "Mesh"}}{{/crossLink}}, this is the index of the first element for that triangle.
  326.  
  327. Fires a {{#crossLink "Pin/primIndex:event"}}{{/crossLink}} event on change.
  328.  
  329. @property primIndex
  330. @default 0
  331. @type Number
  332. */
  333. set primIndex(value) {
  334.  
  335. value = value || 0;
  336.  
  337. if (value === this._primIndex) {
  338. return;
  339. }
  340.  
  341. this._primIndex = value;
  342.  
  343. this._setLocalPosDirty();
  344.  
  345. /**
  346. * Fired whenever this Pin's {{#crossLink "Pin/primIndex:property"}}{{/crossLink}} property changes.
  347. *
  348. * @event primIndex
  349. * @param value Number The property's new value
  350. */
  351. this.fire("primIndex", this._primIndex);
  352. }
  353.  
  354. get primIndex() {
  355. return this._primIndex;
  356. }
  357.  
  358. /**
  359. Barycentric coordinates of this Pin within its triangle.
  360.  
  361. A value of ````[0.3, 0.3, 0.3]```` is the center of the triangle.
  362.  
  363. Fires a {{#crossLink "Pin/bary:event"}}{{/crossLink}} event on change.
  364.  
  365. @property bary
  366. @default [0.3,0.3,0.3]
  367. @type Float32Array
  368. */
  369. set bary(value) {
  370. this._bary = value || xeogl.math.vec3([.3, .3, .3]);
  371. this._setLocalPosDirty();
  372.  
  373. /**
  374. * Fired whenever this Pin's {{#crossLink "Pin/bary:property"}}{{/crossLink}} property changes.
  375. * @event bary
  376. * @param value Float32Array The property's new value
  377. */
  378. this.fire("bary", this._bary);
  379. }
  380.  
  381. get bary() {
  382. return this._bary;
  383. }
  384.  
  385. /**
  386. How far the Pin is lifted out of its triangle, along the surface normal vector.
  387.  
  388. This is used when occlusion culling, to ensure that the Pin is not lost inside
  389. the surface it's attached to.
  390.  
  391. Fires a {{#crossLink "Pin/offset:event"}}{{/crossLink}} event on change.
  392.  
  393. @property offset
  394. @default 0.2
  395. @type Number
  396. */
  397. set offset(value) {
  398. this._offset = value !== undefined ? value : 0.2;
  399. this._setWorldPosDirty();
  400.  
  401. /**
  402. * Fired whenever this Pin's {{#crossLink "Pin/offset:property"}}{{/crossLink}} property changes.
  403. *
  404. * @event offset
  405. * @param value Number The property's new value
  406. */
  407. this.fire("offset", this._offset);
  408. }
  409.  
  410. get offset() {
  411. return this._offset;
  412. }
  413.  
  414. /**
  415. Indicates whether occlusion testing is performed for this Pin.
  416.  
  417. When this is true, then {{#crossLink "Pin/visible:property"}}{{/crossLink}} will
  418. be false whenever the Pin is hidden behind something else in the 3D view.
  419.  
  420. Set this false if the Pin is to remain visible when hidden behind things while
  421. being within the canvas.
  422.  
  423. Fires a {{#crossLink "Pin/occludable:event"}}{{/crossLink}} event on change.
  424.  
  425. @property occludable
  426. @default false
  427. @type Float32Array
  428. */
  429. set occludable(value) {
  430.  
  431. value = !!value;
  432.  
  433. if (value === this._occludable) {
  434. return;
  435. }
  436.  
  437. this._occludable = value;
  438.  
  439. if (this._occludable) {
  440. if (!this._visTester) {
  441. this._visTester = getVisibilityTester(this.scene);
  442. }
  443. this._visTester.addPin(this);
  444. } else {
  445. if (this._visTester) {
  446. this._visTester.removePin(this);
  447. }
  448. this._setVisible(true);
  449. }
  450.  
  451. /**
  452. * Fired whenever this Pin's {{#crossLink "Pin/occludable:property"}}{{/crossLink}} property changes.
  453. * @event occludable
  454. * @param value Float32Array The property's new value
  455. */
  456. this.fire("occludable", this._occludable);
  457. }
  458.  
  459. get occludable() {
  460. return this._occludable;
  461. }
  462.  
  463. /**
  464. Local-space 3D coordinates of this Pin.
  465.  
  466. This is read-only and is automatically calculated.
  467.  
  468. Fires a {{#crossLink "Pin/localPos:event"}}{{/crossLink}} event on change.
  469.  
  470. @property localPos
  471. @default [0,0,0]
  472. @type Float32Array
  473. @final
  474. */
  475. get localPos() {
  476. this.__update();
  477. return this._localPos;
  478. }
  479.  
  480. /**
  481. World-space 3D coordinates of this Pin.
  482.  
  483. This is read-only and is automatically calculated.
  484.  
  485. Fires a {{#crossLink "Pin/worldPos:event"}}{{/crossLink}} event on change.
  486.  
  487. @property worldPos
  488. @default [0,0,0]
  489. @type Float32Array
  490. @final
  491. */
  492. get worldPos() {
  493. this.__update();
  494. return this._worldPos;
  495. }
  496.  
  497. /**
  498. View-space 3D coordinates of this Pin.
  499.  
  500. This is read-only and is automatically calculated.
  501.  
  502. @property viewPos
  503. @default [0,0,0]
  504. @type Float32Array
  505. @final
  506. */
  507. get viewPos() {
  508. this.__update();
  509. xeogl.math.transformPoint3(this.scene.camera.viewMatrix, this.worldPos, this._viewPos);
  510. return this._viewPos;
  511. }
  512.  
  513. /**
  514. Canvas-space 2D coordinates of this Pin.
  515.  
  516. This is read-only and is automatically calculated.
  517.  
  518. @property canvasPos
  519. @default [0,0]
  520. @type Float32Array
  521. @final
  522. */
  523. get canvasPos() {
  524.  
  525. tempVec4a.set(this.viewPos);
  526.  
  527. xeogl.math.transformPoint4(this.scene.camera.projMatrix, tempVec4a, tempVec4b);
  528.  
  529. const aabb = this.scene.canvas.boundary;
  530.  
  531. this._canvasPos[0] = Math.floor((1 + tempVec4b[0] / tempVec4b[3]) * aabb[2] / 2);
  532. this._canvasPos[1] = Math.floor((1 - tempVec4b[1] / tempVec4b[3]) * aabb[3] / 2);
  533.  
  534. return this._canvasPos;
  535. }
  536.  
  537. /**
  538. World-space normal vector of this Pin.
  539.  
  540. This is read-only and is automatically calculated.
  541.  
  542. Fires a {{#crossLink "Pin/worldNormal:event"}}{{/crossLink}} event on change.
  543.  
  544. @property worldNormal
  545. @default [0,0,1]
  546. @type Float32Array
  547. @final
  548. */
  549. get worldNormal() {
  550. this.__update();
  551. return this._worldNormal;
  552. }
  553.  
  554. /**
  555. Indicates if this Pin is currently visible.
  556.  
  557. This is read-only and is automatically calculated.
  558.  
  559. The Pin is invisible whenever:
  560.  
  561. * {{#crossLink "Pin/canvasPos:property"}}{{/crossLink}} is currently outside the canvas, or
  562. * {{#crossLink "Pin/occludable:property"}}{{/crossLink}} is true and the Pin is currently occluded by something in the 3D view.
  563.  
  564. Fires a {{#crossLink "Pin/visible:event"}}{{/crossLink}} event on change.
  565.  
  566. @property visible
  567. @default true
  568. @type Boolean
  569. @final
  570. */
  571. get visible() {
  572. return !!this._visible;
  573. }
  574.  
  575. _meshAttached(mesh) {
  576. this._onGeometryBoundary = mesh.geometry.on("boundary", this._setLocalPosDirty, this);
  577. this._onMeshBoundary = mesh.on("boundary", this._setWorldPosDirty, this);
  578. this._onMeshVisible = mesh.on("visible", this._meshVisible, this);
  579. this._setLocalPosDirty();
  580. }
  581.  
  582. _setLocalPosDirty() {
  583. if (!this._localPosDirty) {
  584. this._localPosDirty = true;
  585. this._needUpdate();
  586. }
  587. }
  588.  
  589. _setWorldPosDirty() {
  590. if (!this._worldPosDirty) {
  591. this._worldPosDirty = true;
  592. this._needUpdate();
  593. }
  594. }
  595.  
  596. _meshVisible(visible) {
  597. if (!visible) {
  598. this._setVisible(false);
  599. }
  600. if (this._visTester) {
  601. this._visTester.setPinTestable(this.id, visible);
  602. }
  603. }
  604.  
  605. _meshDetached(mesh) {
  606. mesh.geometry.off(this._onGeometryBoundary);
  607. mesh.off(this._onMeshBoundary);
  608. mesh.off(this._onMeshVisible);
  609. this._meshVisible(false);
  610. }
  611.  
  612. _update() {
  613.  
  614. const localPosDirty = this._localPosDirty;
  615. const worldPosDirty = localPosDirty || this._worldPosDirty;
  616.  
  617. this.__update();
  618.  
  619. if (localPosDirty) {
  620.  
  621. /**
  622. * Fired whenever this Pin's {{#crossLink "Pin/localPos:property"}}{{/crossLink}} property changes.
  623. * @event localPos
  624. */
  625. this.fire("localPos", this._localPos);
  626. }
  627.  
  628. if (worldPosDirty) {
  629.  
  630. /**
  631. * Fired whenever this Pin's {{#crossLink "Pin/worldPos:property"}}{{/crossLink}} property changes.
  632. * @event worldPos
  633. */
  634. this.fire("worldPos", this._worldPos);
  635.  
  636. /**
  637. * Fired whenever this Pin's {{#crossLink "Pin/worldNormal:property"}}{{/crossLink}} property changes.
  638. * @event worldNormal
  639. */
  640. this.fire("worldNormal", this._worldNormal);
  641. }
  642. }
  643.  
  644. __update() {
  645.  
  646. const math = xeogl.math;
  647. const mesh = this._attached.mesh;
  648.  
  649. if (!mesh) {
  650. return;
  651. }
  652.  
  653. if (this.destroyed) {
  654. return;
  655. }
  656.  
  657. if (this._localPosDirty) {
  658.  
  659. // Get Local position from mesh's Geometry, primitive index and barycentric coordinates
  660.  
  661. const geometry = mesh.geometry;
  662. const indices = geometry.indices;
  663. const positions = geometry.positions;
  664.  
  665. if (!indices || !positions) {
  666. return;
  667. }
  668.  
  669. const i = this._primIndex;
  670.  
  671. const ia = indices[i];
  672. const ib = indices[i + 1];
  673. const ic = indices[i + 2];
  674.  
  675. const ia3 = ia * 3;
  676. const ib3 = ib * 3;
  677. const ic3 = ic * 3;
  678.  
  679. tempVec3a[0] = positions[ia3];
  680. tempVec3a[1] = positions[ia3 + 1];
  681. tempVec3a[2] = positions[ia3 + 2];
  682.  
  683. tempVec3b[0] = positions[ib3];
  684. tempVec3b[1] = positions[ib3 + 1];
  685. tempVec3b[2] = positions[ib3 + 2];
  686.  
  687. tempVec3c[0] = positions[ic3];
  688. tempVec3c[1] = positions[ic3 + 1];
  689. tempVec3c[2] = positions[ic3 + 2];
  690.  
  691. math.barycentricToCartesian(this._bary, tempVec3a, tempVec3b, tempVec3c, this._localPos);
  692.  
  693. // Lift the cartesian coords out of the plane of the triangle
  694. math.triangleNormal(tempVec3a, tempVec3b, tempVec3c, tempVec3d);
  695. math.mulVec3Scalar(tempVec3d, this._offset, tempVec3d);
  696. math.addVec3(this._localPos, tempVec3d, this._localPos);
  697.  
  698. this._localPosDirty = false;
  699. this._worldPosDirty = true;
  700. this._worldNormalDirty = true;
  701. }
  702.  
  703. if (this._worldPosDirty) {
  704.  
  705. // Transform Local position into World space
  706.  
  707. math.transformPoint3(mesh.worldMatrix, this._localPos, this._worldPos);
  708.  
  709. if (this._visTester) {
  710. this._visTester.setPinWorldPos(this.id, this._worldPos);
  711. }
  712.  
  713. this._worldPosDirty = false;
  714. }
  715.  
  716. if (this._worldNormalDirty) {
  717.  
  718. // Transform Local normal into World space
  719.  
  720. math.transformVec3(mesh.worldMatrix, this._localNormal, this._worldNormal);
  721.  
  722. this._worldNormalDirty = false;
  723. }
  724. }
  725.  
  726. _setVisible(value) { // Called by VisibilityTester
  727.  
  728. if (this._visible === value) {
  729. return;
  730. }
  731.  
  732. this._visible = value !== false;
  733.  
  734. /**
  735. * Fired whenever this Pin's {{#crossLink "Pin/visible:property"}}{{/crossLink}} property changes.
  736. * @event visible
  737. * @param value Float32Array The property's new value
  738. */
  739. this.fire("visible", this._visible);
  740. }
  741.  
  742. getJSON() {
  743. const json = {
  744. primIndex: this._primIndex,
  745. bary: xeogl.math.vecToArray(this._bary),
  746. offset: this._offset,
  747. occludable: this._occludable
  748. };
  749. if (this._attached.mesh) {
  750. json.mesh = this._attached.mesh.id;
  751. }
  752. return json;
  753. }
  754.  
  755. destroy() {
  756. if (this._visTester) {
  757. this._visTester.removePin(this.id);
  758. }
  759. super.destroy();
  760. }
  761. };
  762. }
  763.