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

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

  1. /**
  2. An **Annotation** is a labeled {{#crossLink "Pin"}}{{/crossLink}} that's attached to the surface of a {{#crossLink "Mesh"}}{{/crossLink}}.
  3.  
  4. <a href="../../examples/#annotations_tronTank"><img src="../../assets/images/screenshots/annotationsTank.png"></img></a>
  5.  
  6. ## Overview
  7.  
  8. #### Position
  9.  
  10. An Annotation is positioned within one of the triangles of its {{#crossLink "Mesh"}}Mesh's{{/crossLink}} {{#crossLink "Geometry"}}{{/crossLink}}. Wherever that triangle goes within the 3D view, the Annotation will automatically follow. An Annotation specifies its position with two properties:
  11.  
  12. * {{#crossLink "Pin/primIndex:property"}}{{/crossLink}}, which indicates the index of the triangle within the {{#crossLink "Geometry"}}{{/crossLink}} {{#crossLink "Geometry/indices:property"}}{{/crossLink}}, and
  13. * {{#crossLink "Pin/bary:property"}}{{/crossLink}}, the barycentric coordinates of the position within the triangle.
  14.  
  15. From these, an Annotation dynamically calculates its Cartesian coordinates, which it provides in each xeogl coordinate space:
  16.  
  17. * {{#crossLink "Pin/localPos:property"}}{{/crossLink}} - 3D position local to the coordinate space of the {{#crossLink "Geometry"}}{{/crossLink}},
  18. * {{#crossLink "Pin/worldPos:property"}}{{/crossLink}} - 3D World-space position,
  19. * {{#crossLink "Pin/viewPos:property"}}{{/crossLink}} - 3D View-space position, and
  20. * {{#crossLink "Pin/canvasPos:property"}}{{/crossLink}} - 2D Canvas-space position.
  21.  
  22. An Annotation 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.
  23.  
  24. #### Appearance
  25.  
  26. As shown in the screen shot above, an Annotation is rendered as a dot with a label attached to it, using HTML elements.
  27.  
  28. * {{#crossLink "Annotation/glyph:property"}}{{/crossLink}} specifies a character to appear in the dot,
  29. * {{#crossLink "Annotation/title:property"}}{{/crossLink}} and {{#crossLink "Annotation/desc:property"}}{{/crossLink}} specify a title and description to appear in the label, and
  30. * {{#crossLink "Annotation/pinShown:property"}}{{/crossLink}} and {{#crossLink "Annotation/labelShown:property"}}{{/crossLink}} specify whether the pin and label are shown.
  31.  
  32. Use the stylesheet in <a href="/examples/js/annotations/annotation-style.css">annotation-style.css</a> to set the default appearance for Annotations. Use that stylesheet as a guide for your own custom styles.
  33.  
  34. #### Visibility
  35.  
  36. * {{#crossLink "Pin/occludable:property"}}{{/crossLink}} specifies whether the Annotation becomes invisible whenever its occluded by other objects in the 3D view, and
  37. * {{#crossLink "Pin/visible:property"}}{{/crossLink}} indicates if the Annotations is currently visible.
  38.  
  39. #### Vantage points
  40.  
  41. Each Annotation may be configured with a vantage point from which to view it, given as {{#crossLink "Annotation/eye:property"}}{{/crossLink}}, {{#crossLink "Annotation/look:property"}}{{/crossLink}} and {{#crossLink "Annotation/up:property"}}{{/crossLink}} properties. To focus attention on an Annotation, you could set the {{#crossLink "Camera"}}Camera's{{/crossLink}} {{#crossLink "Lookat"}}{{/crossLink}} to that
  42. vantage point, or even fly to the vantage point using a {{#crossLink "CameraFlightAnimation"}}{{/crossLink}} (which we'll demonstrate in the usage example below).
  43.  
  44. #### Interaction
  45.  
  46. An Annotation fires a {{#crossLink "Annotation/pinClicked:event"}}"pinClicked"{{/crossLink}} event whenever you click its dot. In the usage example, we make that event show the Annotation's label and set the {{#crossLink "Camera"}}{{/crossLink}} to the vantage point.
  47.  
  48. ## Examples
  49.  
  50. * [Annotation demo](../../examples/#annotations_tronTank)
  51. * [AnnotationStory demo](../../examples/#annotations_annotationStory_tronTank)
  52.  
  53. ## Usage
  54.  
  55. In the example below, we use a {{#crossLink "GLTFModel"}}{{/crossLink}} to load a glTF model of a
  56. reciprocating saw. Once the {{#crossLink "GLTFModel"}}{{/crossLink}} has loaded, we'll then create Annotations on three of its {{#crossLink "Mesh"}}Meshes{{/crossLink}}. Finally, we wire
  57. a callback to the {{#crossLink "Annotation/pinClicked:event"}}"pinClicked"{{/crossLink}} event from
  58. each Annotation, so that when you click its {{#crossLink "Pin"}}{{/crossLink}}, its label is shown and the {{#crossLink "Camera"}}{{/crossLink}} is positioned at its vantage point.
  59.  
  60. ````javascript
  61. <script src="../build/xeogl.js"></script>
  62. <script src="js/annotations/pin.js"></script>
  63. <script src="js/annotations/annotation.js"></script>
  64.  
  65. <link href="js/annotations/annotation-style.css" rel="stylesheet"/>
  66.  
  67. <script>
  68.  
  69. var model = new xeogl.GLTFModel({
  70. src: "models/gltf/ReciprocatingSaw/PBR-SpecGloss/Reciprocating_Saw.gltf",
  71. transform: new xeogl.Rotate({
  72. xyz: [1, 0, 0],
  73. angle: 90
  74. })
  75. });
  76.  
  77. model.on("loaded", function () {
  78.  
  79. // Position the camera to look at the model
  80.  
  81. var camera = model.scene.camera;
  82. camera.eye = [-110.89, -44.85, 276.65];
  83. camera.look = [-110.89, -44.85, -0.46];
  84. camera.up = [0, 1, 0];
  85. camera.zoom(20);
  86.  
  87. // Create three annotations on meshes
  88. // within the model
  89.  
  90. var a1 = new xeogl.Annotation({
  91. mesh: model.meshes[156], // Red handle
  92. primIndex: 125,
  93. bary: [0.3, 0.3, 0.3],
  94. occludable: true,
  95. glyph: "1",
  96. title: "Handle",
  97. desc: "This is the handle. It allows us to grab onto the saw so we can hold it and make things with it.",
  98. eye: [-355.481, -0.871, 116.711],
  99. look: [-227.456, -57.628, 5.428],
  100. up: [0.239, 0.948, -0.208],
  101. pinShown: true,
  102. labelShown: false
  103. });
  104.  
  105. var a2 = new xeogl.Annotation({
  106. mesh: model.meshes[156], // Red handle and cover
  107. primIndex: 10260,
  108. bary: [0.333, 0.333, 0.333],
  109. occludable: true,
  110. glyph: "2",
  111. title: "Handle and cover",
  112. desc: "This is the handle and cover. It provides something grab the saw with, and covers the things inside.",
  113. eye: [-123.206, -4.094, 169.849],
  114. look: [-161.838, -37.875, 37.313],
  115. up: [-0.066, 0.971, -0.228],
  116. pinShown: true,
  117. labelShown: false
  118. });
  119.  
  120. var a3 = new xeogl.Annotation({
  121. mesh: modelentities[796], // Barrel
  122. primIndex: 3783,
  123. bary: [0.3, 0.3, 0.3],
  124. occludable: true,
  125. glyph: "3",
  126. title: "Barrel",
  127. desc: "This is the barrel",
  128. eye: [80.0345, 38.255, 60.457],
  129. look: [35.023, -0.166, 8.679],
  130. up: [-0.320, 0.872, -0.368],
  131. pinShown: true,
  132. labelShown: false
  133. });
  134.  
  135. // When each annotation's pin is clicked, we'll show the
  136. // annotation's label and fly the camera to the
  137. // annotation's vantage point
  138.  
  139. var cameraFlight = new xeogl.CameraFlightAnimation();
  140. var lastAnnotation;
  141.  
  142. function pinClicked(annotation) {
  143. if (lastAnnotation) {
  144. annotation.labelShown = false;
  145. }
  146. annotation.labelShown = true;
  147. cameraFlight.flyTo(annotation);
  148. lastAnnotation = annotation;
  149. }
  150.  
  151. a1.on("pinClicked", pinClicked);
  152. a2.on("pinClicked", pinClicked);
  153. a3.on("pinClicked", pinClicked);
  154.  
  155. // If desired, we can also dynamically track the Cartesian coordinates
  156. // of each annotation in Local and World coordinate spaces
  157.  
  158. a1.on("localPos", function(localPos) {
  159. console.log("Local pos changed: " + JSON.stringify(localPos, null, "\t"));
  160. });
  161.  
  162. a1.on("worldPos", function(worldPos) {
  163. console.log("World pos changed: " + JSON.stringify(worldPos, null, "\t"));
  164. });
  165. });
  166. </script>
  167. ````
  168. @class Annotation
  169. @module xeogl
  170. @submodule annotations
  171. @constructor
  172. @param [scene] {Scene} Parent {{#crossLink "Scene"}}Scene{{/crossLink}} - creates this Pin in the default
  173. {{#crossLink "Scene"}}Scene{{/crossLink}} when omitted.
  174. @param [cfg] {*} Configs
  175. @param [cfg.id] {String} Optional ID, unique among all components in the parent {{#crossLink "Scene"}}Scene{{/crossLink}},
  176. generated automatically when omitted.
  177. @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to the Annotation.
  178. @param [cfg.mesh] {Number|String|Mesh} ID or instance of the {{#crossLink "Mesh"}}{{/crossLink}} the Annotation is attached to.
  179. @param [cfg.bary=[0.3,0.3,0.3]] {Float32Array} Barycentric coordinates of the Annotation within its triangle.
  180. @param [cfg.primIndex=0] {Number} Index of the triangle containing the Annotation. Within the {{#crossLink "Mesh"}}{{/crossLink}} {{#crossLink "Geometry"}}{{/crossLink}}
  181. {{#crossLink "Geometry/indices:property"}}{{/crossLink}}, this is the index of the first
  182. element for that triangle.
  183. @param [cfg.offset=0.2] {Number} How far the Annotation is lifted out of its triangle, along the surface normal vector. This is used when occlusion culling, to ensure that the Annotation is not lost inside the surface it's attached to.
  184. @param [cfg.occludable=true] {Boolean} Indicates whether occlusion testing is performed for the Annotation, where it will be flagged invisible whenever it's hidden by something else in the 3D camera.
  185. @param [cfg.glyph=""] {String} Short piece of text to show inside the pin for the Annotation. Automatically truncated to 2 characters.
  186. @param [cfg.title=""] {String} Title text for the Annotation's label. Automatically truncated to 64 characters.
  187. @param [cfg.desc=""] {String} Description text for the Annotation's label. Automatically truncated to 1025 characters.
  188. @param [cfg.eye=[0,0,-10]] {Float32Array} Position of the eye when looking at the Annotation.
  189. @param [cfg.look=[0,0,0]] {Float32Array} Position of the look when looking at the Annotation.
  190. @param [cfg.up=[0,1,0]] {Float32Array} Direction of the "up" vector when looking at the Annotation.
  191. @param [cfg.pinShown=true] {Boolean} Specifies whether a UI element is shown at the Annotation's pin position (typically a circle).
  192. @param [cfg.labelShown=true] {Boolean} Specifies whether the Annotation's label is shown.
  193. @extends Pin
  194. */
  195. xeogl.Annotation = class xeoglAnnotation extends xeogl.Pin {
  196.  
  197. /**
  198. JavaScript class name for this Component.
  199.  
  200. For example: "xeogl.AmbientLight", "xeogl.ColorTarget", "xeogl.Lights" etc.
  201.  
  202. @property type
  203. @type String
  204. @final
  205. */
  206. get type() {
  207. return "xeogl.Annotation";
  208. }
  209.  
  210. init(cfg) {
  211.  
  212. super.init(cfg);
  213.  
  214. this._link = document.createElement("a");
  215. this._link.href = "javascript:xeogl.scenes[\"" + this.scene.id + "\"].components[\"" + this.id + "\"]._pinClicked()";
  216. document.body.appendChild(this._link);
  217.  
  218. this._spotClickable = document.createElement("div");
  219. this._spotClickable.className = "xeogl-annotation-pinClickable";
  220. this._link.appendChild(this._spotClickable);
  221.  
  222. this._spot = document.createElement("div");
  223. this._spot.innerText = "i";
  224. this._spot.className = "xeogl-annotation-pin";
  225. document.body.appendChild(this._spot);
  226.  
  227. this._label = document.createElement('div');
  228. this._label.className = "xeogl-annotation-label";
  229. document.body.appendChild(this._label);
  230.  
  231. this._titleElement = document.createElement('div');
  232. this._titleElement.className = "xeogl-annotation-title";
  233. this._titleElement.innerHTML = cfg.title || "";
  234. this._label.appendChild(this._titleElement);
  235.  
  236. this._descElement = document.createElement('div');
  237. this._descElement.className = "xeogl-annotation-desc";
  238. this._descElement.innerHTML = cfg.desc || "";
  239. this._label.appendChild(this._descElement);
  240.  
  241. this.glyph = cfg.glyph;
  242. this.title = cfg.title;
  243. this.desc = cfg.desc;
  244. this.eye = cfg.eye;
  245. this.look = cfg.look;
  246. this.up = cfg.up;
  247.  
  248. this.pinShown = cfg.pinShown;
  249. this.labelShown = cfg.labelShown;
  250.  
  251. this._tick = this.scene.on("tick", this._updateLayout, this);
  252.  
  253. this.on("visible", this._updateVisibility, this);
  254.  
  255. // this._updateVisibility();
  256. }
  257.  
  258. _pinClicked() {
  259.  
  260. /**
  261. Fired whenever the mouse is clicked on this Annotation's {{#crossLink "Annotation/pin:property"}}{{/crossLink}}.
  262.  
  263. @event pinClicked
  264. */
  265. this.fire("pinClicked", this)
  266. }
  267.  
  268. /**
  269. Short piece of text to show inside the pin for the Annotation.
  270.  
  271. Usually this would be a single number or letter.
  272.  
  273. Automatically truncated to 2 characters.
  274.  
  275. Fires a {{#crossLink "Annotation/glyph:event"}}{{/crossLink}} event on change.
  276.  
  277. @property glyph
  278. @default ""
  279. @type {String}
  280. */
  281. set glyph(glyph) {
  282.  
  283. if (this._glyph === glyph) {
  284. return;
  285. }
  286.  
  287. this._glyph = glyph || ""; // TODO: Limit to 2 chars
  288. this._spot.innerText = this._glyph;
  289.  
  290. /**
  291. Fired whenever this Annotation's {{#crossLink "Annotation/glyph:property"}}{{/crossLink}} property changes.
  292.  
  293. @event glyph
  294. @param value {Number} The property's new value
  295. */
  296. this.fire("glyph", this._glyph);
  297. }
  298.  
  299. get glyph() {
  300. return this._glyph;
  301. }
  302.  
  303.  
  304. /**
  305. Title text for the Annotation's label.
  306.  
  307. Automatically truncated to 64 characters.
  308.  
  309. Fires a {{#crossLink "Annotation/title:event"}}{{/crossLink}} event on change.
  310.  
  311. @property title
  312. @default ""
  313. @type {String}
  314. */
  315. set title(title) {
  316.  
  317. if (this._title === title) {
  318. return;
  319. }
  320.  
  321. this._title = title || ""; // TODO: Limit to 64 chars
  322. this._titleElement.innerHTML = this._title;
  323.  
  324. /**
  325. Fired whenever this Annotation's {{#crossLink "Annotation/title:property"}}{{/crossLink}} property changes.
  326.  
  327. @event title
  328. @param value {Number} The property's new value
  329. */
  330. this.fire("title", this._title);
  331. }
  332.  
  333. get title() {
  334. return this._title;
  335. }
  336.  
  337. /**
  338. Description text for the Annotation's label.
  339.  
  340. Automatically truncated to 1025 characters.
  341.  
  342. Fires a {{#crossLink "Annotation/desc:event"}}{{/crossLink}} event on change.
  343.  
  344. @property desc
  345. @default ""
  346. @type {String}
  347. */
  348. set desc(desc) {
  349.  
  350. if (this._desc === desc) {
  351. return;
  352. }
  353.  
  354. this._desc = desc || ""; // TODO: Limit to 1025 chars
  355. this._descElement.innerHTML = this._desc;
  356.  
  357. /**
  358. Fired whenever this Annotation's {{#crossLink "Annotation/desc:property"}}{{/crossLink}} property changes.
  359.  
  360. @event desc
  361. @param value {Number} The property's new value
  362. */
  363. this.fire("desc", this._desc);
  364. }
  365.  
  366. get desc() {
  367. return this._desc;
  368. }
  369.  
  370. /**
  371. Position of the eye when looking at the Annotation.
  372.  
  373. Fires a {{#crossLink "Annotation/eye:event"}}{{/crossLink}} event on change.
  374.  
  375. @property eye
  376. @default [0,0,10]
  377. @type {Float32Array}
  378. */
  379. set eye(value) {
  380.  
  381. value = value || [0, 0, 10];
  382.  
  383. if (this._eye && this._eye[0] === value[0] && this._eye[1] === value[1] && this._eye[2] === value[2]) {
  384. return;
  385. }
  386.  
  387. (this._eye = this._eye || xeogl.math.vec3()).set(value);
  388.  
  389. /**
  390. Fired whenever this Annotation's {{#crossLink "Annotation/eye:property"}}{{/crossLink}} property changes.
  391.  
  392. @event eye
  393. @param value {Number} The property's new value
  394. */
  395. this.fire("eye", this._eye);
  396. }
  397.  
  398. get eye() {
  399. return this._eye;
  400. }
  401.  
  402. /**
  403. Point-of-interest when looking at the Annotation.
  404.  
  405. Fires a {{#crossLink "Annotation/look:event"}}{{/crossLink}} event on change.
  406.  
  407. @property look
  408. @default [0,0,0]
  409. @type {Float32Array}
  410. */
  411. set look(value) {
  412.  
  413. value = value || [0, 0, 0];
  414.  
  415. if (this._look && this._look[0] === value[0] && this._look[1] === value[1] && this._look[2] === value[2]) {
  416. return;
  417. }
  418.  
  419. (this._look = this._look || xeogl.math.vec3()).set(value);
  420.  
  421. /**
  422. Fired whenever this Annotation's {{#crossLink "Annotation/look:property"}}{{/crossLink}} property changes.
  423.  
  424. @event look
  425. @param value {Number} The property's new value
  426. */
  427. this.fire("look", this._look);
  428. }
  429.  
  430. get look() {
  431. return this._look;
  432. }
  433.  
  434. /**
  435. "Up" vector when looking at the Annotation.
  436.  
  437. Fires a {{#crossLink "Annotation/up:event"}}{{/crossLink}} event on change.
  438.  
  439. @property up
  440. @default [0,1,0]
  441. @type {Float32Array}
  442. */
  443. set up(value) {
  444.  
  445. value = value || [0, 1, 0];
  446.  
  447. if (this._up && this._up[0] === value[0] && this._up[1] === value[1] && this._up[2] === value[2]) {
  448. return;
  449. }
  450.  
  451. (this._up = this._up || xeogl.math.vec3()).set(value);
  452.  
  453. /**
  454. Fired whenever this Annotation's {{#crossLink "Annotation/up:property"}}{{/crossLink}} property changes.
  455.  
  456. @event up
  457. @param value {Number} The property's new value
  458. */
  459. this.fire("up", this._up);
  460. }
  461.  
  462. get up() {
  463. return this._up;
  464. }
  465.  
  466. /**
  467. Specifies whether a UI element is shown at the Annotation's pin position (typically a circle).
  468.  
  469. Fires a {{#crossLink "Annotation/pinShown:event"}}{{/crossLink}} event on change.
  470.  
  471. @property pinShown
  472. @default true
  473. @type {Boolean}
  474. */
  475. set pinShown(shown) {
  476.  
  477. shown = shown !== false;
  478.  
  479. if (this._pinShown === shown) {
  480. return;
  481. }
  482.  
  483. this._pinShown = shown;
  484. this._spot.style.visibility = this._pinShown ? "visible" : "hidden";
  485. this._spotClickable.style.visibility = this._pinShown ? "visible" : "hidden";
  486.  
  487. /**
  488. Fired whenever this Annotation's {{#crossLink "Annotation/pinShown:property"}}{{/crossLink}} property changes.
  489.  
  490. @event pinShown
  491. @param value {Number} The property's new value
  492. */
  493. this.fire("pinShown", this._pinShown);
  494. }
  495.  
  496. get pinShown() {
  497. return this._pinShown;
  498. }
  499.  
  500. /**
  501. Specifies whether the label is shown for the Annotation.
  502.  
  503. Fires a {{#crossLink "Annotation/labelShown:event"}}{{/crossLink}} event on change.
  504.  
  505. @property labelShown
  506. @default true
  507. @type {Boolean}
  508. */
  509. set labelShown(shown) {
  510.  
  511. shown = shown !== false;
  512.  
  513. if (this._labelShown === shown) {
  514. return;
  515. }
  516.  
  517. this._labelShown = shown;
  518. this._label.style.visibility = this._labelShown && this.visible ? "visible" : "hidden";
  519.  
  520. /**
  521. Fired whenever this Annotation's {{#crossLink "Annotation/labelShown:property"}}{{/crossLink}} property changes.
  522.  
  523. @event labelShown
  524. @param value {Number} The property's new value
  525. */
  526. this.fire("labelShown", this._labelShown);
  527. }
  528.  
  529. get labelShown() {
  530. return this._labelShown;
  531. }
  532.  
  533. _updateVisibility() {
  534. const visible = this.visible;
  535. this._spotClickable.style.visibility = visible && this._pinShown ? "visible" : "hidden";
  536. this._spot.style.visibility = visible && this._pinShown ? "visible" : "hidden";
  537. this._label.style.visibility = visible && this._labelShown ? "visible" : "hidden";
  538. }
  539.  
  540. _updateLayout() {
  541. const visible = this.visible;
  542. if (visible) {
  543. const canvas = this.scene.canvas.canvas;
  544. const left = canvas.offsetLeft;
  545. const top = canvas.offsetTop;
  546. const canvasPos = this.canvasPos;
  547. this._spot.style.left = (Math.floor(left + canvasPos[0]) - 12) + "px";
  548. this._spot.style.top = (Math.floor(top + canvasPos[1]) - 12) + "px";
  549. this._spotClickable.style.left = (Math.floor(left + canvasPos[0]) - 25 + 1) + "px";
  550. this._spotClickable.style.top = (Math.floor(top + canvasPos[1]) - 25 + 1) + "px";
  551. const offsetX = 20;
  552. const offsetY = -17;
  553. this._label.style.left = 20 + (canvasPos[0] + offsetX) + "px";
  554. this._label.style.top = (canvasPos[1] + offsetY) + "px";
  555. this._spot.style["z-index"] = 90005 + Math.floor(this.viewPos[2] * 10) + 1;
  556. }
  557. }
  558.  
  559. getJSON() {
  560. const math = xeogl.math;
  561. const json = {
  562. primIndex: this.primIndex,
  563. bary: math.vecToArray(this.bary),
  564. offset: this.offset,
  565. occludable: this.occludable,
  566. glyph: this._glyph,
  567. title: this._title,
  568. desc: this._desc,
  569. eye: math.vecToArray(this._eye),
  570. look: math.vecToArray(this._look),
  571. up: math.vecToArray(this._up),
  572. pinShown: this._pinShown,
  573. labelShown: this._labelShown
  574. };
  575. if (this._attached.mesh) {
  576. json.mesh = this._attached.mesh.id;
  577. }
  578. return json;
  579. }
  580.  
  581. destroy() {
  582. super.destroy();
  583. this.scene.off(this._tick);
  584. this._link.parentNode.removeChild(this._link);
  585. this._spot.parentNode.removeChild(this._spot);
  586. this._label.parentNode.removeChild(this._label);
  587. }
  588. };