retiam 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +213 -209
- package/bin/export.js +322 -322
- package/bin/init.js +425 -425
- package/bin/lint.js +354 -354
- package/bin/render-config.js +86 -0
- package/bin/render.js +517 -600
- package/bin/snapshot.js +231 -231
- package/dist/{UTIF-pBEih-vH.js → UTIF-X7q9996p.js} +1 -1
- package/dist/assets/roboto-msdf.json +3642 -3642
- package/dist/{index-CnM0BhUR.js → index-Be4c9A99.js} +9988 -9941
- package/dist/{main-Bw2LTrnR.js → main-IC5HXR90.js} +1 -1
- package/dist/retiam.es.js +739 -734
- package/dist/src/engine/_core/Material.d.ts +13 -18
- package/dist/src/engine/_core/Scene.d.ts +3 -0
- package/dist/src/engine/_core/SnapshotManager.d.ts +3 -0
- package/dist/src/engine/components/fragment/ModelFragment.d.ts +1 -6
- package/dist/src/engine/material/shading/CelShading.d.ts +16 -0
- package/dist/src/engine/material/shading/ClayShading.d.ts +6 -0
- package/dist/src/engine/material/shading/GoochShading.d.ts +14 -0
- package/dist/src/engine/material/shading/PBRShading.d.ts +6 -0
- package/dist/src/engine/material/shading/Shading.d.ts +17 -0
- package/dist/src/engine/material/shading/index.d.ts +5 -0
- package/dist/src/engine/render/RenderConfig.d.ts +1 -23
- package/dist/src/index.d.ts +1 -0
- package/package.json +107 -116
- package/readme.md +437 -143
package/bin/export.js
CHANGED
|
@@ -1,323 +1,323 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// tools/builder/build-embed.js
|
|
4
|
-
import { build } from 'vite';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
|
|
8
|
-
const rootDir = process.cwd();
|
|
9
|
-
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
const isHelp = args.includes('--help') || args.includes('-h') || args.includes('help');
|
|
12
|
-
if (isHelp) {
|
|
13
|
-
console.log(`
|
|
14
|
-
📦 Retiam WebGPU Engine - Web Embed Bundle Exporter
|
|
15
|
-
|
|
16
|
-
Usage:
|
|
17
|
-
npx retiam export [file] [outputDir] [options]
|
|
18
|
-
|
|
19
|
-
Arguments:
|
|
20
|
-
[file] Source scene file to bundle (default: src/main.ts)
|
|
21
|
-
[outputDir] Target distribution folder (default: outputs/embed)
|
|
22
|
-
|
|
23
|
-
Options:
|
|
24
|
-
-o, --out <dir> Target distribution folder (default: outputs/embed)
|
|
25
|
-
--help, -h Show this help message and exit
|
|
26
|
-
|
|
27
|
-
Description:
|
|
28
|
-
Compiles the scene and Retiam engine into a self-contained, standalone
|
|
29
|
-
JavaScript web component (<retiam-scene>) for embedding into web pages,
|
|
30
|
-
blogs, and documentation with zero external dependencies.
|
|
31
|
-
|
|
32
|
-
Examples:
|
|
33
|
-
npx retiam export src/scenes/AStarScene.ts
|
|
34
|
-
npx retiam export src/scenes/AStarScene.ts ./outputs/embed
|
|
35
|
-
npx retiam export src/scenes/AStarScene.ts -o ./public/embeds
|
|
36
|
-
`);
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let targetOutDir = 'outputs/embed';
|
|
41
|
-
let inputPath = null;
|
|
42
|
-
|
|
43
|
-
for (let i = 0; i < args.length; i++) {
|
|
44
|
-
if (args[i] === '-o' || args[i] === '--out') {
|
|
45
|
-
targetOutDir = args[++i];
|
|
46
|
-
} else if (!args[i].startsWith('-')) {
|
|
47
|
-
if (!inputPath) {
|
|
48
|
-
inputPath = args[i];
|
|
49
|
-
} else {
|
|
50
|
-
targetOutDir = args[i];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
inputPath = inputPath || 'src/main.ts';
|
|
56
|
-
const fullInputPath = path.resolve(rootDir, inputPath);
|
|
57
|
-
|
|
58
|
-
if (!fs.existsSync(fullInputPath)) {
|
|
59
|
-
console.error(`❌ Error: Scene file not found at "${inputPath}"`);
|
|
60
|
-
console.log('Run "npx retiam export --help" for more details.');
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const sceneName = path.basename(inputPath, path.extname(inputPath)).toLowerCase();
|
|
65
|
-
const outputFile = `${sceneName}.js`;
|
|
66
|
-
|
|
67
|
-
const tempEntryPath = path.resolve(rootDir, 'src/_temp_embed_entry.ts');
|
|
68
|
-
let rel = path.relative(path.dirname(tempEntryPath), fullInputPath).replace(/\\/g, '/');
|
|
69
|
-
if (!rel.startsWith('.')) rel = './' + rel;
|
|
70
|
-
const relativeScenePath = rel;
|
|
71
|
-
|
|
72
|
-
const entryCode = `
|
|
73
|
-
import { Scene } from "./engine/_core/Scene";
|
|
74
|
-
import * as SceneModule from "${relativeScenePath}";
|
|
75
|
-
|
|
76
|
-
export class RetiamElement extends HTMLElement {
|
|
77
|
-
private canvas;
|
|
78
|
-
public sceneInstance;
|
|
79
|
-
private resizeObserver;
|
|
80
|
-
|
|
81
|
-
async connectedCallback() {
|
|
82
|
-
if (!navigator.gpu) {
|
|
83
|
-
this.innerHTML = '<div style="color:red;padding:20px;">WebGPU not supported</div>';
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Base styles if not specified
|
|
88
|
-
if (!this.style.display) this.style.display = "block";
|
|
89
|
-
if (!this.style.position) this.style.position = "relative";
|
|
90
|
-
|
|
91
|
-
this.canvas = document.createElement("canvas");
|
|
92
|
-
this.canvas.style.display = "block";
|
|
93
|
-
this.canvas.style.width = "100%";
|
|
94
|
-
this.canvas.style.height = "100%";
|
|
95
|
-
this.appendChild(this.canvas);
|
|
96
|
-
|
|
97
|
-
const updateCanvasSize = () => {
|
|
98
|
-
if (!this.canvas) return;
|
|
99
|
-
const rect = this.getBoundingClientRect();
|
|
100
|
-
const dpr = window.devicePixelRatio || 1;
|
|
101
|
-
const w = Math.floor((rect.width || 800) * dpr);
|
|
102
|
-
const h = Math.floor((rect.height || 450) * dpr);
|
|
103
|
-
if (w > 0 && h > 0 && (this.canvas.width !== w || this.canvas.height !== h)) {
|
|
104
|
-
this.canvas.width = w;
|
|
105
|
-
this.canvas.height = h;
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
updateCanvasSize();
|
|
110
|
-
|
|
111
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
112
|
-
updateCanvasSize();
|
|
113
|
-
});
|
|
114
|
-
this.resizeObserver.observe(this);
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
// 1. Search exports for an actual Scene class (e.g., SubnetRouteGame)
|
|
118
|
-
let SceneClass = null;
|
|
119
|
-
for (const key of Object.keys(SceneModule)) {
|
|
120
|
-
const item = (SceneModule as any)[key];
|
|
121
|
-
if (item && item.prototype && item.prototype instanceof Scene) {
|
|
122
|
-
SceneClass = item;
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (SceneClass) {
|
|
128
|
-
// Instantiate class directly with the component's canvas
|
|
129
|
-
this.sceneInstance = new (SceneClass as any)();
|
|
130
|
-
await this.sceneInstance.start(this.canvas);
|
|
131
|
-
} else {
|
|
132
|
-
// 2. Fallback: If no Scene class was exported, pass canvas to default export
|
|
133
|
-
const defaultFn = (SceneModule as any).default;
|
|
134
|
-
if (typeof defaultFn === 'function') {
|
|
135
|
-
const origGetElementById = document.getElementById.bind(document);
|
|
136
|
-
const compCanvas = this.canvas;
|
|
137
|
-
const origStart = Scene.prototype.start;
|
|
138
|
-
const self = this;
|
|
139
|
-
|
|
140
|
-
// Intercept Scene.prototype.start to capture the instantiated Scene
|
|
141
|
-
Scene.prototype.start = async function(...args: any[]) {
|
|
142
|
-
self.sceneInstance = this;
|
|
143
|
-
return await origStart.apply(this, args);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
document.getElementById = function(id) {
|
|
147
|
-
if (id === 'canvas') return compCanvas;
|
|
148
|
-
return origGetElementById(id);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
await defaultFn(this.canvas);
|
|
153
|
-
} finally {
|
|
154
|
-
document.getElementById = origGetElementById;
|
|
155
|
-
Scene.prototype.start = origStart;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (this.sceneInstance) {
|
|
161
|
-
(window as any).retiamScene = this.sceneInstance;
|
|
162
|
-
(window as any).retiamElement = this;
|
|
163
|
-
this.dispatchEvent(new CustomEvent("retiam-ready", {
|
|
164
|
-
detail: { scene: this.sceneInstance, element: this },
|
|
165
|
-
bubbles: true
|
|
166
|
-
}));
|
|
167
|
-
this.dispatchEvent(new CustomEvent("retiam-scene-ready", {
|
|
168
|
-
detail: { scene: this.sceneInstance, element: this },
|
|
169
|
-
bubbles: true
|
|
170
|
-
}));
|
|
171
|
-
}
|
|
172
|
-
} catch (err) {
|
|
173
|
-
console.error("[Retiam Embed] Scene execution error:", err);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
disconnectedCallback() {
|
|
178
|
-
if (this.resizeObserver) {
|
|
179
|
-
this.resizeObserver.disconnect();
|
|
180
|
-
this.resizeObserver = null;
|
|
181
|
-
}
|
|
182
|
-
if (this.sceneInstance) {
|
|
183
|
-
this.sceneInstance.destroy();
|
|
184
|
-
this.sceneInstance = null;
|
|
185
|
-
}
|
|
186
|
-
if ((window as any).retiamScene === this.sceneInstance) {
|
|
187
|
-
(window as any).retiamScene = null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export const RetiamSceneElement = RetiamElement;
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
if (!customElements.get("retiam")) {
|
|
196
|
-
customElements.define("retiam", RetiamElement);
|
|
197
|
-
}
|
|
198
|
-
} catch (e) {}
|
|
199
|
-
|
|
200
|
-
if (!customElements.get("retiam-scene")) {
|
|
201
|
-
customElements.define("retiam-scene", RetiamElement);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function autoMountRetiamTags() {
|
|
205
|
-
const nodes = document.querySelectorAll("retiam, Retiam");
|
|
206
|
-
nodes.forEach(node => {
|
|
207
|
-
if (!(node as any).__retiamMounted) {
|
|
208
|
-
(node as any).__retiamMounted = true;
|
|
209
|
-
const elem = document.createElement("retiam-scene");
|
|
210
|
-
for (let i = 0; i < node.attributes.length; i++) {
|
|
211
|
-
const attr = node.attributes[i];
|
|
212
|
-
elem.setAttribute(attr.name, attr.value);
|
|
213
|
-
}
|
|
214
|
-
node.replaceWith(elem);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (typeof document !== "undefined") {
|
|
220
|
-
const styleId = "retiam-embed-default-style";
|
|
221
|
-
if (!document.getElementById(styleId)) {
|
|
222
|
-
const style = document.createElement("style");
|
|
223
|
-
style.id = styleId;
|
|
224
|
-
style.textContent = "retiam, retiam-scene { display: block; position: relative; }";
|
|
225
|
-
document.head?.appendChild(style);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (document.readyState === "loading") {
|
|
229
|
-
document.addEventListener("DOMContentLoaded", autoMountRetiamTags);
|
|
230
|
-
} else {
|
|
231
|
-
autoMountRetiamTags();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
const obs = new MutationObserver(() => autoMountRetiamTags());
|
|
236
|
-
obs.observe(document.documentElement || document.body, { childList: true, subtree: true });
|
|
237
|
-
} catch (e) {}
|
|
238
|
-
}
|
|
239
|
-
`;
|
|
240
|
-
|
|
241
|
-
fs.writeFileSync(tempEntryPath, entryCode);
|
|
242
|
-
|
|
243
|
-
function minifyWGSL(code) {
|
|
244
|
-
return code
|
|
245
|
-
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
|
|
246
|
-
.replace(/\/\/.*/g, '') // Remove line comments
|
|
247
|
-
.split('\n') // Process line by line
|
|
248
|
-
.map(line => line.trim()) // Trim whitespace per line
|
|
249
|
-
.filter(line => line.length > 0) // Remove empty lines
|
|
250
|
-
.join('\n'); // Preserve line breaks for WebGPU debugging
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const wgslPlugin = () => ({
|
|
254
|
-
name: 'wgsl-loader',
|
|
255
|
-
transform(code, id) {
|
|
256
|
-
if (id.endsWith('.wgsl')) {
|
|
257
|
-
const minified = minifyWGSL(code);
|
|
258
|
-
return { code: `export default ${JSON.stringify(minified)};`, map: null };
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const tzaPlugin = () => ({
|
|
264
|
-
name: 'tza-loader',
|
|
265
|
-
enforce: 'pre',
|
|
266
|
-
load(id) {
|
|
267
|
-
if (/\.(tza|ttf|png)$/i.test(id) || id.endsWith('.json?url')) {
|
|
268
|
-
const filePath = id.replace(/\?url$/, '');
|
|
269
|
-
const source = fs.readFileSync(filePath);
|
|
270
|
-
const referenceId = this.emitFile({
|
|
271
|
-
type: 'asset',
|
|
272
|
-
fileName: `assets/${path.basename(filePath)}`,
|
|
273
|
-
source: source
|
|
274
|
-
});
|
|
275
|
-
return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
|
|
276
|
-
}
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
console.log(`🚀 Building standalone embed for: ${inputPath} -> ${targetOutDir}/${outputFile}`);
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
await build({
|
|
285
|
-
configFile: false,
|
|
286
|
-
publicDir: false,
|
|
287
|
-
assetsInclude: ['**/*.tza', '**/*.ttf', '**/*.png'],
|
|
288
|
-
plugins: [tzaPlugin(), wgslPlugin()],
|
|
289
|
-
resolve: {
|
|
290
|
-
alias: [
|
|
291
|
-
{ find: '../../index', replacement: path.resolve(rootDir, 'src/engine/_core/Constants.ts') },
|
|
292
|
-
{ find: '../index', replacement: path.resolve(rootDir, 'src/engine/_core/Constants.ts') }
|
|
293
|
-
]
|
|
294
|
-
},
|
|
295
|
-
build: {
|
|
296
|
-
outDir: targetOutDir,
|
|
297
|
-
emptyOutDir: false,
|
|
298
|
-
lib: {
|
|
299
|
-
entry: tempEntryPath,
|
|
300
|
-
name: 'RetiamEmbed',
|
|
301
|
-
fileName: () => outputFile,
|
|
302
|
-
formats: ['iife']
|
|
303
|
-
},
|
|
304
|
-
rollupOptions: {
|
|
305
|
-
output: { inlineDynamicImports: true }
|
|
306
|
-
},
|
|
307
|
-
minify: 'esbuild'
|
|
308
|
-
},
|
|
309
|
-
esbuild: {
|
|
310
|
-
drop: ['debugger'],
|
|
311
|
-
legalComments: 'none',
|
|
312
|
-
treeShaking: true
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
console.log(`✅ Build successful! Output: ${targetOutDir}/${outputFile}`);
|
|
317
|
-
} catch (e) {
|
|
318
|
-
console.error("❌ Build failed:", e);
|
|
319
|
-
} finally {
|
|
320
|
-
if (fs.existsSync(tempEntryPath)) {
|
|
321
|
-
fs.unlinkSync(tempEntryPath);
|
|
322
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// tools/builder/build-embed.js
|
|
4
|
+
import { build } from 'vite';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
|
|
8
|
+
const rootDir = process.cwd();
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const isHelp = args.includes('--help') || args.includes('-h') || args.includes('help');
|
|
12
|
+
if (isHelp) {
|
|
13
|
+
console.log(`
|
|
14
|
+
📦 Retiam WebGPU Engine - Web Embed Bundle Exporter
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
npx retiam export [file] [outputDir] [options]
|
|
18
|
+
|
|
19
|
+
Arguments:
|
|
20
|
+
[file] Source scene file to bundle (default: src/main.ts)
|
|
21
|
+
[outputDir] Target distribution folder (default: outputs/embed)
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
-o, --out <dir> Target distribution folder (default: outputs/embed)
|
|
25
|
+
--help, -h Show this help message and exit
|
|
26
|
+
|
|
27
|
+
Description:
|
|
28
|
+
Compiles the scene and Retiam engine into a self-contained, standalone
|
|
29
|
+
JavaScript web component (<retiam-scene>) for embedding into web pages,
|
|
30
|
+
blogs, and documentation with zero external dependencies.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
npx retiam export src/scenes/AStarScene.ts
|
|
34
|
+
npx retiam export src/scenes/AStarScene.ts ./outputs/embed
|
|
35
|
+
npx retiam export src/scenes/AStarScene.ts -o ./public/embeds
|
|
36
|
+
`);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let targetOutDir = 'outputs/embed';
|
|
41
|
+
let inputPath = null;
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < args.length; i++) {
|
|
44
|
+
if (args[i] === '-o' || args[i] === '--out') {
|
|
45
|
+
targetOutDir = args[++i];
|
|
46
|
+
} else if (!args[i].startsWith('-')) {
|
|
47
|
+
if (!inputPath) {
|
|
48
|
+
inputPath = args[i];
|
|
49
|
+
} else {
|
|
50
|
+
targetOutDir = args[i];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
inputPath = inputPath || 'src/main.ts';
|
|
56
|
+
const fullInputPath = path.resolve(rootDir, inputPath);
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(fullInputPath)) {
|
|
59
|
+
console.error(`❌ Error: Scene file not found at "${inputPath}"`);
|
|
60
|
+
console.log('Run "npx retiam export --help" for more details.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const sceneName = path.basename(inputPath, path.extname(inputPath)).toLowerCase();
|
|
65
|
+
const outputFile = `${sceneName}.js`;
|
|
66
|
+
|
|
67
|
+
const tempEntryPath = path.resolve(rootDir, 'src/_temp_embed_entry.ts');
|
|
68
|
+
let rel = path.relative(path.dirname(tempEntryPath), fullInputPath).replace(/\\/g, '/');
|
|
69
|
+
if (!rel.startsWith('.')) rel = './' + rel;
|
|
70
|
+
const relativeScenePath = rel;
|
|
71
|
+
|
|
72
|
+
const entryCode = `
|
|
73
|
+
import { Scene } from "./engine/_core/Scene";
|
|
74
|
+
import * as SceneModule from "${relativeScenePath}";
|
|
75
|
+
|
|
76
|
+
export class RetiamElement extends HTMLElement {
|
|
77
|
+
private canvas;
|
|
78
|
+
public sceneInstance;
|
|
79
|
+
private resizeObserver;
|
|
80
|
+
|
|
81
|
+
async connectedCallback() {
|
|
82
|
+
if (!navigator.gpu) {
|
|
83
|
+
this.innerHTML = '<div style="color:red;padding:20px;">WebGPU not supported</div>';
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Base styles if not specified
|
|
88
|
+
if (!this.style.display) this.style.display = "block";
|
|
89
|
+
if (!this.style.position) this.style.position = "relative";
|
|
90
|
+
|
|
91
|
+
this.canvas = document.createElement("canvas");
|
|
92
|
+
this.canvas.style.display = "block";
|
|
93
|
+
this.canvas.style.width = "100%";
|
|
94
|
+
this.canvas.style.height = "100%";
|
|
95
|
+
this.appendChild(this.canvas);
|
|
96
|
+
|
|
97
|
+
const updateCanvasSize = () => {
|
|
98
|
+
if (!this.canvas) return;
|
|
99
|
+
const rect = this.getBoundingClientRect();
|
|
100
|
+
const dpr = window.devicePixelRatio || 1;
|
|
101
|
+
const w = Math.floor((rect.width || 800) * dpr);
|
|
102
|
+
const h = Math.floor((rect.height || 450) * dpr);
|
|
103
|
+
if (w > 0 && h > 0 && (this.canvas.width !== w || this.canvas.height !== h)) {
|
|
104
|
+
this.canvas.width = w;
|
|
105
|
+
this.canvas.height = h;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
updateCanvasSize();
|
|
110
|
+
|
|
111
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
112
|
+
updateCanvasSize();
|
|
113
|
+
});
|
|
114
|
+
this.resizeObserver.observe(this);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// 1. Search exports for an actual Scene class (e.g., SubnetRouteGame)
|
|
118
|
+
let SceneClass = null;
|
|
119
|
+
for (const key of Object.keys(SceneModule)) {
|
|
120
|
+
const item = (SceneModule as any)[key];
|
|
121
|
+
if (item && item.prototype && item.prototype instanceof Scene) {
|
|
122
|
+
SceneClass = item;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (SceneClass) {
|
|
128
|
+
// Instantiate class directly with the component's canvas
|
|
129
|
+
this.sceneInstance = new (SceneClass as any)();
|
|
130
|
+
await this.sceneInstance.start(this.canvas);
|
|
131
|
+
} else {
|
|
132
|
+
// 2. Fallback: If no Scene class was exported, pass canvas to default export
|
|
133
|
+
const defaultFn = (SceneModule as any).default;
|
|
134
|
+
if (typeof defaultFn === 'function') {
|
|
135
|
+
const origGetElementById = document.getElementById.bind(document);
|
|
136
|
+
const compCanvas = this.canvas;
|
|
137
|
+
const origStart = Scene.prototype.start;
|
|
138
|
+
const self = this;
|
|
139
|
+
|
|
140
|
+
// Intercept Scene.prototype.start to capture the instantiated Scene
|
|
141
|
+
Scene.prototype.start = async function(...args: any[]) {
|
|
142
|
+
self.sceneInstance = this;
|
|
143
|
+
return await origStart.apply(this, args);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
document.getElementById = function(id) {
|
|
147
|
+
if (id === 'canvas') return compCanvas;
|
|
148
|
+
return origGetElementById(id);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await defaultFn(this.canvas);
|
|
153
|
+
} finally {
|
|
154
|
+
document.getElementById = origGetElementById;
|
|
155
|
+
Scene.prototype.start = origStart;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.sceneInstance) {
|
|
161
|
+
(window as any).retiamScene = this.sceneInstance;
|
|
162
|
+
(window as any).retiamElement = this;
|
|
163
|
+
this.dispatchEvent(new CustomEvent("retiam-ready", {
|
|
164
|
+
detail: { scene: this.sceneInstance, element: this },
|
|
165
|
+
bubbles: true
|
|
166
|
+
}));
|
|
167
|
+
this.dispatchEvent(new CustomEvent("retiam-scene-ready", {
|
|
168
|
+
detail: { scene: this.sceneInstance, element: this },
|
|
169
|
+
bubbles: true
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error("[Retiam Embed] Scene execution error:", err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
disconnectedCallback() {
|
|
178
|
+
if (this.resizeObserver) {
|
|
179
|
+
this.resizeObserver.disconnect();
|
|
180
|
+
this.resizeObserver = null;
|
|
181
|
+
}
|
|
182
|
+
if (this.sceneInstance) {
|
|
183
|
+
this.sceneInstance.destroy();
|
|
184
|
+
this.sceneInstance = null;
|
|
185
|
+
}
|
|
186
|
+
if ((window as any).retiamScene === this.sceneInstance) {
|
|
187
|
+
(window as any).retiamScene = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const RetiamSceneElement = RetiamElement;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
if (!customElements.get("retiam")) {
|
|
196
|
+
customElements.define("retiam", RetiamElement);
|
|
197
|
+
}
|
|
198
|
+
} catch (e) {}
|
|
199
|
+
|
|
200
|
+
if (!customElements.get("retiam-scene")) {
|
|
201
|
+
customElements.define("retiam-scene", RetiamElement);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function autoMountRetiamTags() {
|
|
205
|
+
const nodes = document.querySelectorAll("retiam, Retiam");
|
|
206
|
+
nodes.forEach(node => {
|
|
207
|
+
if (!(node as any).__retiamMounted) {
|
|
208
|
+
(node as any).__retiamMounted = true;
|
|
209
|
+
const elem = document.createElement("retiam-scene");
|
|
210
|
+
for (let i = 0; i < node.attributes.length; i++) {
|
|
211
|
+
const attr = node.attributes[i];
|
|
212
|
+
elem.setAttribute(attr.name, attr.value);
|
|
213
|
+
}
|
|
214
|
+
node.replaceWith(elem);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (typeof document !== "undefined") {
|
|
220
|
+
const styleId = "retiam-embed-default-style";
|
|
221
|
+
if (!document.getElementById(styleId)) {
|
|
222
|
+
const style = document.createElement("style");
|
|
223
|
+
style.id = styleId;
|
|
224
|
+
style.textContent = "retiam, retiam-scene { display: block; position: relative; }";
|
|
225
|
+
document.head?.appendChild(style);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (document.readyState === "loading") {
|
|
229
|
+
document.addEventListener("DOMContentLoaded", autoMountRetiamTags);
|
|
230
|
+
} else {
|
|
231
|
+
autoMountRetiamTags();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const obs = new MutationObserver(() => autoMountRetiamTags());
|
|
236
|
+
obs.observe(document.documentElement || document.body, { childList: true, subtree: true });
|
|
237
|
+
} catch (e) {}
|
|
238
|
+
}
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
fs.writeFileSync(tempEntryPath, entryCode);
|
|
242
|
+
|
|
243
|
+
function minifyWGSL(code) {
|
|
244
|
+
return code
|
|
245
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
|
|
246
|
+
.replace(/\/\/.*/g, '') // Remove line comments
|
|
247
|
+
.split('\n') // Process line by line
|
|
248
|
+
.map(line => line.trim()) // Trim whitespace per line
|
|
249
|
+
.filter(line => line.length > 0) // Remove empty lines
|
|
250
|
+
.join('\n'); // Preserve line breaks for WebGPU debugging
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const wgslPlugin = () => ({
|
|
254
|
+
name: 'wgsl-loader',
|
|
255
|
+
transform(code, id) {
|
|
256
|
+
if (id.endsWith('.wgsl')) {
|
|
257
|
+
const minified = minifyWGSL(code);
|
|
258
|
+
return { code: `export default ${JSON.stringify(minified)};`, map: null };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const tzaPlugin = () => ({
|
|
264
|
+
name: 'tza-loader',
|
|
265
|
+
enforce: 'pre',
|
|
266
|
+
load(id) {
|
|
267
|
+
if (/\.(tza|ttf|png)$/i.test(id) || id.endsWith('.json?url')) {
|
|
268
|
+
const filePath = id.replace(/\?url$/, '');
|
|
269
|
+
const source = fs.readFileSync(filePath);
|
|
270
|
+
const referenceId = this.emitFile({
|
|
271
|
+
type: 'asset',
|
|
272
|
+
fileName: `assets/${path.basename(filePath)}`,
|
|
273
|
+
source: source
|
|
274
|
+
});
|
|
275
|
+
return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
console.log(`🚀 Building standalone embed for: ${inputPath} -> ${targetOutDir}/${outputFile}`);
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
await build({
|
|
285
|
+
configFile: false,
|
|
286
|
+
publicDir: false,
|
|
287
|
+
assetsInclude: ['**/*.tza', '**/*.ttf', '**/*.png'],
|
|
288
|
+
plugins: [tzaPlugin(), wgslPlugin()],
|
|
289
|
+
resolve: {
|
|
290
|
+
alias: [
|
|
291
|
+
{ find: '../../index', replacement: path.resolve(rootDir, 'src/engine/_core/Constants.ts') },
|
|
292
|
+
{ find: '../index', replacement: path.resolve(rootDir, 'src/engine/_core/Constants.ts') }
|
|
293
|
+
]
|
|
294
|
+
},
|
|
295
|
+
build: {
|
|
296
|
+
outDir: targetOutDir,
|
|
297
|
+
emptyOutDir: false,
|
|
298
|
+
lib: {
|
|
299
|
+
entry: tempEntryPath,
|
|
300
|
+
name: 'RetiamEmbed',
|
|
301
|
+
fileName: () => outputFile,
|
|
302
|
+
formats: ['iife']
|
|
303
|
+
},
|
|
304
|
+
rollupOptions: {
|
|
305
|
+
output: { inlineDynamicImports: true }
|
|
306
|
+
},
|
|
307
|
+
minify: 'esbuild'
|
|
308
|
+
},
|
|
309
|
+
esbuild: {
|
|
310
|
+
drop: ['debugger'],
|
|
311
|
+
legalComments: 'none',
|
|
312
|
+
treeShaking: true
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
console.log(`✅ Build successful! Output: ${targetOutDir}/${outputFile}`);
|
|
317
|
+
} catch (e) {
|
|
318
|
+
console.error("❌ Build failed:", e);
|
|
319
|
+
} finally {
|
|
320
|
+
if (fs.existsSync(tempEntryPath)) {
|
|
321
|
+
fs.unlinkSync(tempEntryPath);
|
|
322
|
+
}
|
|
323
323
|
}
|