abstract-3d 4.0.0-beta.8 → 4.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 (96) hide show
  1. package/README.md +2 -0
  2. package/lib/abstract-3d.d.ts +6 -1
  3. package/lib/abstract-3d.d.ts.map +1 -1
  4. package/lib/abstract-3d.js +24 -2
  5. package/lib/abstract-3d.js.map +1 -1
  6. package/lib/renderers/react/react.d.ts.map +1 -1
  7. package/lib/renderers/react/react.js +9 -3
  8. package/lib/renderers/react/react.js.map +1 -1
  9. package/lib/renderers/svg/index.d.ts +1 -1
  10. package/lib/renderers/svg/index.d.ts.map +1 -1
  11. package/lib/renderers/svg/svg-geometries/shared.d.ts +14 -2
  12. package/lib/renderers/svg/svg-geometries/shared.d.ts.map +1 -1
  13. package/lib/renderers/svg/svg-geometries/shared.js +15 -1
  14. package/lib/renderers/svg/svg-geometries/shared.js.map +1 -1
  15. package/lib/renderers/svg/svg-geometries/svg-box.d.ts.map +1 -1
  16. package/lib/renderers/svg/svg-geometries/svg-box.js +41 -30
  17. package/lib/renderers/svg/svg-geometries/svg-box.js.map +1 -1
  18. package/lib/renderers/svg/svg-geometries/svg-cone.d.ts.map +1 -1
  19. package/lib/renderers/svg/svg-geometries/svg-cone.js +3 -3
  20. package/lib/renderers/svg/svg-geometries/svg-cone.js.map +1 -1
  21. package/lib/renderers/svg/svg-geometries/svg-cylinder.d.ts.map +1 -1
  22. package/lib/renderers/svg/svg-geometries/svg-cylinder.js +18 -6
  23. package/lib/renderers/svg/svg-geometries/svg-cylinder.js.map +1 -1
  24. package/lib/renderers/svg/svg-geometries/svg-image.d.ts.map +1 -1
  25. package/lib/renderers/svg/svg-geometries/svg-image.js +3 -2
  26. package/lib/renderers/svg/svg-geometries/svg-image.js.map +1 -1
  27. package/lib/renderers/svg/svg-geometries/svg-line.d.ts.map +1 -1
  28. package/lib/renderers/svg/svg-geometries/svg-line.js +3 -1
  29. package/lib/renderers/svg/svg-geometries/svg-line.js.map +1 -1
  30. package/lib/renderers/svg/svg-geometries/svg-plane.d.ts.map +1 -1
  31. package/lib/renderers/svg/svg-geometries/svg-plane.js +4 -2
  32. package/lib/renderers/svg/svg-geometries/svg-plane.js.map +1 -1
  33. package/lib/renderers/svg/svg-geometries/svg-polygon.d.ts.map +1 -1
  34. package/lib/renderers/svg/svg-geometries/svg-polygon.js +4 -2
  35. package/lib/renderers/svg/svg-geometries/svg-polygon.js.map +1 -1
  36. package/lib/renderers/svg/svg-geometries/svg-shape.d.ts.map +1 -1
  37. package/lib/renderers/svg/svg-geometries/svg-shape.js +4 -2
  38. package/lib/renderers/svg/svg-geometries/svg-shape.js.map +1 -1
  39. package/lib/renderers/svg/svg-geometries/svg-text.js +1 -1
  40. package/lib/renderers/svg/svg-geometries/svg-text.js.map +1 -1
  41. package/lib/renderers/svg/svg-order.d.ts +9 -0
  42. package/lib/renderers/svg/svg-order.d.ts.map +1 -0
  43. package/lib/renderers/svg/svg-order.js +148 -0
  44. package/lib/renderers/svg/svg-order.js.map +1 -0
  45. package/lib/renderers/svg/svg.d.ts.map +1 -1
  46. package/lib/renderers/svg/svg.js +13 -9
  47. package/lib/renderers/svg/svg.js.map +1 -1
  48. package/lib/renderers/three/build/build.d.ts +2 -0
  49. package/lib/renderers/three/build/build.d.ts.map +1 -1
  50. package/lib/renderers/three/build/build.js +1 -1
  51. package/lib/renderers/three/build/build.js.map +1 -1
  52. package/lib/renderers/three/build/materials.d.ts +5 -1
  53. package/lib/renderers/three/build/materials.d.ts.map +1 -1
  54. package/lib/renderers/three/build/materials.js +53 -17
  55. package/lib/renderers/three/build/materials.js.map +1 -1
  56. package/lib/renderers/three/environment/studio.d.ts +3 -2
  57. package/lib/renderers/three/environment/studio.d.ts.map +1 -1
  58. package/lib/renderers/three/environment/studio.js +4 -3
  59. package/lib/renderers/three/environment/studio.js.map +1 -1
  60. package/lib/renderers/three/geometry/geometry.d.ts.map +1 -1
  61. package/lib/renderers/three/geometry/geometry.js +11 -5
  62. package/lib/renderers/three/geometry/geometry.js.map +1 -1
  63. package/lib/renderers/three/shadow/key-light.js +1 -1
  64. package/lib/renderers/three/viewer/options.d.ts +7 -1
  65. package/lib/renderers/three/viewer/options.d.ts.map +1 -1
  66. package/lib/renderers/three/viewer/options.js +1 -0
  67. package/lib/renderers/three/viewer/options.js.map +1 -1
  68. package/lib/renderers/three/viewer/viewer.d.ts +5 -0
  69. package/lib/renderers/three/viewer/viewer.d.ts.map +1 -1
  70. package/lib/renderers/three/viewer/viewer.js +54 -11
  71. package/lib/renderers/three/viewer/viewer.js.map +1 -1
  72. package/package.json +1 -1
  73. package/src/abstract-3d.ts +30 -2
  74. package/src/renderers/react/react.tsx +14 -4
  75. package/src/renderers/svg/index.ts +1 -1
  76. package/src/renderers/svg/svg-geometries/shared.ts +26 -3
  77. package/src/renderers/svg/svg-geometries/svg-box.ts +42 -36
  78. package/src/renderers/svg/svg-geometries/svg-cone.ts +17 -3
  79. package/src/renderers/svg/svg-geometries/svg-cylinder.ts +25 -6
  80. package/src/renderers/svg/svg-geometries/svg-image.ts +3 -2
  81. package/src/renderers/svg/svg-geometries/svg-line.ts +3 -1
  82. package/src/renderers/svg/svg-geometries/svg-plane.ts +9 -2
  83. package/src/renderers/svg/svg-geometries/svg-polygon.ts +9 -2
  84. package/src/renderers/svg/svg-geometries/svg-shape.ts +9 -2
  85. package/src/renderers/svg/svg-geometries/svg-text.ts +1 -1
  86. package/src/renderers/svg/svg-order.test.ts +35 -0
  87. package/src/renderers/svg/svg-order.ts +172 -0
  88. package/src/renderers/svg/svg.ts +13 -9
  89. package/src/renderers/three/build/build.ts +3 -1
  90. package/src/renderers/three/build/materials.ts +57 -17
  91. package/src/renderers/three/environment/studio.ts +4 -3
  92. package/src/renderers/three/geometry/geometry.test.ts +17 -5
  93. package/src/renderers/three/geometry/geometry.ts +12 -5
  94. package/src/renderers/three/shadow/key-light.ts +1 -1
  95. package/src/renderers/three/viewer/options.ts +8 -1
  96. package/src/renderers/three/viewer/viewer.ts +57 -10
@@ -17,7 +17,12 @@ const bounds = (mesh: Mesh): ReadonlyArray<number> => {
17
17
  return [...geometry.boundingBox!.min.toArray(), ...geometry.boundingBox!.max.toArray()].map((v) => Math.round(v * 1000) / 1000 + 0);
18
18
  };
19
19
 
20
- const bevel = (mesh: Mesh): ReadonlyArray<number> => [...new Set(createSolidGeometry(mesh.geometry).getAttribute("bevel").array)];
20
+ /** The radius and joined sides' bits, the same for every vertex */
21
+ const bevel = (mesh: Mesh): ReadonlyArray<number> => {
22
+ const attribute = createSolidGeometry(mesh.geometry).getAttribute("bevel");
23
+ const pairs = new Set(Array.from({ length: attribute.count }, (_, i) => `${attribute.getX(i)} ${attribute.getY(i)}`));
24
+ return [...pairs].flatMap((pair) => pair.split(" ").map(Number));
25
+ };
21
26
 
22
27
  describe("getSolidShape", () => {
23
28
  it("shares unit geometries between primitives of any size, scaled by the local matrix", () => {
@@ -81,15 +86,22 @@ describe("createSolidGeometry", () => {
81
86
  });
82
87
 
83
88
  it("gives boxes with a radius it as the radius their edges are shaded rounded by, with a geometry per radius", () => {
84
- expect(bevel(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], 4))).toEqual([4]);
89
+ expect(bevel(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], 4))).toEqual([4, 2 ** 12 - 1]);
85
90
  expect(key(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], 4))).toBe(key(box(vec3(9, 9, 9), m, vec3Zero, vec3Zero, [], 4)));
86
91
  expect(key(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], 4))).not.toBe(key(box(vec3(1, 1, 1), m)));
87
92
  });
88
93
 
94
+ it("rounds all edges of boxes with a radius, or only their rounded edges, a bit each in the order of boxEdges, with a geometry per edges", () => {
95
+ expect(bevel(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], 4, ["BottomBack", "FrontRight", "TopRight"]))).toEqual([4, 1 + 2 ** 7 + 2 ** 11]);
96
+ expect(bevel(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], 4, []))).toEqual([4, 0]);
97
+ expect(key(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], 4, ["TopFront"]))).not.toBe(key(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], 4)));
98
+ });
99
+
89
100
  it("keeps boxes without a radius, with holes, and other shapes sharp", () => {
90
- expect(bevel(box(vec3(1, 1, 1), m))).toEqual([0]);
91
- expect(bevel(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [roundHole(vec2(0, 0), 0.1)], 4))).toEqual([0]);
92
- expect(bevel(sphere(1, m))).toEqual([0]);
101
+ expect(bevel(box(vec3(1, 1, 1), m))).toEqual([0, 0]);
102
+ expect(bevel(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [], undefined, ["TopFront"]))).toEqual([0, 0]);
103
+ expect(bevel(box(vec3(1, 1, 1), m, vec3Zero, vec3Zero, [roundHole(vec2(0, 0), 0.1)], 4))).toEqual([0, 0]);
104
+ expect(bevel(sphere(1, m))).toEqual([0, 0]);
93
105
  });
94
106
  });
95
107
 
@@ -16,7 +16,7 @@ import {
16
16
  } from "three";
17
17
  import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";
18
18
  import type { Hole, Mesh } from "../../../abstract-3d.js";
19
- import { isZero, vec3, vec3Zero } from "../../../abstract-3d.js";
19
+ import { boxEdges, isZero, vec3, vec3Zero } from "../../../abstract-3d.js";
20
20
  import { addHoles, CircleCurve, CYLINDER_SEGMENTS, cylinderCutPositions, holeIsZero } from "../../shared/three-geometry.js";
21
21
  import { transform } from "../compile/compile.js";
22
22
 
@@ -42,7 +42,10 @@ export function getSolidShape(g: Mesh["geometry"]): SolidShape | undefined {
42
42
  const size = g.type === "Box" ? g.size : vec3(g.size.x, g.size.y, 1);
43
43
  return g.holes?.some((h) => !holeIsZero(h))
44
44
  ? { key: JSON.stringify([g.type, g.size, g.holes]), local: transform(g.pos, g.rot) }
45
- : { key: g.type === "Box" && g.radius ? `Box ${g.radius}` : g.type, local: transform(g.pos, g.rot, undefined, size) };
45
+ : {
46
+ key: g.type === "Box" && g.radius ? `Box ${g.radius} ${g.roundedEdges?.join() ?? ""}` : g.type,
47
+ local: transform(g.pos, g.rot, undefined, size),
48
+ };
46
49
  }
47
50
  case "Image":
48
51
  return { key: "Plane", local: transform(g.pos, g.rot, undefined, vec3(g.size.x, g.size.y, 1)) };
@@ -86,9 +89,13 @@ export function createSolidGeometry(g: Mesh["geometry"]): BufferGeometry {
86
89
  if (!geometry.hasAttribute("uv")) {
87
90
  geometry.setAttribute("uv", new Float32BufferAttribute(new Float32Array(count * 2), 2));
88
91
  }
89
- // The radius a unit box's edges are shaded as rounded by, 0 for sharp; every geometry has it, as a batch's geometries share their attributes
90
- const radius = geometry instanceof BoxGeometry && g.type === "Box" ? (g.radius ?? 0) : 0;
91
- geometry.setAttribute("bevel", new Float32BufferAttribute(new Float32Array(count).fill(radius), 1));
92
+ // The radius a unit box's edges are shaded as rounded by, 0 for sharp, and a bit per rounded edge, in the order of `boxEdges`; every geometry has
93
+ // it, as a batch's geometries share their attributes
94
+ const bevel =
95
+ geometry instanceof BoxGeometry && g.type === "Box" && g.radius
96
+ ? [g.radius, boxEdges.reduce((edges, edge, i) => ((g.roundedEdges?.includes(edge) ?? true) ? edges + 2 ** i : edges), 0)]
97
+ : [0, 0];
98
+ geometry.setAttribute("bevel", new Float32BufferAttribute(Array.from({ length: count }, () => bevel).flat(), 2));
92
99
  Object.keys(geometry.attributes).forEach((name) => {
93
100
  if (name !== "position" && name !== "normal" && name !== "uv" && name !== "bevel") {
94
101
  geometry.deleteAttribute(name);
@@ -8,7 +8,7 @@ import { getShadowCasters } from "./shadow.js";
8
8
  */
9
9
 
10
10
  const direction = new Vector3(-1, 2, 1.2).normalize();
11
- const intensity = 2.5;
11
+ const intensity = 1.6;
12
12
  const mapSize = 2048;
13
13
  /** Of the key light kept out of its shadows, so faces under an overhang, e.g. a recessed base frame, aren't black */
14
14
  const shadowIntensity = 0.6;
@@ -43,8 +43,14 @@ export type ViewerOptions<Content> = {
43
43
  readonly gizmo?: ControlsHelper;
44
44
  /** Lights added to the photographed studio and key light that light the scene; none by default */
45
45
  readonly lights?: ReadonlyArray<Object3D> | ((scene: Scene) => ReadonlyArray<Object3D>);
46
- /** Of the studio's light, 0.8 by default, the key light giving the rest */
46
+ /** Of the studio's light, or without the studio of its plain light, 0.6 by default, the key light giving the rest */
47
47
  readonly environmentIntensity?: number;
48
+ /**
49
+ * The photographed studio's light and reflections, on by default. Off, three-point lighting emulates it, a weak sky and floor light, the key light,
50
+ * a fill and a rim light, without reflections, so metals are satin at most; a first visit then shows the scene about 400 ms sooner, as its shaders
51
+ * compile without the reflection code. Changing it rebuilds the scene
52
+ */
53
+ readonly studio?: boolean;
48
54
  /** A soft shadow on the ground under the scene, rendered once per build; on by default */
49
55
  readonly groundShadow?: boolean;
50
56
  /** The solids' shadows on themselves, e.g. under a roof, cast by the key light from the upper left front; on by default */
@@ -90,6 +96,7 @@ export const effects: { readonly [K in keyof ViewerOptions<unknown>]-?: Readonly
90
96
  gizmo: ["gizmo"],
91
97
  lights: ["lights"],
92
98
  environmentIntensity: ["lights"],
99
+ studio: ["build", "lights"],
93
100
  groundShadow: ["shadow"],
94
101
  shadows: ["shadow"],
95
102
  quality: ["quality"],
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  type BufferGeometry,
3
+ DirectionalLight,
3
4
  Group,
5
+ HemisphereLight,
4
6
  NeutralToneMapping,
5
7
  OrthographicCamera,
6
8
  PerspectiveCamera,
@@ -71,6 +73,8 @@ export type Viewer<Content> = {
71
73
  structure: StructureState;
72
74
  compiled?: CompiledScene;
73
75
  built?: BuiltScene;
76
+ /** The previous build and ground shadow, out of the scene, disposed once the next frame has given their shader programs to the new ones */
77
+ readonly retired: { built?: BuiltScene; shadow?: GroundShadow };
74
78
  gizmo?: ViewportGizmo;
75
79
  shadow?: GroundShadow;
76
80
  /** The key light that casts the solids' shadows on themselves */
@@ -127,13 +131,16 @@ export function createViewer<Content>(container: HTMLElement, options: ViewerOpt
127
131
  stats: { frames: 0, frameMs: 0, drawCalls: 0, pickMs: 0, paintMs: 0 },
128
132
  structure: { activeComponents: options.activeComponents, relationsActive: options.activeRelations !== undefined },
129
133
  studio: {},
134
+ retired: {},
130
135
  loaded: false,
131
136
  fitted: false,
132
137
  disposed: false,
133
138
  };
134
139
  v.scene.add(v.lights);
135
140
  // Loaded while the first frame builds the scene
136
- loadStudio(v).catch(() => undefined);
141
+ if (options.studio !== false) {
142
+ loadStudio(v).catch(() => undefined);
143
+ }
137
144
  const signal = { signal: v.listeners.signal };
138
145
  canvas.addEventListener("pointermove", (e) => onPointerMove(v, e), signal);
139
146
  canvas.addEventListener("pointerleave", (e) => setHovered(v, undefined, e), signal);
@@ -166,6 +173,7 @@ export function disposeViewer<Content>(v: Viewer<Content>): void {
166
173
  v.resizeObserver.disconnect();
167
174
  v.controls.orbit.dispose();
168
175
  v.gizmo?.dispose();
176
+ disposeRetired(v);
169
177
  if (v.built) {
170
178
  disposeScene(v.built);
171
179
  }
@@ -197,9 +205,12 @@ function apply<Content>(v: Viewer<Content>, changes: ReadonlySet<Change>): void
197
205
  rebuild(v);
198
206
  }
199
207
  if (changes.has("shadow") || changes.has("build")) {
200
- if (v.shadow) {
201
- disposeGroundShadow(v.shadow);
208
+ if (v.retired.shadow) {
209
+ disposeGroundShadow(v.retired.shadow);
202
210
  }
211
+ v.retired.shadow = v.shadow;
212
+ v.shadow?.mesh.removeFromParent();
213
+ v.shadow?.floor.removeFromParent();
203
214
  v.shadow = v.built && options.groundShadow !== false ? createGroundShadow(v.renderer, v.scene, v.built) : undefined;
204
215
  if (v.keyLight) {
205
216
  disposeKeyLight(v.keyLight);
@@ -255,9 +266,11 @@ function apply<Content>(v: Viewer<Content>, changes: ReadonlySet<Change>): void
255
266
  }
256
267
 
257
268
  function rebuild<Content>(v: Viewer<Content>): void {
258
- if (v.built) {
259
- disposeScene(v.built);
269
+ if (v.retired.built) {
270
+ disposeScene(v.retired.built);
260
271
  }
272
+ v.retired.built = v.built;
273
+ v.built?.root.removeFromParent();
261
274
  const { options, compiled } = v;
262
275
  v.built =
263
276
  compiled &&
@@ -273,7 +286,7 @@ function rebuild<Content>(v: Viewer<Content>): void {
273
286
  v.scene.add(v.built.root);
274
287
  v.built.dimensions.visible = options.showDimensions ?? true;
275
288
  // Compiled in parallel where the driver can, as three.js's compileAsync example, and after the studio arrived, which they use
276
- loadStudio(v)
289
+ (options.studio === false ? Promise.resolve() : loadStudio(v))
277
290
  .catch(() => undefined)
278
291
  .then(() => v.renderer.compileAsync(v.scene, v.camera))
279
292
  .then(() => onLoaded(v))
@@ -281,6 +294,17 @@ function rebuild<Content>(v: Viewer<Content>): void {
281
294
  }
282
295
  }
283
296
 
297
+ /** Three deletes a shader program once no material uses it, so the previous build's are disposed after the new build's took the programs over */
298
+ function disposeRetired<Content>(v: Viewer<Content>): void {
299
+ if (v.retired.built) {
300
+ disposeScene(v.retired.built);
301
+ }
302
+ if (v.retired.shadow) {
303
+ disposeGroundShadow(v.retired.shadow);
304
+ }
305
+ Object.assign(v.retired, { built: undefined, shadow: undefined });
306
+ }
307
+
284
308
  function onLoaded<Content>(v: Viewer<Content>): void {
285
309
  if (!v.disposed) {
286
310
  v.loaded = true;
@@ -317,6 +341,7 @@ function renderFrame<Content>(v: Viewer<Content>, now: number): void {
317
341
  }
318
342
  if (v.loaded) {
319
343
  renderer.render(v.scene, camera);
344
+ disposeRetired(v);
320
345
  }
321
346
  gizmo?.render();
322
347
  v.overlays.renderer.render(v.overlays.scene, camera);
@@ -496,12 +521,26 @@ function getGroupArgs<Content>(
496
521
  }
497
522
  }
498
523
 
499
- /** The scene is lit by a photographed studio as its environment; apps can add their own lights */
524
+ /** The scene is lit by a photographed studio as its environment, or without it by a plain sky and floor light; apps can add their own lights */
500
525
  function updateLights<Content>(v: Viewer<Content>): void {
501
- const { scene, lights, environmentIntensity = studioIntensity } = v.options;
526
+ const { scene, lights, environmentIntensity = studioIntensity, studio = true } = v.options;
502
527
  v.lights.clear();
503
528
  (typeof lights === "function" ? (scene ? lights(scene) : []) : (lights ?? [])).forEach((o) => v.lights.add(o));
504
529
  v.scene.environmentIntensity = environmentIntensity;
530
+ v.scene.environment = studio ? (v.studio.texture ?? null) : null;
531
+ if (studio) {
532
+ loadStudio(v).catch(() => undefined);
533
+ } else {
534
+ // The studio emulated by three-point lighting: a weak sky and floor light, so faces differ by which way they turn, the key light, a fill from
535
+ // the other side, so the key light's shadow side isn't flat, and a rim light from behind, which outlines edges
536
+ const [fill, rim] = [
537
+ new DirectionalLight(0xffffff, environmentIntensity * fillIntensity),
538
+ new DirectionalLight(0xffffff, environmentIntensity * rimIntensity),
539
+ ];
540
+ fill.position.copy(fillDirection);
541
+ rim.position.copy(rimDirection);
542
+ v.lights.add(new HemisphereLight(0xffffff, floorColor, environmentIntensity * skyIntensity), fill, rim);
543
+ }
505
544
  }
506
545
 
507
546
  /** The studio, loaded once, its own chunk; set when it arrives, so the scene's shaders are compiled after it, with it */
@@ -514,7 +553,7 @@ function loadStudio<Content>(v: Viewer<Content>): Promise<unknown> {
514
553
  return texture;
515
554
  }
516
555
  v.studio.texture = texture;
517
- v.scene.environment = texture;
556
+ v.scene.environment = v.options.studio === false ? null : texture;
518
557
  invalidate(v);
519
558
  return texture;
520
559
  });
@@ -543,7 +582,15 @@ function updateOverlays<Content>(v: Viewer<Content>): void {
543
582
  }
544
583
 
545
584
  /** The studio's share of the light, the key light giving the rest, so the key light's shadows show */
546
- const studioIntensity = 0.8;
585
+ const studioIntensity = 0.6;
586
+ /** Without the studio, of its three-point lighting, by the environment's intensity; the key light is the studio's, the floor as the studio's */
587
+ const skyIntensity = 1.5;
588
+ const fillIntensity = 1.5;
589
+ const rimIntensity = 2;
590
+ const floorColor = 0xe6e6e6;
591
+ /** From the right, opposite the key light from the upper left front, and lower; the rim light from behind and above */
592
+ const fillDirection = new Vector3(1.5, 0.5, 1);
593
+ const rimDirection = new Vector3(0.5, 1.5, -1.5);
547
594
 
548
595
  const raycaster = new Raycaster();
549
596
  const ndc = new Vector2();