abstract-3d 4.0.0-beta.0 → 4.0.0-beta.2

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 (41) hide show
  1. package/README.md +1 -1
  2. package/lib/renderers/react/react.d.ts.map +1 -1
  3. package/lib/renderers/react/react.js +4 -3
  4. package/lib/renderers/react/react.js.map +1 -1
  5. package/lib/renderers/three/appearance/appearance.d.ts.map +1 -1
  6. package/lib/renderers/three/appearance/appearance.js +4 -1
  7. package/lib/renderers/three/appearance/appearance.js.map +1 -1
  8. package/lib/renderers/three/build/build.js +7 -3
  9. package/lib/renderers/three/build/build.js.map +1 -1
  10. package/lib/renderers/three/build/materials.d.ts +7 -3
  11. package/lib/renderers/three/build/materials.d.ts.map +1 -1
  12. package/lib/renderers/three/build/materials.js +10 -5
  13. package/lib/renderers/three/build/materials.js.map +1 -1
  14. package/lib/renderers/three/quality/quality.d.ts +4 -24
  15. package/lib/renderers/three/quality/quality.d.ts.map +1 -1
  16. package/lib/renderers/three/quality/quality.js +6 -43
  17. package/lib/renderers/three/quality/quality.js.map +1 -1
  18. package/lib/renderers/three/shadow/shadow.d.ts +15 -0
  19. package/lib/renderers/three/shadow/shadow.d.ts.map +1 -0
  20. package/lib/renderers/three/shadow/shadow.js +81 -0
  21. package/lib/renderers/three/shadow/shadow.js.map +1 -0
  22. package/lib/renderers/three/viewer/options.d.ts +6 -2
  23. package/lib/renderers/three/viewer/options.d.ts.map +1 -1
  24. package/lib/renderers/three/viewer/options.js +2 -0
  25. package/lib/renderers/three/viewer/options.js.map +1 -1
  26. package/lib/renderers/three/viewer/viewer.d.ts +3 -2
  27. package/lib/renderers/three/viewer/viewer.d.ts.map +1 -1
  28. package/lib/renderers/three/viewer/viewer.js +24 -20
  29. package/lib/renderers/three/viewer/viewer.js.map +1 -1
  30. package/package.json +1 -3
  31. package/src/renderers/react/react.tsx +5 -4
  32. package/src/renderers/three/appearance/appearance.test.ts +5 -0
  33. package/src/renderers/three/appearance/appearance.ts +4 -1
  34. package/src/renderers/three/build/build.ts +6 -3
  35. package/src/renderers/three/build/materials.ts +14 -8
  36. package/src/renderers/three/quality/quality.test.ts +11 -4
  37. package/src/renderers/three/quality/quality.ts +5 -65
  38. package/src/renderers/three/shadow/shadow.ts +116 -0
  39. package/src/renderers/three/viewer/options.ts +8 -2
  40. package/src/renderers/three/viewer/viewer.ts +26 -23
  41. package/src/custom_typings/n8ao.d.ts +0 -11
@@ -46,7 +46,9 @@ export type ViewerOptions<Content> = {
46
46
  /** Lights faces by which way they turn and gives metal reflections: `studio` by default, `room` for three's RoomEnvironment, or null for none */
47
47
  readonly environment?: Texture | "studio" | "room" | null;
48
48
  readonly environmentIntensity?: number;
49
- /** `low` by default, without ambient occlusion */
49
+ /** A soft shadow on the ground under the scene, rendered once per build; on by default */
50
+ readonly groundShadow?: boolean;
51
+ /** The resolution: `low` 1, `medium` up to 1.5 and `high` up to 2 pixels per CSS pixel; `auto` by default, high until moving gets slow */
50
52
  readonly quality?: Quality;
51
53
  readonly useAlphaTest?: boolean;
52
54
  readonly materialStateImages?: Record<string, string>;
@@ -61,10 +63,12 @@ export type ViewerOptions<Content> = {
61
63
  readonly onHoverGroup?: (id: string | undefined, rootData: Data, data: Data, event: SceneEvent) => void;
62
64
  readonly onContextMenuGroup?: (id: string, rootData: Data, data: Data, left: number, top: number, event: SceneEvent) => void;
63
65
  readonly onClickRelation?: (relation: Relation, event: SceneEvent) => void;
66
+ /** A click, not a drag, on nothing clickable, e.g. the background; r3f's Canvas event of the same name */
67
+ readonly onPointerMissed?: (event: MouseEvent) => void;
64
68
  };
65
69
 
66
70
  /** What an update has to redo, in the order it's redone */
67
- export type Change = "compile" | "build" | "camera" | "controls" | "gizmo" | "lights" | "quality" | "paint" | "dimensions" | "overlays";
71
+ export type Change = "compile" | "build" | "shadow" | "camera" | "controls" | "gizmo" | "lights" | "quality" | "paint" | "dimensions" | "overlays";
68
72
 
69
73
  /** What passing each option redoes; callbacks and what's read later need nothing */
70
74
  export const effects: { readonly [K in keyof ViewerOptions<unknown>]-?: ReadonlyArray<Change> } = {
@@ -86,6 +90,7 @@ export const effects: { readonly [K in keyof ViewerOptions<unknown>]-?: Readonly
86
90
  lights: ["lights"],
87
91
  environment: ["lights"],
88
92
  environmentIntensity: ["lights"],
93
+ groundShadow: ["shadow"],
89
94
  quality: ["quality"],
90
95
  popovers: ["overlays"],
91
96
  getTooltips: ["overlays"],
@@ -95,4 +100,5 @@ export const effects: { readonly [K in keyof ViewerOptions<unknown>]-?: Readonly
95
100
  onHoverGroup: [],
96
101
  onContextMenuGroup: [],
97
102
  onClickRelation: [],
103
+ onPointerMissed: [],
98
104
  };
@@ -32,8 +32,10 @@ import { configureControls, createControls, setPose } from "../camera/controls.j
32
32
  import { createGizmo } from "../camera/gizmo.js";
33
33
  import type { CompiledScene } from "../compile/compile.js";
34
34
  import { compileScene } from "../compile/compile.js";
35
- import type { RenderPath, TierGovernor } from "../quality/quality.js";
36
- import { createRenderPath, disposeRenderPath, recordFrame, renderPath } from "../quality/quality.js";
35
+ import type { TierGovernor } from "../quality/quality.js";
36
+ import { getPixelRatio, recordFrame } from "../quality/quality.js";
37
+ import type { GroundShadow } from "../shadow/shadow.js";
38
+ import { createGroundShadow, disposeGroundShadow } from "../shadow/shadow.js";
37
39
  import type { GlyphAtlas } from "../text/text.js";
38
40
  import { createGlyphAtlas } from "../text/text.js";
39
41
  import type { Change, ViewerOptions } from "./options.js";
@@ -77,7 +79,7 @@ export type Viewer<Content> = {
77
79
  compiled?: CompiledScene;
78
80
  built?: BuiltScene;
79
81
  gizmo?: ViewportGizmo;
80
- path?: RenderPath;
82
+ shadow?: GroundShadow;
81
83
  governor?: TierGovernor;
82
84
  readonly environments: Partial<Record<"studio" | "room", Texture>>;
83
85
  /** The scene is built and its shaders compiled */
@@ -164,12 +166,12 @@ export function disposeViewer<Content>(v: Viewer<Content>): void {
164
166
  v.resizeObserver.disconnect();
165
167
  v.controls.orbit.dispose();
166
168
  v.gizmo?.dispose();
167
- if (v.path) {
168
- disposeRenderPath(v.path);
169
- }
170
169
  if (v.built) {
171
170
  disposeScene(v.built);
172
171
  }
172
+ if (v.shadow) {
173
+ disposeGroundShadow(v.shadow);
174
+ }
173
175
  Object.values(v.environments).forEach((e) => e.dispose());
174
176
  v.geometries.forEach((g) => g.dispose());
175
177
  v.atlas.texture.dispose();
@@ -191,6 +193,12 @@ function apply<Content>(v: Viewer<Content>, changes: ReadonlySet<Change>): void
191
193
  v.structure = { activeComponents: options.activeComponents, relationsActive: options.activeRelations !== undefined };
192
194
  rebuild(v);
193
195
  }
196
+ if (changes.has("shadow") || changes.has("build")) {
197
+ if (v.shadow) {
198
+ disposeGroundShadow(v.shadow);
199
+ }
200
+ v.shadow = v.built && options.groundShadow !== false ? createGroundShadow(v.renderer, v.scene, v.built) : undefined;
201
+ }
194
202
  if (changes.has("camera")) {
195
203
  const { near = 0.1, far = 2000, ...rest } = options.camera ?? { type: "Perspective" };
196
204
  v.camera = v.cameras[rest.type];
@@ -211,11 +219,9 @@ function apply<Content>(v: Viewer<Content>, changes: ReadonlySet<Change>): void
211
219
  updateLights(v);
212
220
  }
213
221
  if (changes.has("quality")) {
214
- const quality = options.quality ?? "low";
215
- v.governor = { quality, tier: quality === "auto" ? "medium" : quality, sum: 0, count: 0 };
216
- }
217
- if (changes.has("quality") || changes.has("camera")) {
218
- updateRenderPath(v);
222
+ const quality = options.quality ?? "auto";
223
+ v.governor = { quality, tier: quality === "auto" ? "high" : quality, sum: 0, count: 0 };
224
+ v.renderer.setPixelRatio(getPixelRatio(v.governor.tier, window.devicePixelRatio));
219
225
  }
220
226
  if ((changes.has("paint") || changes.has("build")) && v.built) {
221
227
  const start = performance.now();
@@ -284,7 +290,7 @@ function renderFrame<Content>(v: Viewer<Content>, now: number): void {
284
290
  renderer.info.reset();
285
291
  const moving = v.controls.orbit.update(Math.min(interval / 1000, 0.1)) || !!gizmo?.animating;
286
292
  if (moving && loop.wasMoving && v.governor && recordFrame(v.governor, interval)) {
287
- updateRenderPath(v);
293
+ renderer.setPixelRatio(getPixelRatio(v.governor.tier, window.devicePixelRatio));
288
294
  }
289
295
  loop.wasMoving = moving;
290
296
  const animated = !!built && built.animatedItems.length > 0;
@@ -296,11 +302,13 @@ function renderFrame<Content>(v: Viewer<Content>, now: number): void {
296
302
  if (built) {
297
303
  cullScene(built, camera.position);
298
304
  }
299
- const fading = v.loaded && v.path ? renderPath(v.path, renderer, v.scene, camera, moving || animated) : false;
305
+ if (v.loaded) {
306
+ renderer.render(v.scene, camera);
307
+ }
300
308
  gizmo?.render();
301
309
  v.overlays.renderer.render(v.overlays.scene, camera);
302
310
  Object.assign(v.stats, { frames: v.stats.frames + 1, frameMs: performance.now() - start, drawCalls: renderer.info.render.calls });
303
- if (moving || animated || fading) {
311
+ if (moving || animated) {
304
312
  invalidate(v);
305
313
  } else {
306
314
  loop.last = 0;
@@ -310,7 +318,6 @@ function renderFrame<Content>(v: Viewer<Content>, now: number): void {
310
318
  function onResize<Content>(v: Viewer<Content>, rect: DOMRectReadOnly): void {
311
319
  Object.assign(v.size, { width: rect.width, height: rect.height });
312
320
  v.renderer.setSize(rect.width, rect.height, false);
313
- v.path?.composer?.setSize(rect.width, rect.height);
314
321
  v.overlays.renderer.setSize(rect.width, rect.height);
315
322
  v.gizmo?.update();
316
323
  fitPose(v);
@@ -369,13 +376,16 @@ function hoverAtPointer<Content>(v: Viewer<Content>): void {
369
376
 
370
377
  function onClick<Content>(v: Viewer<Content>, event: MouseEvent): void {
371
378
  const { down } = v.pointer;
372
- const hit = down && Math.hypot(event.clientX - down.x, event.clientY - down.y) <= clickTolerance ? pick(v, event) : undefined;
379
+ const clicked = !!down && Math.hypot(event.clientX - down.x, event.clientY - down.y) <= clickTolerance;
380
+ const hit = clicked ? pick(v, event) : undefined;
373
381
  const target = hit && v.compiled!.targets[hit.target];
374
382
  const args = hit && getGroupArgs(v, hit);
375
383
  if (hit && target?.kind === "relation") {
376
384
  v.options.onClickRelation?.(target.relation, { nativeEvent: event, point: hit.point });
377
385
  } else if (hit && args) {
378
386
  v.options.onClickGroup?.(...args, { nativeEvent: event, point: hit.point });
387
+ } else if (clicked) {
388
+ v.options.onPointerMissed?.(event);
379
389
  }
380
390
  }
381
391
 
@@ -529,12 +539,5 @@ function updateOverlays<Content>(v: Viewer<Content>): void {
529
539
  setOverlays(v.overlays, overlays, options.onOverlays);
530
540
  }
531
541
 
532
- function updateRenderPath<Content>(v: Viewer<Content>): void {
533
- if (v.path) {
534
- disposeRenderPath(v.path);
535
- }
536
- v.path = createRenderPath(v.renderer, v.scene, v.camera, v.governor?.tier ?? "low");
537
- }
538
-
539
542
  const raycaster = new Raycaster();
540
543
  const ndc = new Vector2();
@@ -1,11 +0,0 @@
1
- declare module "n8ao" {
2
- import type { Camera, Scene, WebGLRenderTarget } from "three";
3
- import type { Pass } from "three/addons/postprocessing/Pass.js";
4
-
5
- type AOPass = Pass & {
6
- readonly configuration: Record<string, unknown> & { intensity: number };
7
- readonly beautyRenderTarget: WebGLRenderTarget;
8
- };
9
-
10
- export const N8AOPass: new (scene: Scene, camera: Camera, width?: number, height?: number) => AOPass;
11
- }