ohzi-core 13.3.0 → 15.0.0

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.
Files changed (226) hide show
  1. package/README.md +2 -1
  2. package/build/index.mjs +1 -1
  3. package/build/index.mjs.map +1 -1
  4. package/package.json +12 -6
  5. package/src/Capabilities.ts +0 -2
  6. package/src/Debug.ts +38 -33
  7. package/src/Graphics.ts +2 -2
  8. package/src/Initializer.ts +2 -1
  9. package/src/SceneManager.ts +4 -5
  10. package/src/action_sequencer/NumberInterpolator.ts +2 -0
  11. package/src/blitter_index.ts +1 -2
  12. package/src/camera_controller/CameraController.ts +106 -63
  13. package/src/camera_controller/states/common/CommonCameraState.ts +2 -1
  14. package/src/components/AxisHelper.ts +1 -0
  15. package/src/components/Line.ts +35 -27
  16. package/src/components/PlaneHelper.ts +1 -1
  17. package/src/components/Text2D.ts +3 -1
  18. package/src/components/WorldImage.ts +27 -20
  19. package/src/components/sdf_text/SDFTextBatch.ts +16 -26
  20. package/src/dev_bridge/CameraBridge.ts +363 -0
  21. package/src/dev_bridge/CaptureService.ts +256 -0
  22. package/src/dev_bridge/CommandDispatcher.ts +114 -0
  23. package/src/dev_bridge/ConsoleBuffer.ts +286 -0
  24. package/src/dev_bridge/DebugDrawer.ts +399 -0
  25. package/src/dev_bridge/DevBridge.ts +195 -0
  26. package/src/dev_bridge/InputSynthesizer.ts +287 -0
  27. package/src/dev_bridge/PerformanceProbe.ts +118 -0
  28. package/src/dev_bridge/RenderModeRegistry.ts +66 -0
  29. package/src/dev_bridge/SceneEditor.ts +288 -0
  30. package/src/dev_bridge/SceneInspector.ts +388 -0
  31. package/src/dev_bridge/ViewNavigator.ts +101 -0
  32. package/src/dev_bridge/protocol.ts +31 -0
  33. package/src/index.ts +21 -14
  34. package/src/materials/AddMaterial.ts +14 -7
  35. package/src/materials/BlitNodeMaterial.ts +85 -0
  36. package/src/materials/BloomComposeMaterial.ts +21 -9
  37. package/src/materials/BoxBlurMaterial.ts +26 -6
  38. package/src/materials/ClearDepthNormalMaterial.ts +12 -6
  39. package/src/materials/DepthNormalMaterial.ts +25 -14
  40. package/src/materials/DualFilteringBlurMaterial.ts +32 -5
  41. package/src/materials/GaussianBlurMaterial.ts +36 -14
  42. package/src/materials/LineMaterial.ts +52 -0
  43. package/src/materials/LuminosityHighPassMaterial.ts +23 -10
  44. package/src/materials/NormalMaterial.ts +10 -11
  45. package/src/materials/SDFTextMaterial.ts +63 -16
  46. package/src/materials/SSAOComposeMaterial.ts +14 -5
  47. package/src/materials/SSAOMaterial.ts +123 -24
  48. package/src/materials/ScreenSpaceTextureMaterial.ts +19 -31
  49. package/src/materials/UnrealBloomComposeMaterial.ts +41 -32
  50. package/src/materials/WorldImageMaterial.ts +51 -0
  51. package/src/materials/deferred/DeferredPointLightMaterial.ts +45 -14
  52. package/src/materials/deferred/depth_normal_encoding.ts +36 -0
  53. package/src/primitives/Cube.ts +1 -1
  54. package/src/primitives/Sphere.ts +1 -1
  55. package/src/raycast/GroupRaycaster.ts +2 -2
  56. package/src/render_mode/BloomRender.ts +18 -4
  57. package/src/render_mode/DebugNormalsRender.ts +6 -1
  58. package/src/render_mode/DeferredRender.ts +5 -34
  59. package/src/render_mode/NormalAORender.ts +9 -19
  60. package/src/render_mode/UnrealBloomRender.ts +2 -1
  61. package/src/render_mode/VRRender.ts +6 -3
  62. package/src/render_utilities/Blitter.ts +4 -4
  63. package/src/render_utilities/Blurrer.ts +7 -0
  64. package/src/render_utilities/DualFilteringBlurrer.ts +26 -10
  65. package/src/resource_loader/AudioLoader.ts +5 -3
  66. package/src/resource_loader/HDRTextureLoader.ts +1 -1
  67. package/src/scenes/AbstractScene.ts +7 -3
  68. package/src/scenes/Compilator.ts +5 -1
  69. package/src/scenes/loading_states/LoadingState.ts +15 -7
  70. package/src/scenes/loading_states/RegularLoadingState.ts +2 -1
  71. package/src/utilities/CameraUtilities.ts +57 -79
  72. package/src/utilities/HTMLUtilities.ts +2 -1
  73. package/src/utilities/MeshSampler.ts +8 -1
  74. package/src/utilities/OrthographicFrustumPointFitter.ts +53 -4
  75. package/src/view_components/ApplicationView.ts +1 -1
  76. package/src/view_components/ViewComponent.ts +1 -1
  77. package/src/view_components/ViewManager.ts +7 -1
  78. package/src/view_components/transition/DrawIOAnimationSheet.ts +1 -1
  79. package/types/Capabilities.d.ts +0 -1
  80. package/types/SceneManager.d.ts +1 -1
  81. package/types/blitter_index.d.ts +1 -2
  82. package/types/camera_controller/CameraController.d.ts +9 -0
  83. package/types/components/Line.d.ts +3 -2
  84. package/types/components/WorldImage.d.ts +3 -2
  85. package/types/index.d.ts +2 -7
  86. package/types/materials/AddMaterial.d.ts +2 -2
  87. package/types/materials/BlitNodeMaterial.d.ts +18 -0
  88. package/types/materials/BloomComposeMaterial.d.ts +2 -2
  89. package/types/materials/BoxBlurMaterial.d.ts +2 -2
  90. package/types/materials/ClearDepthNormalMaterial.d.ts +2 -2
  91. package/types/materials/DepthNormalMaterial.d.ts +2 -2
  92. package/types/materials/DualFilteringBlurMaterial.d.ts +2 -2
  93. package/types/materials/GaussianBlurMaterial.d.ts +2 -2
  94. package/types/materials/LuminosityHighPassMaterial.d.ts +2 -2
  95. package/types/materials/NormalMaterial.d.ts +2 -2
  96. package/types/materials/SDFTextMaterial.d.ts +2 -2
  97. package/types/materials/SSAOComposeMaterial.d.ts +2 -2
  98. package/types/materials/SSAOMaterial.d.ts +2 -2
  99. package/types/materials/ScreenSpaceTextureMaterial.d.ts +2 -2
  100. package/types/materials/UnrealBloomComposeMaterial.d.ts +2 -2
  101. package/types/materials/deferred/DeferredPointLightMaterial.d.ts +2 -2
  102. package/types/render_mode/DeferredRender.d.ts +0 -2
  103. package/types/render_mode/NormalAORender.d.ts +0 -2
  104. package/types/render_utilities/Blitter.d.ts +2 -2
  105. package/types/render_utilities/DualFilteringBlurrer.d.ts +0 -2
  106. package/types/utilities/CameraUtilities.d.ts +0 -1
  107. package/types/utilities/OrthographicFrustumPointFitter.d.ts +14 -8
  108. package/build/index.module.js +0 -2
  109. package/build/index.module.js.map +0 -1
  110. package/src/components/EdgeMesh.ts +0 -150
  111. package/src/components/GeometryEdgeVisualizer.ts +0 -49
  112. package/src/components/MultiLinePath.ts +0 -25
  113. package/src/components/MultiLineText2D.ts +0 -65
  114. package/src/components/Shape2D.ts +0 -42
  115. package/src/components/Shape3D.ts +0 -38
  116. package/src/gpu_particles/GPUParticleSystem.ts +0 -122
  117. package/src/gpu_particles/ParticleAttribute.ts +0 -110
  118. package/src/gpu_particles/ParticlePositionAttribute.ts +0 -46
  119. package/src/materials/AlphaFilterMaterial.ts +0 -21
  120. package/src/materials/BaseShaderMaterial.ts +0 -20
  121. package/src/materials/BlitMaterial.ts +0 -27
  122. package/src/materials/DeferredRendererComposeMaterial.ts +0 -36
  123. package/src/materials/DisplayNormalTextureMaterial.ts +0 -18
  124. package/src/materials/FXAAMaterial.ts +0 -12
  125. package/src/materials/MedianFilterMaterial.ts +0 -46
  126. package/src/materials/SDFScreenTextMaterial.ts +0 -32
  127. package/src/materials/UnrealBlurMaterial.ts +0 -20
  128. package/src/materials/UnrealComposeMaterial.ts +0 -27
  129. package/src/materials/ViewPositionMaterial.ts +0 -19
  130. package/src/materials/gpu_particles/AttributeUpdateMaterial.ts +0 -35
  131. package/src/materials/gpu_particles/BasicParticleMaterial.ts +0 -25
  132. package/src/materials/gpu_particles/ParticleStorageMaterial.ts +0 -36
  133. package/src/materials/gpu_particles/PositionStorageMaterial.ts +0 -27
  134. package/src/render_utilities/MedianFilter.ts +0 -72
  135. package/src/render_utilities/ViewPositionRenderer.ts +0 -53
  136. package/src/shaders/add/add.frag +0 -28
  137. package/src/shaders/anti_aliasing/fxaa.frag +0 -72
  138. package/src/shaders/basic_color/basic_color.frag +0 -6
  139. package/src/shaders/basic_color/basic_color.vert +0 -10
  140. package/src/shaders/basic_line/basic_line.frag +0 -37
  141. package/src/shaders/basic_line/basic_line.vert +0 -37
  142. package/src/shaders/basic_texture/basic_texture.frag +0 -10
  143. package/src/shaders/basic_texture/basic_texture.vert +0 -17
  144. package/src/shaders/bloom/bloom_compose.frag +0 -22
  145. package/src/shaders/bloom/cheap_bloom_compose.frag +0 -13
  146. package/src/shaders/box_blur/box_blur.frag +0 -24
  147. package/src/shaders/box_blur/compose.frag +0 -31
  148. package/src/shaders/clear/clear_depth_normal.frag +0 -25
  149. package/src/shaders/copy/copy.frag +0 -7
  150. package/src/shaders/copy/copy.vert +0 -6
  151. package/src/shaders/deferred/blit_copy.vert +0 -11
  152. package/src/shaders/deferred/deferred_compose.frag +0 -59
  153. package/src/shaders/deferred/point_light.frag +0 -66
  154. package/src/shaders/deferred/point_light.vert +0 -13
  155. package/src/shaders/depth_normals/debug_normals.frag +0 -48
  156. package/src/shaders/depth_normals/depth_normals.frag +0 -30
  157. package/src/shaders/depth_normals/depth_normals.vert +0 -13
  158. package/src/shaders/dual_filter_blur/alpha_filter.frag +0 -14
  159. package/src/shaders/dual_filter_blur/downsample.frag +0 -21
  160. package/src/shaders/dual_filter_blur/upsample.frag +0 -24
  161. package/src/shaders/edge_visualizer/edge_visualizer.frag +0 -18
  162. package/src/shaders/edge_visualizer/edge_visualizer.vert +0 -16
  163. package/src/shaders/edges/corners.frag +0 -11
  164. package/src/shaders/edges/corners.vert +0 -31
  165. package/src/shaders/edges/edges.frag +0 -7
  166. package/src/shaders/edges/edges.vert +0 -27
  167. package/src/shaders/gaussian_blur/gaussian_blur.frag +0 -44
  168. package/src/shaders/gaussian_blur/unreal_bloom_compose.frag +0 -36
  169. package/src/shaders/gpu_particles/common_utils.glsl +0 -13
  170. package/src/shaders/gpu_particles/generic_storage.frag +0 -6
  171. package/src/shaders/gpu_particles/store/store_position.vert +0 -14
  172. package/src/shaders/gpu_particles/update/basic_update.frag +0 -21
  173. package/src/shaders/gpu_particles/visualize/visualize.frag +0 -11
  174. package/src/shaders/gpu_particles/visualize/visualize.vert +0 -12
  175. package/src/shaders/grid/grid.frag +0 -17
  176. package/src/shaders/grid/grid.vert +0 -10
  177. package/src/shaders/luminosity_high_pass/luminosity_high_pass.frag +0 -38
  178. package/src/shaders/median_filter/median_filter.frag +0 -134
  179. package/src/shaders/normal/normal.frag +0 -6
  180. package/src/shaders/normal/normal.vert +0 -6
  181. package/src/shaders/sdf_text/sdf_screen_text.frag +0 -51
  182. package/src/shaders/sdf_text/sdf_screen_text.vert +0 -42
  183. package/src/shaders/sdf_text/sdf_text.frag +0 -53
  184. package/src/shaders/sdf_text/sdf_text.vert +0 -42
  185. package/src/shaders/ssao/hbao.frag +0 -264
  186. package/src/shaders/ssao/ssao.frag +0 -95
  187. package/src/shaders/ssao/ssao.vert +0 -11
  188. package/src/shaders/ssao/ssao_compose.frag +0 -14
  189. package/src/shaders/transparent_mix/copy.frag +0 -7
  190. package/src/shaders/transparent_mix/transparent_mix.frag +0 -19
  191. package/src/shaders/transparent_mix/transparent_mix.vert +0 -8
  192. package/src/shaders/ui/ss_texture.frag +0 -11
  193. package/src/shaders/ui/ss_texture.vert +0 -32
  194. package/src/shaders/ui/ws_texture.vert +0 -39
  195. package/src/shaders/unreal_blur/unreal_blur.frag +0 -25
  196. package/src/shaders/unreal_blur/unreal_compose.frag +0 -24
  197. package/src/shaders/write_view_position/write_view_position.frag +0 -7
  198. package/src/shaders/write_view_position/write_view_position.vert +0 -9
  199. package/src/types.d.ts +0 -14
  200. package/types/components/EdgeMesh.d.ts +0 -16
  201. package/types/components/GeometryEdgeVisualizer.d.ts +0 -8
  202. package/types/components/MultiLinePath.d.ts +0 -8
  203. package/types/components/MultiLineText2D.d.ts +0 -12
  204. package/types/components/Shape2D.d.ts +0 -7
  205. package/types/components/Shape3D.d.ts +0 -6
  206. package/types/gpu_particles/GPUParticleSystem.d.ts +0 -19
  207. package/types/gpu_particles/ParticleAttribute.d.ts +0 -20
  208. package/types/gpu_particles/ParticlePositionAttribute.d.ts +0 -6
  209. package/types/materials/AlphaFilterMaterial.d.ts +0 -7
  210. package/types/materials/BaseShaderMaterial.d.ts +0 -4
  211. package/types/materials/BlitMaterial.d.ts +0 -4
  212. package/types/materials/DeferredRendererComposeMaterial.d.ts +0 -10
  213. package/types/materials/DisplayNormalTextureMaterial.d.ts +0 -4
  214. package/types/materials/FXAAMaterial.d.ts +0 -5
  215. package/types/materials/MedianFilterMaterial.d.ts +0 -8
  216. package/types/materials/SDFScreenTextMaterial.d.ts +0 -8
  217. package/types/materials/UIElementMaterial.d.ts +0 -4
  218. package/types/materials/UnrealBlurMaterial.d.ts +0 -5
  219. package/types/materials/UnrealComposeMaterial.d.ts +0 -5
  220. package/types/materials/ViewPositionMaterial.d.ts +0 -5
  221. package/types/materials/gpu_particles/AttributeUpdateMaterial.d.ts +0 -8
  222. package/types/materials/gpu_particles/BasicParticleMaterial.d.ts +0 -5
  223. package/types/materials/gpu_particles/ParticleStorageMaterial.d.ts +0 -5
  224. package/types/materials/gpu_particles/PositionStorageMaterial.d.ts +0 -5
  225. package/types/render_utilities/MedianFilter.d.ts +0 -12
  226. package/types/render_utilities/ViewPositionRenderer.d.ts +0 -12
@@ -1,36 +1,31 @@
1
- import line_fs from '../shaders/basic_line/basic_line.frag';
2
- import line_vs from '../shaders/basic_line/basic_line.vert';
1
+ import { LineMaterial } from '../materials/LineMaterial';
3
2
 
4
- import type { Vector3 } from 'three';
5
- import { BufferAttribute, BufferGeometry, Color, Mesh, ShaderMaterial } from 'three';
3
+ import type { Color, ColorRepresentation, Vector3 } from 'three';
4
+ import { BufferAttribute, BufferGeometry, Mesh } from 'three';
5
+
6
+ // An empty geometry with every attribute LineMaterial reads.
7
+ function create_line_geometry(): BufferGeometry
8
+ {
9
+ const geometry = new BufferGeometry();
10
+ geometry.setAttribute('position', new BufferAttribute(new Float32Array([]), 3));
11
+ geometry.setAttribute('next_position', new BufferAttribute(new Float32Array([]), 3));
12
+ geometry.setAttribute('previous_position', new BufferAttribute(new Float32Array([]), 3));
13
+ geometry.setAttribute('orientation', new BufferAttribute(new Float32Array([]), 1));
14
+ geometry.setAttribute('coverage', new BufferAttribute(new Float32Array([]), 1));
15
+
16
+ return geometry;
17
+ }
6
18
 
7
19
  class Line extends Mesh
8
20
  {
9
21
  _length: number;
10
22
  accumulated_length: number;
11
- material: ShaderMaterial
23
+ material: LineMaterial;
12
24
 
13
25
  constructor(points?: Vector3[])
14
26
  {
15
- const geometry = new BufferGeometry();
16
- geometry.setAttribute('position', new BufferAttribute(new Float32Array([]), 3));
17
- geometry.setAttribute('next_position', new BufferAttribute(new Float32Array([]), 3));
18
- geometry.setAttribute('previous_position', new BufferAttribute(new Float32Array([]), 3));
19
- geometry.setAttribute('orientation', new BufferAttribute(new Float32Array([]), 1));
20
- geometry.setAttribute('coverage', new BufferAttribute(new Float32Array([]), 1));
21
-
22
- const material = new ShaderMaterial({
23
- uniforms: {
24
- _Thickness: { value: 0.2 },
25
- _Length: { value: 0 },
26
- _ElapsedTime: { value: 0 },
27
- _Color: { value: new Color('#FF0000') }
28
- },
29
- vertexShader: line_vs,
30
- fragmentShader: line_fs,
31
- transparent: true,
32
- depthWrite: false
33
- });
27
+ const geometry = create_line_geometry();
28
+ const material = new LineMaterial();
34
29
 
35
30
  super(geometry, material);
36
31
  this.material = material;
@@ -106,7 +101,18 @@ class Line extends Mesh
106
101
  indices.push(index + 1);
107
102
  }
108
103
 
109
- this.geometry.setIndex(indices);
104
+ // WebGPURenderer sizes each GPU buffer at its first upload and later only writes
105
+ // into it, and it frees a geometry's buffers on its first dispose() only. So a new
106
+ // point count moves the line to a new geometry, which the next render uploads at
107
+ // the new size, and frees the old one. The index depends only on the point count,
108
+ // so it is kept otherwise.
109
+ if (vertexList.length / 3 !== this.geometry.getAttribute('position').count)
110
+ {
111
+ const previous_geometry = this.geometry;
112
+ this.geometry = create_line_geometry();
113
+ this.geometry.setIndex(indices);
114
+ previous_geometry.dispose();
115
+ }
110
116
  (this.geometry.getAttribute('position') as BufferAttribute).copy(new BufferAttribute(vertexList, 3));
111
117
  (this.geometry.getAttribute('next_position') as BufferAttribute).copy(new BufferAttribute(nextPositionList, 3));
112
118
  (this.geometry.getAttribute('previous_position') as BufferAttribute).copy(new BufferAttribute(previousPositionList, 3));
@@ -180,14 +186,16 @@ class Line extends Mesh
180
186
  {
181
187
  this.parent.remove(this);
182
188
  }
189
+
190
+ super.dispose();
183
191
  }
184
192
 
185
- set color(col)
193
+ set color(col: ColorRepresentation)
186
194
  {
187
195
  this.material.uniforms._Color.value.set(col);
188
196
  }
189
197
 
190
- get color()
198
+ get color(): Color
191
199
  {
192
200
  return this.material.uniforms._Color.value;
193
201
  }
@@ -27,7 +27,7 @@ class PlaneHelper extends Object3D
27
27
 
28
28
  dispose()
29
29
  {
30
-
30
+ super.dispose();
31
31
  }
32
32
  }
33
33
 
@@ -2,7 +2,7 @@ import { SimpleTextDrawer } from '../canvas_drawer/SimpleTextDrawer';
2
2
  import { WorldImage } from './WorldImage';
3
3
 
4
4
  import type { CanvasTexture, Vector2 } from 'three';
5
- import { LinearFilter, LinearMipMapLinearFilter } from 'three';
5
+ import { LinearFilter, LinearMipMapLinearFilter, SRGBColorSpace } from 'three';
6
6
 
7
7
  class Text2D extends WorldImage
8
8
  {
@@ -22,6 +22,8 @@ class Text2D extends WorldImage
22
22
 
23
23
  canvas_texture.minFilter = LinearMipMapLinearFilter;
24
24
  canvas_texture.minFilter = LinearFilter;
25
+ // Canvas colors are CSS colors, so sRGB. Tagged, they render as the given color.
26
+ canvas_texture.colorSpace = SRGBColorSpace;
25
27
  canvas_texture.needsUpdate = true;
26
28
  super(canvas_texture, pivot);
27
29
 
@@ -1,31 +1,20 @@
1
- import basic_texture_frag from '../shaders/basic_texture/basic_texture.frag';
2
- import basic_texture_vert from '../shaders/basic_texture/basic_texture.vert';
1
+ import { WorldImageMaterial } from '../materials/WorldImageMaterial';
3
2
 
4
3
  import type { Texture } from 'three';
5
- import { DoubleSide, Mesh, PlaneGeometry, ShaderMaterial, Vector2, Vector3 } from 'three';
4
+ import { Mesh, PlaneGeometry, Vector2, Vector3 } from 'three';
6
5
 
7
6
  class WorldImage extends Mesh
8
7
  {
9
8
  current_scale: number;
9
+ sized_texture: Texture;
10
+ texture_size: Vector2;
10
11
  tmp_bb_size: Vector3;
11
- material: ShaderMaterial;
12
+ material: WorldImageMaterial;
12
13
 
13
14
  constructor(texture: Texture, pivot: Vector2)
14
15
  {
15
16
  pivot = pivot || new Vector2(0, 0);
16
- const material = new ShaderMaterial({
17
- uniforms: {
18
- _MainTex: { value: texture },
19
- _ScreenAligned: { value: 0 },
20
- _Scale: { value: 1 },
21
- _Opacity: { value: 1 }
22
- },
23
- vertexShader: basic_texture_vert,
24
- fragmentShader: basic_texture_frag,
25
- transparent: true,
26
- depthWrite: false,
27
- side: DoubleSide
28
- });
17
+ const material = new WorldImageMaterial(texture);
29
18
  const geometry = new PlaneGeometry(1, 1, 1);
30
19
  geometry.translate(-pivot.x / 2, -pivot.y / 2, 0);
31
20
  // @ts-expect-error -- threejs issue
@@ -33,6 +22,9 @@ class WorldImage extends Mesh
33
22
  geometry.scale(current_scale, 1, 1);
34
23
  super(geometry, material);
35
24
  this.current_scale = current_scale;
25
+ this.sized_texture = texture;
26
+ // @ts-expect-error -- threejs issue
27
+ this.texture_size = new Vector2(texture.image.width, texture.image.height);
36
28
  this.geometry.computeBoundingBox();
37
29
 
38
30
  this.tmp_bb_size = new Vector3();
@@ -42,8 +34,21 @@ class WorldImage extends Mesh
42
34
 
43
35
  update_texture()
44
36
  {
45
- this.material.uniforms._MainTex.value.needsUpdate = true;
46
- const img = this.material.uniforms._MainTex.value.image;
37
+ const texture = this.material.uniforms._MainTex.value;
38
+ const img = texture.image as { width: number, height: number };
39
+
40
+ // WebGPURenderer allocates the GPU texture at the image size once and later only
41
+ // uploads into it, so an image that changed size needs a new one. A texture swapped
42
+ // into _MainTex may be shared, so it is only measured, never freed.
43
+ const resized = img.width !== this.texture_size.x || img.height !== this.texture_size.y;
44
+
45
+ if (texture === this.sized_texture && resized)
46
+ {
47
+ texture.dispose();
48
+ }
49
+ this.sized_texture = texture;
50
+ this.texture_size.set(img.width, img.height);
51
+ texture.needsUpdate = true;
47
52
 
48
53
  this.geometry.scale(1 / this.current_scale, 1, 1);
49
54
  this.current_scale = img.width / img.height;
@@ -60,7 +65,7 @@ class WorldImage extends Mesh
60
65
  set size(value)
61
66
  {
62
67
  this.scale.copy(value);
63
- this.material.uniforms._Scale.value = value;
68
+ this.material.uniforms._Scale.value.copy(value);
64
69
  }
65
70
 
66
71
  set screen_aligned(boolean)
@@ -89,6 +94,8 @@ class WorldImage extends Mesh
89
94
  this.parent.remove(this);
90
95
  this.material.uniforms._MainTex.value.dispose();
91
96
  this.material.dispose();
97
+
98
+ super.dispose();
92
99
  }
93
100
  }
94
101
 
@@ -5,13 +5,17 @@ import { SDFText } from './SDFText';
5
5
  import {
6
6
  DynamicDrawUsage,
7
7
  Float32BufferAttribute,
8
- InstancedBufferAttribute,
9
8
  InstancedBufferGeometry,
9
+ InstancedInterleavedBuffer,
10
+ InterleavedBufferAttribute,
10
11
  Mesh,
11
12
  PlaneGeometry,
12
13
  Vector2
13
14
  } from 'three';
14
15
 
16
+ // The per glyph attributes, four floats each, in the order they sit in an instance.
17
+ const GLYPH_ATTRIBUTES = ['transformsCol0', 'transformsCol1', 'transformsCol2', 'transformsCol3', 'glyph_bounds', 'plane_bounds', 'color'];
18
+
15
19
  class SDFTextBatch extends Mesh
16
20
  {
17
21
  font_layout: any;
@@ -33,32 +37,16 @@ class SDFTextBatch extends Mesh
33
37
  instanced_geometry.setAttribute('uv', new Float32BufferAttribute(geometry.getAttribute('uv').array, 2));
34
38
  instanced_geometry.index = geometry.index;
35
39
 
36
- const transformsCol0 = new InstancedBufferAttribute(new Float32Array(max_allocations * 4), 4, false);
37
- const transformsCol1 = new InstancedBufferAttribute(new Float32Array(max_allocations * 4), 4, false);
38
- const transformsCol2 = new InstancedBufferAttribute(new Float32Array(max_allocations * 4), 4, false);
39
- const transformsCol3 = new InstancedBufferAttribute(new Float32Array(max_allocations * 4), 4, false);
40
-
41
- const glyph_bounds = new InstancedBufferAttribute(new Float32Array(max_allocations * 4), 4, false);
42
- const plane_bounds = new InstancedBufferAttribute(new Float32Array(max_allocations * 4), 4, false);
43
-
44
- const color = new InstancedBufferAttribute(new Float32Array(max_allocations * 4), 4, false);
45
-
46
- transformsCol0.usage = DynamicDrawUsage;
47
- transformsCol1.usage = DynamicDrawUsage;
48
- transformsCol2.usage = DynamicDrawUsage;
49
- transformsCol3.usage = DynamicDrawUsage;
50
- glyph_bounds.usage = DynamicDrawUsage;
51
- plane_bounds.usage = DynamicDrawUsage;
52
- color.usage = DynamicDrawUsage;
40
+ // One interleaved buffer holds every per glyph attribute. WebGPU allows 8 vertex
41
+ // buffers per pipeline, and one buffer per attribute would take 9 with position
42
+ // and uv.
43
+ const glyphs = new InstancedInterleavedBuffer(new Float32Array(max_allocations * GLYPH_ATTRIBUTES.length * 4), GLYPH_ATTRIBUTES.length * 4);
44
+ glyphs.setUsage(DynamicDrawUsage);
53
45
 
54
- instanced_geometry.setAttribute('transformsCol0', transformsCol0);
55
- instanced_geometry.setAttribute('transformsCol1', transformsCol1);
56
- instanced_geometry.setAttribute('transformsCol2', transformsCol2);
57
- instanced_geometry.setAttribute('transformsCol3', transformsCol3);
58
-
59
- instanced_geometry.setAttribute('glyph_bounds', glyph_bounds);
60
- instanced_geometry.setAttribute('plane_bounds', plane_bounds);
61
- instanced_geometry.setAttribute('color', color);
46
+ GLYPH_ATTRIBUTES.forEach((name, i) =>
47
+ {
48
+ instanced_geometry.setAttribute(name, new InterleavedBufferAttribute(glyphs, 4, i * 4));
49
+ });
62
50
 
63
51
  super(instanced_geometry, new SDFTextMaterial(atlas_texture));
64
52
 
@@ -195,6 +183,8 @@ class SDFTextBatch extends Mesh
195
183
  {
196
184
  this.geometry.dispose();
197
185
  this.material.dispose();
186
+
187
+ super.dispose();
198
188
  }
199
189
  }
200
190
 
@@ -0,0 +1,363 @@
1
+ import type { InspectableObject } from './SceneInspector';
2
+
3
+ const PRECISION = 10000;
4
+
5
+ export interface OrbitalController
6
+ {
7
+ current_tilt: number;
8
+ current_orientation: number;
9
+ current_azimuth: number;
10
+ normalized_zoom: number;
11
+ min_zoom: number;
12
+ max_zoom: number;
13
+ reference_position: { x: number; y: number; z: number };
14
+ get_current_tilt(): number;
15
+ get_current_orientation(): number;
16
+ get_current_azimuth(): number;
17
+ set_normalized_zoom(zoom: number): void;
18
+ set_rotation(tilt?: number, orientation?: number, azimuth?: number): void;
19
+ focus_on_bounding_box(box: unknown, scale?: number): void;
20
+ }
21
+
22
+ export interface BridgeCamera
23
+ {
24
+ position: { x: number; y: number; z: number };
25
+ fov?: number;
26
+ near?: number;
27
+ far?: number;
28
+ clear_color?: { getHexString?: () => string };
29
+ clear_alpha?: number;
30
+ updateProjectionMatrix?: () => void;
31
+ }
32
+
33
+ export interface ControllerState
34
+ {
35
+ tilt: number;
36
+ orientation: number;
37
+ azimuth: number;
38
+ zoom: number;
39
+ min_zoom: number;
40
+ max_zoom: number;
41
+ target: number[];
42
+ }
43
+
44
+ export interface CameraState
45
+ {
46
+ position: number[];
47
+ fov: number | null;
48
+ near: number | null;
49
+ far: number | null;
50
+ clear_color: string | null;
51
+ clear_alpha: number | null;
52
+ controller: ControllerState | null;
53
+ changed?: string[];
54
+ }
55
+
56
+ // Changes arrive off the dev-bridge wire and are therefore untrusted.
57
+ export interface CameraChanges
58
+ {
59
+ tilt?: unknown;
60
+ orientation?: unknown;
61
+ azimuth?: unknown;
62
+ zoom?: unknown;
63
+ fov?: unknown;
64
+ target?: unknown;
65
+ position?: unknown;
66
+ }
67
+
68
+ export interface FrameRequest
69
+ {
70
+ uuid?: unknown;
71
+ name?: unknown;
72
+ scale?: unknown;
73
+ }
74
+
75
+ // Speaks the CameraController's own idiom. The controller is orbital, driven by
76
+ // tilt, orientation, azimuth and a normalised zoom in degrees, and it rewrites
77
+ // the camera transform every update. Assigning camera.position directly while a
78
+ // controller is active would simply be overwritten on the next frame.
79
+ class CameraBridge
80
+ {
81
+ get(camera: BridgeCamera, controller: OrbitalController | null): CameraState
82
+ {
83
+ return this.describe(this.require_camera(camera), controller);
84
+ }
85
+
86
+ set(camera: BridgeCamera, controller: OrbitalController | null, changes: CameraChanges): CameraState
87
+ {
88
+ const target_camera = this.require_camera(camera);
89
+ const changed: string[] = [];
90
+
91
+ if (controller !== null)
92
+ {
93
+ this.apply_orbit(controller, changes, changed);
94
+ }
95
+
96
+ this.apply_position(target_camera, controller, changes, changed);
97
+ this.apply_fov(target_camera, changes, changed);
98
+
99
+ const state = this.describe(target_camera, controller);
100
+ state.changed = changed;
101
+
102
+ return state;
103
+ }
104
+
105
+ frame(
106
+ camera: BridgeCamera,
107
+ controller: OrbitalController | null,
108
+ root: InspectableObject,
109
+ request: FrameRequest,
110
+ make_bounds: (object: InspectableObject) => unknown
111
+ ): CameraState
112
+ {
113
+ const target_camera = this.require_camera(camera);
114
+
115
+ if (controller === null)
116
+ {
117
+ throw this.error('no_controller', 'The active scene has no camera_controller, so framing is unavailable.');
118
+ }
119
+
120
+ const uuid = typeof request.uuid === 'string' ? request.uuid : null;
121
+ const name = typeof request.name === 'string' ? request.name : null;
122
+
123
+ if (uuid === null && name === null)
124
+ {
125
+ throw this.error('not_found', 'Pass a uuid or a name to identify the object to frame.');
126
+ }
127
+
128
+ const found = this.find(root, uuid, name);
129
+
130
+ if (found === null)
131
+ {
132
+ const label = uuid === null ? `name '${name === null ? '' : name}'` : `uuid '${uuid}'`;
133
+
134
+ throw this.error('not_found', `No object matching ${label} in the current scene.`);
135
+ }
136
+
137
+ const scale = typeof request.scale === 'number' && Number.isFinite(request.scale) ? request.scale : 1;
138
+
139
+ controller.focus_on_bounding_box(make_bounds(found), scale);
140
+
141
+ const state = this.describe(target_camera, controller);
142
+ state.changed = ['framed'];
143
+
144
+ return state;
145
+ }
146
+
147
+ // Angles go through set_rotation, which is nullish-checked, so an omitted
148
+ // value keeps the current angle and an explicit 0 is applied. It also records
149
+ // old_orientation, which assigning the fields directly would skip.
150
+ private apply_orbit(controller: OrbitalController, changes: CameraChanges, changed: string[]): void
151
+ {
152
+ const tilt = this.number(changes.tilt);
153
+ const orientation = this.number(changes.orientation);
154
+ const azimuth = this.number(changes.azimuth);
155
+
156
+ if (tilt !== null || orientation !== null || azimuth !== null)
157
+ {
158
+ if (tilt !== null)
159
+ {
160
+ changed.push('tilt');
161
+ }
162
+
163
+ if (orientation !== null)
164
+ {
165
+ changed.push('orientation');
166
+ }
167
+
168
+ if (azimuth !== null)
169
+ {
170
+ changed.push('azimuth');
171
+ }
172
+
173
+ controller.set_rotation(
174
+ tilt === null ? undefined : tilt,
175
+ orientation === null ? undefined : orientation,
176
+ azimuth === null ? undefined : azimuth
177
+ );
178
+ }
179
+
180
+ const zoom = this.number(changes.zoom);
181
+
182
+ if (zoom !== null)
183
+ {
184
+ controller.set_normalized_zoom(zoom);
185
+ changed.push('zoom');
186
+ }
187
+
188
+ const target = this.triple(changes.target);
189
+
190
+ if (target !== null)
191
+ {
192
+ controller.reference_position.x = target[0];
193
+ controller.reference_position.y = target[1];
194
+ controller.reference_position.z = target[2];
195
+ changed.push('target');
196
+ }
197
+ }
198
+
199
+ private apply_position(
200
+ camera: BridgeCamera,
201
+ controller: OrbitalController | null,
202
+ changes: CameraChanges,
203
+ changed: string[]
204
+ ): void
205
+ {
206
+ const position = this.triple(changes.position);
207
+
208
+ if (position === null)
209
+ {
210
+ return;
211
+ }
212
+
213
+ if (controller !== null)
214
+ {
215
+ throw this.error(
216
+ 'controller_owns_camera',
217
+ 'A CameraController is driving this camera and would overwrite a raw position on the next frame. Use tilt, orientation, azimuth, zoom and target instead.'
218
+ );
219
+ }
220
+
221
+ camera.position.x = position[0];
222
+ camera.position.y = position[1];
223
+ camera.position.z = position[2];
224
+ changed.push('position');
225
+ }
226
+
227
+ private apply_fov(camera: BridgeCamera, changes: CameraChanges, changed: string[]): void
228
+ {
229
+ const fov = this.number(changes.fov);
230
+
231
+ if (fov === null)
232
+ {
233
+ return;
234
+ }
235
+
236
+ camera.fov = fov;
237
+ changed.push('fov');
238
+
239
+ if (typeof camera.updateProjectionMatrix === 'function')
240
+ {
241
+ camera.updateProjectionMatrix();
242
+ }
243
+ }
244
+
245
+ private describe(camera: BridgeCamera, controller: OrbitalController | null): CameraState
246
+ {
247
+ return {
248
+ position: [this.round(camera.position.x), this.round(camera.position.y), this.round(camera.position.z)],
249
+ fov: typeof camera.fov === 'number' ? camera.fov : null,
250
+ near: typeof camera.near === 'number' ? camera.near : null,
251
+ far: typeof camera.far === 'number' ? camera.far : null,
252
+ clear_color: this.hex(camera.clear_color),
253
+ clear_alpha: typeof camera.clear_alpha === 'number' ? camera.clear_alpha : null,
254
+ controller: controller === null ? null : {
255
+ tilt: controller.get_current_tilt(),
256
+ orientation: controller.get_current_orientation(),
257
+ azimuth: controller.get_current_azimuth(),
258
+ zoom: controller.normalized_zoom,
259
+ min_zoom: controller.min_zoom,
260
+ max_zoom: controller.max_zoom,
261
+ target: [
262
+ this.round(controller.reference_position.x),
263
+ this.round(controller.reference_position.y),
264
+ this.round(controller.reference_position.z)
265
+ ]
266
+ }
267
+ };
268
+ }
269
+
270
+ private find(node: InspectableObject, uuid: string | null, name: string | null): InspectableObject | null
271
+ {
272
+ const matches = uuid !== null ? node.uuid === uuid : node.name === name;
273
+
274
+ if (matches)
275
+ {
276
+ return node;
277
+ }
278
+
279
+ const children = Array.isArray(node.children) ? node.children : [];
280
+
281
+ for (const child of children)
282
+ {
283
+ const found = this.find(child, uuid, name);
284
+
285
+ if (found !== null)
286
+ {
287
+ return found;
288
+ }
289
+ }
290
+
291
+ return null;
292
+ }
293
+
294
+ private require_camera(camera: BridgeCamera): BridgeCamera
295
+ {
296
+ if (typeof camera !== 'object' || camera === null || typeof camera.position !== 'object')
297
+ {
298
+ throw this.error('no_camera', 'No camera is active. CameraManager.current is not set yet.');
299
+ }
300
+
301
+ return camera;
302
+ }
303
+
304
+ private hex(color: { getHexString?: () => string } | undefined): string | null
305
+ {
306
+ if (typeof color !== 'object' || color === null || typeof color.getHexString !== 'function')
307
+ {
308
+ return null;
309
+ }
310
+
311
+ return `#${color.getHexString()}`;
312
+ }
313
+
314
+ private number(value: unknown): number | null
315
+ {
316
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
317
+ }
318
+
319
+ private triple(value: unknown): number[] | null
320
+ {
321
+ if (!Array.isArray(value))
322
+ {
323
+ return null;
324
+ }
325
+
326
+ const supplied = value as unknown[];
327
+ const out: number[] = [];
328
+
329
+ for (let i = 0; i < 3; i++)
330
+ {
331
+ const entry = this.number(supplied[i]);
332
+
333
+ if (entry === null)
334
+ {
335
+ return null;
336
+ }
337
+
338
+ out.push(entry);
339
+ }
340
+
341
+ return out;
342
+ }
343
+
344
+ private round(value: unknown): number
345
+ {
346
+ if (typeof value !== 'number' || !Number.isFinite(value))
347
+ {
348
+ return 0;
349
+ }
350
+
351
+ return Math.round(value * PRECISION) / PRECISION;
352
+ }
353
+
354
+ private error(code: string, message: string): Error
355
+ {
356
+ const error: Error & { code?: string } = new Error(message);
357
+ error.code = code;
358
+
359
+ return error;
360
+ }
361
+ }
362
+
363
+ export { CameraBridge };