@volter/editor-threejs 0.5.144 → 0.5.146

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@volter/editor-threejs",
3
3
  "author": "Volter AI, Inc.",
4
- "version": "0.5.144",
4
+ "version": "0.5.146",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -135,12 +135,12 @@
135
135
  "serving": "./dist-node/serving.mjs"
136
136
  },
137
137
  "dependencies": {
138
- "@volter/editor-project": "0.5.144",
138
+ "@volter/editor-project": "0.5.146",
139
139
  "@sparkjsdev/spark": "^2.1.0",
140
140
  "three.quarks": "^0.16.0",
141
141
  "zod": "^4.3.6",
142
142
  "typescript": "5.9.3",
143
- "@volter/editor-sdk": "0.5.144",
143
+ "@volter/editor-sdk": "0.5.146",
144
144
  "postprocessing": "6.38.3",
145
145
  "quarks.core": "0.16.0",
146
146
  "@fortawesome/free-solid-svg-icons": "^7.2.0"
@@ -11,6 +11,7 @@ import {
11
11
  } from '@volter/editor-threejs/render/viewport-shading';
12
12
  import type { EffectComposer, EffectPass, RenderPass } from 'postprocessing';
13
13
  import * as THREE from 'three';
14
+ import { DepthDrawMaterials } from '../../render/depth-draw-materials';
14
15
  import { threeObject } from '../../adapter/three-contract';
15
16
  import { axisViewName, cameraPresetDirection, type ModelCameraPreset } from '../asset-workflow/model-inspection';
16
17
  import { activeKeymapNavigation } from '@volter/editor-sdk/kit/keymap-presets';
@@ -118,6 +119,7 @@ const TOPOLOGY_WIRE_COLOR = 0x11161d;
118
119
  export class Object3DDocumentSession {
119
120
  readonly profiler = createPerformanceProfiler();
120
121
  private readonly shading = new ViewportShadingRenderer();
122
+ private readonly depthDrawMaterials = new DepthDrawMaterials();
121
123
  private readonly helpers: THREE.Object3D[] = [];
122
124
  private skeletonHelper: THREE.SkeletonHelper | null = null;
123
125
  private boundsHelper: THREE.BoxHelper | null = null;
@@ -1340,6 +1342,7 @@ export class Object3DDocumentSession {
1340
1342
 
1341
1343
  render(renderSolid: (camera: THREE.Camera) => void, interactive = false): void {
1342
1344
  const finishDraw = this.beforeRender?.(interactive);
1345
+ const restoreDepthDraw = this.depthDrawMaterials.bind(this.renderer);
1343
1346
  try {
1344
1347
  this.boneSelectionHighlight?.update();
1345
1348
  const camera = this.camera();
@@ -1386,6 +1389,7 @@ export class Object3DDocumentSession {
1386
1389
  }
1387
1390
  this.shading.render(this.scene, mode, () => renderSolid(camera), isEditorViewportShadingTarget);
1388
1391
  } finally {
1392
+ restoreDepthDraw();
1389
1393
  finishDraw?.();
1390
1394
  }
1391
1395
  }
@@ -1576,6 +1580,7 @@ export class Object3DDocumentSession {
1576
1580
  this.selectionOutlinePass = null;
1577
1581
  this.composer = null;
1578
1582
  this.shading.dispose();
1583
+ this.depthDrawMaterials.dispose();
1579
1584
  this.listeners.clear();
1580
1585
  }
1581
1586
 
@@ -0,0 +1,70 @@
1
+ import * as THREE from 'three';
2
+
3
+ type Variant = {material: THREE.MeshDepthMaterial; frame: number; version: number};
4
+
5
+ /** Keep ordinary, instanced and batched depth draws from invalidating one
6
+ * another's current program. Only the material argument of an already selected
7
+ * draw changes: Three still owns visibility, groups, order and override rules.
8
+ * Custom hooks and deforming/textured depth materials take the original path. */
9
+ export class DepthDrawMaterials {
10
+ private readonly sources = new Map<THREE.MeshDepthMaterial, Map<number, Variant>>();
11
+ private frame = 0;
12
+ private count = 0;
13
+
14
+ bind(renderer: THREE.WebGLRenderer): () => void {
15
+ this.frame++;
16
+ const original = renderer.renderBufferDirect;
17
+ const cache = this;
18
+ renderer.renderBufferDirect = function (camera, scene, geometry, material, object, group) {
19
+ const selected = scene?.overrideMaterial === material
20
+ ? cache.get(material, geometry, object) : material;
21
+ return original.call(this, camera, scene, geometry, selected, object, group);
22
+ };
23
+ return () => { renderer.renderBufferDirect = original; };
24
+ }
25
+
26
+ private get(source: THREE.Material, geometry: THREE.BufferGeometry, object: THREE.Object3D): THREE.Material {
27
+ if (!(source instanceof THREE.MeshDepthMaterial) || source.transparent ||
28
+ source.map || source.alphaMap || source.displacementMap || source.clippingPlanes ||
29
+ source.onBeforeCompile !== THREE.Material.prototype.onBeforeCompile ||
30
+ source.customProgramCacheKey !== THREE.Material.prototype.customProgramCacheKey ||
31
+ source.onBeforeRender !== THREE.Material.prototype.onBeforeRender ||
32
+ !(object instanceof THREE.Mesh) || object instanceof THREE.SkinnedMesh ||
33
+ Object.keys(geometry.morphAttributes).length ||
34
+ object.onAfterRender !== THREE.Object3D.prototype.onAfterRender) return source;
35
+ const batched = object instanceof THREE.BatchedMesh;
36
+ const instanced = object instanceof THREE.InstancedMesh;
37
+ if (object.onBeforeRender !== (batched ? THREE.BatchedMesh.prototype.onBeforeRender : THREE.Object3D.prototype.onBeforeRender) ||
38
+ (instanced && (object.instanceColor || object.morphTexture))) return source;
39
+ // A batch's color-texture presence must be explicit. Unknown/custom batches
40
+ // keep their original material rather than relying on Three's internals.
41
+ if (batched && Reflect.get(object, 'colorTexture') !== null) return source;
42
+ const key = (batched ? 1 : instanced ? 2 : 0) +
43
+ (source.vertexColors && geometry.getAttribute('color')?.itemSize === 4 ? 4 : 0);
44
+ let variants = this.sources.get(source);
45
+ if (!variants) {
46
+ if (this.count >= 32) return source;
47
+ variants = new Map();
48
+ this.sources.set(source, variants);
49
+ }
50
+ let variant = variants.get(key);
51
+ if (!variant) {
52
+ if (this.count >= 32) return source;
53
+ variant = {material: source.clone(), frame: this.frame, version: source.version};
54
+ variants.set(key, variant);
55
+ this.count++;
56
+ } else if (variant.frame !== this.frame) {
57
+ variant.material.copy(source);
58
+ if (variant.version !== source.version) variant.material.needsUpdate = true;
59
+ variant.version = source.version;
60
+ variant.frame = this.frame;
61
+ }
62
+ return variant.material;
63
+ }
64
+
65
+ dispose(): void {
66
+ for (const variants of this.sources.values()) for (const {material} of variants.values()) material.dispose();
67
+ this.sources.clear();
68
+ this.count = 0;
69
+ }
70
+ }