ohzi-core 14.0.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.
- package/README.md +2 -1
- package/build/index.mjs +1 -1
- package/build/index.mjs.map +1 -1
- package/package.json +8 -5
- package/src/Capabilities.ts +0 -2
- package/src/Debug.ts +38 -33
- package/src/Graphics.ts +2 -2
- package/src/Initializer.ts +2 -1
- package/src/SceneManager.ts +4 -5
- package/src/action_sequencer/NumberInterpolator.ts +2 -0
- package/src/blitter_index.ts +1 -2
- package/src/camera_controller/CameraController.ts +100 -59
- package/src/camera_controller/states/common/CommonCameraState.ts +2 -1
- package/src/components/AxisHelper.ts +1 -0
- package/src/components/Line.ts +35 -27
- package/src/components/PlaneHelper.ts +1 -1
- package/src/components/Text2D.ts +3 -1
- package/src/components/WorldImage.ts +27 -20
- package/src/components/sdf_text/SDFTextBatch.ts +16 -26
- package/src/dev_bridge/DebugDrawer.ts +399 -0
- package/src/index.ts +9 -12
- package/src/materials/AddMaterial.ts +14 -7
- package/src/materials/BlitNodeMaterial.ts +85 -0
- package/src/materials/BloomComposeMaterial.ts +21 -9
- package/src/materials/BoxBlurMaterial.ts +26 -6
- package/src/materials/ClearDepthNormalMaterial.ts +12 -6
- package/src/materials/DepthNormalMaterial.ts +25 -14
- package/src/materials/DualFilteringBlurMaterial.ts +32 -5
- package/src/materials/GaussianBlurMaterial.ts +36 -14
- package/src/materials/LineMaterial.ts +52 -0
- package/src/materials/LuminosityHighPassMaterial.ts +23 -10
- package/src/materials/NormalMaterial.ts +10 -11
- package/src/materials/SDFTextMaterial.ts +63 -16
- package/src/materials/SSAOComposeMaterial.ts +14 -5
- package/src/materials/SSAOMaterial.ts +123 -24
- package/src/materials/ScreenSpaceTextureMaterial.ts +19 -31
- package/src/materials/UnrealBloomComposeMaterial.ts +41 -32
- package/src/materials/WorldImageMaterial.ts +51 -0
- package/src/materials/deferred/DeferredPointLightMaterial.ts +45 -14
- package/src/materials/deferred/depth_normal_encoding.ts +36 -0
- package/src/primitives/Cube.ts +1 -1
- package/src/primitives/Sphere.ts +1 -1
- package/src/raycast/GroupRaycaster.ts +2 -2
- package/src/render_mode/BloomRender.ts +18 -4
- package/src/render_mode/DebugNormalsRender.ts +6 -1
- package/src/render_mode/DeferredRender.ts +5 -34
- package/src/render_mode/NormalAORender.ts +9 -19
- package/src/render_mode/UnrealBloomRender.ts +2 -1
- package/src/render_mode/VRRender.ts +6 -3
- package/src/render_utilities/Blitter.ts +4 -4
- package/src/render_utilities/Blurrer.ts +7 -0
- package/src/render_utilities/DualFilteringBlurrer.ts +26 -10
- package/src/resource_loader/AudioLoader.ts +5 -3
- package/src/resource_loader/HDRTextureLoader.ts +1 -1
- package/src/scenes/AbstractScene.ts +7 -3
- package/src/scenes/Compilator.ts +5 -1
- package/src/scenes/loading_states/LoadingState.ts +15 -7
- package/src/scenes/loading_states/RegularLoadingState.ts +2 -1
- package/src/utilities/CameraUtilities.ts +57 -79
- package/src/utilities/HTMLUtilities.ts +2 -1
- package/src/utilities/MeshSampler.ts +8 -1
- package/src/utilities/OrthographicFrustumPointFitter.ts +53 -4
- package/src/view_components/ApplicationView.ts +1 -1
- package/src/view_components/ViewComponent.ts +1 -1
- package/src/view_components/ViewManager.ts +7 -1
- package/src/view_components/transition/DrawIOAnimationSheet.ts +1 -1
- package/types/Capabilities.d.ts +0 -1
- package/types/SceneManager.d.ts +1 -1
- package/types/blitter_index.d.ts +1 -2
- package/types/camera_controller/CameraController.d.ts +9 -0
- package/types/components/Line.d.ts +3 -2
- package/types/components/WorldImage.d.ts +3 -2
- package/types/index.d.ts +2 -7
- package/types/materials/AddMaterial.d.ts +2 -2
- package/types/materials/BlitNodeMaterial.d.ts +18 -0
- package/types/materials/BloomComposeMaterial.d.ts +2 -2
- package/types/materials/BoxBlurMaterial.d.ts +2 -2
- package/types/materials/ClearDepthNormalMaterial.d.ts +2 -2
- package/types/materials/DepthNormalMaterial.d.ts +2 -2
- package/types/materials/DualFilteringBlurMaterial.d.ts +2 -2
- package/types/materials/GaussianBlurMaterial.d.ts +2 -2
- package/types/materials/LuminosityHighPassMaterial.d.ts +2 -2
- package/types/materials/NormalMaterial.d.ts +2 -2
- package/types/materials/SDFTextMaterial.d.ts +2 -2
- package/types/materials/SSAOComposeMaterial.d.ts +2 -2
- package/types/materials/SSAOMaterial.d.ts +2 -2
- package/types/materials/ScreenSpaceTextureMaterial.d.ts +2 -2
- package/types/materials/UnrealBloomComposeMaterial.d.ts +2 -2
- package/types/materials/deferred/DeferredPointLightMaterial.d.ts +2 -2
- package/types/render_mode/DeferredRender.d.ts +0 -2
- package/types/render_mode/NormalAORender.d.ts +0 -2
- package/types/render_utilities/Blitter.d.ts +2 -2
- package/types/render_utilities/DualFilteringBlurrer.d.ts +0 -2
- package/types/utilities/CameraUtilities.d.ts +0 -1
- package/types/utilities/OrthographicFrustumPointFitter.d.ts +14 -8
- package/build/index.module.js +0 -2
- package/build/index.module.js.map +0 -1
- package/src/components/EdgeMesh.ts +0 -150
- package/src/components/GeometryEdgeVisualizer.ts +0 -49
- package/src/components/MultiLinePath.ts +0 -25
- package/src/components/MultiLineText2D.ts +0 -65
- package/src/components/Shape2D.ts +0 -42
- package/src/components/Shape3D.ts +0 -38
- package/src/gpu_particles/GPUParticleSystem.ts +0 -122
- package/src/gpu_particles/ParticleAttribute.ts +0 -110
- package/src/gpu_particles/ParticlePositionAttribute.ts +0 -46
- package/src/materials/AlphaFilterMaterial.ts +0 -21
- package/src/materials/BaseShaderMaterial.ts +0 -20
- package/src/materials/BlitMaterial.ts +0 -27
- package/src/materials/DeferredRendererComposeMaterial.ts +0 -36
- package/src/materials/DisplayNormalTextureMaterial.ts +0 -18
- package/src/materials/FXAAMaterial.ts +0 -12
- package/src/materials/MedianFilterMaterial.ts +0 -46
- package/src/materials/SDFScreenTextMaterial.ts +0 -32
- package/src/materials/UnrealBlurMaterial.ts +0 -20
- package/src/materials/UnrealComposeMaterial.ts +0 -27
- package/src/materials/ViewPositionMaterial.ts +0 -19
- package/src/materials/gpu_particles/AttributeUpdateMaterial.ts +0 -35
- package/src/materials/gpu_particles/BasicParticleMaterial.ts +0 -25
- package/src/materials/gpu_particles/ParticleStorageMaterial.ts +0 -36
- package/src/materials/gpu_particles/PositionStorageMaterial.ts +0 -27
- package/src/render_utilities/MedianFilter.ts +0 -72
- package/src/render_utilities/ViewPositionRenderer.ts +0 -53
- package/src/shaders/add/add.frag +0 -28
- package/src/shaders/anti_aliasing/fxaa.frag +0 -72
- package/src/shaders/basic_color/basic_color.frag +0 -6
- package/src/shaders/basic_color/basic_color.vert +0 -10
- package/src/shaders/basic_line/basic_line.frag +0 -37
- package/src/shaders/basic_line/basic_line.vert +0 -37
- package/src/shaders/basic_texture/basic_texture.frag +0 -10
- package/src/shaders/basic_texture/basic_texture.vert +0 -17
- package/src/shaders/bloom/bloom_compose.frag +0 -22
- package/src/shaders/bloom/cheap_bloom_compose.frag +0 -13
- package/src/shaders/box_blur/box_blur.frag +0 -24
- package/src/shaders/box_blur/compose.frag +0 -31
- package/src/shaders/clear/clear_depth_normal.frag +0 -25
- package/src/shaders/copy/copy.frag +0 -7
- package/src/shaders/copy/copy.vert +0 -6
- package/src/shaders/deferred/blit_copy.vert +0 -11
- package/src/shaders/deferred/deferred_compose.frag +0 -59
- package/src/shaders/deferred/point_light.frag +0 -66
- package/src/shaders/deferred/point_light.vert +0 -13
- package/src/shaders/depth_normals/debug_normals.frag +0 -48
- package/src/shaders/depth_normals/depth_normals.frag +0 -30
- package/src/shaders/depth_normals/depth_normals.vert +0 -13
- package/src/shaders/dual_filter_blur/alpha_filter.frag +0 -14
- package/src/shaders/dual_filter_blur/downsample.frag +0 -21
- package/src/shaders/dual_filter_blur/upsample.frag +0 -24
- package/src/shaders/edge_visualizer/edge_visualizer.frag +0 -18
- package/src/shaders/edge_visualizer/edge_visualizer.vert +0 -16
- package/src/shaders/edges/corners.frag +0 -11
- package/src/shaders/edges/corners.vert +0 -31
- package/src/shaders/edges/edges.frag +0 -7
- package/src/shaders/edges/edges.vert +0 -27
- package/src/shaders/gaussian_blur/gaussian_blur.frag +0 -44
- package/src/shaders/gaussian_blur/unreal_bloom_compose.frag +0 -36
- package/src/shaders/gpu_particles/common_utils.glsl +0 -13
- package/src/shaders/gpu_particles/generic_storage.frag +0 -6
- package/src/shaders/gpu_particles/store/store_position.vert +0 -14
- package/src/shaders/gpu_particles/update/basic_update.frag +0 -21
- package/src/shaders/gpu_particles/visualize/visualize.frag +0 -11
- package/src/shaders/gpu_particles/visualize/visualize.vert +0 -12
- package/src/shaders/grid/grid.frag +0 -17
- package/src/shaders/grid/grid.vert +0 -10
- package/src/shaders/luminosity_high_pass/luminosity_high_pass.frag +0 -38
- package/src/shaders/median_filter/median_filter.frag +0 -134
- package/src/shaders/normal/normal.frag +0 -6
- package/src/shaders/normal/normal.vert +0 -6
- package/src/shaders/sdf_text/sdf_screen_text.frag +0 -51
- package/src/shaders/sdf_text/sdf_screen_text.vert +0 -42
- package/src/shaders/sdf_text/sdf_text.frag +0 -53
- package/src/shaders/sdf_text/sdf_text.vert +0 -42
- package/src/shaders/ssao/hbao.frag +0 -264
- package/src/shaders/ssao/ssao.frag +0 -95
- package/src/shaders/ssao/ssao.vert +0 -11
- package/src/shaders/ssao/ssao_compose.frag +0 -14
- package/src/shaders/transparent_mix/copy.frag +0 -7
- package/src/shaders/transparent_mix/transparent_mix.frag +0 -19
- package/src/shaders/transparent_mix/transparent_mix.vert +0 -8
- package/src/shaders/ui/ss_texture.frag +0 -11
- package/src/shaders/ui/ss_texture.vert +0 -32
- package/src/shaders/ui/ws_texture.vert +0 -39
- package/src/shaders/unreal_blur/unreal_blur.frag +0 -25
- package/src/shaders/unreal_blur/unreal_compose.frag +0 -24
- package/src/shaders/write_view_position/write_view_position.frag +0 -7
- package/src/shaders/write_view_position/write_view_position.vert +0 -9
- package/src/types.d.ts +0 -14
- package/types/components/EdgeMesh.d.ts +0 -16
- package/types/components/GeometryEdgeVisualizer.d.ts +0 -8
- package/types/components/MultiLinePath.d.ts +0 -8
- package/types/components/MultiLineText2D.d.ts +0 -12
- package/types/components/Shape2D.d.ts +0 -7
- package/types/components/Shape3D.d.ts +0 -6
- package/types/gpu_particles/GPUParticleSystem.d.ts +0 -19
- package/types/gpu_particles/ParticleAttribute.d.ts +0 -20
- package/types/gpu_particles/ParticlePositionAttribute.d.ts +0 -6
- package/types/materials/AlphaFilterMaterial.d.ts +0 -7
- package/types/materials/BaseShaderMaterial.d.ts +0 -4
- package/types/materials/BlitMaterial.d.ts +0 -4
- package/types/materials/DeferredRendererComposeMaterial.d.ts +0 -10
- package/types/materials/DisplayNormalTextureMaterial.d.ts +0 -4
- package/types/materials/FXAAMaterial.d.ts +0 -5
- package/types/materials/MedianFilterMaterial.d.ts +0 -8
- package/types/materials/SDFScreenTextMaterial.d.ts +0 -8
- package/types/materials/UIElementMaterial.d.ts +0 -4
- package/types/materials/UnrealBlurMaterial.d.ts +0 -5
- package/types/materials/UnrealComposeMaterial.d.ts +0 -5
- package/types/materials/ViewPositionMaterial.d.ts +0 -5
- package/types/materials/gpu_particles/AttributeUpdateMaterial.d.ts +0 -8
- package/types/materials/gpu_particles/BasicParticleMaterial.d.ts +0 -5
- package/types/materials/gpu_particles/ParticleStorageMaterial.d.ts +0 -5
- package/types/materials/gpu_particles/PositionStorageMaterial.d.ts +0 -5
- package/types/render_utilities/MedianFilter.d.ts +0 -12
- package/types/render_utilities/ViewPositionRenderer.d.ts +0 -12
package/src/components/Line.ts
CHANGED
|
@@ -1,36 +1,31 @@
|
|
|
1
|
-
import
|
|
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,
|
|
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:
|
|
23
|
+
material: LineMaterial;
|
|
12
24
|
|
|
13
25
|
constructor(points?: Vector3[])
|
|
14
26
|
{
|
|
15
|
-
const geometry =
|
|
16
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/components/Text2D.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
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:
|
|
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
|
|
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
|
|
46
|
-
const img =
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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,399 @@
|
|
|
1
|
+
import { SDFTextBatch } from '../components/sdf_text/SDFTextBatch';
|
|
2
|
+
import { WorldImage } from '../components/WorldImage';
|
|
3
|
+
|
|
4
|
+
import type { Material, Mesh, Object3D, Texture } from 'three';
|
|
5
|
+
import { Box3, Color as ThreeColor, Sphere, TextureLoader, Vector3 } from 'three';
|
|
6
|
+
|
|
7
|
+
type Color = number | string;
|
|
8
|
+
|
|
9
|
+
interface DebugLike
|
|
10
|
+
{
|
|
11
|
+
draw_cube(pos?: Vector3, size?: number, color?: Color): Object3D;
|
|
12
|
+
draw_sphere(pos: Vector3, size: number, color?: Color): Object3D;
|
|
13
|
+
draw_plane(width?: number, height?: number, color?: Color): Object3D;
|
|
14
|
+
draw_math_sphere(sphere: Sphere, color?: Color): Object3D;
|
|
15
|
+
draw_bounding_box(bb: Box3, color?: Color): Object3D;
|
|
16
|
+
draw_label(text: string, pos?: Vector3, size?: number, color?: Color): Object3D;
|
|
17
|
+
scene: Object3D;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface DebugDrawRequest
|
|
21
|
+
{
|
|
22
|
+
shape?: unknown;
|
|
23
|
+
position?: unknown;
|
|
24
|
+
size?: unknown;
|
|
25
|
+
color?: unknown;
|
|
26
|
+
object?: unknown;
|
|
27
|
+
text?: unknown;
|
|
28
|
+
font?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// An msdf-atlas-gen layout (the JSON it writes with -json) and its atlas image.
|
|
32
|
+
interface SDFFont
|
|
33
|
+
{
|
|
34
|
+
layout: unknown;
|
|
35
|
+
atlas: Texture;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type SDFFontLoader = (layout_url: string, atlas_url: string) => Promise<SDFFont>;
|
|
39
|
+
|
|
40
|
+
interface DebugDrawerOptions
|
|
41
|
+
{
|
|
42
|
+
// URL of the font sdf_text uses when the request names none.
|
|
43
|
+
sdf_font?: string;
|
|
44
|
+
// Loads a font. Defaults to fetching the layout and loading the atlas image.
|
|
45
|
+
load_sdf_font?: SDFFontLoader;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface SDFGlyph
|
|
49
|
+
{
|
|
50
|
+
unicode: number;
|
|
51
|
+
planeBounds?: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface SDFLayout
|
|
55
|
+
{
|
|
56
|
+
atlas: { width: number, height: number, yOrigin?: string };
|
|
57
|
+
glyphs: SDFGlyph[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface DebugDrawResult
|
|
61
|
+
{
|
|
62
|
+
id: string;
|
|
63
|
+
shape: string;
|
|
64
|
+
helpers: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface DebugClearRequest
|
|
68
|
+
{
|
|
69
|
+
id?: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface DebugClearResult
|
|
73
|
+
{
|
|
74
|
+
removed: number;
|
|
75
|
+
helpers: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const SHAPES = ['cube', 'sphere', 'plane', 'math_sphere', 'bounding_box', 'label', 'sdf_text'];
|
|
79
|
+
|
|
80
|
+
// At 48px a glyph is about 27 pixels wide, so 200 keeps the label canvas below the
|
|
81
|
+
// 8192 pixel texture limit WebGPU guarantees.
|
|
82
|
+
const MAX_LABEL_LENGTH = 200;
|
|
83
|
+
|
|
84
|
+
async function load_sdf_font(layout_url: string, atlas_url: string): Promise<SDFFont>
|
|
85
|
+
{
|
|
86
|
+
const response = await fetch(layout_url);
|
|
87
|
+
|
|
88
|
+
if (!response.ok)
|
|
89
|
+
{
|
|
90
|
+
throw new Error(`HTTP ${response.status}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// TextureLoader rejects with the image's error event, which says nothing useful.
|
|
94
|
+
const atlas_request = new TextureLoader().loadAsync(atlas_url).catch(() =>
|
|
95
|
+
{
|
|
96
|
+
throw new Error(`no atlas image at ${atlas_url}`);
|
|
97
|
+
});
|
|
98
|
+
const [layout, atlas] = await Promise.all([response.json() as Promise<unknown>, atlas_request]);
|
|
99
|
+
|
|
100
|
+
return { layout, atlas };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Only helpers drawn through this class are tracked, so debug_clear never touches the
|
|
104
|
+
// helpers the app draws for itself into Debug.scene.
|
|
105
|
+
class DebugDrawer
|
|
106
|
+
{
|
|
107
|
+
private helpers = new Map<string, Object3D>();
|
|
108
|
+
private sdf_font: string | undefined;
|
|
109
|
+
private load_sdf_font: SDFFontLoader;
|
|
110
|
+
// Loaded fonts by layout URL. Every sdf_text helper shares its font's atlas, so
|
|
111
|
+
// clearing a helper never disposes it.
|
|
112
|
+
private sdf_fonts = new Map<string, Promise<SDFFont>>();
|
|
113
|
+
|
|
114
|
+
constructor(options: DebugDrawerOptions = {})
|
|
115
|
+
{
|
|
116
|
+
this.sdf_font = options.sdf_font;
|
|
117
|
+
this.load_sdf_font = options.load_sdf_font ?? load_sdf_font;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// sdf_text loads its font first, so it resolves later. Every other shape draws now.
|
|
121
|
+
draw(debug: DebugLike, root: Object3D, request: DebugDrawRequest): DebugDrawResult | Promise<DebugDrawResult>
|
|
122
|
+
{
|
|
123
|
+
const shape = typeof request.shape === 'string' && SHAPES.includes(request.shape) ? request.shape : null;
|
|
124
|
+
|
|
125
|
+
if (shape === null)
|
|
126
|
+
{
|
|
127
|
+
throw this.error('bad_request', `Unknown shape. Valid shapes: ${SHAPES.join(', ')}.`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const position = this.position(request.position);
|
|
131
|
+
const size = this.size(request.size);
|
|
132
|
+
const color = this.color(request.color);
|
|
133
|
+
|
|
134
|
+
if (shape === 'sdf_text')
|
|
135
|
+
{
|
|
136
|
+
return this.draw_sdf_text(debug, this.text(request.text), this.font_url(request.font), position, size, color);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return this.track(shape, this.build(debug, root, shape, request, position, size, color));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private track(shape: string, helper: Object3D): DebugDrawResult
|
|
143
|
+
{
|
|
144
|
+
this.helpers.set(helper.uuid, helper);
|
|
145
|
+
|
|
146
|
+
return { id: helper.uuid, shape, helpers: this.helpers.size };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private async draw_sdf_text(debug: DebugLike, text: string, font_url: string, position: Vector3, size: number, color: Color | undefined): Promise<DebugDrawResult>
|
|
150
|
+
{
|
|
151
|
+
const font = await this.font(font_url);
|
|
152
|
+
const layout = this.sdf_layout(font.layout, font_url);
|
|
153
|
+
|
|
154
|
+
// SDFText looks glyphs up by UTF-16 unit (charCodeAt), so this check does too.
|
|
155
|
+
const drawable = Array.from({ length: text.length }, (_, i) => text.charCodeAt(i));
|
|
156
|
+
|
|
157
|
+
if (!drawable.some((unit) => layout.glyphs.some((glyph) => glyph.unicode === unit && glyph.planeBounds !== undefined)))
|
|
158
|
+
{
|
|
159
|
+
throw this.error('bad_request', `The font at ${font_url} has none of the characters of the text.`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// One batch per helper, so debug_clear can remove each text on its own.
|
|
163
|
+
const batch = new SDFTextBatch(layout, font.atlas);
|
|
164
|
+
const element = batch.add_text(text);
|
|
165
|
+
element.set_position(position);
|
|
166
|
+
element.set_size(size);
|
|
167
|
+
element.set_color(new ThreeColor(color ?? 0xffffff));
|
|
168
|
+
// The text never changes, so one upload is enough.
|
|
169
|
+
batch.update(true);
|
|
170
|
+
debug.scene.add(batch);
|
|
171
|
+
|
|
172
|
+
return this.track('sdf_text', batch);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private font(url: string): Promise<SDFFont>
|
|
176
|
+
{
|
|
177
|
+
let font = this.sdf_fonts.get(url);
|
|
178
|
+
|
|
179
|
+
if (font === undefined)
|
|
180
|
+
{
|
|
181
|
+
font = this.load_sdf_font(url, url.replace(/\.json(?=$|[?#])/, '.png')).catch((cause: unknown) =>
|
|
182
|
+
{
|
|
183
|
+
// Forget the failure, so the next request tries again.
|
|
184
|
+
this.sdf_fonts.delete(url);
|
|
185
|
+
|
|
186
|
+
const reason = cause instanceof Error ? cause.message : typeof cause === 'string' ? cause : 'unknown error';
|
|
187
|
+
|
|
188
|
+
throw this.error('not_found', `Could not load the SDF font at ${url}: ${reason}`);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
this.sdf_fonts.set(url, font);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return font;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private font_url(value: unknown): string
|
|
198
|
+
{
|
|
199
|
+
if (value === undefined && this.sdf_font === undefined)
|
|
200
|
+
{
|
|
201
|
+
throw this.error('bad_request', 'sdf_text needs a font: no default SDF font is configured.');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const url = value ?? this.sdf_font;
|
|
205
|
+
|
|
206
|
+
if (typeof url !== 'string' || !/\.json(?=$|[?#])/.test(url))
|
|
207
|
+
{
|
|
208
|
+
throw this.error('bad_request', 'font must be the URL of an msdf-atlas-gen .json layout, with its .png atlas next to it.');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return url;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private sdf_layout(value: unknown, url: string): SDFLayout
|
|
215
|
+
{
|
|
216
|
+
const layout = value as Partial<SDFLayout> | null;
|
|
217
|
+
|
|
218
|
+
if (typeof layout?.atlas?.width !== 'number' || typeof layout.atlas.height !== 'number' || !Array.isArray(layout.glyphs))
|
|
219
|
+
{
|
|
220
|
+
throw this.error('bad_request', `The font at ${url} is not an msdf-atlas-gen layout.`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// The material maps atlas bounds counted from the bottom.
|
|
224
|
+
if (layout.atlas.yOrigin === 'top')
|
|
225
|
+
{
|
|
226
|
+
throw this.error('bad_request', `The font at ${url} has yOrigin top. Generate it with -yorigin bottom.`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return layout as SDFLayout;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private build(debug: DebugLike, root: Object3D, shape: string, request: DebugDrawRequest, position: Vector3, size: number, color: Color | undefined): Object3D
|
|
233
|
+
{
|
|
234
|
+
switch (shape)
|
|
235
|
+
{
|
|
236
|
+
case 'cube':
|
|
237
|
+
return debug.draw_cube(position, size, color);
|
|
238
|
+
|
|
239
|
+
case 'sphere':
|
|
240
|
+
// Sphere's size is its radius, which is what the contract promises.
|
|
241
|
+
return debug.draw_sphere(position, size, color);
|
|
242
|
+
|
|
243
|
+
case 'plane':
|
|
244
|
+
{
|
|
245
|
+
const plane = debug.draw_plane(size, size, color);
|
|
246
|
+
plane.position.copy(position);
|
|
247
|
+
|
|
248
|
+
return plane;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
case 'math_sphere':
|
|
252
|
+
return debug.draw_math_sphere(new Sphere(position, size), color);
|
|
253
|
+
|
|
254
|
+
case 'label':
|
|
255
|
+
// Size is the text height.
|
|
256
|
+
return debug.draw_label(this.text(request.text), position, size, color);
|
|
257
|
+
|
|
258
|
+
default:
|
|
259
|
+
return debug.draw_bounding_box(this.bounds(root, request.object), color);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
clear(request: DebugClearRequest): DebugClearResult
|
|
264
|
+
{
|
|
265
|
+
if (request.id === undefined)
|
|
266
|
+
{
|
|
267
|
+
const removed = this.helpers.size;
|
|
268
|
+
|
|
269
|
+
this.helpers.forEach((helper) => this.dispose(helper));
|
|
270
|
+
this.helpers.clear();
|
|
271
|
+
|
|
272
|
+
return { removed, helpers: 0 };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const helper = typeof request.id === 'string' ? this.helpers.get(request.id) : undefined;
|
|
276
|
+
|
|
277
|
+
if (helper === undefined)
|
|
278
|
+
{
|
|
279
|
+
throw this.error('not_found', 'No helper with that id was drawn through debug_draw.');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this.dispose(helper);
|
|
283
|
+
this.helpers.delete(helper.uuid);
|
|
284
|
+
|
|
285
|
+
return { removed: 1, helpers: this.helpers.size };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private dispose(helper: Object3D): void
|
|
289
|
+
{
|
|
290
|
+
helper.removeFromParent();
|
|
291
|
+
|
|
292
|
+
helper.traverse((node) =>
|
|
293
|
+
{
|
|
294
|
+
const mesh = node as Partial<Mesh>;
|
|
295
|
+
|
|
296
|
+
mesh.geometry?.dispose();
|
|
297
|
+
|
|
298
|
+
const materials: Material[] = Array.isArray(mesh.material) ? mesh.material : mesh.material ? [mesh.material] : [];
|
|
299
|
+
materials.forEach((material) => material.dispose());
|
|
300
|
+
|
|
301
|
+
// A label owns its canvas texture.
|
|
302
|
+
if (node instanceof WorldImage)
|
|
303
|
+
{
|
|
304
|
+
node.material.uniforms._MainTex.value.dispose();
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private bounds(root: Object3D, target: unknown): Box3
|
|
310
|
+
{
|
|
311
|
+
const { name, uuid } = (typeof target === 'object' && target !== null ? target : {}) as { name?: unknown, uuid?: unknown };
|
|
312
|
+
|
|
313
|
+
if (typeof uuid !== 'string' && typeof name !== 'string')
|
|
314
|
+
{
|
|
315
|
+
throw this.error('bad_request', 'bounding_box needs an object with a name or a uuid.');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// uuid wins over name, like SceneEditor.
|
|
319
|
+
const found = typeof uuid === 'string'
|
|
320
|
+
? root.getObjectByProperty('uuid', uuid)
|
|
321
|
+
: root.getObjectByName(name as string);
|
|
322
|
+
|
|
323
|
+
if (found === undefined)
|
|
324
|
+
{
|
|
325
|
+
const label = typeof uuid === 'string' ? `uuid ${uuid}` : `name ${name as string}`;
|
|
326
|
+
|
|
327
|
+
throw this.error('not_found', `No object matching ${label} in the current scene.`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const box = new Box3().setFromObject(found);
|
|
331
|
+
|
|
332
|
+
if (box.isEmpty())
|
|
333
|
+
{
|
|
334
|
+
throw this.error('bad_request', `Object "${found.name || found.uuid}" has no geometry, so its bounds are empty.`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return box;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private text(value: unknown): string
|
|
341
|
+
{
|
|
342
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > MAX_LABEL_LENGTH)
|
|
343
|
+
{
|
|
344
|
+
throw this.error('bad_request', `label and sdf_text need a text of 1 to ${MAX_LABEL_LENGTH} characters.`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return value;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private position(value: unknown): Vector3
|
|
351
|
+
{
|
|
352
|
+
if (value === undefined)
|
|
353
|
+
{
|
|
354
|
+
return new Vector3();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (!Array.isArray(value) || value.length !== 3 || !value.every((n) => typeof n === 'number' && Number.isFinite(n)))
|
|
358
|
+
{
|
|
359
|
+
throw this.error('bad_request', 'position must be [x, y, z] with finite numbers.');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return new Vector3(value[0] as number, value[1] as number, value[2] as number);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private size(value: unknown): number
|
|
366
|
+
{
|
|
367
|
+
if (value === undefined)
|
|
368
|
+
{
|
|
369
|
+
return 1;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
373
|
+
{
|
|
374
|
+
throw this.error('bad_request', 'size must be a positive finite number.');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return value;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private color(value: unknown): Color | undefined
|
|
381
|
+
{
|
|
382
|
+
if (value === undefined || typeof value === 'number' || typeof value === 'string')
|
|
383
|
+
{
|
|
384
|
+
return value as Color | undefined;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
throw this.error('bad_request', 'color must be a number or a string.');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private error(code: string, message: string): Error
|
|
391
|
+
{
|
|
392
|
+
const error: Error & { code?: string } = new Error(message);
|
|
393
|
+
error.code = code;
|
|
394
|
+
|
|
395
|
+
return error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export { DebugDrawer };
|