@kite3d/engine 0.17.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.
@@ -1,61 +1,66 @@
1
1
  const l = new TextEncoder();
2
2
  async function N(e, t = {}) {
3
- const n = await e.exportScene({
3
+ const r = await e.exportScene({
4
4
  exportExt: "gltf",
5
5
  preserveUUIDs: !0,
6
6
  viewerConfig: !0,
7
7
  embedUrlImages: !1,
8
- onlyVisible: !0,
8
+ onlyVisible: !1,
9
+ shouldExportObject: x,
9
10
  jsonSpaces: 2
10
11
  }, !1);
11
- if (!n) throw new Error("The scene exporter returned no glTF data");
12
- return x(JSON.parse(await n.text()), t);
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
- async function x(e, t = {}) {
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 n = U(e);
18
- u(n), j(n);
19
- const r = T(t.scenePath || "assets/main.scene.gltf"), s = z(r), f = [];
20
- E(n, s, P(r), f), await A(n, s, f, 16);
21
- const i = y(n);
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, m) => d.path.localeCompare(m.path))
31
+ files: f.sort((d, u) => d.path.localeCompare(u.path))
27
32
  };
28
33
  }
29
- function u(e) {
34
+ function g(e) {
30
35
  if (Array.isArray(e)) {
31
- e.forEach(u);
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, n = a(t) ? t.defaultCamera : void 0, r = a(n) ? n.object : void 0;
38
- a(r) && delete r.uuid;
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(u);
45
+ Object.values(e).forEach(g);
41
46
  }
42
47
  }
43
48
  function j(e) {
44
49
  const t = /* @__PURE__ */ new Map();
45
- p(e, (n, r) => {
46
- t.set(n, (t.get(n) || 0) + 1), typeof r == "string" && t.set(r, (t.get(r) || 0) + 1);
47
- }), p(e, (n, r, s) => {
48
- n === "uuid" && typeof r == "string" && t.get(r) === 1 && delete s[n];
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 p(e, t) {
56
+ function m(e, t) {
52
57
  if (Array.isArray(e)) {
53
- e.forEach((n) => p(n, t));
58
+ e.forEach((r) => m(r, t));
54
59
  return;
55
60
  }
56
61
  if (a(e))
57
- for (const [n, r] of Object.entries(e))
58
- t(n, r, e), p(r, 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 E(e, t, n, r) {
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
- }), m = new Uint8Array(i);
89
+ }), u = new Uint8Array(i);
85
90
  f.forEach((o, c) => {
86
- o && m.set(o, d[c]);
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 g = `${n}.bin`;
94
- e.buffers = [{ byteLength: m.byteLength, uri: g }], r.push({ path: $(t, g), bytes: m });
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 A(e, t, n, r) {
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), m = d.mimeType || i.mimeType || "application/octet-stream", o = `assets/textures/${(await I(d.bytes)).slice(0, r)}.${S(m)}`;
102
- s.set(o, d.bytes), i.uri = C(t, o), i.mimeType = m;
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) n.push({ path: i, bytes: d });
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] ? O(t[3]) : l.encode(decodeURIComponent(t[3])),
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 O(e) {
119
+ function I(e) {
115
120
  const t = atob(e);
116
- return Uint8Array.from(t, (n) => n.charCodeAt(0));
121
+ return Uint8Array.from(t, (r) => r.charCodeAt(0));
117
122
  }
118
- async function I(e) {
123
+ async function S(e) {
119
124
  const t = await crypto.subtle.digest("SHA-256", e);
120
- return Array.from(new Uint8Array(t), (n) => n.toString(16).padStart(2, "0")).join("");
125
+ return Array.from(new Uint8Array(t), (r) => r.toString(16).padStart(2, "0")).join("");
121
126
  }
122
- function S(e) {
123
- var r;
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] || ((r = t.split("/")[1]) == null ? void 0 : r.replace(/[^a-z0-9.+-]/g, "")) || "bin";
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 T(e) {
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 z(e) {
155
+ function P(e) {
151
156
  const t = e.lastIndexOf("/");
152
157
  return t < 0 ? "" : e.slice(0, t);
153
158
  }
154
- function P(e) {
155
- const t = e.slice(e.lastIndexOf("/") + 1), n = t.lastIndexOf(".");
156
- return n < 0 ? t : t.slice(0, n);
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 $(e, t) {
163
+ function W(e, t) {
159
164
  return e ? `${e}/${t}` : t;
160
165
  }
161
166
  function C(e, t) {
162
- const n = e ? e.split("/") : [], r = t.split("/");
163
- for (; n.length && r.length && n[0] === r[0]; )
164
- n.shift(), r.shift();
165
- return [...n.map(() => ".."), ...r].join("/") || ".";
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
- x as serializeSceneGltfDocument
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
@@ -1,4 +1,4 @@
1
- const n = "0.17.0", e = {
1
+ const n = "0.19.0-alpha.1", e = {
2
2
  version: n
3
3
  }, o = e.version;
4
4
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kite3d/engine",
3
- "version": "0.17.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 startup-failures.spec.ts release-gates.spec.ts",
65
- "test:runtime": "npm run build && vitest run test/unit && playwright test"
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' | 'generator'
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', 'generator'])
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, template, or generator source with a stable authored id')
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, previews, and saved camera. */
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 && generators.length === 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' || source.metadata.role === 'generator') return
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
@@ -8,7 +8,6 @@ export * from './sceneSerialization.ts'
8
8
  export * from './authoring.ts'
9
9
  export * from './authoringValidation.ts'
10
10
  export * from './plugins/HtmlUiComponent.ts'
11
- export * from './plugins/GeneratorComponent.ts'
12
11
  export * from './plugins/cannon/CannonPhysicsPlugin.ts'
13
12
  export * from './plugins/cannon/Cannon3DBodyComponent.ts'
14
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 and generator previews without starting components, physics, the timeline, or main.js. */
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()
@@ -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'
@@ -2,18 +2,11 @@ import {parse, ParseError} from 'jsonc-parser'
2
2
 
3
3
  export const settingsKey = 'kite3d'
4
4
  export const assetUrlPrefix = `/${settingsKey}/`
5
+ export const REMOVED_GENERATOR_GUIDANCE = 'Generator components were removed in Kite3D 0.19.0.'
5
6
 
6
7
  export type JSONValue = string | number | boolean | null | JSONValue[] | {[key: string]: JSONValue}
7
8
  export type ProjectPackageJSON = Record<string, JSONValue> & {mainScene: string}
8
9
 
9
- export interface ProjectGeneratorState {
10
- componentId: string
11
- module: string
12
- nodeIndex: number
13
- nodeName: string
14
- params: Record<string, unknown>
15
- }
16
-
17
10
  export interface AssetsJSONManifest {
18
11
  files: Record<string, {
19
12
  path: string
@@ -147,45 +140,28 @@ export function validateSceneSource(path: string, text: string): void {
147
140
  }
148
141
  }
149
142
 
150
- export function readProjectGeneratorStates(text: string): ProjectGeneratorState[] {
151
- try {
152
- const document = JSON.parse(text) as {
153
- nodes?: Array<{
154
- name?: unknown
155
- extras?: {EntityComponentPlugin?: Record<string, {type?: unknown, state?: unknown}>}
156
- }>
157
- }
158
- const generators: ProjectGeneratorState[] = []
159
- for (const [nodeIndex, node] of (document.nodes || []).entries()) {
160
- for (const [componentId, component] of Object.entries(node.extras?.EntityComponentPlugin || {})) {
161
- if (component.type !== 'Generator' || !isRecord(component.state)) continue
162
- generators.push({
163
- componentId,
164
- module: typeof component.state.module === 'string' ? component.state.module : '',
165
- nodeIndex,
166
- nodeName: typeof node.name === 'string' ? node.name : `Node ${nodeIndex}`,
167
- params: isRecord(component.state.params) ? component.state.params : {},
168
- })
169
- }
170
- }
171
- return generators
172
- } catch {
173
- 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
+ })
174
159
  }
160
+ return nodes
175
161
  }
176
162
 
177
- export function updateProjectGeneratorState(
178
- text: string,
179
- generator: Pick<ProjectGeneratorState, 'componentId' | 'nodeIndex' | 'nodeName'>,
180
- update: Partial<Pick<ProjectGeneratorState, 'module' | 'params'>>,
181
- ): {text: string, state: Record<string, unknown>} {
182
- const document = JSON.parse(text) as {
183
- nodes?: Array<{extras?: {EntityComponentPlugin?: Record<string, {state?: unknown}>}}>
184
- }
185
- const state = document.nodes?.[generator.nodeIndex]?.extras?.EntityComponentPlugin?.[generator.componentId]?.state
186
- if (!isRecord(state)) throw new Error(`Generator component is missing on ${generator.nodeName}`)
187
- Object.assign(state, update)
188
- 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.`
189
165
  }
190
166
 
191
167
  export async function parsePackageJsonSettingsConfig(json: ProjectPackageJSON, _project?: unknown): Promise<ProjectConfigSettings> {