@kite3d/engine 0.18.0 → 0.19.0-alpha.1
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/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +7 -7
- package/dist/authoring.js.map +1 -1
- package/dist/authoringValidation.d.ts +2 -5
- package/dist/authoringValidation.js +266 -289
- package/dist/authoringValidation.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1145 -1440
- package/dist/index.js.map +1 -1
- package/dist/projectFormat.js +70 -81
- package/dist/projectFormat.js.map +1 -1
- package/dist/runtime/createGame.d.ts +1 -1
- package/dist/runtime/index.d.ts +0 -2
- package/dist/runtime/projectFormat.d.ts +7 -14
- package/dist/runtime.js +14086 -15497
- package/dist/runtime.js.map +1 -1
- package/dist/sceneSerialization.js +57 -52
- package/dist/sceneSerialization.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/authoring.ts +3 -3
- package/src/authoringValidation.ts +3 -40
- package/src/index.ts +0 -2
- package/src/runtime/createGame.ts +1 -5
- package/src/runtime/index.ts +0 -2
- package/src/runtime/projectFormat.ts +20 -47
- package/src/sceneSerialization.ts +10 -2
- package/dist/generatorParams.d.ts +0 -23
- package/dist/plugins/GeneratorComponent.d.ts +0 -44
- package/src/generatorParams.ts +0 -182
- package/src/plugins/GeneratorComponent.ts +0 -287
|
@@ -1,61 +1,66 @@
|
|
|
1
1
|
const l = new TextEncoder();
|
|
2
2
|
async function N(e, t = {}) {
|
|
3
|
-
const
|
|
3
|
+
const r = await e.exportScene({
|
|
4
4
|
exportExt: "gltf",
|
|
5
5
|
preserveUUIDs: !0,
|
|
6
6
|
viewerConfig: !0,
|
|
7
7
|
embedUrlImages: !1,
|
|
8
|
-
onlyVisible: !
|
|
8
|
+
onlyVisible: !1,
|
|
9
|
+
shouldExportObject: x,
|
|
9
10
|
jsonSpaces: 2
|
|
10
11
|
}, !1);
|
|
11
|
-
if (!
|
|
12
|
-
return
|
|
12
|
+
if (!r) throw new Error("The scene exporter returned no glTF data");
|
|
13
|
+
return E(JSON.parse(await r.text()), t);
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
function x(e) {
|
|
16
|
+
var t, r;
|
|
17
|
+
return ((t = e.userData) == null ? void 0 : t.excludeFromExport) !== !0 && ((r = e.userData) == null ? void 0 : r.isWidgetRoot) !== !0 && e.isWidget !== !0 && e.assetType !== "widget";
|
|
18
|
+
}
|
|
19
|
+
async function E(e, t = {}) {
|
|
15
20
|
if (!a(e) || !a(e.asset))
|
|
16
21
|
throw new Error("The scene is not a JSON glTF document");
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const i = y(
|
|
22
|
+
const r = U(e);
|
|
23
|
+
g(r), j(r);
|
|
24
|
+
const n = z(t.scenePath || "assets/main.scene.gltf"), s = P(n), f = [];
|
|
25
|
+
A(r, s, $(n), f), await O(r, s, f, 16);
|
|
26
|
+
const i = y(r);
|
|
22
27
|
return {
|
|
23
28
|
document: i,
|
|
24
29
|
gltf: l.encode(`${JSON.stringify(i, null, 2)}
|
|
25
30
|
`),
|
|
26
|
-
files: f.sort((d,
|
|
31
|
+
files: f.sort((d, u) => d.path.localeCompare(u.path))
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
|
-
function
|
|
34
|
+
function g(e) {
|
|
30
35
|
if (Array.isArray(e)) {
|
|
31
|
-
e.forEach(
|
|
36
|
+
e.forEach(g);
|
|
32
37
|
return;
|
|
33
38
|
}
|
|
34
39
|
if (a(e)) {
|
|
35
40
|
if (e.rootSceneModelRoot === !0 && delete e.gltfUUID, a(e.WEBGI_viewer)) {
|
|
36
41
|
h(e.WEBGI_viewer);
|
|
37
|
-
const t = e.WEBGI_viewer.scene,
|
|
38
|
-
a(
|
|
42
|
+
const t = e.WEBGI_viewer.scene, r = a(t) ? t.defaultCamera : void 0, n = a(r) ? r.object : void 0;
|
|
43
|
+
a(n) && delete n.uuid;
|
|
39
44
|
}
|
|
40
|
-
Object.values(e).forEach(
|
|
45
|
+
Object.values(e).forEach(g);
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
function j(e) {
|
|
44
49
|
const t = /* @__PURE__ */ new Map();
|
|
45
|
-
|
|
46
|
-
t.set(
|
|
47
|
-
}),
|
|
48
|
-
|
|
50
|
+
m(e, (r, n) => {
|
|
51
|
+
t.set(r, (t.get(r) || 0) + 1), typeof n == "string" && t.set(n, (t.get(n) || 0) + 1);
|
|
52
|
+
}), m(e, (r, n, s) => {
|
|
53
|
+
r === "uuid" && typeof n == "string" && t.get(n) === 1 && delete s[r];
|
|
49
54
|
});
|
|
50
55
|
}
|
|
51
|
-
function
|
|
56
|
+
function m(e, t) {
|
|
52
57
|
if (Array.isArray(e)) {
|
|
53
|
-
e.forEach((
|
|
58
|
+
e.forEach((r) => m(r, t));
|
|
54
59
|
return;
|
|
55
60
|
}
|
|
56
61
|
if (a(e))
|
|
57
|
-
for (const [
|
|
58
|
-
t(
|
|
62
|
+
for (const [r, n] of Object.entries(e))
|
|
63
|
+
t(r, n, e), m(n, t);
|
|
59
64
|
}
|
|
60
65
|
function h(e) {
|
|
61
66
|
if (Array.isArray(e)) {
|
|
@@ -64,7 +69,7 @@ function h(e) {
|
|
|
64
69
|
}
|
|
65
70
|
a(e) && (e.isEuler === !0 && (typeof e.order != "string" && (e.order = "XYZ"), typeof e.x != "number" && (e.x = 0), typeof e.y != "number" && (e.y = 0), typeof e.z != "number" && (e.z = 0)), e.autoAspect === !0 && (delete e.aspect, a(e.object) && delete e.object.aspect), Object.values(e).forEach(h));
|
|
66
71
|
}
|
|
67
|
-
function
|
|
72
|
+
function A(e, t, r, n) {
|
|
68
73
|
const s = e.buffers;
|
|
69
74
|
if (!(s != null && s.length)) return;
|
|
70
75
|
const f = s.map((o) => {
|
|
@@ -81,46 +86,46 @@ function E(e, t, n, r) {
|
|
|
81
86
|
const d = f.map((o) => {
|
|
82
87
|
const c = J(i);
|
|
83
88
|
return i = c + ((o == null ? void 0 : o.byteLength) || 0), c;
|
|
84
|
-
}),
|
|
89
|
+
}), u = new Uint8Array(i);
|
|
85
90
|
f.forEach((o, c) => {
|
|
86
|
-
o &&
|
|
91
|
+
o && u.set(o, d[c]);
|
|
87
92
|
});
|
|
88
93
|
for (const o of e.bufferViews || []) {
|
|
89
94
|
const c = d[o.buffer];
|
|
90
95
|
if (c === void 0) throw new Error(`Invalid glTF buffer index: ${o.buffer}`);
|
|
91
96
|
o.byteOffset = (o.byteOffset || 0) + c, o.buffer = 0;
|
|
92
97
|
}
|
|
93
|
-
const
|
|
94
|
-
e.buffers = [{ byteLength:
|
|
98
|
+
const p = `${r}.bin`;
|
|
99
|
+
e.buffers = [{ byteLength: u.byteLength, uri: p }], n.push({ path: W(t, p), bytes: u });
|
|
95
100
|
}
|
|
96
|
-
async function
|
|
101
|
+
async function O(e, t, r, n) {
|
|
97
102
|
var f;
|
|
98
103
|
const s = /* @__PURE__ */ new Map();
|
|
99
104
|
for (const i of e.images || []) {
|
|
100
105
|
if (!((f = i.uri) != null && f.startsWith("data:"))) continue;
|
|
101
|
-
const d = w(i.uri),
|
|
102
|
-
s.set(o, d.bytes), i.uri = C(t, o), i.mimeType =
|
|
106
|
+
const d = w(i.uri), u = d.mimeType || i.mimeType || "application/octet-stream", o = `assets/textures/${(await S(d.bytes)).slice(0, n)}.${T(u)}`;
|
|
107
|
+
s.set(o, d.bytes), i.uri = C(t, o), i.mimeType = u;
|
|
103
108
|
}
|
|
104
|
-
for (const [i, d] of s)
|
|
109
|
+
for (const [i, d] of s) r.push({ path: i, bytes: d });
|
|
105
110
|
}
|
|
106
111
|
function w(e) {
|
|
107
112
|
const t = /^data:([^;,]*)(;base64)?,(.*)$/s.exec(e);
|
|
108
113
|
if (!t) throw new Error("Invalid data URL in glTF");
|
|
109
114
|
return {
|
|
110
|
-
bytes: t[2] ?
|
|
115
|
+
bytes: t[2] ? I(t[3]) : l.encode(decodeURIComponent(t[3])),
|
|
111
116
|
mimeType: t[1] || void 0
|
|
112
117
|
};
|
|
113
118
|
}
|
|
114
|
-
function
|
|
119
|
+
function I(e) {
|
|
115
120
|
const t = atob(e);
|
|
116
|
-
return Uint8Array.from(t, (
|
|
121
|
+
return Uint8Array.from(t, (r) => r.charCodeAt(0));
|
|
117
122
|
}
|
|
118
|
-
async function
|
|
123
|
+
async function S(e) {
|
|
119
124
|
const t = await crypto.subtle.digest("SHA-256", e);
|
|
120
|
-
return Array.from(new Uint8Array(t), (
|
|
125
|
+
return Array.from(new Uint8Array(t), (r) => r.toString(16).padStart(2, "0")).join("");
|
|
121
126
|
}
|
|
122
|
-
function
|
|
123
|
-
var
|
|
127
|
+
function T(e) {
|
|
128
|
+
var n;
|
|
124
129
|
const t = e.toLowerCase().split(";", 1)[0];
|
|
125
130
|
return {
|
|
126
131
|
"image/avif": "avif",
|
|
@@ -130,7 +135,7 @@ function S(e) {
|
|
|
130
135
|
"image/png": "png",
|
|
131
136
|
"image/svg+xml": "svg",
|
|
132
137
|
"image/webp": "webp"
|
|
133
|
-
}[t] || ((
|
|
138
|
+
}[t] || ((n = t.split("/")[1]) == null ? void 0 : n.replace(/[^a-z0-9.+-]/g, "")) || "bin";
|
|
134
139
|
}
|
|
135
140
|
function y(e) {
|
|
136
141
|
return Array.isArray(e) ? e.map(y) : a(e) ? Object.fromEntries(Object.keys(e).sort().map((t) => [t, y(e[t])])) : e;
|
|
@@ -141,34 +146,34 @@ function U(e) {
|
|
|
141
146
|
function a(e) {
|
|
142
147
|
return !!e && typeof e == "object" && !Array.isArray(e);
|
|
143
148
|
}
|
|
144
|
-
function
|
|
149
|
+
function z(e) {
|
|
145
150
|
const t = e.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
146
151
|
if (!t || t.startsWith("/") || t.split("/").includes(".."))
|
|
147
152
|
throw new Error(`Invalid project path: ${e}`);
|
|
148
153
|
return t;
|
|
149
154
|
}
|
|
150
|
-
function
|
|
155
|
+
function P(e) {
|
|
151
156
|
const t = e.lastIndexOf("/");
|
|
152
157
|
return t < 0 ? "" : e.slice(0, t);
|
|
153
158
|
}
|
|
154
|
-
function
|
|
155
|
-
const t = e.slice(e.lastIndexOf("/") + 1),
|
|
156
|
-
return
|
|
159
|
+
function $(e) {
|
|
160
|
+
const t = e.slice(e.lastIndexOf("/") + 1), r = t.lastIndexOf(".");
|
|
161
|
+
return r < 0 ? t : t.slice(0, r);
|
|
157
162
|
}
|
|
158
|
-
function
|
|
163
|
+
function W(e, t) {
|
|
159
164
|
return e ? `${e}/${t}` : t;
|
|
160
165
|
}
|
|
161
166
|
function C(e, t) {
|
|
162
|
-
const
|
|
163
|
-
for (;
|
|
164
|
-
|
|
165
|
-
return [...
|
|
167
|
+
const r = e ? e.split("/") : [], n = t.split("/");
|
|
168
|
+
for (; r.length && n.length && r[0] === n[0]; )
|
|
169
|
+
r.shift(), n.shift();
|
|
170
|
+
return [...r.map(() => ".."), ...n].join("/") || ".";
|
|
166
171
|
}
|
|
167
172
|
function J(e) {
|
|
168
173
|
return e + 3 & -4;
|
|
169
174
|
}
|
|
170
175
|
export {
|
|
171
176
|
N as serializeSceneGltf,
|
|
172
|
-
|
|
177
|
+
E as serializeSceneGltfDocument
|
|
173
178
|
};
|
|
174
179
|
//# sourceMappingURL=sceneSerialization.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneSerialization.js","sources":["../src/sceneSerialization.ts"],"sourcesContent":["import type {ThreeViewer} from 'threepipe'\n\nexport interface SerializeSceneGltfOptions {\n scenePath?: string\n}\n\nexport interface SerializedSceneFile {\n path: string\n bytes: Uint8Array\n}\n\nexport interface SerializedSceneGltf {\n document: Record<string, unknown>\n gltf: Uint8Array\n files: SerializedSceneFile[]\n}\n\ninterface GltfBuffer {\n byteLength?: number\n uri?: string\n [key: string]: unknown\n}\n\ninterface GltfBufferView {\n buffer: number\n byteOffset?: number\n [key: string]: unknown\n}\n\ninterface GltfImage {\n uri?: string\n mimeType?: string\n [key: string]: unknown\n}\n\ninterface GltfDocument extends Record<string, unknown> {\n buffers?: GltfBuffer[]\n bufferViews?: GltfBufferView[]\n images?: GltfImage[]\n}\n\nconst encoder = new TextEncoder()\n\n/** Export a viewer scene as deterministic text glTF plus external resources. */\nexport async function serializeSceneGltf(\n viewer: Pick<ThreeViewer, 'exportScene'>,\n options: SerializeSceneGltfOptions = {},\n): Promise<SerializedSceneGltf> {\n const blob = await viewer.exportScene({\n exportExt: 'gltf',\n preserveUUIDs: true,\n viewerConfig: true,\n embedUrlImages: false,\n onlyVisible: true,\n jsonSpaces: 2,\n }, false)\n if (!blob) throw new Error('The scene exporter returned no glTF data')\n return serializeSceneGltfDocument(JSON.parse(await blob.text()), options)\n}\n\n/** Canonicalize JSON glTF and extract every embedded resource. */\nexport async function serializeSceneGltfDocument(\n input: unknown,\n options: SerializeSceneGltfOptions = {},\n): Promise<SerializedSceneGltf> {\n if (!isRecord(input) || !isRecord(input.asset)) {\n throw new Error('The scene is not a JSON glTF document')\n }\n const document = cloneJson(input) as GltfDocument\n removeVolatileViewerIds(document)\n removeUnreferencedUuids(document)\n const scenePath = normalizeProjectPath(options.scenePath || 'assets/main.scene.gltf')\n const sceneDirectory = directoryName(scenePath)\n const files: SerializedSceneFile[] = []\n\n extractBuffers(document, sceneDirectory, fileStem(scenePath), files)\n await extractImages(document, sceneDirectory, files, 16)\n\n const sorted = sortObjectKeys(document) as GltfDocument\n return {\n document: sorted,\n gltf: encoder.encode(`${JSON.stringify(sorted, null, 2)}\\n`),\n files: files.sort((left, right) => left.path.localeCompare(right.path)),\n }\n}\n\nfunction removeVolatileViewerIds(value: unknown): void {\n if (Array.isArray(value)) {\n value.forEach(removeVolatileViewerIds)\n return\n }\n if (!isRecord(value)) return\n if (value.rootSceneModelRoot === true) delete value.gltfUUID\n if (isRecord(value.WEBGI_viewer)) {\n canonicalizeViewerConfig(value.WEBGI_viewer)\n const scene = value.WEBGI_viewer.scene\n const camera = isRecord(scene) ? scene.defaultCamera : undefined\n const object = isRecord(camera) ? camera.object : undefined\n if (isRecord(object)) delete object.uuid\n }\n Object.values(value).forEach(removeVolatileViewerIds)\n}\n\nfunction removeUnreferencedUuids(document: GltfDocument): void {\n const uses = new Map<string, number>()\n visitJson(document, (key, value) => {\n uses.set(key, (uses.get(key) || 0) + 1)\n if (typeof value === 'string') uses.set(value, (uses.get(value) || 0) + 1)\n })\n visitJson(document, (key, value, owner) => {\n if (key === 'uuid' && typeof value === 'string' && uses.get(value) === 1) delete owner[key]\n })\n}\n\nfunction visitJson(\n value: unknown,\n visit: (key: string, value: unknown, owner: Record<string, unknown>) => void,\n): void {\n if (Array.isArray(value)) {\n value.forEach((child) => visitJson(child, visit))\n return\n }\n if (!isRecord(value)) return\n for (const [key, child] of Object.entries(value)) {\n visit(key, child, value)\n visitJson(child, visit)\n }\n}\n\nfunction canonicalizeViewerConfig(value: unknown): void {\n if (Array.isArray(value)) {\n value.forEach(canonicalizeViewerConfig)\n return\n }\n if (!isRecord(value)) return\n if (value.isEuler === true) {\n if (typeof value.order !== 'string') value.order = 'XYZ'\n if (typeof value.x !== 'number') value.x = 0\n if (typeof value.y !== 'number') value.y = 0\n if (typeof value.z !== 'number') value.z = 0\n }\n if (value.autoAspect === true) {\n delete value.aspect\n if (isRecord(value.object)) delete value.object.aspect\n }\n Object.values(value).forEach(canonicalizeViewerConfig)\n}\n\nfunction extractBuffers(\n document: GltfDocument,\n sceneDirectory: string,\n baseName: string,\n files: SerializedSceneFile[],\n): void {\n const buffers = document.buffers\n if (!buffers?.length) return\n\n const embedded = buffers.map((buffer) => buffer.uri?.startsWith('data:') ? decodeDataUrl(buffer.uri).bytes : undefined)\n if (embedded.every((value) => value === undefined)) return\n if (embedded.some((value, index) => value === undefined && buffers[index]?.uri)) {\n throw new Error('A scene cannot combine embedded and external buffers during serialization')\n }\n\n let byteLength = 0\n const offsets = embedded.map((bytes) => {\n const offset = align4(byteLength)\n byteLength = offset + (bytes?.byteLength || 0)\n return offset\n })\n const combined = new Uint8Array(byteLength)\n embedded.forEach((bytes, index) => {\n if (bytes) combined.set(bytes, offsets[index])\n })\n\n for (const view of document.bufferViews || []) {\n const offset = offsets[view.buffer]\n if (offset === undefined) throw new Error(`Invalid glTF buffer index: ${view.buffer}`)\n view.byteOffset = (view.byteOffset || 0) + offset\n view.buffer = 0\n }\n\n const binName = `${baseName}.bin`\n document.buffers = [{byteLength: combined.byteLength, uri: binName}]\n files.push({path: joinProjectPath(sceneDirectory, binName), bytes: combined})\n}\n\nasync function extractImages(\n document: GltfDocument,\n sceneDirectory: string,\n files: SerializedSceneFile[],\n hashLength: number,\n): Promise<void> {\n const byPath = new Map<string, Uint8Array>()\n for (const image of document.images || []) {\n if (!image.uri?.startsWith('data:')) continue\n const decoded = decodeDataUrl(image.uri)\n const mimeType = decoded.mimeType || image.mimeType || 'application/octet-stream'\n const hash = await sha256(decoded.bytes)\n const path = `assets/textures/${hash.slice(0, hashLength)}.${imageExtension(mimeType)}`\n byPath.set(path, decoded.bytes)\n image.uri = relativeProjectPath(sceneDirectory, path)\n image.mimeType = mimeType\n }\n for (const [path, bytes] of byPath) files.push({path, bytes})\n}\n\nfunction decodeDataUrl(uri: string): {bytes: Uint8Array, mimeType?: string} {\n const match = /^data:([^;,]*)(;base64)?,(.*)$/s.exec(uri)\n if (!match) throw new Error('Invalid data URL in glTF')\n return {\n bytes: match[2] ? decodeBase64(match[3]) : encoder.encode(decodeURIComponent(match[3])),\n mimeType: match[1] || undefined,\n }\n}\n\nfunction decodeBase64(value: string): Uint8Array {\n const decoded = atob(value)\n return Uint8Array.from(decoded, (character) => character.charCodeAt(0))\n}\n\nasync function sha256(bytes: Uint8Array): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', bytes as BufferSource)\n return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, '0')).join('')\n}\n\nfunction imageExtension(mimeType: string): string {\n const normalized = mimeType.toLowerCase().split(';', 1)[0]\n const extensions: Record<string, string> = {\n 'image/avif': 'avif',\n 'image/gif': 'gif',\n 'image/jpeg': 'jpg',\n 'image/ktx2': 'ktx2',\n 'image/png': 'png',\n 'image/svg+xml': 'svg',\n 'image/webp': 'webp',\n }\n return extensions[normalized] || normalized.split('/')[1]?.replace(/[^a-z0-9.+-]/g, '') || 'bin'\n}\n\nfunction sortObjectKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortObjectKeys)\n if (!isRecord(value)) return value\n return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortObjectKeys(value[key])]))\n}\n\nfunction cloneJson<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction normalizeProjectPath(path: string): string {\n const normalized = path.replace(/\\\\/g, '/').replace(/^\\.\\//, '')\n if (!normalized || normalized.startsWith('/') || normalized.split('/').includes('..')) {\n throw new Error(`Invalid project path: ${path}`)\n }\n return normalized\n}\n\nfunction directoryName(path: string): string {\n const end = path.lastIndexOf('/')\n return end < 0 ? '' : path.slice(0, end)\n}\n\nfunction fileStem(path: string): string {\n const name = path.slice(path.lastIndexOf('/') + 1)\n const end = name.lastIndexOf('.')\n return end < 0 ? name : name.slice(0, end)\n}\n\nfunction joinProjectPath(directory: string, name: string): string {\n return directory ? `${directory}/${name}` : name\n}\n\nfunction relativeProjectPath(fromDirectory: string, target: string): string {\n const from = fromDirectory ? fromDirectory.split('/') : []\n const to = target.split('/')\n while (from.length && to.length && from[0] === to[0]) {\n from.shift()\n to.shift()\n }\n return [...from.map(() => '..'), ...to].join('/') || '.'\n}\n\nfunction align4(value: number): number {\n return (value + 3) & ~3\n}\n"],"names":["encoder","serializeSceneGltf","viewer","options","blob","serializeSceneGltfDocument","input","isRecord","document","cloneJson","removeVolatileViewerIds","removeUnreferencedUuids","scenePath","normalizeProjectPath","sceneDirectory","directoryName","files","extractBuffers","fileStem","extractImages","sorted","sortObjectKeys","left","right","value","canonicalizeViewerConfig","scene","camera","object","uses","visitJson","key","owner","visit","child","baseName","buffers","embedded","buffer","_a","decodeDataUrl","index","byteLength","offsets","bytes","offset","align4","combined","view","binName","joinProjectPath","hashLength","byPath","image","decoded","mimeType","path","sha256","imageExtension","relativeProjectPath","uri","match","decodeBase64","character","digest","normalized","end","name","directory","fromDirectory","target","from","to"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sceneSerialization.js","sources":["../src/sceneSerialization.ts"],"sourcesContent":["import type {IObject3D, ThreeViewer} from 'threepipe'\n\nexport interface SerializeSceneGltfOptions {\n scenePath?: string\n}\n\nexport interface SerializedSceneFile {\n path: string\n bytes: Uint8Array\n}\n\nexport interface SerializedSceneGltf {\n document: Record<string, unknown>\n gltf: Uint8Array\n files: SerializedSceneFile[]\n}\n\ninterface GltfBuffer {\n byteLength?: number\n uri?: string\n [key: string]: unknown\n}\n\ninterface GltfBufferView {\n buffer: number\n byteOffset?: number\n [key: string]: unknown\n}\n\ninterface GltfImage {\n uri?: string\n mimeType?: string\n [key: string]: unknown\n}\n\ninterface GltfDocument extends Record<string, unknown> {\n buffers?: GltfBuffer[]\n bufferViews?: GltfBufferView[]\n images?: GltfImage[]\n}\n\nconst encoder = new TextEncoder()\n\n/** Export a viewer scene as deterministic text glTF plus external resources. */\nexport async function serializeSceneGltf(\n viewer: Pick<ThreeViewer, 'exportScene'>,\n options: SerializeSceneGltfOptions = {},\n): Promise<SerializedSceneGltf> {\n const blob = await viewer.exportScene({\n exportExt: 'gltf',\n preserveUUIDs: true,\n viewerConfig: true,\n embedUrlImages: false,\n onlyVisible: false,\n shouldExportObject: isAuthoredSceneObject,\n jsonSpaces: 2,\n }, false)\n if (!blob) throw new Error('The scene exporter returned no glTF data')\n return serializeSceneGltfDocument(JSON.parse(await blob.text()), options)\n}\n\nfunction isAuthoredSceneObject(object: IObject3D): boolean {\n return object.userData?.excludeFromExport !== true\n && object.userData?.isWidgetRoot !== true\n && object.isWidget !== true\n && object.assetType !== 'widget'\n}\n\n/** Canonicalize JSON glTF and extract every embedded resource. */\nexport async function serializeSceneGltfDocument(\n input: unknown,\n options: SerializeSceneGltfOptions = {},\n): Promise<SerializedSceneGltf> {\n if (!isRecord(input) || !isRecord(input.asset)) {\n throw new Error('The scene is not a JSON glTF document')\n }\n const document = cloneJson(input) as GltfDocument\n removeVolatileViewerIds(document)\n removeUnreferencedUuids(document)\n const scenePath = normalizeProjectPath(options.scenePath || 'assets/main.scene.gltf')\n const sceneDirectory = directoryName(scenePath)\n const files: SerializedSceneFile[] = []\n\n extractBuffers(document, sceneDirectory, fileStem(scenePath), files)\n await extractImages(document, sceneDirectory, files, 16)\n\n const sorted = sortObjectKeys(document) as GltfDocument\n return {\n document: sorted,\n gltf: encoder.encode(`${JSON.stringify(sorted, null, 2)}\\n`),\n files: files.sort((left, right) => left.path.localeCompare(right.path)),\n }\n}\n\nfunction removeVolatileViewerIds(value: unknown): void {\n if (Array.isArray(value)) {\n value.forEach(removeVolatileViewerIds)\n return\n }\n if (!isRecord(value)) return\n if (value.rootSceneModelRoot === true) delete value.gltfUUID\n if (isRecord(value.WEBGI_viewer)) {\n canonicalizeViewerConfig(value.WEBGI_viewer)\n const scene = value.WEBGI_viewer.scene\n const camera = isRecord(scene) ? scene.defaultCamera : undefined\n const object = isRecord(camera) ? camera.object : undefined\n if (isRecord(object)) delete object.uuid\n }\n Object.values(value).forEach(removeVolatileViewerIds)\n}\n\nfunction removeUnreferencedUuids(document: GltfDocument): void {\n const uses = new Map<string, number>()\n visitJson(document, (key, value) => {\n uses.set(key, (uses.get(key) || 0) + 1)\n if (typeof value === 'string') uses.set(value, (uses.get(value) || 0) + 1)\n })\n visitJson(document, (key, value, owner) => {\n if (key === 'uuid' && typeof value === 'string' && uses.get(value) === 1) delete owner[key]\n })\n}\n\nfunction visitJson(\n value: unknown,\n visit: (key: string, value: unknown, owner: Record<string, unknown>) => void,\n): void {\n if (Array.isArray(value)) {\n value.forEach((child) => visitJson(child, visit))\n return\n }\n if (!isRecord(value)) return\n for (const [key, child] of Object.entries(value)) {\n visit(key, child, value)\n visitJson(child, visit)\n }\n}\n\nfunction canonicalizeViewerConfig(value: unknown): void {\n if (Array.isArray(value)) {\n value.forEach(canonicalizeViewerConfig)\n return\n }\n if (!isRecord(value)) return\n if (value.isEuler === true) {\n if (typeof value.order !== 'string') value.order = 'XYZ'\n if (typeof value.x !== 'number') value.x = 0\n if (typeof value.y !== 'number') value.y = 0\n if (typeof value.z !== 'number') value.z = 0\n }\n if (value.autoAspect === true) {\n delete value.aspect\n if (isRecord(value.object)) delete value.object.aspect\n }\n Object.values(value).forEach(canonicalizeViewerConfig)\n}\n\nfunction extractBuffers(\n document: GltfDocument,\n sceneDirectory: string,\n baseName: string,\n files: SerializedSceneFile[],\n): void {\n const buffers = document.buffers\n if (!buffers?.length) return\n\n const embedded = buffers.map((buffer) => buffer.uri?.startsWith('data:') ? decodeDataUrl(buffer.uri).bytes : undefined)\n if (embedded.every((value) => value === undefined)) return\n if (embedded.some((value, index) => value === undefined && buffers[index]?.uri)) {\n throw new Error('A scene cannot combine embedded and external buffers during serialization')\n }\n\n let byteLength = 0\n const offsets = embedded.map((bytes) => {\n const offset = align4(byteLength)\n byteLength = offset + (bytes?.byteLength || 0)\n return offset\n })\n const combined = new Uint8Array(byteLength)\n embedded.forEach((bytes, index) => {\n if (bytes) combined.set(bytes, offsets[index])\n })\n\n for (const view of document.bufferViews || []) {\n const offset = offsets[view.buffer]\n if (offset === undefined) throw new Error(`Invalid glTF buffer index: ${view.buffer}`)\n view.byteOffset = (view.byteOffset || 0) + offset\n view.buffer = 0\n }\n\n const binName = `${baseName}.bin`\n document.buffers = [{byteLength: combined.byteLength, uri: binName}]\n files.push({path: joinProjectPath(sceneDirectory, binName), bytes: combined})\n}\n\nasync function extractImages(\n document: GltfDocument,\n sceneDirectory: string,\n files: SerializedSceneFile[],\n hashLength: number,\n): Promise<void> {\n const byPath = new Map<string, Uint8Array>()\n for (const image of document.images || []) {\n if (!image.uri?.startsWith('data:')) continue\n const decoded = decodeDataUrl(image.uri)\n const mimeType = decoded.mimeType || image.mimeType || 'application/octet-stream'\n const hash = await sha256(decoded.bytes)\n const path = `assets/textures/${hash.slice(0, hashLength)}.${imageExtension(mimeType)}`\n byPath.set(path, decoded.bytes)\n image.uri = relativeProjectPath(sceneDirectory, path)\n image.mimeType = mimeType\n }\n for (const [path, bytes] of byPath) files.push({path, bytes})\n}\n\nfunction decodeDataUrl(uri: string): {bytes: Uint8Array, mimeType?: string} {\n const match = /^data:([^;,]*)(;base64)?,(.*)$/s.exec(uri)\n if (!match) throw new Error('Invalid data URL in glTF')\n return {\n bytes: match[2] ? decodeBase64(match[3]) : encoder.encode(decodeURIComponent(match[3])),\n mimeType: match[1] || undefined,\n }\n}\n\nfunction decodeBase64(value: string): Uint8Array {\n const decoded = atob(value)\n return Uint8Array.from(decoded, (character) => character.charCodeAt(0))\n}\n\nasync function sha256(bytes: Uint8Array): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', bytes as BufferSource)\n return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, '0')).join('')\n}\n\nfunction imageExtension(mimeType: string): string {\n const normalized = mimeType.toLowerCase().split(';', 1)[0]\n const extensions: Record<string, string> = {\n 'image/avif': 'avif',\n 'image/gif': 'gif',\n 'image/jpeg': 'jpg',\n 'image/ktx2': 'ktx2',\n 'image/png': 'png',\n 'image/svg+xml': 'svg',\n 'image/webp': 'webp',\n }\n return extensions[normalized] || normalized.split('/')[1]?.replace(/[^a-z0-9.+-]/g, '') || 'bin'\n}\n\nfunction sortObjectKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortObjectKeys)\n if (!isRecord(value)) return value\n return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortObjectKeys(value[key])]))\n}\n\nfunction cloneJson<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction normalizeProjectPath(path: string): string {\n const normalized = path.replace(/\\\\/g, '/').replace(/^\\.\\//, '')\n if (!normalized || normalized.startsWith('/') || normalized.split('/').includes('..')) {\n throw new Error(`Invalid project path: ${path}`)\n }\n return normalized\n}\n\nfunction directoryName(path: string): string {\n const end = path.lastIndexOf('/')\n return end < 0 ? '' : path.slice(0, end)\n}\n\nfunction fileStem(path: string): string {\n const name = path.slice(path.lastIndexOf('/') + 1)\n const end = name.lastIndexOf('.')\n return end < 0 ? name : name.slice(0, end)\n}\n\nfunction joinProjectPath(directory: string, name: string): string {\n return directory ? `${directory}/${name}` : name\n}\n\nfunction relativeProjectPath(fromDirectory: string, target: string): string {\n const from = fromDirectory ? fromDirectory.split('/') : []\n const to = target.split('/')\n while (from.length && to.length && from[0] === to[0]) {\n from.shift()\n to.shift()\n }\n return [...from.map(() => '..'), ...to].join('/') || '.'\n}\n\nfunction align4(value: number): number {\n return (value + 3) & ~3\n}\n"],"names":["encoder","serializeSceneGltf","viewer","options","blob","isAuthoredSceneObject","serializeSceneGltfDocument","object","_a","_b","input","isRecord","document","cloneJson","removeVolatileViewerIds","removeUnreferencedUuids","scenePath","normalizeProjectPath","sceneDirectory","directoryName","files","extractBuffers","fileStem","extractImages","sorted","sortObjectKeys","left","right","value","canonicalizeViewerConfig","scene","camera","uses","visitJson","key","owner","visit","child","baseName","buffers","embedded","buffer","decodeDataUrl","index","byteLength","offsets","bytes","offset","align4","combined","view","binName","joinProjectPath","hashLength","byPath","image","decoded","mimeType","path","sha256","imageExtension","relativeProjectPath","uri","match","decodeBase64","character","digest","normalized","end","name","directory","fromDirectory","target","from","to"],"mappings":"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"}
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kite3d/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0-alpha.1",
|
|
4
4
|
"description": "The open Kite3D game runtime, project format, scripting helpers, and game plugins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"build": "vite build && vite build --config vite.runtime.config.ts",
|
|
61
61
|
"typecheck": "tsc --noEmit",
|
|
62
62
|
"lint": "eslint src --ext ts --report-unused-disable-directives --max-warnings 0",
|
|
63
|
-
"test": "vitest run test/unit",
|
|
64
|
-
"test:gates": "npm run build && playwright test persistence-gate.spec.ts
|
|
65
|
-
"test:runtime": "npm run build &&
|
|
63
|
+
"test": "vitest run test/unit --passWithNoTests",
|
|
64
|
+
"test:gates": "npm run build && playwright test persistence-gate.spec.ts",
|
|
65
|
+
"test:runtime": "npm run build && npm test && playwright test"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"cannon-es": "^0.20.0",
|
package/src/authoring.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type {IObject3D} from 'threepipe'
|
|
|
3
3
|
export const KITE3D_AUTHORING_METADATA_KEY = 'kite3dAuthoring'
|
|
4
4
|
export const KITE3D_RUNTIME_METADATA_KEY = 'kite3dRuntime'
|
|
5
5
|
|
|
6
|
-
export type AuthoringRole = 'direct' | 'template'
|
|
6
|
+
export type AuthoringRole = 'direct' | 'template'
|
|
7
7
|
|
|
8
8
|
export interface AuthoringMetadata {
|
|
9
9
|
role: AuthoringRole
|
|
@@ -40,7 +40,7 @@ interface TrackedRuntimeObject {
|
|
|
40
40
|
scene?: RuntimeScene
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const authoringRoles = new Set<AuthoringRole>(['direct', 'template'
|
|
43
|
+
const authoringRoles = new Set<AuthoringRole>(['direct', 'template'])
|
|
44
44
|
const trackedRuntimeObjects = new Map<IObject3D, TrackedRuntimeObject>()
|
|
45
45
|
|
|
46
46
|
/** Store a validated, serializable authored identity on an object. */
|
|
@@ -123,7 +123,7 @@ export class RuntimeObjectOwner {
|
|
|
123
123
|
cloneFrom<T extends IObject3D>(source: T, parent: IObject3D | undefined = this.runtimeParent, options: RuntimeCloneOptions = {}): T {
|
|
124
124
|
const sourceMetadata = getAuthoringMetadata(source)
|
|
125
125
|
if (!sourceMetadata || sourceMetadata.sourceId) {
|
|
126
|
-
throw new Error('Runtime clones require a direct
|
|
126
|
+
throw new Error('Runtime clones require a direct or template source with a stable authored id')
|
|
127
127
|
}
|
|
128
128
|
if (!parent) throw new Error('Runtime clones require a parent or an attached runtime root')
|
|
129
129
|
const scene = findTrackedScene(parent) || findRuntimeScene(source) || findRuntimeScene(parent)
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
|
|
19
19
|
export type AuthoringFailureCode =
|
|
20
20
|
| 'NO_VISIBLE_AUTHORED_CONTENT'
|
|
21
|
-
| 'GENERATOR_PREVIEW_MISSING'
|
|
22
21
|
| 'RUNTIME_OBJECT_AFTER_STOP'
|
|
23
22
|
| 'MISSING_AUTHORING_SOURCE'
|
|
24
23
|
| 'RUNTIME_SOURCE_DRIFT'
|
|
@@ -45,7 +44,6 @@ export interface AuthoringQualityReport {
|
|
|
45
44
|
visibleAuthoredContent: boolean
|
|
46
45
|
selectableAuthoredContent: boolean
|
|
47
46
|
relationshipsValid: boolean
|
|
48
|
-
generatorPreviews: boolean
|
|
49
47
|
cameraUseful: boolean
|
|
50
48
|
}
|
|
51
49
|
metrics: {
|
|
@@ -53,8 +51,6 @@ export interface AuthoringQualityReport {
|
|
|
53
51
|
renderableCount: number
|
|
54
52
|
visibleRenderableCount: number
|
|
55
53
|
selectableCount: number
|
|
56
|
-
generatorCount: number
|
|
57
|
-
generatorPreviewCount: number
|
|
58
54
|
cameraFramedRenderableCount: number
|
|
59
55
|
cameraInsideRenderableCount: number
|
|
60
56
|
}
|
|
@@ -99,7 +95,7 @@ interface SourceRecord {
|
|
|
99
95
|
metadata: AuthoringMetadata
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
/** Validate the stopped authoring hierarchy, its sources,
|
|
98
|
+
/** Validate the stopped authoring hierarchy, its sources, and saved camera. */
|
|
103
99
|
export function authoringQualityReport(viewer: ViewerLike): AuthoringQualityReport {
|
|
104
100
|
const scene = viewer.scene as SceneLike
|
|
105
101
|
if (!scene?.modelRoot) throw new Error('Authoring validation requires viewer.scene.modelRoot')
|
|
@@ -107,9 +103,6 @@ export function authoringQualityReport(viewer: ViewerLike): AuthoringQualityRepo
|
|
|
107
103
|
|
|
108
104
|
const issues: AuthoringValidationIssue[] = []
|
|
109
105
|
const sources = collectSources(scene.modelRoot)
|
|
110
|
-
const generators: SourceRecord[] = []
|
|
111
|
-
const previews: SourceRecord[] = []
|
|
112
|
-
const boundedPreviewSourceIds = new Set<string>()
|
|
113
106
|
const visibleBounds: Array<{object: IObject3D, bounds: Box3}> = []
|
|
114
107
|
let authoredObjectCount = 0
|
|
115
108
|
let renderableCount = 0
|
|
@@ -119,9 +112,6 @@ export function authoringQualityReport(viewer: ViewerLike): AuthoringQualityRepo
|
|
|
119
112
|
scene.modelRoot.traverse((object) => {
|
|
120
113
|
if (object === scene.modelRoot) return
|
|
121
114
|
authoredObjectCount += 1
|
|
122
|
-
const metadata = getAuthoringMetadata(object)
|
|
123
|
-
if (metadata?.role === 'generator' && !metadata.sourceId) generators.push({object, metadata})
|
|
124
|
-
if (metadata?.role === 'generator' && metadata.sourceId) previews.push({object, metadata})
|
|
125
115
|
if (!isRenderable(object)) return
|
|
126
116
|
renderableCount += 1
|
|
127
117
|
if (!isEffectivelyVisible(object, scene.modelRoot)) return
|
|
@@ -130,34 +120,11 @@ export function authoringQualityReport(viewer: ViewerLike): AuthoringQualityRepo
|
|
|
130
120
|
const bounds = new Box3().setFromObject(object)
|
|
131
121
|
if (!bounds.isEmpty() && finiteVector(bounds.min) && finiteVector(bounds.max)) {
|
|
132
122
|
visibleBounds.push({object, bounds})
|
|
133
|
-
if (metadata?.role === 'generator' && metadata.sourceId) boundedPreviewSourceIds.add(metadata.sourceId)
|
|
134
123
|
}
|
|
135
124
|
})
|
|
136
|
-
|
|
137
|
-
for (const preview of previews) {
|
|
138
|
-
const source = sources.get(preview.metadata.sourceId || '')
|
|
139
|
-
if (!source || source.metadata.role !== 'generator') {
|
|
140
|
-
pushIssue(issues, {
|
|
141
|
-
code: 'MISSING_AUTHORING_SOURCE',
|
|
142
|
-
severity: 'error',
|
|
143
|
-
message: `Generator output source "${preview.metadata.sourceId || '(missing)'}" is not present beneath modelRoot.`,
|
|
144
|
-
object: objectEvidence(preview.object),
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
for (const generator of generators) {
|
|
149
|
-
if (!boundedPreviewSourceIds.has(generator.metadata.id)) {
|
|
150
|
-
pushIssue(issues, {
|
|
151
|
-
code: 'GENERATOR_PREVIEW_MISSING',
|
|
152
|
-
severity: 'error',
|
|
153
|
-
message: 'An authored generator needs a bounded stopped-mode preview.',
|
|
154
|
-
object: objectEvidence(generator.object),
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
125
|
issues.push(...runtimeRelationshipIssues(scene, sources))
|
|
159
126
|
|
|
160
|
-
const emptyScene = authoredObjectCount === 0
|
|
127
|
+
const emptyScene = authoredObjectCount === 0
|
|
161
128
|
if (visibleRenderableCount === 0 || selectableCount === 0) {
|
|
162
129
|
issues.push({
|
|
163
130
|
code: 'NO_VISIBLE_AUTHORED_CONTENT',
|
|
@@ -178,7 +145,6 @@ export function authoringQualityReport(viewer: ViewerLike): AuthoringQualityRepo
|
|
|
178
145
|
const errors = issues.filter(({severity}) => severity === 'error')
|
|
179
146
|
const relationshipsValid = !errors.some(({code}) =>
|
|
180
147
|
code === 'MISSING_AUTHORING_SOURCE' || code === 'RUNTIME_SOURCE_DRIFT')
|
|
181
|
-
const generatorPreviews = !errors.some(({code}) => code === 'GENERATOR_PREVIEW_MISSING')
|
|
182
148
|
const ok = errors.length === 0
|
|
183
149
|
return {
|
|
184
150
|
ok,
|
|
@@ -189,7 +155,6 @@ export function authoringQualityReport(viewer: ViewerLike): AuthoringQualityRepo
|
|
|
189
155
|
visibleAuthoredContent: visibleRenderableCount > 0,
|
|
190
156
|
selectableAuthoredContent: selectableCount > 0,
|
|
191
157
|
relationshipsValid,
|
|
192
|
-
generatorPreviews,
|
|
193
158
|
cameraUseful: cameraResult.useful,
|
|
194
159
|
},
|
|
195
160
|
metrics: {
|
|
@@ -197,8 +162,6 @@ export function authoringQualityReport(viewer: ViewerLike): AuthoringQualityRepo
|
|
|
197
162
|
renderableCount,
|
|
198
163
|
visibleRenderableCount,
|
|
199
164
|
selectableCount,
|
|
200
|
-
generatorCount: generators.length,
|
|
201
|
-
generatorPreviewCount: boundedPreviewSourceIds.size,
|
|
202
165
|
cameraFramedRenderableCount: cameraResult.framed,
|
|
203
166
|
cameraInsideRenderableCount: cameraResult.inside,
|
|
204
167
|
},
|
|
@@ -415,7 +378,7 @@ function runtimeRelationshipIssues(scene: SceneLike, sources: Map<string, Source
|
|
|
415
378
|
return
|
|
416
379
|
}
|
|
417
380
|
const runtime = getRuntimeObjectMetadata(object)
|
|
418
|
-
if (!runtime || runtime.kind === 'effect'
|
|
381
|
+
if (!runtime || runtime.kind === 'effect') return
|
|
419
382
|
const overrides = new Set<RuntimeMutableProperty>(runtime.overrides || [])
|
|
420
383
|
if (JSON.stringify(runtimeSignature(source.object, overrides)) !== JSON.stringify(runtimeSignature(object, overrides))) {
|
|
421
384
|
pushIssue(issues, {
|
package/src/index.ts
CHANGED
|
@@ -7,9 +7,7 @@ export * from './paths.ts'
|
|
|
7
7
|
export * from './sceneSerialization.ts'
|
|
8
8
|
export * from './authoring.ts'
|
|
9
9
|
export * from './authoringValidation.ts'
|
|
10
|
-
export * from './generatorParams.ts'
|
|
11
10
|
export * from './plugins/HtmlUiComponent.ts'
|
|
12
|
-
export * from './plugins/GeneratorComponent.ts'
|
|
13
11
|
export * from './plugins/cannon/CannonPhysicsPlugin.ts'
|
|
14
12
|
export * from './plugins/cannon/Cannon3DBodyComponent.ts'
|
|
15
13
|
export * from './plugins/cannon/Cannon3DShapeComponent.ts'
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
import {MeshoptDecoder} from 'meshoptimizer'
|
|
19
19
|
import {registerScripts} from '../scripts.ts'
|
|
20
20
|
import {HtmlUiComponent} from '../plugins/HtmlUiComponent.ts'
|
|
21
|
-
import {GeneratorComponent} from '../plugins/GeneratorComponent.ts'
|
|
22
21
|
import {CannonPhysicsPlugin} from '../plugins/cannon/CannonPhysicsPlugin.ts'
|
|
23
22
|
import {
|
|
24
23
|
installGameHooks,
|
|
@@ -73,7 +72,7 @@ export function createGame(options: CreateGameOptions): Promise<CreatedGame> {
|
|
|
73
72
|
return createProjectGame(options, true)
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
/** Load the saved project
|
|
75
|
+
/** Load the saved project without starting components, physics, the timeline, or main.js. */
|
|
77
76
|
export function createStoppedGame(options: CreateGameOptions): Promise<CreatedGame> {
|
|
78
77
|
return createProjectGame(options, false)
|
|
79
78
|
}
|
|
@@ -137,8 +136,6 @@ async function createProjectGame({
|
|
|
137
136
|
viewer.timeline.endTime = 0
|
|
138
137
|
gameHooks = installGameHooks()
|
|
139
138
|
entityComponents.addComponentType(HtmlUiComponent)
|
|
140
|
-
entityComponents.addComponentType(GeneratorComponent)
|
|
141
|
-
GeneratorComponent.configureViewer(viewer, {base: baseUrl, onError: reportError})
|
|
142
139
|
|
|
143
140
|
// Three's LoadingManager delegates through this importer hook. It covers
|
|
144
141
|
// glTF buffers/textures and nested imports without patching global fetch.
|
|
@@ -158,7 +155,6 @@ async function createProjectGame({
|
|
|
158
155
|
}
|
|
159
156
|
await nestedAssets.loadObjectDependencies(loadedScene as IObject3D)
|
|
160
157
|
await nestedAssets.waitForPending()
|
|
161
|
-
await GeneratorComponent.waitForViewer(viewer)
|
|
162
158
|
|
|
163
159
|
if (start) {
|
|
164
160
|
viewer.timeline.reset()
|
package/src/runtime/index.ts
CHANGED
|
@@ -6,8 +6,6 @@ export * from './migrations.ts'
|
|
|
6
6
|
export {RuntimeNestedAssetLoader} from './nestedAssets.ts'
|
|
7
7
|
export {registerScripts, walkScriptExports} from '../scripts.ts'
|
|
8
8
|
export {HtmlUiComponent} from '../plugins/HtmlUiComponent.ts'
|
|
9
|
-
export {GeneratorComponent, markGenerated, removeGeneratedChildren, resolveGeneratorModule, runGenerator} from '../plugins/GeneratorComponent.ts'
|
|
10
|
-
export type {GeneratorContext, GeneratorModule, GeneratorParams, GeneratorViewerOptions, RunGeneratorOptions} from '../plugins/GeneratorComponent.ts'
|
|
11
9
|
export {CannonPhysicsPlugin} from '../plugins/cannon/CannonPhysicsPlugin.ts'
|
|
12
10
|
export {serializeSceneGltf, serializeSceneGltfDocument} from '../sceneSerialization.ts'
|
|
13
11
|
export type {SerializeSceneGltfOptions, SerializedSceneFile, SerializedSceneGltf} from '../sceneSerialization.ts'
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
import {parse, ParseError} from 'jsonc-parser'
|
|
2
|
-
import type {GeneratorParamSchema} from '../generatorParams.ts'
|
|
3
2
|
|
|
4
3
|
export const settingsKey = 'kite3d'
|
|
5
4
|
export const assetUrlPrefix = `/${settingsKey}/`
|
|
5
|
+
export const REMOVED_GENERATOR_GUIDANCE = 'Generator components were removed in Kite3D 0.19.0.'
|
|
6
6
|
|
|
7
7
|
export type JSONValue = string | number | boolean | null | JSONValue[] | {[key: string]: JSONValue}
|
|
8
8
|
export type ProjectPackageJSON = Record<string, JSONValue> & {mainScene: string}
|
|
9
9
|
|
|
10
|
-
export interface ProjectGeneratorState {
|
|
11
|
-
componentId: string
|
|
12
|
-
module: string
|
|
13
|
-
nodeIndex: number
|
|
14
|
-
nodeName: string
|
|
15
|
-
params: Record<string, unknown>
|
|
16
|
-
schema: GeneratorParamSchema
|
|
17
|
-
}
|
|
18
|
-
|
|
19
10
|
export interface AssetsJSONManifest {
|
|
20
11
|
files: Record<string, {
|
|
21
12
|
path: string
|
|
@@ -149,46 +140,28 @@ export function validateSceneSource(path: string, text: string): void {
|
|
|
149
140
|
}
|
|
150
141
|
}
|
|
151
142
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
nodeName: typeof node.name === 'string' ? node.name : `Node ${nodeIndex}`,
|
|
169
|
-
params: isRecord(component.state.params) ? component.state.params : {},
|
|
170
|
-
schema: {},
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return generators
|
|
175
|
-
} catch {
|
|
176
|
-
return []
|
|
143
|
+
/** Find nodes that still carry the component removed in Kite3D 0.19.0. */
|
|
144
|
+
export function findRemovedGeneratorNodes(text: string): Array<{nodeIndex: number, nodeName: string}> {
|
|
145
|
+
const document = JSON.parse(text) as {
|
|
146
|
+
nodes?: Array<{
|
|
147
|
+
name?: unknown
|
|
148
|
+
extras?: {EntityComponentPlugin?: Record<string, {type?: unknown}>}
|
|
149
|
+
}>
|
|
150
|
+
}
|
|
151
|
+
const nodes: Array<{nodeIndex: number, nodeName: string}> = []
|
|
152
|
+
for (const [nodeIndex, node] of (document.nodes || []).entries()) {
|
|
153
|
+
const components = Object.values(node.extras?.EntityComponentPlugin || {})
|
|
154
|
+
if (!components.some(({type}) => type === 'Generator')) continue
|
|
155
|
+
nodes.push({
|
|
156
|
+
nodeIndex,
|
|
157
|
+
nodeName: typeof node.name === 'string' && node.name.trim() ? node.name : `Node ${nodeIndex}`,
|
|
158
|
+
})
|
|
177
159
|
}
|
|
160
|
+
return nodes
|
|
178
161
|
}
|
|
179
162
|
|
|
180
|
-
export function
|
|
181
|
-
|
|
182
|
-
generator: Pick<ProjectGeneratorState, 'componentId' | 'nodeIndex' | 'nodeName'>,
|
|
183
|
-
update: Partial<Pick<ProjectGeneratorState, 'module' | 'params'>>,
|
|
184
|
-
): {text: string, state: Record<string, unknown>} {
|
|
185
|
-
const document = JSON.parse(text) as {
|
|
186
|
-
nodes?: Array<{extras?: {EntityComponentPlugin?: Record<string, {state?: unknown}>}}>
|
|
187
|
-
}
|
|
188
|
-
const state = document.nodes?.[generator.nodeIndex]?.extras?.EntityComponentPlugin?.[generator.componentId]?.state
|
|
189
|
-
if (!isRecord(state)) throw new Error(`Generator component is missing on ${generator.nodeName}`)
|
|
190
|
-
Object.assign(state, update)
|
|
191
|
-
return {text: JSON.stringify(document, null, 2), state}
|
|
163
|
+
export function removedGeneratorMessage(nodeName: string): string {
|
|
164
|
+
return `${REMOVED_GENERATOR_GUIDANCE} Convert ${nodeName} to asset files. See the guide's Scene asset management section.`
|
|
192
165
|
}
|
|
193
166
|
|
|
194
167
|
export async function parsePackageJsonSettingsConfig(json: ProjectPackageJSON, _project?: unknown): Promise<ProjectConfigSettings> {
|