@xeokit/xeokit-sdk 2.6.77-slim-rc4 → 2.6.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,35 +16,18 @@ const zeroVec = new Float64Array([0, 0, 1]);
16
16
  const quat = new Float64Array(4);
17
17
 
18
18
  /**
19
- * Controls a {@link SectionPlane} with mouse and touch input.
19
+ * Controls a transformation with mouse and touch input.
20
20
  *
21
21
  * @private
22
22
  */
23
- class Control {
23
+ class TransformControl {
24
24
 
25
25
  /** @private */
26
- constructor(plugin) {
27
-
28
- /**
29
- * ID of this Control.
30
- *
31
- * SectionPlaneControls are mapped by this ID in {@link SectionPlanesPlugin#sectionPlaneControls}.
32
- *
33
- * @property id
34
- * @type {String|Number}
35
- */
36
- this.id = null;
37
-
38
- const viewer = plugin.viewer;
39
- const camera = viewer.camera;
26
+ constructor(viewer) {
40
27
  const cameraControl = viewer.cameraControl;
41
28
  const scene = viewer.scene;
42
29
  const canvas = scene.canvas.canvas;
43
30
 
44
- this._visible = false;
45
-
46
- let ignoreNextSectionPlaneDirUpdate = false;
47
-
48
31
  // Builds the Entities that represent this Control.
49
32
  const NO_STATE_INHERIT = false;
50
33
  const arrowLength = 1.0;
@@ -58,7 +41,7 @@ class Control {
58
41
  });
59
42
 
60
43
  const pos = math.vec3();
61
- const setPos = (function() {
44
+ this._setPosition = (function() {
62
45
  const origin = math.vec3();
63
46
  const rtcPos = math.vec3();
64
47
  return function(p) {
@@ -69,6 +52,8 @@ class Control {
69
52
  };
70
53
  })();
71
54
 
55
+ this._setQuaternion = q => { rootNode.quaternion = q; };
56
+
72
57
  const arrowGeometry = (radiusBottom, height) => new ReadableGeometry(rootNode, buildCylinderGeometry({
73
58
  radiusTop: 0.001,
74
59
  radiusBottom: radiusBottom,
@@ -156,6 +141,7 @@ class Control {
156
141
  clippable: false,
157
142
  backfaces: true,
158
143
  visible: false,
144
+ isUI: true,
159
145
  isObject: false
160
146
  }), NO_STATE_INHERIT);
161
147
 
@@ -168,6 +154,7 @@ class Control {
168
154
  clippable: false,
169
155
  backfaces: true,
170
156
  visible: false,
157
+ isUI: true,
171
158
  isObject: false
172
159
  }), NO_STATE_INHERIT);
173
160
 
@@ -179,6 +166,7 @@ class Control {
179
166
  collidable: true,
180
167
  clippable: false,
181
168
  visible: false,
169
+ isUI: true,
182
170
  isObject: false
183
171
  }), NO_STATE_INHERIT);
184
172
 
@@ -190,6 +178,7 @@ class Control {
190
178
  collidable: true,
191
179
  clippable: false,
192
180
  visible: false,
181
+ isUI: true,
193
182
  isObject: false
194
183
  }), NO_STATE_INHERIT);
195
184
 
@@ -203,6 +192,7 @@ class Control {
203
192
  collidable: true,
204
193
  clippable: false,
205
194
  visible: false,
195
+ isUI: true,
206
196
  isObject: false
207
197
  }), NO_STATE_INHERIT);
208
198
 
@@ -222,6 +212,7 @@ class Control {
222
212
  collidable: true,
223
213
  clippable: false,
224
214
  visible: false,
215
+ isUI: true,
225
216
  isObject: false
226
217
  }), NO_STATE_INHERIT);
227
218
 
@@ -233,6 +224,7 @@ class Control {
233
224
  collidable: true,
234
225
  clippable: false,
235
226
  visible: false,
227
+ isUI: true,
236
228
  isObject: false
237
229
  }), NO_STATE_INHERIT);
238
230
 
@@ -244,6 +236,7 @@ class Control {
244
236
  collidable: true,
245
237
  clippable: false,
246
238
  visible: false,
239
+ isUI: true,
247
240
  isObject: false
248
241
  }), NO_STATE_INHERIT);
249
242
 
@@ -255,6 +248,7 @@ class Control {
255
248
  collidable: true,
256
249
  clippable: false,
257
250
  visible: false,
251
+ isUI: true,
258
252
  isObject: false
259
253
  }), NO_STATE_INHERIT);
260
254
 
@@ -266,6 +260,7 @@ class Control {
266
260
  collidable: true,
267
261
  clippable: false,
268
262
  visible: false,
263
+ isUI: true,
269
264
  isObject: false
270
265
  }), NO_STATE_INHERIT);
271
266
 
@@ -306,9 +301,9 @@ class Control {
306
301
  return closestPointOnAxis(initCanvasPos, initOffset) && math.subVec3(initOffset, pos, initOffset) && ((canvasPos) => {
307
302
  if (closestPointOnAxis(canvasPos, tempVec3)) {
308
303
  math.subVec3(tempVec3, initOffset, tempVec3);
309
- setPos(tempVec3);
310
- if (this._sectionPlane) {
311
- this._sectionPlane.pos = tempVec3;
304
+ this._setPosition(tempVec3);
305
+ if (this._handlers) {
306
+ this._handlers.onPosition(tempVec3);
312
307
  }
313
308
  }
314
309
  });
@@ -319,9 +314,9 @@ class Control {
319
314
  setActivated: a => hoop.visible = a,
320
315
  initDragAction: (initCanvasPos) => {
321
316
  const rotationFromCanvasPos = (function() {
322
- const planeCanvasPos = camera.projectWorldPos(pos);
317
+ const planeCanvasPos = scene.camera.projectWorldPos(pos);
323
318
  localToWorldVec(rgb, tempVec3);
324
- math.transformVec3(camera.normalMatrix, tempVec3, tempVec3);
319
+ math.transformVec3(scene.camera.normalMatrix, tempVec3, tempVec3);
325
320
  const axisCoeff = Math.sign(tempVec3[2]);
326
321
  return (canvasPos) => {
327
322
  const dx = canvasPos[0] - planeCanvasPos[0];
@@ -335,101 +330,46 @@ class Control {
335
330
  return canvasPos => {
336
331
  const rotation = rotationFromCanvasPos(canvasPos);
337
332
  rootNode.rotate(rgb, (rotation - lastRotation) * 180 / Math.PI);
338
- if (this._sectionPlane) {
339
- ignoreNextSectionPlaneDirUpdate = true;
340
- this._sectionPlane.quaternion = rootNode.quaternion;
333
+ if (this._handlers) {
334
+ this._handlers.onQuaternion(rootNode.quaternion);
341
335
  }
342
336
  lastRotation = rotation;
343
337
  };
344
338
  }
345
339
  };
346
340
 
341
+ let positionActive = false;
342
+ let rotationActive = false;
343
+ let visible = false;
344
+
345
+ const updatePositionHandle = () => {
346
+ const v = visible && positionActive;
347
+ arrowHandle.visible = shaftHandle.visible = arrow.visible = shaft.visible = !!v;
348
+ if (! v) {
349
+ bigArrowHead.visible = false;
350
+ }
351
+ };
352
+ const updateRotationHandle = () => {
353
+ const v = visible && rotationActive;
354
+ rotateHandle.visible = curve.visible = arrow1.visible = arrow2.visible = !!v;
355
+ if (! v) {
356
+ hoop.visible = false;
357
+ }
358
+ };
359
+
347
360
  return {
361
+ setPositionActive: (a) => { positionActive = a; updatePositionHandle(); },
362
+ setRotationActive: (a) => { rotationActive = a; updateRotationHandle(); },
348
363
  set visible(v) {
349
- arrow.visible = arrowHandle.visible = shaft.visible = shaftHandle.visible = curve.visible = rotateHandle.visible = arrow1.visible = arrow2.visible = v;
350
- if (! v) {
351
- bigArrowHead.visible = v;
352
- hoop.visible = v;
353
- }
354
- },
355
- set culled(c) {
356
- arrow.culled = arrowHandle.culled = shaft.culled = shaftHandle.culled = curve.culled = rotateHandle.culled = arrow1.culled = arrow2.culled = c;
357
- if (! c) {
358
- bigArrowHead.culled = c;
359
- hoop.culled = c;
360
- }
364
+ visible = v;
365
+ updatePositionHandle();
366
+ updateRotationHandle();
361
367
  }
362
368
  };
363
369
  };
364
370
 
365
- this._displayMeshes = [ // Meshes that are always visible
366
- rootNode.addChild(new Mesh(rootNode, { // plane
367
- geometry: new ReadableGeometry(rootNode, {
368
- primitive: "triangles",
369
- positions: [
370
- 0.5, 0.5, 0.0, 0.5, -0.5, 0.0, // 0
371
- -0.5, -0.5, 0.0, -0.5, 0.5, 0.0, // 1
372
- 0.5, 0.5, -0.0, 0.5, -0.5, -0.0, // 2
373
- -0.5, -0.5, -0.0, -0.5, 0.5, -0.0 // 3
374
- ],
375
- indices: [0, 1, 2, 2, 3, 0]
376
- }),
377
- material: new PhongMaterial(rootNode, {
378
- emissive: [0, 0.0, 0],
379
- diffuse: [0, 0, 0],
380
- backfaces: true
381
- }),
382
- opacity: 0.6,
383
- ghosted: true,
384
- ghostMaterial: new EmphasisMaterial(rootNode, {
385
- edges: false,
386
- filled: true,
387
- fillColor: [1, 1, 0],
388
- edgeColor: [0, 0, 0],
389
- fillAlpha: 0.1,
390
- backfaces: true
391
- }),
392
- pickable: false,
393
- collidable: true,
394
- clippable: false,
395
- visible: false,
396
- scale: [2.4, 2.4, 1],
397
- isObject: false
398
- }), NO_STATE_INHERIT),
399
-
400
- rootNode.addChild(new Mesh(rootNode, { // Visible frame
401
- geometry: new ReadableGeometry(rootNode, buildTorusGeometry({
402
- center: [0, 0, 0],
403
- radius: 1.7,
404
- tube: tubeRadius * 2,
405
- radialSegments: 4,
406
- tubeSegments: 4,
407
- arc: Math.PI * 2.0
408
- })),
409
- material: new PhongMaterial(rootNode, {
410
- emissive: [0, 0, 0],
411
- diffuse: [0, 0, 0],
412
- specular: [0, 0, 0],
413
- shininess: 0
414
- }),
415
- //highlighted: true,
416
- highlightMaterial: new EmphasisMaterial(rootNode, {
417
- edges: false,
418
- edgeColor: [0.0, 0.0, 0.0],
419
- filled: true,
420
- fillColor: [0.8, 0.8, 0.8],
421
- fillAlpha: 1.0
422
- }),
423
- pickable: false,
424
- collidable: false,
425
- clippable: false,
426
- visible: false,
427
- scale: [1, 1, .1],
428
- rotation: [0, 0, 45],
429
- isObject: false
430
- }), NO_STATE_INHERIT),
431
-
432
- rootNode.addChild(new Mesh(rootNode, { // center
371
+ this._displayMeshes = {
372
+ center: rootNode.addChild(new Mesh(rootNode, { // center
433
373
  geometry: new ReadableGeometry(rootNode, buildSphereGeometry({
434
374
  radius: 0.05
435
375
  })),
@@ -447,21 +387,25 @@ class Control {
447
387
  isObject: false
448
388
  }), NO_STATE_INHERIT),
449
389
 
450
- //----------------------------------------------------------------------------------------------------------
451
- //
452
- //----------------------------------------------------------------------------------------------------------
453
-
454
- addAxis([1,0,0], math.vec3PairToQuaternion([1,0,0], [0,0,1])),
455
- addAxis([0,1,0], math.eulerToQuaternion([90,0,0], "XYZ")),
456
- addAxis([0,0,1], math.identityQuaternion())
457
- ];
390
+ x: addAxis([1,0,0], math.vec3PairToQuaternion([1,0,0], [0,0,1])),
391
+ y: addAxis([0,1,0], math.eulerToQuaternion([90,0,0], "XYZ")),
392
+ z: addAxis([0,0,1], math.identityQuaternion())
393
+ };
458
394
 
459
395
  const cleanups = [ ];
460
396
 
461
397
  { // Keep gizmo screen size constant
462
398
  let lastDist = -1;
463
- const setRootNodeScale = size => { if (size !== rootNode.scale[0]) { rootNode.scale = [size, size, size]; } };
399
+ const setRootNodeScale = size => {
400
+ if (rootNode.scale[0] !== size) {
401
+ rootNode.scale = [size, size, size];
402
+ if (this._handlers && this._handlers.onScreenScale) {
403
+ this._handlers.onScreenScale(rootNode.scale);
404
+ }
405
+ }
406
+ };
464
407
  const onSceneTick = scene.on("tick", () => {
408
+ const camera = scene.camera;
465
409
  const dist = Math.abs(math.distVec3(camera.eye, pos));
466
410
  if (camera.projection === "perspective") {
467
411
  if (dist !== lastDist) {
@@ -487,9 +431,11 @@ class Control {
487
431
  transformToNode(canvas.ownerDocument.documentElement, canvas, vec2);
488
432
  };
489
433
 
434
+ const pickIds = Object.keys(handlers);
490
435
  const pickHandler = (e) => {
491
436
  copyCanvasPos(e, canvasPos);
492
- const pickResult = viewer.scene.pick({ canvasPos: canvasPos });
437
+ // This doesn't guarantee the gizmo is prioritized (as it should be) by other Scene::pick calls
438
+ const pickResult = scene.pick({ canvasPos: canvasPos });//, includeEntities: pickIds });
493
439
  const pickEntity = pickResult && pickResult.entity;
494
440
  const pickId = pickEntity && pickEntity.id;
495
441
  return (pickId in handlers) && handlers[pickId];
@@ -522,6 +468,9 @@ class Control {
522
468
  }
523
469
  };
524
470
  }
471
+ return !!dragAction;
472
+ } else {
473
+ return false;
525
474
  }
526
475
  };
527
476
 
@@ -530,16 +479,22 @@ class Control {
530
479
  cleanups.push(() => canvas.removeEventListener(type, listener));
531
480
  };
532
481
 
533
- addCanvasEventListener("mousedown", (e) => {
482
+ const preventDefaultStopPropagation = e => {
534
483
  e.preventDefault();
535
- if (e.which === 1) {
536
- startDrag(e, event => (event.which === 1) && event);
484
+ e.stopPropagation();
485
+ e.stopImmediatePropagation();
486
+ };
487
+
488
+ addCanvasEventListener("mousedown", (e) => {
489
+ if ((e.which === 1) && startDrag(e, event => (event.which === 1) && event)) {
490
+ preventDefaultStopPropagation(e);
537
491
  }
538
492
  });
539
493
 
540
494
  addCanvasEventListener("mousemove", (e) => {
541
495
  if (currentDrag) {
542
496
  currentDrag.onChange(e);
497
+ preventDefaultStopPropagation(e);
543
498
  } else {
544
499
  if (deactivateActive) {
545
500
  deactivateActive();
@@ -558,6 +513,9 @@ class Control {
558
513
  if (currentDrag) {
559
514
  currentDrag.onChange(e);
560
515
  currentDrag.cleanup();
516
+ // Calling preventDefaultStopPropagation would interfere with cameraControl
517
+ // by making it follow the pointer until next click
518
+ // preventDefaultStopPropagation(e);
561
519
  }
562
520
  });
563
521
 
@@ -567,116 +525,75 @@ class Control {
567
525
  if (event.touches.length === 1)
568
526
  {
569
527
  const touchStartId = event.touches[0].identifier;
570
- startDrag(event, event => [...event.changedTouches].find(e => e.identifier === touchStartId));
528
+ if (startDrag(event, event => [...event.changedTouches].find(e => e.identifier === touchStartId))) {
529
+ preventDefaultStopPropagation(event);
530
+ }
571
531
  }
572
532
  });
573
533
 
574
534
  addCanvasEventListener("touchmove", event => {
575
- event.preventDefault();
576
- currentDrag && currentDrag.onChange(event);
535
+ if (currentDrag) {
536
+ currentDrag.onChange(event);
537
+ preventDefaultStopPropagation(event);
538
+ }
577
539
  });
578
540
 
579
541
  addCanvasEventListener("touchend", event => {
580
- event.preventDefault();
581
542
  if (currentDrag) {
582
543
  currentDrag.onChange(event);
583
544
  currentDrag.cleanup();
545
+ // See the comment in mouseup listener
546
+ // preventDefaultStopPropagation(e);
584
547
  }
585
548
  });
586
549
  }
587
550
 
588
- this._unbindSectionPlane = () => { };
589
-
590
- this.__setSectionPlane = sectionPlane => {
591
- this.id = sectionPlane.id;
592
- this._sectionPlane = sectionPlane;
593
- const setPosFromSectionPlane = () => setPos(sectionPlane.pos);
594
- const setDirFromSectionPlane = () => rootNode.quaternion = sectionPlane.quaternion;
595
- setPosFromSectionPlane();
596
- setDirFromSectionPlane();
597
- const onSectionPlanePos = sectionPlane.on("pos", setPosFromSectionPlane);
598
- const onSectionPlaneDir = sectionPlane.on("dir", () => {
599
- if (!ignoreNextSectionPlaneDirUpdate) {
600
- setDirFromSectionPlane();
601
- } else {
602
- ignoreNextSectionPlaneDirUpdate = false;
603
- }
604
- });
605
-
606
- this._unbindSectionPlane = () => {
607
- this.id = null;
608
- this._sectionPlane = null;
609
- sectionPlane.off(onSectionPlanePos);
610
- sectionPlane.off(onSectionPlaneDir);
611
- this._unbindSectionPlane = () => { };
612
- };
613
- };
614
-
615
551
  this.__destroy = () => {
616
552
  cleanups.forEach(c => c());
617
- this._unbindSectionPlane();
618
553
  rootNode.destroy();
619
- this._displayMeshes = [ ];
620
554
  for (let id in handlers) {
621
555
  delete handlers[id];
622
556
  }
623
557
  };
624
558
  }
625
559
 
626
- _destroy() {
560
+ destroy() {
627
561
  this.__destroy();
628
562
  }
629
563
 
630
564
  /**
631
- * Called by SectionPlanesPlugin to assign this Control to a SectionPlane.
632
- * SectionPlanesPlugin keeps SectionPlaneControls in a reuse pool.
633
- * Call with a null or undefined value to disconnect the Control ffrom whatever SectionPlane it was assigned to.
565
+ * Called to assign this Control to Handlers.
566
+ * Call with a null or undefined value to disconnect the Control from whatever Handlers it was assigned to.
634
567
  * @private
635
568
  */
636
- _setSectionPlane(sectionPlane) {
637
- this._unbindSectionPlane();
638
- if (sectionPlane) {
639
- this.__setSectionPlane(sectionPlane);
640
- }
641
- }
642
-
643
- /**
644
- * Gets the {@link SectionPlane} controlled by this Control.
645
- * @returns {SectionPlane} The SectionPlane.
646
- */
647
- get sectionPlane() {
648
- return this._sectionPlane;
569
+ setHandlers(handlers) {
570
+ Object.values(this._displayMeshes).forEach(m => m.visible = !!handlers);
571
+ this._handlers = handlers;
572
+ this._displayMeshes.x.setPositionActive(handlers && handlers.onPosition);
573
+ this._displayMeshes.y.setPositionActive(handlers && handlers.onPosition);
574
+ this._displayMeshes.z.setPositionActive(handlers && handlers.onPosition);
575
+ this._displayMeshes.x.setRotationActive(handlers && handlers.onQuaternion);
576
+ this._displayMeshes.y.setRotationActive(handlers && handlers.onQuaternion);
577
+ this._displayMeshes.z.setRotationActive(handlers && handlers.onQuaternion);
649
578
  }
650
579
 
651
580
  /**
652
- * Sets if this Control is visible.
581
+ * Sets the World-space position of this Control.
653
582
  *
654
- * @type {Boolean}
583
+ * @param {Number[]} value New position.
655
584
  */
656
- setVisible(visible = true) {
657
- if (this._visible !== visible) {
658
- this._visible = visible;
659
- this._displayMeshes.forEach(m => m.visible = visible);
660
- }
585
+ setPosition(p) {
586
+ this._setPosition(p);
661
587
  }
662
588
 
663
589
  /**
664
- * Gets if this Control is visible.
590
+ * Sets the quaternion of this Control.
665
591
  *
666
- * @type {Boolean}
667
- */
668
- getVisible() {
669
- return this._visible;
670
- }
671
-
672
- /**
673
- * Sets if this Control is culled. This is called by SectionPlanesPlugin to
674
- * temporarily hide the Control while a snapshot is being taken by Viewer#getSnapshot().
675
- * @param culled
592
+ * @param {Number[]} value New quaternion.
676
593
  */
677
- setCulled(culled) {
678
- this._displayMeshes.forEach(m => m.culled = culled);
594
+ setQuaternion(q) {
595
+ this._setQuaternion(q);
679
596
  }
680
597
  }
681
598
 
682
- export {Control};
599
+ export {TransformControl};
@@ -0,0 +1 @@
1
+ export * from "./TransformControl.js";
@@ -11,6 +11,7 @@ export * from "./SectionPlanesPlugin/index.js";
11
11
  export * from "./StoreyViewsPlugin/index.js";
12
12
  export * from "./FaceAlignedSectionPlanesPlugin/index.js";
13
13
  export * from "./STLLoaderPlugin/index.js";
14
+ export * from "./TransformControl/index.js";
14
15
  export * from "./TreeViewPlugin/index.js";
15
16
  export * from "./ViewCullPlugin/index.js";
16
17
  export * from "./XKTLoaderPlugin/index.js";
@@ -229,6 +229,7 @@ class Camera extends Component {
229
229
  super(owner, cfg);
230
230
 
231
231
  this._state = new RenderState({
232
+ offsetEyeFromLook: true, // TODO: treat as legacy and get rid of at some later point
232
233
  deviceMatrix: math.mat4(),
233
234
  hasDeviceMatrix: false, // True when deviceMatrix set to other than identity
234
235
  matrix: math.mat4(),
@@ -289,7 +290,7 @@ class Camera extends Component {
289
290
  // the front off the view (not a problem with perspective, since objects close enough
290
291
  // to be clipped by the front plane are usually too big to see anything of their cross-sections).
291
292
  let eye;
292
- if (this.projection === "ortho") {
293
+ if (this.projection === "ortho" && this._state.offsetEyeFromLook) {
293
294
  math.subVec3(this._eye, this._look, eyeLookVec);
294
295
  math.normalizeVec3(eyeLookVec, eyeLookVecNorm);
295
296
  math.mulVec3Scalar(eyeLookVecNorm, 1000.0, eyeLookOffset);
@@ -871,6 +872,25 @@ class Camera extends Component {
871
872
  return this._project;
872
873
  }
873
874
 
875
+ /**
876
+ * Gets whether to offset the ortho camera's effective eye from its look
877
+ *
878
+ * Default value for legacy reasons is ````true````.
879
+ */
880
+ get offsetEyeFromLook() {
881
+ return this._state.offsetEyeFromLook;
882
+ }
883
+
884
+ /**
885
+ * Offsets the ortho camera's effective eye from its look
886
+ *
887
+ * Default value for legacy reasons is ````true````.
888
+ */
889
+ set offsetEyeFromLook(v) {
890
+ this._state.offsetEyeFromLook = v;
891
+ this._needUpdate(0); // Ensure matrix built on next "tick"
892
+ }
893
+
874
894
  /**
875
895
  * Get the 2D canvas position of a 3D world position.
876
896
  *
@@ -261,6 +261,16 @@ const math = {
261
261
  return (v1[0] === v2[0] && v1[1] === v2[1] && v1[2] === v2[2]);
262
262
  },
263
263
 
264
+ /**
265
+ * Returns true if the two 4-element vectors are the same.
266
+ * @param v1
267
+ * @param v2
268
+ * @returns {Boolean}
269
+ */
270
+ compareVec4(v1, v2) {
271
+ return (v1[0] === v2[0] && v1[1] === v2[1] && v1[2] === v2[2] && v1[3] === v2[3]);
272
+ },
273
+
264
274
  /**
265
275
  * Negates a three-element vector.
266
276
  * @method negateVec3
@@ -260,7 +260,8 @@ class Mesh extends Component {
260
260
  pickHash: "",
261
261
  offset: math.vec3(),
262
262
  origin: null,
263
- originHash: null
263
+ originHash: null,
264
+ isUI: cfg.isUI
264
265
  });
265
266
 
266
267
  this._drawRenderer = null;
@@ -613,6 +614,15 @@ class Mesh extends Component {
613
614
  return this._isObject;
614
615
  }
615
616
 
617
+ /**
618
+ * Returns ````true```` if this Mesh is an UI object.
619
+ *
620
+ * @type {Boolean}
621
+ */
622
+ get isUI() {
623
+ return this._state.isUI;
624
+ }
625
+
616
626
  /**
617
627
  * Gets the Mesh's World-space 3D axis-aligned bounding box.
618
628
  *