abstract-3d 3.1.2 → 4.0.0-beta.10
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 +53 -1
- package/lib/abstract-3d.d.ts +27 -1
- package/lib/abstract-3d.d.ts.map +1 -1
- package/lib/abstract-3d.js +26 -2
- package/lib/abstract-3d.js.map +1 -1
- package/lib/renderers/dxf/dxf-encoding/dxf-build.d.ts.map +1 -1
- package/lib/renderers/dxf/dxf-encoding/dxf-build.js +1 -2
- package/lib/renderers/dxf/dxf-encoding/dxf-build.js.map +1 -1
- package/lib/renderers/ifc/ifc.test.d.ts +2 -0
- package/lib/renderers/ifc/ifc.test.d.ts.map +1 -0
- package/lib/renderers/ifc/ifc.test.js +380 -0
- package/lib/renderers/ifc/ifc.test.js.map +1 -0
- package/lib/renderers/index.d.ts +1 -0
- package/lib/renderers/index.d.ts.map +1 -1
- package/lib/renderers/index.js +1 -0
- package/lib/renderers/index.js.map +1 -1
- package/lib/renderers/react/index.d.ts +2 -5
- package/lib/renderers/react/index.d.ts.map +1 -1
- package/lib/renderers/react/index.js +1 -2
- package/lib/renderers/react/index.js.map +1 -1
- package/lib/renderers/react/react.d.ts +16 -42
- package/lib/renderers/react/react.d.ts.map +1 -1
- package/lib/renderers/react/react.js +56 -32
- package/lib/renderers/react/react.js.map +1 -1
- package/lib/renderers/three/appearance/appearance.d.ts +46 -0
- package/lib/renderers/three/appearance/appearance.d.ts.map +1 -0
- package/lib/renderers/three/appearance/appearance.js +102 -0
- package/lib/renderers/three/appearance/appearance.js.map +1 -0
- package/lib/renderers/three/appearance/appearance.test.d.ts +2 -0
- package/lib/renderers/three/appearance/appearance.test.d.ts.map +1 -0
- package/lib/renderers/three/appearance/appearance.test.js +97 -0
- package/lib/renderers/three/appearance/appearance.test.js.map +1 -0
- package/lib/renderers/three/appearance.d.ts +41 -0
- package/lib/renderers/three/appearance.d.ts.map +1 -0
- package/lib/renderers/three/appearance.js +100 -0
- package/lib/renderers/three/appearance.js.map +1 -0
- package/lib/renderers/three/build/build.d.ts +53 -0
- package/lib/renderers/three/build/build.d.ts.map +1 -0
- package/lib/renderers/three/build/build.js +181 -0
- package/lib/renderers/three/build/build.js.map +1 -0
- package/lib/renderers/three/build/build.test.d.ts +2 -0
- package/lib/renderers/three/build/build.test.d.ts.map +1 -0
- package/lib/renderers/three/build/build.test.js +120 -0
- package/lib/renderers/three/build/build.test.js.map +1 -0
- package/lib/renderers/three/build/materials.d.ts +20 -0
- package/lib/renderers/three/build/materials.d.ts.map +1 -0
- package/lib/renderers/three/build/materials.js +168 -0
- package/lib/renderers/three/build/materials.js.map +1 -0
- package/lib/renderers/three/build/parts.d.ts +65 -0
- package/lib/renderers/three/build/parts.d.ts.map +1 -0
- package/lib/renderers/three/build/parts.js +203 -0
- package/lib/renderers/three/build/parts.js.map +1 -0
- package/lib/renderers/three/build.d.ts +50 -0
- package/lib/renderers/three/build.d.ts.map +1 -0
- package/lib/renderers/three/build.js +210 -0
- package/lib/renderers/three/build.js.map +1 -0
- package/lib/renderers/three/camera/camera.d.ts +40 -0
- package/lib/renderers/three/camera/camera.d.ts.map +1 -0
- package/lib/renderers/three/camera/camera.js +51 -0
- package/lib/renderers/three/camera/camera.js.map +1 -0
- package/lib/renderers/three/camera/camera.test.d.ts +2 -0
- package/lib/renderers/three/camera/camera.test.d.ts.map +1 -0
- package/lib/renderers/three/camera/camera.test.js +46 -0
- package/lib/renderers/three/camera/camera.test.js.map +1 -0
- package/lib/renderers/three/camera/controls.d.ts +24 -0
- package/lib/renderers/three/camera/controls.d.ts.map +1 -0
- package/lib/renderers/three/camera/controls.js +39 -0
- package/lib/renderers/three/camera/controls.js.map +1 -0
- package/lib/renderers/three/camera/gizmo.d.ts +44 -0
- package/lib/renderers/three/camera/gizmo.d.ts.map +1 -0
- package/lib/renderers/three/camera/gizmo.js +43 -0
- package/lib/renderers/three/camera/gizmo.js.map +1 -0
- package/lib/renderers/three/camera.d.ts +63 -0
- package/lib/renderers/three/camera.d.ts.map +1 -0
- package/lib/renderers/three/camera.js +85 -0
- package/lib/renderers/three/camera.js.map +1 -0
- package/lib/renderers/three/compile/compile.d.ts +59 -0
- package/lib/renderers/three/compile/compile.d.ts.map +1 -0
- package/lib/renderers/three/compile/compile.js +88 -0
- package/lib/renderers/three/compile/compile.js.map +1 -0
- package/lib/renderers/three/compile/compile.test.d.ts +2 -0
- package/lib/renderers/three/compile/compile.test.d.ts.map +1 -0
- package/lib/renderers/three/compile/compile.test.js +93 -0
- package/lib/renderers/three/compile/compile.test.js.map +1 -0
- package/lib/renderers/three/compile.d.ts +58 -0
- package/lib/renderers/three/compile.d.ts.map +1 -0
- package/lib/renderers/three/compile.js +89 -0
- package/lib/renderers/three/compile.js.map +1 -0
- package/lib/renderers/three/controls.d.ts +27 -0
- package/lib/renderers/three/controls.d.ts.map +1 -0
- package/lib/renderers/three/controls.js +115 -0
- package/lib/renderers/three/controls.js.map +1 -0
- package/lib/renderers/three/environment/studio.d.ts +9 -0
- package/lib/renderers/three/environment/studio.d.ts.map +1 -0
- package/lib/renderers/three/environment/studio.js +15 -0
- package/lib/renderers/three/environment/studio.js.map +1 -0
- package/lib/renderers/three/geometry/geometry.d.ts +21 -0
- package/lib/renderers/three/geometry/geometry.d.ts.map +1 -0
- package/lib/renderers/three/geometry/geometry.js +141 -0
- package/lib/renderers/three/geometry/geometry.js.map +1 -0
- package/lib/renderers/three/geometry/geometry.test.d.ts +2 -0
- package/lib/renderers/three/geometry/geometry.test.d.ts.map +1 -0
- package/lib/renderers/three/geometry/geometry.test.js +80 -0
- package/lib/renderers/three/geometry/geometry.test.js.map +1 -0
- package/lib/renderers/three/geometry.d.ts +20 -0
- package/lib/renderers/three/geometry.d.ts.map +1 -0
- package/lib/renderers/three/geometry.js +164 -0
- package/lib/renderers/three/geometry.js.map +1 -0
- package/lib/renderers/three/gizmo.d.ts +49 -0
- package/lib/renderers/three/gizmo.d.ts.map +1 -0
- package/lib/renderers/three/gizmo.js +172 -0
- package/lib/renderers/three/gizmo.js.map +1 -0
- package/lib/renderers/three/index.d.ts +16 -0
- package/lib/renderers/three/index.d.ts.map +1 -0
- package/lib/renderers/three/index.js +6 -0
- package/lib/renderers/three/index.js.map +1 -0
- package/lib/renderers/three/input.d.ts +25 -0
- package/lib/renderers/three/input.d.ts.map +1 -0
- package/lib/renderers/three/input.js +71 -0
- package/lib/renderers/three/input.js.map +1 -0
- package/lib/renderers/three/lines.d.ts +16 -0
- package/lib/renderers/three/lines.d.ts.map +1 -0
- package/lib/renderers/three/lines.js +63 -0
- package/lib/renderers/three/lines.js.map +1 -0
- package/lib/renderers/three/materials.d.ts +15 -0
- package/lib/renderers/three/materials.d.ts.map +1 -0
- package/lib/renderers/three/materials.js +70 -0
- package/lib/renderers/three/materials.js.map +1 -0
- package/lib/renderers/three/options.d.ts +68 -0
- package/lib/renderers/three/options.d.ts.map +1 -0
- package/lib/renderers/three/options.js +64 -0
- package/lib/renderers/three/options.js.map +1 -0
- package/lib/renderers/three/overlays.d.ts +25 -0
- package/lib/renderers/three/overlays.d.ts.map +1 -0
- package/lib/renderers/three/overlays.js +57 -0
- package/lib/renderers/three/overlays.js.map +1 -0
- package/lib/renderers/three/quality/quality.d.ts +18 -0
- package/lib/renderers/three/quality/quality.d.ts.map +1 -0
- package/lib/renderers/three/quality/quality.js +28 -0
- package/lib/renderers/three/quality/quality.js.map +1 -0
- package/lib/renderers/three/quality/quality.test.d.ts +2 -0
- package/lib/renderers/three/quality/quality.test.d.ts.map +1 -0
- package/lib/renderers/three/quality/quality.test.js +23 -0
- package/lib/renderers/three/quality/quality.test.js.map +1 -0
- package/lib/renderers/three/quality.d.ts +24 -0
- package/lib/renderers/three/quality.d.ts.map +1 -0
- package/lib/renderers/three/quality.js +67 -0
- package/lib/renderers/three/quality.js.map +1 -0
- package/lib/renderers/three/shadow/key-light.d.ts +6 -0
- package/lib/renderers/three/shadow/key-light.d.ts.map +1 -0
- package/lib/renderers/three/shadow/key-light.js +39 -0
- package/lib/renderers/three/shadow/key-light.js.map +1 -0
- package/lib/renderers/three/shadow/shadow.d.ts +21 -0
- package/lib/renderers/three/shadow/shadow.d.ts.map +1 -0
- package/lib/renderers/three/shadow/shadow.js +120 -0
- package/lib/renderers/three/shadow/shadow.js.map +1 -0
- package/lib/renderers/three/text/text.d.ts +55 -0
- package/lib/renderers/three/text/text.d.ts.map +1 -0
- package/lib/renderers/three/text/text.js +90 -0
- package/lib/renderers/three/text/text.js.map +1 -0
- package/lib/renderers/three/text/text.test.d.ts +2 -0
- package/lib/renderers/three/text/text.test.d.ts.map +1 -0
- package/lib/renderers/three/text/text.test.js +70 -0
- package/lib/renderers/three/text/text.test.js.map +1 -0
- package/lib/renderers/three/text.d.ts +53 -0
- package/lib/renderers/three/text.d.ts.map +1 -0
- package/lib/renderers/three/text.js +151 -0
- package/lib/renderers/three/text.js.map +1 -0
- package/lib/renderers/three/viewer/options.d.ts +82 -0
- package/lib/renderers/three/viewer/options.d.ts.map +1 -0
- package/lib/renderers/three/viewer/options.js +34 -0
- package/lib/renderers/three/viewer/options.js.map +1 -0
- package/lib/renderers/three/viewer/options.test.d.ts +2 -0
- package/lib/renderers/three/viewer/options.test.d.ts.map +1 -0
- package/lib/renderers/three/viewer/options.test.js +38 -0
- package/lib/renderers/three/viewer/options.test.js.map +1 -0
- package/lib/renderers/three/viewer/overlays.d.ts +31 -0
- package/lib/renderers/three/viewer/overlays.d.ts.map +1 -0
- package/lib/renderers/three/viewer/overlays.js +43 -0
- package/lib/renderers/three/viewer/overlays.js.map +1 -0
- package/lib/renderers/three/viewer/viewer.d.ts +102 -0
- package/lib/renderers/three/viewer/viewer.d.ts.map +1 -0
- package/lib/renderers/three/viewer/viewer.js +488 -0
- package/lib/renderers/three/viewer/viewer.js.map +1 -0
- package/lib/renderers/three/viewer.d.ts +38 -0
- package/lib/renderers/three/viewer.d.ts.map +1 -0
- package/lib/renderers/three/viewer.js +435 -0
- package/lib/renderers/three/viewer.js.map +1 -0
- package/package.json +34 -16
- package/src/abstract-3d.ts +60 -2
- package/src/renderers/dxf/dxf-encoding/dxf-build.ts +1 -2
- package/src/renderers/ifc/ifc.test.ts +7 -5
- package/src/renderers/index.ts +1 -0
- package/src/renderers/react/index.ts +2 -5
- package/src/renderers/react/react.tsx +107 -181
- package/src/renderers/three/appearance/appearance.test.ts +153 -0
- package/src/renderers/three/appearance/appearance.ts +150 -0
- package/src/renderers/three/build/build.test.ts +149 -0
- package/src/renderers/three/build/build.ts +278 -0
- package/src/renderers/three/build/materials.ts +222 -0
- package/src/renderers/three/build/parts.ts +277 -0
- package/src/renderers/three/camera/camera.test.ts +53 -0
- package/src/renderers/three/camera/camera.ts +82 -0
- package/src/renderers/three/camera/controls.ts +65 -0
- package/src/renderers/three/camera/gizmo.ts +94 -0
- package/src/renderers/three/compile/compile.test.ts +126 -0
- package/src/renderers/three/compile/compile.ts +151 -0
- package/src/renderers/three/environment/studio.ts +18 -0
- package/src/renderers/three/geometry/geometry.test.ts +121 -0
- package/src/renderers/three/geometry/geometry.ts +189 -0
- package/src/renderers/three/index.ts +15 -0
- package/src/renderers/three/quality/quality.test.ts +37 -0
- package/src/renderers/three/quality/quality.ts +36 -0
- package/src/renderers/three/shadow/key-light.ts +43 -0
- package/src/renderers/three/shadow/shadow.ts +170 -0
- package/src/renderers/three/text/text.test.ts +79 -0
- package/src/renderers/three/text/text.ts +128 -0
- package/src/renderers/three/viewer/options.ts +112 -0
- package/src/renderers/three/viewer/overlays.ts +71 -0
- package/src/renderers/three/viewer/viewer.ts +596 -0
- package/lib/renderers/react/react-hotspot.d.ts +0 -27
- package/lib/renderers/react/react-hotspot.d.ts.map +0 -1
- package/lib/renderers/react/react-hotspot.js +0 -37
- package/lib/renderers/react/react-hotspot.js.map +0 -1
- package/src/custom_typings/react-three-fiber.d.ts +0 -9
- package/src/custom_typings/react-three-postprocessing.d.ts +0 -1
- package/src/renderers/react/react-camera.tsx +0 -391
- package/src/renderers/react/react-dimension.tsx +0 -122
- package/src/renderers/react/react-group.test.ts +0 -56
- package/src/renderers/react/react-group.tsx +0 -202
- package/src/renderers/react/react-image-material.tsx +0 -139
- package/src/renderers/react/react-material.tsx +0 -91
- package/src/renderers/react/react-mesh.tsx +0 -512
- package/src/renderers/react/react-relation.tsx +0 -86
- package/src/renderers/react/react-scene.tsx +0 -111
- package/src/renderers/react/react-types.ts +0 -9
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Matrix4, Vector3 } from "three";
|
|
2
|
+
import { assert, describe, expect, it } from "vitest";
|
|
3
|
+
import type { Group, Scene } from "../../../abstract-3d.js";
|
|
4
|
+
import { alignedDimension, box, group, line, sphere, text, vec3, vec3Zero } from "../../../abstract-3d.js";
|
|
5
|
+
import type { CompiledScene, Item } from "./compile.js";
|
|
6
|
+
import { compileScene, getItemMatrix } from "./compile.js";
|
|
7
|
+
|
|
8
|
+
const red = { normal: "rgb(255, 0, 0)" };
|
|
9
|
+
const scene = (groups: ReadonlyArray<Group>, rest: Partial<Scene> = {}): Scene => ({ size_deprecated: vec3(1, 1, 1), groups, ...rest });
|
|
10
|
+
const apply = (m: Matrix4 | undefined, x: number, y: number, z: number): ReadonlyArray<number> => {
|
|
11
|
+
assert(m);
|
|
12
|
+
return new Vector3(x, y, z)
|
|
13
|
+
.applyMatrix4(m)
|
|
14
|
+
.toArray()
|
|
15
|
+
.map((v) => Math.round(v * 1000) / 1000 + 0);
|
|
16
|
+
};
|
|
17
|
+
const getItem = (compiled: CompiledScene, index: number): Item => {
|
|
18
|
+
const item = compiled.items.at(index);
|
|
19
|
+
assert(item);
|
|
20
|
+
return item;
|
|
21
|
+
};
|
|
22
|
+
// Destructured, as the build's and lint's compiler options disagree on reading an index signature by dot or bracket
|
|
23
|
+
const groupId = (g: Group): string => {
|
|
24
|
+
const { id = "" }: Record<string, string | undefined> = g.data ?? {};
|
|
25
|
+
return id;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe("compileScene", () => {
|
|
29
|
+
it("flattens nested groups into items with world matrices", () => {
|
|
30
|
+
const inner = group([sphere(1, red)], vec3(0, 10, 0));
|
|
31
|
+
const compiled = compileScene(scene([group([box(vec3(1, 1, 1), red)], vec3(100, 0, 0), vec3Zero, [inner])]));
|
|
32
|
+
expect(compiled.items.map((i) => i.role)).toEqual(["solid", "solid"]);
|
|
33
|
+
expect(apply(compiled.items.at(0)?.matrix, 0, 0, 0)).toEqual([100, 0, 0]);
|
|
34
|
+
expect(apply(compiled.items.at(1)?.matrix, 0, 0, 0)).toEqual([100, 10, 0]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("applies group rotation before the group's position", () => {
|
|
38
|
+
const compiled = compileScene(scene([group([box(vec3(1, 1, 1), red)], vec3(5, 0, 0), vec3(0, 0, Math.PI / 2))]));
|
|
39
|
+
expect(apply(compiled.items.at(0)?.matrix, 1, 0, 0)).toEqual([5, 1, 0]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("rotates the scene by its deprecated rotation, then moves it by minus its deprecated center", () => {
|
|
43
|
+
const g = group([box(vec3(1, 1, 1), red)], vec3(10, 0, 0));
|
|
44
|
+
const centered = compileScene(scene([g], { center_deprecated: vec3(10, 0, 0) }));
|
|
45
|
+
expect(apply(centered.items.at(0)?.matrix, 0, 0, 0)).toEqual([0, 0, 0]);
|
|
46
|
+
const rotated = compileScene(scene([g], { center_deprecated: vec3(-10, 0, 0), rotation_deprecated: vec3(0, Math.PI, 0) }));
|
|
47
|
+
expect(apply(rotated.items.at(0)?.matrix, 1, 0, 0)).toEqual([-1, 0, 0]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("gives every item of a top-level group that group's target, and the data of the group it's in", () => {
|
|
51
|
+
const inner = group([sphere(1, red)], vec3Zero, vec3Zero, [], { part: "motor" });
|
|
52
|
+
const fan = group([box(vec3(1, 1, 1), red)], vec3Zero, vec3Zero, [inner], { id: "fan" });
|
|
53
|
+
const compiled = compileScene(scene([fan, group([box(vec3(1, 1, 1), red)], vec3Zero, vec3Zero, [], { id: "coil" })]), groupId);
|
|
54
|
+
expect(compiled.targets).toEqual([
|
|
55
|
+
{ kind: "group", id: "fan", root: 0, rootData: { id: "fan" } },
|
|
56
|
+
{ kind: "group", id: "coil", root: 1, rootData: { id: "coil" } },
|
|
57
|
+
]);
|
|
58
|
+
expect(compiled.items.map((i) => [i.target, i.data])).toEqual([
|
|
59
|
+
[0, { id: "fan" }],
|
|
60
|
+
[0, { part: "motor" }],
|
|
61
|
+
[1, { id: "coil" }],
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("gives groups an empty id without createGroupId", () => {
|
|
66
|
+
const compiled = compileScene(scene([group([box(vec3(1, 1, 1), red)])]));
|
|
67
|
+
expect(compiled.targets[0]).toMatchObject({ kind: "group", id: "" });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("assigns roles by geometry type", () => {
|
|
71
|
+
const g = group([box(vec3(1, 1, 1), red), text(vec3Zero, "A", 10, red), line(vec3Zero, vec3(1, 0, 0), 1, red)]);
|
|
72
|
+
expect(compileScene(scene([g])).items.map((i) => i.role)).toEqual(["solid", "text", "line"]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("makes items of animated groups relative to an animation that rotates the group", () => {
|
|
76
|
+
const fan = group(
|
|
77
|
+
[box(vec3(1, 1, 1), red)],
|
|
78
|
+
vec3(0, 5, 0),
|
|
79
|
+
vec3Zero,
|
|
80
|
+
[],
|
|
81
|
+
{},
|
|
82
|
+
{ transform: { rot: vec3(0, 0, Math.PI), trans: vec3Zero }, duration: 1000 }
|
|
83
|
+
);
|
|
84
|
+
const compiled = compileScene(scene([group([], vec3(10, 0, 0), vec3Zero, [fan])]));
|
|
85
|
+
expect(compiled.animations).toHaveLength(1);
|
|
86
|
+
expect(compiled.animations.at(0)?.speed).toEqual(vec3(0, 0, Math.PI));
|
|
87
|
+
expect(compiled.items.at(0)?.animation).toBe(0);
|
|
88
|
+
const world = (seconds: number): ReadonlyArray<number> => apply(getItemMatrix(compiled, getItem(compiled, 0), seconds, new Matrix4()), 1, 0, 0);
|
|
89
|
+
expect(world(0)).toEqual([11, 5, 0]);
|
|
90
|
+
expect(world(0.5)).toEqual([10, 6, 0]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("chains nested animations", () => {
|
|
94
|
+
const spin = { transform: { rot: vec3(0, 0, Math.PI), trans: vec3Zero }, duration: 1000 };
|
|
95
|
+
const inner = group([box(vec3(1, 1, 1), red)], vec3(1, 0, 0), vec3Zero, [], {}, spin);
|
|
96
|
+
const compiled = compileScene(scene([group([], vec3Zero, vec3Zero, [inner], {}, spin)]));
|
|
97
|
+
expect(compiled.animations.map((a) => a.parent)).toEqual([undefined, 0]);
|
|
98
|
+
expect(apply(getItemMatrix(compiled, getItem(compiled, 0), 0.5, new Matrix4()), 0, 0, 0)).toEqual([0, 1, 0]);
|
|
99
|
+
expect(apply(getItemMatrix(compiled, getItem(compiled, 0), 0.5, new Matrix4()), 1, 0, 0)).toEqual([-1, 1, 0]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("converts aligned dimensions to dimension items with a target per dimension", () => {
|
|
103
|
+
const dimension = alignedDimension(vec3Zero, vec3(1000, 0, 0), vec3(0, -100, 0), "1000", ["front"], undefined, "fan");
|
|
104
|
+
const compiled = compileScene(scene([], { dimensions_deprecated: { dimensions: [dimension], material: red } }));
|
|
105
|
+
const items = compiled.items.filter((i) => i.style === "dimension");
|
|
106
|
+
expect(items.length).toBeGreaterThan(0);
|
|
107
|
+
expect(new Set(items.map((i) => i.role))).toEqual(new Set(["line", "text", "solid"]));
|
|
108
|
+
expect(compiled.targets[items.at(0)?.target ?? -1]).toEqual({ kind: "dimension", id: "fan" });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("adds relation shapes, with an outline around their boxes", () => {
|
|
112
|
+
const relation = { id: "r1", pos: vec3Zero, groups: [group([box(vec3(10, 10, 10), red), sphere(1, red)], vec3(5, 0, 0))] };
|
|
113
|
+
const compiled = compileScene(scene([], { relations: [relation] }));
|
|
114
|
+
expect(compiled.items.map((i) => i.style)).toEqual(["relation", "outline", "relation"]);
|
|
115
|
+
expect(compiled.targets[getItem(compiled, 0).target]).toEqual({ kind: "relation", relation });
|
|
116
|
+
const outline = getItem(compiled, 1).mesh.geometry;
|
|
117
|
+
expect(outline.type === "Box" && outline.size).toEqual(vec3(10.125, 10.125, 10.125));
|
|
118
|
+
expect(apply(compiled.items.at(0)?.matrix, 0, 0, 0)).toEqual([5, 0, 0]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("keeps the world matrix of each top-level group for its tooltips", () => {
|
|
122
|
+
const compiled = compileScene(scene([group([], vec3(3, 0, 0))], { center_deprecated: vec3(1, 0, 0) }));
|
|
123
|
+
expect(apply(compiled.roots.at(0), 0, 0, 0)).toEqual([2, 0, 0]);
|
|
124
|
+
expect(apply(compiled.matrix, 0, 0, 0)).toEqual([-1, 0, 0]);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Euler, Matrix4, Quaternion, Vector3 } from "three";
|
|
2
|
+
import type { Group, Mesh, Relation, Scene, Vec3 } from "../../../abstract-3d.js";
|
|
3
|
+
import { dimensionConvertToTypeMesh, vec3, vec3Scale, vec3Zero } from "../../../abstract-3d.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scene → flat items with world matrices, with no GPU objects, so everything here is testable in Node; groups transform as the r3f renderer's
|
|
7
|
+
* react-group.tsx
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** How the item's geometry is drawn */
|
|
11
|
+
export type Role = "solid" | "text" | "line" | "image";
|
|
12
|
+
/** What the item belongs to, which decides its material and whether it's shown */
|
|
13
|
+
export type Style = "group" | "dimension" | "relation" | "outline";
|
|
14
|
+
|
|
15
|
+
/** What a pointer event on an item is about, and what hover and selection apply to */
|
|
16
|
+
export type Target =
|
|
17
|
+
| { readonly kind: "group"; readonly id: string; readonly root: number; readonly rootData: Record<string, string> | undefined }
|
|
18
|
+
| { readonly kind: "dimension"; readonly id: string | undefined }
|
|
19
|
+
| { readonly kind: "relation"; readonly relation: Relation };
|
|
20
|
+
|
|
21
|
+
export type Item = {
|
|
22
|
+
readonly mesh: Mesh;
|
|
23
|
+
readonly role: Role;
|
|
24
|
+
readonly style: Style;
|
|
25
|
+
/** World matrix, or for an animated item its matrix relative to its animation's group */
|
|
26
|
+
readonly matrix: Matrix4;
|
|
27
|
+
readonly target: number;
|
|
28
|
+
/** The data of the group the mesh is in */
|
|
29
|
+
readonly data: Record<string, string> | undefined;
|
|
30
|
+
readonly animation: number | undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type Animation = {
|
|
34
|
+
readonly parent: number | undefined;
|
|
35
|
+
/** The matrix of the group's parent, in world space or relative to the parent animation's group */
|
|
36
|
+
readonly base: Matrix4;
|
|
37
|
+
readonly pos: Vec3;
|
|
38
|
+
readonly rot: Vec3;
|
|
39
|
+
/** Radians per second around x, y and z */
|
|
40
|
+
readonly speed: Vec3;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type CompiledScene = {
|
|
44
|
+
readonly items: ReadonlyArray<Item>;
|
|
45
|
+
readonly targets: ReadonlyArray<Target>;
|
|
46
|
+
readonly animations: ReadonlyArray<Animation>;
|
|
47
|
+
/** World matrix of the scene's root, from its deprecated center and rotation */
|
|
48
|
+
readonly matrix: Matrix4;
|
|
49
|
+
/** World matrix of each top-level group */
|
|
50
|
+
readonly roots: ReadonlyArray<Matrix4>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type Output = { readonly items: Array<Item>; readonly animations: Array<Animation> };
|
|
54
|
+
type ItemBase = Pick<Item, "style" | "target" | "data" | "animation">;
|
|
55
|
+
|
|
56
|
+
const outlineScale = 1.0125;
|
|
57
|
+
|
|
58
|
+
/** Targets are the top-level groups, then the dimensions, then the relations */
|
|
59
|
+
export function compileScene(scene: Scene, createGroupId?: (g: Group) => string): CompiledScene {
|
|
60
|
+
const out: Output = { items: [], animations: [] };
|
|
61
|
+
const center = scene.center_deprecated ?? vec3Zero;
|
|
62
|
+
const matrix = new Matrix4().makeTranslation(-center.x, -center.y, -center.z).multiply(transform(vec3Zero, scene.rotation_deprecated));
|
|
63
|
+
const dimensions = scene.dimensions_deprecated?.dimensions ?? [];
|
|
64
|
+
const relations = scene.relations ?? [];
|
|
65
|
+
const targets: ReadonlyArray<Target> = [
|
|
66
|
+
...scene.groups.map((g, root): Target => ({ kind: "group", id: createGroupId?.(g) ?? "", root, rootData: g.data })),
|
|
67
|
+
...dimensions.map((d): Target => ({ kind: "dimension", id: d.id })),
|
|
68
|
+
...relations.map((relation): Target => ({ kind: "relation", relation })),
|
|
69
|
+
];
|
|
70
|
+
scene.groups.forEach((g, i) => addGroup(out, g, matrix, undefined, "group", i));
|
|
71
|
+
dimensions.forEach((dimension, i) => {
|
|
72
|
+
const mesh = dimensionConvertToTypeMesh(dimension, scene.rotation_deprecated ?? vec3Zero, scene.dimensions_deprecated?.material);
|
|
73
|
+
const base = { style: "dimension", target: scene.groups.length + i, data: undefined, animation: undefined } as const;
|
|
74
|
+
addMeshes(out, mesh.meshes, matrix.clone().multiply(transform(mesh.pos, mesh.rot)), base);
|
|
75
|
+
});
|
|
76
|
+
relations.forEach((relation, i) =>
|
|
77
|
+
relation.groups?.forEach((g) => addGroup(out, g, matrix, undefined, "relation", scene.groups.length + dimensions.length + i))
|
|
78
|
+
);
|
|
79
|
+
return { ...out, targets, matrix, roots: scene.groups.map((g) => matrix.clone().multiply(transform(g.pos, g.rot))) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function addGroup(out: Output, g: Group, parent: Matrix4, parentAnimation: number | undefined, style: "group" | "relation", target: number): void {
|
|
83
|
+
const animation = g.animation ? out.animations.push(createAnimation(g, parent, parentAnimation)) - 1 : parentAnimation;
|
|
84
|
+
// An animated group's items are relative to it, so its animation can move them
|
|
85
|
+
const matrix = g.animation ? new Matrix4() : parent.clone().multiply(transform(g.pos, g.rot));
|
|
86
|
+
addMeshes(out, g.meshes ?? [], matrix, { style, target, data: g.data, animation });
|
|
87
|
+
g.groups?.forEach((child) => addGroup(out, child, matrix, animation, style, target));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function addMeshes(out: Output, meshes: ReadonlyArray<Mesh>, matrix: Matrix4, base: ItemBase): void {
|
|
91
|
+
meshes.forEach((mesh) => {
|
|
92
|
+
out.items.push({ ...base, mesh, role: getRole(mesh), matrix });
|
|
93
|
+
if (base.style === "relation" && mesh.geometry.type === "Box") {
|
|
94
|
+
const outline = { ...mesh, geometry: { ...mesh.geometry, size: vec3Scale(mesh.geometry.size, outlineScale) } };
|
|
95
|
+
out.items.push({ ...base, style: "outline", mesh: outline, role: "solid", matrix });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The item's world matrix at `seconds` into its animations, written to `out` */
|
|
101
|
+
export function getItemMatrix(compiled: CompiledScene, item: Item, seconds: number, out: Matrix4): Matrix4 {
|
|
102
|
+
return item.animation === undefined
|
|
103
|
+
? out.copy(item.matrix)
|
|
104
|
+
: getAnimationMatrix(compiled.animations, item.animation, seconds, out).multiply(item.matrix);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getAnimationMatrix(animations: ReadonlyArray<Animation>, index: number, seconds: number, out: Matrix4): Matrix4 {
|
|
108
|
+
const a = animations[index]!;
|
|
109
|
+
const rot = vec3(a.rot.x + a.speed.x * seconds, a.rot.y + a.speed.y * seconds, a.rot.z + a.speed.z * seconds);
|
|
110
|
+
out.copy(a.base).multiply(transform(a.pos, rot, scratch));
|
|
111
|
+
return a.parent === undefined ? out : out.premultiply(getAnimationMatrix(animations, a.parent, seconds, new Matrix4()));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The r3f renderer turned an animated group by its rotation per duration in milliseconds */
|
|
115
|
+
function createAnimation(g: Group, parent: Matrix4, parentAnimation: number | undefined): Animation {
|
|
116
|
+
const { rot } = g.animation!.transform;
|
|
117
|
+
const perSecond = 1000 / g.animation!.duration;
|
|
118
|
+
return {
|
|
119
|
+
parent: parentAnimation,
|
|
120
|
+
base: parent.clone(),
|
|
121
|
+
pos: g.pos,
|
|
122
|
+
rot: g.rot ?? vec3Zero,
|
|
123
|
+
speed: vec3(rot.x * perSecond, rot.y * perSecond, rot.z * perSecond),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getRole(mesh: Mesh): Role {
|
|
128
|
+
switch (mesh.geometry.type) {
|
|
129
|
+
case "Text":
|
|
130
|
+
return "text";
|
|
131
|
+
case "Line":
|
|
132
|
+
case "CulledLine":
|
|
133
|
+
return "line";
|
|
134
|
+
case "Image":
|
|
135
|
+
return "image";
|
|
136
|
+
default:
|
|
137
|
+
return "solid";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const scratch = new Matrix4();
|
|
142
|
+
const euler = new Euler();
|
|
143
|
+
const quaternion = new Quaternion();
|
|
144
|
+
const position = new Vector3();
|
|
145
|
+
const scale = new Vector3();
|
|
146
|
+
|
|
147
|
+
/** Translation, then XYZ Euler rotation, then scale, as on a three.js Object3D */
|
|
148
|
+
export function transform(pos: Vec3, rot: Vec3 | undefined, out = new Matrix4(), size?: Vec3): Matrix4 {
|
|
149
|
+
quaternion.setFromEuler(euler.set(rot?.x ?? 0, rot?.y ?? 0, rot?.z ?? 0));
|
|
150
|
+
return out.compose(position.set(pos.x, pos.y, pos.z), quaternion, scale.set(size?.x ?? 1, size?.y ?? 1, size?.z ?? 1));
|
|
151
|
+
}
|