zincjs 2.4.1-beta.4 → 2.4.1-beta.5

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.
@@ -2,7 +2,7 @@ import * as THREE from 'three/webgpu';
2
2
  import { Label } from './label';
3
3
  import { InstancedPoints } from '../three/InstancedPoints';
4
4
  import { ZincObject } from './zincObject';
5
- import { getCircularTexture, toBufferGeometry } from '../utilities';
5
+ import { getCircularTexture, toBufferGeometry, updateWorldMatrixFromAncestors } from '../utilities';
6
6
  import { createInstancedPointsMaterial } from '../tsl/pointsMaterial';
7
7
 
8
8
  //Maximum number of points a programmatically built (addPoints) Pointset can
@@ -124,27 +124,26 @@ const Pointset = function () {
124
124
  if (morphVertices) {
125
125
  const bottomPositions = positionFrames[bottomFrame];
126
126
  const topPositions = positionFrames[topFrame];
127
+ //pointPositions shares its array with instancePosition
127
128
  const positions = mesh.pointPositions;
128
129
  const length = activeCount * 3;
129
130
  for (let i = 0; i < length; i++) {
130
131
  positions[i] = proportion * bottomPositions[i] + (1 - proportion) * topPositions[i];
131
132
  }
132
- const instancePosition = mesh.geometry.getAttribute('instancePosition');
133
- instancePosition.array.set(positions.subarray(0, length));
134
- instancePosition.needsUpdate = true;
133
+ mesh.geometry.getAttribute('instancePosition').needsUpdate = true;
135
134
  this.boundingBoxUpdateRequired = true;
136
135
  }
137
136
  if (morphColours && colorFrames) {
138
137
  const bottomColors = colorFrames[Math.min(bottomFrame, colorFrames.length - 1)];
139
138
  const topColors = colorFrames[Math.min(topFrame, colorFrames.length - 1)];
140
- for (let i = 0; i < activeCount; i++) {
141
- const index = i * 3;
142
- _tempColor.setRGB(
143
- proportion * bottomColors[index] + (1 - proportion) * topColors[index],
144
- proportion * bottomColors[index + 1] + (1 - proportion) * topColors[index + 1],
145
- proportion * bottomColors[index + 2] + (1 - proportion) * topColors[index + 2],
146
- );
147
- mesh.setColorAt(i, _tempColor);
139
+ if (!mesh.instanceColor) {
140
+ mesh.setColorAt(0, _tempColor);
141
+ }
142
+ //Write the blended colours straight into the instance colours
143
+ const colors = mesh.instanceColor.array;
144
+ const length = activeCount * 3;
145
+ for (let i = 0; i < length; i++) {
146
+ colors[i] = proportion * bottomColors[i] + (1 - proportion) * topColors[i];
148
147
  }
149
148
  mesh.instanceColor.needsUpdate = true;
150
149
  }
@@ -188,14 +187,13 @@ const Pointset = function () {
188
187
 
189
188
  const mesh = new InstancedPoints(quadGeometry, material, numberOfPoints);
190
189
  mesh.count = numberOfPoints;
191
- mesh.pointPositions = new Float32Array(numberOfPoints * 3);
192
- mesh.pointPositions.set(positionFrames[0]);
193
- mesh.userData = this;
194
- this.drawRange = numberOfPoints;
195
-
196
190
  const instancePosition = quadGeometry.getAttribute('instancePosition');
197
191
  instancePosition.array.set(positionFrames[0]);
198
192
  instancePosition.needsUpdate = true;
193
+ //Share the array to avoid keeping and updating two copies
194
+ mesh.pointPositions = instancePosition.array;
195
+ mesh.userData = this;
196
+ this.drawRange = numberOfPoints;
199
197
 
200
198
  const initialColorFrame = colorFrames ? colorFrames[0] : extractStaticColorFrame(geometry);
201
199
  if (initialColorFrame) {
@@ -230,7 +228,8 @@ const Pointset = function () {
230
228
  label.setPosition(coord[0], coord[1], coord[2]);
231
229
  const sprite = label.getSprite();
232
230
  sprite.material.sizeAttenuation = false;
233
- sprite.material.alphaTest = 0.5;
231
+ //Only discard fully transparent pixels to keep the smooth edges
232
+ sprite.material.alphaTest = 0.05;
234
233
  sprite.material.transparent = true;
235
234
  sprite.material.depthWrite = false;
236
235
  sprite.material.depthTest = labelDepthTest;
@@ -261,6 +260,7 @@ const Pointset = function () {
261
260
  * @param {Number} colour - A hex value of the colour for the points
262
261
  */
263
262
  this.addPoints = (coords, labels, colour) => {
263
+ this.requestRender();
264
264
  if (coords && coords.length > 0) {
265
265
  let mesh = this.getMorph();
266
266
  let current = this.drawRange;
@@ -272,7 +272,7 @@ const Pointset = function () {
272
272
  material.color.set(colour);
273
273
  material.alphaTest = 0.5;
274
274
  mesh = new InstancedPoints(quadGeometry, material, DEFAULT_CAPACITY);
275
- mesh.pointPositions = new Float32Array(DEFAULT_CAPACITY * 3);
275
+ mesh.pointPositions = quadGeometry.getAttribute('instancePosition').array;
276
276
  mesh.count = 0;
277
277
  mesh.userData = this;
278
278
  this.setMorph(mesh);
@@ -281,9 +281,6 @@ const Pointset = function () {
281
281
  coords.forEach(coord => {
282
282
  if (this.drawRange < DEFAULT_CAPACITY) {
283
283
  instancePosition.setXYZ(this.drawRange, coord[0], coord[1], coord[2]);
284
- mesh.pointPositions[this.drawRange * 3] = coord[0];
285
- mesh.pointPositions[this.drawRange * 3 + 1] = coord[1];
286
- mesh.pointPositions[this.drawRange * 3 + 2] = coord[2];
287
284
  this.drawRange++;
288
285
  }
289
286
  });
@@ -311,6 +308,7 @@ const Pointset = function () {
311
308
  * @param {Number} hex - hex value of color to be set
312
309
  */
313
310
  this.setColourHex = function(hex) {
311
+ this.requestRender();
314
312
  this._lod._material.color.setHex(hex);
315
313
  if (this._lod._secondaryMaterial) {
316
314
  this._lod._secondaryMaterial.color.setHex(hex);
@@ -328,6 +326,7 @@ const Pointset = function () {
328
326
  * @param {THREE.Color} colour - colour to be set
329
327
  */
330
328
  this.setColour = (colour) => {
329
+ this.requestRender();
331
330
  this._lod.setColour(colour);
332
331
  for (let i = 0; i < labelSets.length; i++) {
333
332
  if (labelSets[i]) {
@@ -343,6 +342,7 @@ const Pointset = function () {
343
342
  * should be on or off.
344
343
  */
345
344
  this.setLabelDepthTest = (flag) => {
345
+ this.requestRender();
346
346
  labelDepthTest = flag;
347
347
  for (let i = 0; i < labelSets.length; i++) {
348
348
  if (labelSets[i]) {
@@ -357,6 +357,7 @@ const Pointset = function () {
357
357
  * @param {Number} fontWeightIn - Default value is 700
358
358
  */
359
359
  this.setLabelFontWeight = (fontWeightIn) => {
360
+ this.requestRender();
360
361
  fontWeight = fontWeightIn;
361
362
  for (let i = 0; i < labelSets.length; i++) {
362
363
  if (labelSets[i]) {
@@ -372,6 +373,7 @@ const Pointset = function () {
372
373
  * value is 1.
373
374
  */
374
375
  this.setLabelSize = (size) => {
376
+ this.requestRender();
375
377
  labelSize = size;
376
378
  for (let i = 0; i < labelSets.length; i++) {
377
379
  if (labelSets[i]) {
@@ -386,6 +388,7 @@ const Pointset = function () {
386
388
  * @param {boolean} flag - default value is true
387
389
  */
388
390
  this.displayLabels = (flag) => {
391
+ this.requestRender();
389
392
  labelVisibility = flag;
390
393
  for (let i = 0; i < labelSets.length; i++) {
391
394
  if (labelSets[i]) {
@@ -399,6 +402,7 @@ const Pointset = function () {
399
402
  * @param {Number} size - size to be set.
400
403
  */
401
404
  this.setSize = size => {
405
+ this.requestRender();
402
406
  if (this.morph && this.morph.material) {
403
407
  this.morph.material.size = size;
404
408
  this.morph.material.userData.uniforms.pointSize.value = size;
@@ -413,6 +417,7 @@ const Pointset = function () {
413
417
  * should be on or off.
414
418
  */
415
419
  this.setSizeAttenuation = flag => {
420
+ this.requestRender();
416
421
  if (this.morph && this.morph.material) {
417
422
  this.morph.material.sizeAttenuation = flag;
418
423
  this.morph.material.userData.uniforms.sizeAttenuation.value = flag ? 1 : 0;
@@ -438,6 +443,7 @@ const Pointset = function () {
438
443
  * Edit Vertice in index.
439
444
  */
440
445
  this.editVertices = function(coords, i) {
446
+ this.requestRender();
441
447
  if (coords && coords.length) {
442
448
  const mesh = this.getMorph();
443
449
  const maxIndex = i + coords.length - 1;
@@ -452,9 +458,6 @@ const Pointset = function () {
452
458
  label.setPosition(coord[0], coord[1], coord[2]);
453
459
  }
454
460
  instancePosition.setXYZ(index, coord[0], coord[1], coord[2]);
455
- mesh.pointPositions[index * 3] = coord[0];
456
- mesh.pointPositions[index * 3 + 1] = coord[1];
457
- mesh.pointPositions[index * 3 + 2] = coord[2];
458
461
  //Keep the base frame consistent for any pointset that also
459
462
  //happens to be time varying.
460
463
  if (positionFrames[0]) {
@@ -474,6 +477,7 @@ const Pointset = function () {
474
477
  * Delete a vertex in index.
475
478
  */
476
479
  this.deleteVertices = function(index) {
480
+ this.requestRender();
477
481
  const mesh = this.getMorph();
478
482
  if (!mesh || 0 > index || index >= this.drawRange) {
479
483
  return this.drawRange;
@@ -481,7 +485,8 @@ const Pointset = function () {
481
485
  const instancePosition = mesh.geometry.getAttribute('instancePosition');
482
486
  const end = this.drawRange - 1;
483
487
  instancePosition.array.copyWithin(index * 3, (index + 1) * 3, this.drawRange * 3);
484
- mesh.pointPositions.copyWithin(index * 3, (index + 1) * 3, this.drawRange * 3);
488
+ if (mesh.pointPositions !== instancePosition.array)
489
+ mesh.pointPositions.copyWithin(index * 3, (index + 1) * 3, this.drawRange * 3);
485
490
  if (mesh.instanceColor) {
486
491
  mesh.instanceColor.array.copyWithin(index * 3, (index + 1) * 3, this.drawRange * 3);
487
492
  mesh.instanceColor.needsUpdate = true;
@@ -560,8 +565,7 @@ const Pointset = function () {
560
565
  if (this.boundingBoxUpdateRequired) {
561
566
  this._b1.setFromArray(mesh.pointPositions.subarray(0, this.drawRange * 3));
562
567
  this.cachedBoundingBox.copy(this._b1);
563
- mesh.updateWorldMatrix(true, true);
564
- this.cachedBoundingBox.applyMatrix4(mesh.matrixWorld);
568
+ this.cachedBoundingBox.applyMatrix4(updateWorldMatrixFromAncestors(mesh));
565
569
  this.cachedBoundingBox.getCenter(this.center);
566
570
  this.radius = this.center.distanceTo(this.cachedBoundingBox.max);
567
571
  this.boundingBoxUpdateRequired = false;
@@ -586,6 +590,7 @@ const Pointset = function () {
586
590
  * @param {Number} time - Can be any value between 0 to duration.
587
591
  */
588
592
  this.setMorphTime = time => {
593
+ this.requestRender();
589
594
  if (time > this.duration) {
590
595
  this.inbuildTime = this.duration;
591
596
  } else if (0 > time) {
@@ -2,6 +2,7 @@ import * as THREE from 'three/webgpu';
2
2
  //import * as shader from '../shaders/textureSlide.js';
3
3
  import { createWebGPUMaterial } from '../tsl/textureSlides.js';
4
4
  import { TexturePrimitive } from './texturePrimitive';
5
+ import { updateWorldMatrixFromAncestors } from '../utilities';
5
6
 
6
7
 
7
8
  const cloneData3DTexture = (sourceTex) => {
@@ -73,6 +74,7 @@ const TextureSlides = function (textureIn) {
73
74
  * @param {SLIDE_SETTINGS} slideSettings - An array to each slide settings.
74
75
  */
75
76
  this.createSlides = slideSettings => {
77
+ this.requestRender();
76
78
  slideSettings.forEach(slide => this.createSlide(slide));
77
79
  }
78
80
 
@@ -123,6 +125,7 @@ const TextureSlides = function (textureIn) {
123
125
  * @param {SLIDE_SETTINGS} settings - s.
124
126
  */
125
127
  this.modifySlideSettings = (settings) => {
128
+ this.requestRender();
126
129
  if (settings && settings.id &&
127
130
  settings.id in idTextureMap &&
128
131
  idTextureMap[settings.id]) {
@@ -139,6 +142,7 @@ const TextureSlides = function (textureIn) {
139
142
  * created mesh's id.
140
143
  */
141
144
  this.createSlide = settings => {
145
+ this.requestRender();
142
146
  if (this.texture && this.texture.isTextureArray && this.texture.isReady()) {
143
147
  if (settings && settings.direction && settings.value !== undefined) {
144
148
  const geometry = new THREE.PlaneGeometry(1, 1);
@@ -238,6 +242,7 @@ const TextureSlides = function (textureIn) {
238
242
  * @param {Slide} slide - Slide to be remvoed
239
243
  */
240
244
  this.removeSlide = slide => {
245
+ this.requestRender();
241
246
  if (slide) {
242
247
  this.removeSlideWithId(slide.id);
243
248
  }
@@ -249,6 +254,7 @@ const TextureSlides = function (textureIn) {
249
254
  * @param {Number} id - id of slide to be remvoed
250
255
  */
251
256
  this.removeSlideWithId = id => {
257
+ this.requestRender();
252
258
  if (this.morph && id in idTextureMap && idTextureMap[id]) {
253
259
  if (this.morph.getObjectById(id)) {
254
260
  const slide = idTextureMap[id];
@@ -333,14 +339,14 @@ const TextureSlides = function (textureIn) {
333
339
  expandBoxWithSettings(this.cachedBoundingBox, slide.material.userData.uniforms,
334
340
  vector);
335
341
  });
336
- this.morph.updateMatrixWorld (true, true);
337
- this.cachedBoundingBox.applyMatrix4(this.morph.matrixWorld);
342
+ this.cachedBoundingBox.applyMatrix4(updateWorldMatrixFromAncestors(this.morph));
338
343
  this.boundingBoxUpdateRequired = false;
339
344
  }
340
345
  return this.cachedBoundingBox;
341
346
  }
342
347
 
343
348
  this.applyTransformation = (rotation, position, scale) => {
349
+ this.requestRender();
344
350
  const matrix = new THREE.Matrix4();
345
351
  matrix.set(
346
352
  rotation[0],
@@ -369,6 +375,7 @@ const TextureSlides = function (textureIn) {
369
375
  }
370
376
 
371
377
  this.setRenderOrder = (order) => {
378
+ this.requestRender();
372
379
  //multiilayers
373
380
  this.morph.renderOrder = order;
374
381
  }
@@ -394,6 +401,7 @@ const TextureSlides = function (textureIn) {
394
401
  }
395
402
 
396
403
  this.showEdges = (color) => {
404
+ this.requestRender();
397
405
  if (!edgesLine) {
398
406
  const geometry = new THREE.BoxGeometry( 1, 1, 1 );
399
407
  geometry.translate(0.5, 0.5, 0.5);
@@ -408,6 +416,7 @@ const TextureSlides = function (textureIn) {
408
416
 
409
417
 
410
418
  this.setUniformsValue = (name, val) => {
419
+ this.requestRender();
411
420
  this.morph.children.forEach((mesh) => {
412
421
  const material = mesh.material;
413
422
  if (material.userData.uniforms) {
@@ -422,6 +431,7 @@ const TextureSlides = function (textureIn) {
422
431
  }
423
432
 
424
433
  this.discardAlphaPixel = (flag) => {
434
+ this.requestRender();
425
435
  discardAlpha = flag;
426
436
  this.setUniformsValue("discardAlpha", discardAlpha);
427
437
  }
@@ -431,6 +441,7 @@ const TextureSlides = function (textureIn) {
431
441
  }
432
442
 
433
443
  this.setBrightness = (brightnessIn) => {
444
+ this.requestRender();
434
445
  brightness = brightnessIn;
435
446
  this.setUniformsValue("brightness", brightness);
436
447
  }
@@ -440,6 +451,7 @@ const TextureSlides = function (textureIn) {
440
451
  }
441
452
 
442
453
  this.setContrast = (contrastIn) => {
454
+ this.requestRender();
443
455
  if (contrast >= 0 ) {
444
456
  contrast = contrastIn;
445
457
  this.setUniformsValue("contrast", contrast);
@@ -451,6 +463,7 @@ const TextureSlides = function (textureIn) {
451
463
  }
452
464
 
453
465
  this.setNumberOfChannels = (numbersIn) => {
466
+ this.requestRender();
454
467
  nChannels = numbersIn;
455
468
  this.setUniformsValue("nChannels", nChannels);
456
469
  }
@@ -460,6 +473,7 @@ const TextureSlides = function (textureIn) {
460
473
  }
461
474
 
462
475
  this.setMask = (maskTextureIn) => {
476
+ this.requestRender();
463
477
  maskTexture = maskTextureIn;
464
478
  maskEnabled = maskTexture ? true : false;
465
479
  this.morph.children.forEach((mesh) => {
@@ -475,6 +489,7 @@ const TextureSlides = function (textureIn) {
475
489
  }
476
490
 
477
491
  this.hideEdges = () => {
492
+ this.requestRender();
478
493
  if (edgesLine) {
479
494
  edgesLine.visible = false;
480
495
  }
@@ -513,6 +528,7 @@ const TextureSlides = function (textureIn) {
513
528
  * Update the glyphsets if required the render.
514
529
  */
515
530
  this.setMorphTime = (time) => {
531
+ this.requestRender();
516
532
  let newTime = time;
517
533
  if (time > this.duration)
518
534
  newTime = this.duration;
@@ -529,7 +545,7 @@ const TextureSlides = function (textureIn) {
529
545
  /**
530
546
  * Update the glyphsets if required the render.
531
547
  */
532
- this.render = (delta, playAnimation, options) => {
548
+ this.render = (delta, playAnimation, cameraControls, options) => {
533
549
  //console.log("render", delta, playAnimation, this.textureList)
534
550
  if (playAnimation == true && this.timeEnabled &&
535
551
  this.textureList.length > 1) {
@@ -17,6 +17,8 @@ const TextureSlides = function (textureIn) {
17
17
  TexturePrimitive.call(this, textureIn);
18
18
  this.isTextureVolume = true;
19
19
  const textureSettings = [];
20
+ //Map of mesh id to slide mesh
21
+ const idTextureMap = {};
20
22
  this.morph = new THREE.Group();
21
23
  this.group = this.morph;
22
24
  this.morph.userData = this;
@@ -44,6 +44,7 @@ const TubeLines = function () {
44
44
  * @param {Number} width - Width of the lines.
45
45
  */
46
46
  this.setWidth = width => {
47
+ this.requestRender();
47
48
  if (this.morph && this.morph.material) {
48
49
  this.morph.material.linewidth = width;
49
50
  this.morph.material.needsUpdate = true;
@@ -58,6 +59,7 @@ const TubeLines = function () {
58
59
  * can be any value between from 0 to 1.0.
59
60
  */
60
61
  this.setAlpha = function (alpha) {
62
+ this.requestRender();
61
63
  let mesh = this.getMorph();
62
64
  mesh.material.opacity = alpha;
63
65
  mesh.material.transparent = alpha < 1.0;
@@ -69,6 +71,7 @@ const TubeLines = function () {
69
71
  * @param {Boolean} wireframe
70
72
  */
71
73
  this.setWireframe = (wireframe) => {
74
+ this.requestRender();
72
75
  let mesh = this.getMorph();
73
76
  mesh.material.wireframe = wireframe;
74
77
  }
@@ -80,6 +83,7 @@ const TubeLines = function () {
80
83
  * @param {Integer} radialSegments The number of segments that make up the cross-section.
81
84
  */
82
85
  this.setTubeLines = (radius, radialSegments) => {
86
+ this.requestRender();
83
87
  if (radius && radialSegments) {
84
88
  const { geometryIn } = dataIn;
85
89
  let mesh = this.getMorph();
@@ -115,7 +115,15 @@ ZincObject.prototype.getRegion = function() {
115
115
  *
116
116
  * @return {Object}
117
117
  */
118
- ZincObject.prototype.getMorph = function() {
118
+ /**
119
+ * Request the scene to draw the next frame, this is used when render on
120
+ * demand is enabled. See {@link Renderer#setRenderOnDemand}.
121
+ */
122
+ ZincObject.prototype.requestRender = function() {
123
+ this.region?.getScene?.()?.invalidate?.();
124
+ }
125
+
126
+ ZincObject.prototype.getMorph = function() {
119
127
  const morph = this._lod.getCurrentMorph();
120
128
  return morph ? morph : this.morph;
121
129
  }
@@ -159,6 +167,7 @@ ZincObject.prototype.checkTransparentMesh = function() {
159
167
  * time dependent.
160
168
  */
161
169
  ZincObject.prototype.setMesh = function(mesh, localTimeEnabled, localMorphColour) {
170
+ this.requestRender();
162
171
  //Note: we assume all layers are consistent with time frame
163
172
  //Thus adding them to the same animation group should work.
164
173
  //This step is only required for the primary (level 0) mesh.
@@ -267,6 +276,7 @@ ZincObject.prototype.getCurrentTime = function() {
267
276
  * @param {Number} time - Can be any value between 0 to duration.
268
277
  */
269
278
  ZincObject.prototype.setMorphTime = function(time) {
279
+ this.requestRender();
270
280
  let timeChanged = false;
271
281
  if (this.clipAction) {
272
282
  const ratio = time / this.duration;
@@ -293,7 +303,8 @@ ZincObject.prototype.setMorphTime = function(time) {
293
303
  }
294
304
  }
295
305
  if (timeChanged) {
296
- this.boundingBoxUpdateRequired = true;
306
+ if (this.hasMorphPositions())
307
+ this.boundingBoxUpdateRequired = true;
297
308
  this._lod.updateMorphColorAttribute(true);
298
309
  if (this.timeEnabled)
299
310
  this.markerUpdateRequired = true;
@@ -325,6 +336,7 @@ ZincObject.prototype.getVisibility = function() {
325
336
  * @param {Boolean} visible - a boolean flag indicate the visibility to be set
326
337
  */
327
338
  ZincObject.prototype.setVisibility = function(visible) {
339
+ this.requestRender();
328
340
  if (visible !== this.visible) {
329
341
  this.visible = visible;
330
342
  this.group.visible = visible;
@@ -340,6 +352,7 @@ ZincObject.prototype.setVisibility = function(visible) {
340
352
  * can be any value between from 0 to 1.0.
341
353
  */
342
354
  ZincObject.prototype.setAlpha = function(alpha) {
355
+ this.requestRender();
343
356
  const material = this._lod._material;
344
357
  let isTransparent = false;
345
358
  if (alpha < 1.0)
@@ -357,6 +370,7 @@ ZincObject.prototype.setAlpha = function(alpha) {
357
370
  * @param {Boolean} flag - Set frustrum culling on/off based on this flag.
358
371
  */
359
372
  ZincObject.prototype.setFrustumCulled = function(flag) {
373
+ this.requestRender();
360
374
  //multilayers - set for all layers
361
375
  this._lod.setFrustumCulled(flag);
362
376
  }
@@ -368,6 +382,7 @@ ZincObject.prototype.setFrustumCulled = function(flag) {
368
382
  * @param {Boolean} vertexColors - Set display with vertex color on/off.
369
383
  */
370
384
  ZincObject.prototype.setVertexColors = function(vertexColors) {
385
+ this.requestRender();
371
386
  //multilayers - set for all
372
387
  this._lod.setVertexColors(vertexColors);
373
388
 
@@ -390,6 +405,7 @@ ZincObject.prototype.getColour = function() {
390
405
  * @param {THREE.Color} colour - Colour to be set for this geometry.
391
406
  */
392
407
  ZincObject.prototype.setColour = function(colour) {
408
+ this.requestRender();
393
409
  this._lod.setColour(colour);
394
410
  }
395
411
 
@@ -399,6 +415,7 @@ ZincObject.prototype.setColour = function(colour) {
399
415
  * @param {THREE.Color} colour - Colour to be set for this geometry.
400
416
  */
401
417
  ZincObject.prototype.setGreyScale = function(flag) {
418
+ this.requestRender();
402
419
  if (flag) {
403
420
  if (!this.origColour) {
404
421
  if (this._lod._material) {
@@ -439,6 +456,7 @@ ZincObject.prototype.getColourHex = function() {
439
456
  * @param {String} hex - The colour value in hex form.
440
457
  */
441
458
  ZincObject.prototype.setColourHex = function(hex) {
459
+ this.requestRender();
442
460
  this._lod._material.color.setHex(hex);
443
461
  if (this._lod._secondaryMaterial) {
444
462
  this._lod._secondaryMaterial.color.setHex(hex);
@@ -451,6 +469,7 @@ ZincObject.prototype.setColourHex = function(hex) {
451
469
  * @param {String} colour - The colour value in rgb form.
452
470
  */
453
471
  ZincObject.prototype.setEmissiveRGB = function(colour) {
472
+ this.requestRender();
454
473
  if (this._lod._material && this._lod._material.emissive) {
455
474
  this._lod._material.emissive.setRGB(...colour);
456
475
  }
@@ -466,6 +485,7 @@ ZincObject.prototype.setEmissiveRGB = function(colour) {
466
485
  * @param {THREE.Material} material - Material to be set for this geometry.
467
486
  */
468
487
  ZincObject.prototype.setMaterial = function(material) {
488
+ this.requestRender();
469
489
  this._lod.setMaterial(material);
470
490
  }
471
491
 
@@ -486,10 +506,8 @@ ZincObject.prototype.getClosestVertexIndex = function() {
486
506
  let currentDistance = 0;
487
507
  for (let i = 0; i < position.count; i++) {
488
508
  this._v2.fromArray(position.array, i * 3);
489
- currentDistance = this._v2.distanceTo(this._v1);
490
- if (distance == -1)
491
- distance = currentDistance;
492
- else if (distance > (currentDistance)) {
509
+ currentDistance = this._v2.distanceToSquared(this._v1);
510
+ if ((distance == -1) || (distance > currentDistance)) {
493
511
  distance = currentDistance;
494
512
  closestIndex = i;
495
513
  }
@@ -592,6 +610,44 @@ ZincObject.prototype.markerIsRequired = function(options) {
592
610
  return false;
593
611
  }
594
612
 
613
+ //geometry -> whether its morph targets move the vertices
614
+ const morphPositionsCache = new WeakMap();
615
+
616
+ /**
617
+ * Check if the vertices positions change over time, only then will
618
+ * the bounding box change during playback.
619
+ *
620
+ * @return {Boolean}
621
+ */
622
+ ZincObject.prototype.hasMorphPositions = function() {
623
+ const geometry = this.getMorph()?.geometry;
624
+ const morphPositions = geometry?.morphAttributes?.position;
625
+ if (!morphPositions || morphPositions.length === 0)
626
+ return false;
627
+ const cached = morphPositionsCache.get(geometry);
628
+ if (cached && cached.morphPositions === morphPositions)
629
+ return cached.result;
630
+ //Colour only morphs use placeholder targets identical to the base
631
+ //positions, these never move the vertices.
632
+ const basePositions = geometry.getAttribute('position')?.array;
633
+ let result = false;
634
+ for (let i = 0; i < morphPositions.length && !result; i++) {
635
+ const array = morphPositions[i].array;
636
+ if (!basePositions || array.length !== basePositions.length) {
637
+ result = true;
638
+ } else if (array !== basePositions) {
639
+ for (let j = 0; j < array.length; j++) {
640
+ if (array[j] !== basePositions[j]) {
641
+ result = true;
642
+ break;
643
+ }
644
+ }
645
+ }
646
+ }
647
+ morphPositionsCache.set(geometry, { morphPositions, result });
648
+ return result;
649
+ }
650
+
595
651
  /**
596
652
  * Update the marker's position and size based on current viewport.
597
653
  */
@@ -610,6 +666,7 @@ ZincObject.prototype.updateMarker = function(playAnimation, options) {
610
666
  if (position) {
611
667
  this.marker.setPosition(position.x, position.y, position.z);
612
668
  this.markerUpdateRequired = false;
669
+ ndcToBeUpdated = true;
613
670
  }
614
671
  }
615
672
  if (!this.marker.isEnabled()) {
@@ -627,8 +684,9 @@ ZincObject.prototype.updateMarker = function(playAnimation, options) {
627
684
  } else {
628
685
  this.marker.setDefaultSprite();
629
686
  }
630
- if (options && options.camera && (ndcToBeUpdated ||
631
- options.markerCluster.markerUpdateRequired)) {
687
+ //Only update the screen position when the camera or the
688
+ //marker has moved, or the marker has just been enabled.
689
+ if (options && options.camera && ndcToBeUpdated) {
632
690
  this.marker.updateNDC(options.camera.cameraObject);
633
691
  options.markerCluster.markerUpdateRequired = true;
634
692
  }
@@ -637,7 +695,7 @@ ZincObject.prototype.updateMarker = function(playAnimation, options) {
637
695
  if (this.marker && this.marker.isEnabled()) {
638
696
  this.marker.disable();
639
697
  this.group.remove(this.marker.morph);
640
- if (options.markersList &&
698
+ if (options?.markersList &&
641
699
  (this.marker.uuid in options.markersList)) {
642
700
  options.markerCluster.markerUpdateRequired = true;
643
701
  delete options.markersList[this.marker.uuid];
@@ -661,6 +719,7 @@ ZincObject.prototype.initiateMorphColor = function() {
661
719
  }
662
720
 
663
721
  ZincObject.prototype.setRenderOrder = function(renderOrder) {
722
+ this.requestRender();
664
723
  //multiilayers
665
724
  this._lod.setRenderOrder(renderOrder);
666
725
  }
@@ -699,6 +758,7 @@ ZincObject.prototype.getClosestVertexDOMElementCoords = function(scene) {
699
758
  * @return {Boolean}
700
759
  */
701
760
  ZincObject.prototype.setMarkerMode = function(mode, options) {
761
+ this.requestRender();
702
762
  if (mode !== this.markerMode) {
703
763
  if (mode === "on" || mode === "off") {
704
764
  this.markerMode = mode;
@@ -733,7 +793,9 @@ ZincObject.prototype.render = function(delta, playAnimation,
733
793
  }
734
794
  //multilayers
735
795
  if (this.visible && delta != 0) {
736
- this.boundingBoxUpdateRequired = true;
796
+ //Only vertices morphing can change the bounding box
797
+ if (this.hasMorphPositions())
798
+ this.boundingBoxUpdateRequired = true;
737
799
  if (this.morphColour == 1) {
738
800
  this._lod.updateMorphColorAttribute(true);
739
801
  }
@@ -753,6 +815,7 @@ ZincObject.prototype.addLOD = function(loader, level, url, index, preload) {
753
815
  * Add lod from an url into the lod object.
754
816
  */
755
817
  ZincObject.prototype.addVertices = function(coords) {
818
+ this.requestRender();
756
819
  let mesh = this.getMorph();
757
820
  let geometry = undefined;
758
821
  if (!mesh) {
@@ -780,6 +843,7 @@ ZincObject.prototype.addVertices = function(coords) {
780
843
  * Add lod from an url into the lod object.
781
844
  */
782
845
  ZincObject.prototype.deleteVertices = function(index) {
846
+ this.requestRender();
783
847
  let mesh = this.getMorph();
784
848
  if (mesh?.geometry && this.drawRange >= index) {
785
849
  if (removeVertexAtIndex(mesh.geometry, index, true)) {
@@ -798,6 +862,7 @@ ZincObject.prototype.deleteVertices = function(index) {
798
862
  * Set the objects position.
799
863
  */
800
864
  ZincObject.prototype.setPosition = function(x, y, z) {
865
+ this.requestRender();
801
866
  const group = this.getGroup();
802
867
  if (group) {
803
868
  group.position.set(x, y, z);
@@ -817,6 +882,7 @@ ZincObject.prototype.loadAdditionalSources = function(primitivesLoader, sources)
817
882
  * Set the objects scale.
818
883
  */
819
884
  ZincObject.prototype.setScaleAll = function(scale) {
885
+ this.requestRender();
820
886
  const group = this.getGroup();
821
887
  if (group) {
822
888
  group.scale.set(scale, scale, scale);