@kite3d/engine 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/authoring.d.ts +54 -0
  2. package/dist/authoring.js +165 -0
  3. package/dist/authoring.js.map +1 -0
  4. package/dist/authoringValidation.d.ts +110 -0
  5. package/dist/authoringValidation.js +488 -0
  6. package/dist/authoringValidation.js.map +1 -0
  7. package/dist/defaults.d.ts +2 -0
  8. package/dist/fileTypes.d.ts +5 -0
  9. package/dist/fileTypes.js +51 -0
  10. package/dist/fileTypes.js.map +1 -0
  11. package/dist/importMap.d.ts +15 -0
  12. package/dist/importMap.js +62 -0
  13. package/dist/importMap.js.map +1 -0
  14. package/dist/index.d.ts +15 -0
  15. package/dist/index.js +2756 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/migrations.js +5 -0
  18. package/dist/migrations.js.map +1 -0
  19. package/dist/paths.d.ts +9 -0
  20. package/dist/paths.js +13 -0
  21. package/dist/paths.js.map +1 -0
  22. package/dist/plugins/GeneratorComponent.d.ts +40 -0
  23. package/dist/plugins/HtmlUiComponent.d.ts +52 -0
  24. package/dist/plugins/HtmlUiComponent.example.d.ts +0 -0
  25. package/dist/plugins/cannon/Cannon3DBodyComponent.d.ts +27 -0
  26. package/dist/plugins/cannon/Cannon3DShapeComponent.d.ts +57 -0
  27. package/dist/plugins/cannon/CannonPhysicsPlugin.d.ts +63 -0
  28. package/dist/plugins/cannon/CannonRagdollComponent.d.ts +148 -0
  29. package/dist/plugins/cannon/helper.d.ts +24 -0
  30. package/dist/plugins/cannon/threeToCannon.d.ts +63 -0
  31. package/dist/plugins/cannon/utils.d.ts +9 -0
  32. package/dist/projectFormat.js +108 -0
  33. package/dist/projectFormat.js.map +1 -0
  34. package/dist/runtime/createGame.d.ts +29 -0
  35. package/dist/runtime/index.d.ts +18 -0
  36. package/dist/runtime/migrations.d.ts +5 -0
  37. package/dist/runtime/nestedAssets.d.ts +26 -0
  38. package/dist/runtime/projectFormat.d.ts +83 -0
  39. package/dist/runtime/version.d.ts +1 -0
  40. package/dist/runtime.js +72835 -0
  41. package/dist/runtime.js.map +1 -0
  42. package/dist/sceneSerialization.d.ts +17 -0
  43. package/dist/sceneSerialization.js +174 -0
  44. package/dist/sceneSerialization.js.map +1 -0
  45. package/dist/scripts.d.ts +17 -0
  46. package/dist/version.js +7 -0
  47. package/dist/version.js.map +1 -0
  48. package/package.json +86 -0
  49. package/src/authoring.ts +293 -0
  50. package/src/authoringValidation.ts +815 -0
  51. package/src/defaults.ts +277 -0
  52. package/src/fileTypes.ts +53 -0
  53. package/src/importMap.ts +105 -0
  54. package/src/index.ts +15 -0
  55. package/src/paths.ts +9 -0
  56. package/src/plugins/GeneratorComponent.ts +275 -0
  57. package/src/plugins/HtmlUiComponent.example.ts +202 -0
  58. package/src/plugins/HtmlUiComponent.md +219 -0
  59. package/src/plugins/HtmlUiComponent.ts +320 -0
  60. package/src/plugins/cannon/Cannon3DBodyComponent.ts +227 -0
  61. package/src/plugins/cannon/Cannon3DShapeComponent.ts +258 -0
  62. package/src/plugins/cannon/CannonPhysicsPlugin.ts +409 -0
  63. package/src/plugins/cannon/CannonRagdollComponent.ts +1170 -0
  64. package/src/plugins/cannon/helper.ts +255 -0
  65. package/src/plugins/cannon/threeToCannon.ts +441 -0
  66. package/src/plugins/cannon/utils.ts +185 -0
  67. package/src/runtime/createGame.ts +337 -0
  68. package/src/runtime/index.ts +19 -0
  69. package/src/runtime/migrations.ts +6 -0
  70. package/src/runtime/nestedAssets.ts +226 -0
  71. package/src/runtime/projectFormat.ts +233 -0
  72. package/src/runtime/version.ts +3 -0
  73. package/src/sceneSerialization.ts +289 -0
  74. package/src/scripts.ts +52 -0
@@ -0,0 +1,185 @@
1
+ // https://github.com/donmccurdy/three-to-cannon/blob/main/src/utils.ts
2
+
3
+ import { BufferAttribute, BufferGeometry, Mesh, Object3D, Quaternion, Vector3 } from 'threepipe';
4
+
5
+ const _v1 = new Vector3();
6
+ const _v2 = new Vector3();
7
+ const _q1 = new Quaternion();
8
+
9
+ /**
10
+ * Returns a single geometry for the given object. If the object is compound,
11
+ * its geometries are automatically merged. Bake world scale into each
12
+ * geometry, because we can't easily apply that to the cannonjs shapes later.
13
+ */
14
+ export function getGeometry (object: Object3D): BufferGeometry | null {
15
+ const meshes = getMeshes(object);
16
+ if (meshes.length === 0) return null;
17
+
18
+ // Single mesh. Return, preserving original type.
19
+ if (meshes.length === 1) {
20
+ return normalizeGeometry(meshes[0]);
21
+ }
22
+
23
+ // Multiple meshes. Merge and return.
24
+ let mesh: Mesh | undefined;
25
+ const geometries: BufferGeometry[] = [];
26
+ while ((mesh = meshes.pop())) {
27
+ geometries.push(simplifyGeometry(normalizeGeometry(mesh)));
28
+ }
29
+
30
+ return mergeBufferGeometries(geometries);
31
+ }
32
+
33
+ function normalizeGeometry (mesh: Mesh): BufferGeometry {
34
+ // Preserve original type, e.g. CylinderBufferGeometry.
35
+ const geometry: BufferGeometry = mesh.geometry.clone();
36
+
37
+ mesh.updateMatrixWorld();
38
+ mesh.matrixWorld.decompose(_v1, _q1, _v2);
39
+ geometry.scale(_v2.x, _v2.y, _v2.z);
40
+ return geometry;
41
+ }
42
+
43
+ /**
44
+ * Greatly simplified version of BufferGeometryUtils.mergeBufferGeometries.
45
+ * Because we only care about the vertex positions, and not the indices or
46
+ * other attributes, we throw everything else away.
47
+ */
48
+ function mergeBufferGeometries (geometries: BufferGeometry[]): BufferGeometry {
49
+ let vertexCount = 0;
50
+ for (let i = 0; i < geometries.length; i++) {
51
+ const position = geometries[i].attributes.position;
52
+ if (position && position.itemSize === 3) {
53
+ vertexCount += position.count;
54
+ }
55
+ }
56
+
57
+ const positionArray = new Float32Array(vertexCount * 3);
58
+
59
+ let positionOffset = 0;
60
+ for (let i = 0; i < geometries.length; i++) {
61
+ const position = geometries[i].attributes.position;
62
+ if (position && position.itemSize === 3) {
63
+ for (let j = 0; j < position.count; j++) {
64
+ positionArray[positionOffset++] = position.getX(j);
65
+ positionArray[positionOffset++] = position.getY(j);
66
+ positionArray[positionOffset++] = position.getZ(j);
67
+ }
68
+ }
69
+ }
70
+
71
+ return new BufferGeometry().setAttribute('position', new BufferAttribute(positionArray, 3));
72
+ }
73
+
74
+ export function getVertices (geometry: BufferGeometry): Float32Array {
75
+ const position = geometry.attributes.position;
76
+ const vertices = new Float32Array(position.count * 3);
77
+ for (let i = 0; i < position.count; i++) {
78
+ vertices[i * 3] = position.getX(i);
79
+ vertices[i * 3 + 1] = position.getY(i);
80
+ vertices[i * 3 + 2] = position.getZ(i);
81
+ }
82
+ return vertices;
83
+ }
84
+
85
+ /**
86
+ * Returns a flat array of THREE.Mesh instances from the given object. If
87
+ * nested transformations are found, they are applied to child meshes
88
+ * as mesh.userData.matrix, so that each mesh has its position/rotation/scale
89
+ * independently of all of its parents except the top-level object.
90
+ */
91
+ function getMeshes (object: Object3D): Mesh[] {
92
+ const meshes: Mesh[] = [];
93
+ object.traverse(function (o) {
94
+ if ((o as Mesh).isMesh) {
95
+ meshes.push(o as Mesh);
96
+ }
97
+ });
98
+ return meshes;
99
+ }
100
+
101
+ export function getComponent(v: Vector3, component: string): number {
102
+ switch(component) {
103
+ case 'x': return v.x;
104
+ case 'y': return v.y;
105
+ case 'z': return v.z;
106
+ }
107
+ throw new Error(`Unexpected component ${component}`);
108
+ }
109
+
110
+ /**
111
+ * Modified version of BufferGeometryUtils.mergeVertices, ignoring vertex
112
+ * attributes other than position.
113
+ *
114
+ * @param {THREE.BufferGeometry} geometry
115
+ * @param {number} tolerance
116
+ * @return {THREE.BufferGeometry>}
117
+ */
118
+ function simplifyGeometry (geometry: BufferGeometry, tolerance = 1e-4): BufferGeometry {
119
+
120
+ tolerance = Math.max( tolerance, Number.EPSILON );
121
+
122
+ // Generate an index buffer if the geometry doesn't have one, or optimize it
123
+ // if it's already available.
124
+ const hashToIndex: {[key: string]: number} = {};
125
+ const indices = geometry.getIndex();
126
+ const positions = geometry.getAttribute( 'position' );
127
+ const vertexCount = indices ? indices.count : positions.count;
128
+
129
+ // Next value for triangle indices.
130
+ let nextIndex = 0;
131
+
132
+ const newIndices = [];
133
+ const newPositions = [];
134
+
135
+ // Convert the error tolerance to an amount of decimal places to truncate to.
136
+ const decimalShift = Math.log10( 1 / tolerance );
137
+ const shiftMultiplier = Math.pow( 10, decimalShift );
138
+
139
+ for ( let i = 0; i < vertexCount; i ++ ) {
140
+
141
+ const index = indices ? indices.getX( i ) : i;
142
+
143
+ // Generate a hash for the vertex attributes at the current index 'i'.
144
+ let hash = '';
145
+
146
+ // Double tilde truncates the decimal value.
147
+ hash += `${ ~ ~ ( positions.getX( index ) * shiftMultiplier ) },`;
148
+ hash += `${ ~ ~ ( positions.getY( index ) * shiftMultiplier ) },`;
149
+ hash += `${ ~ ~ ( positions.getZ( index ) * shiftMultiplier ) },`;
150
+
151
+ // Add another reference to the vertex if it's already
152
+ // used by another index.
153
+ if ( hash in hashToIndex ) {
154
+
155
+ newIndices.push( hashToIndex[ hash ] );
156
+
157
+ } else {
158
+
159
+ newPositions.push( positions.getX( index ) );
160
+ newPositions.push( positions.getY( index ) );
161
+ newPositions.push( positions.getZ( index ) );
162
+
163
+ hashToIndex[ hash ] = nextIndex;
164
+ newIndices.push( nextIndex );
165
+ nextIndex ++;
166
+
167
+ }
168
+
169
+ }
170
+
171
+ // Construct merged BufferGeometry.
172
+
173
+ const positionAttribute = new BufferAttribute(
174
+ new Float32Array( newPositions ),
175
+ positions.itemSize,
176
+ positions.normalized
177
+ );
178
+
179
+ const result = new BufferGeometry();
180
+ result.setAttribute( 'position', positionAttribute );
181
+ result.setIndex( newIndices );
182
+
183
+ return result;
184
+
185
+ }
@@ -0,0 +1,337 @@
1
+ import {
2
+ Class,
3
+ EntityComponentPlugin,
4
+ GBufferPlugin,
5
+ GLTFAnimationPlugin,
6
+ GLTFMeshOptDecodePlugin,
7
+ IObject3D,
8
+ IViewerPlugin,
9
+ KTX2LoadPlugin,
10
+ KTXLoadPlugin,
11
+ PLYLoadPlugin,
12
+ PopmotionPlugin,
13
+ Rhino3dmLoadPlugin,
14
+ STLLoadPlugin,
15
+ ThreeViewer,
16
+ USDZLoadPlugin,
17
+ } from 'threepipe'
18
+ import {MeshoptDecoder} from 'meshoptimizer'
19
+ import {registerScripts} from '../scripts.ts'
20
+ import {HtmlUiComponent} from '../plugins/HtmlUiComponent.ts'
21
+ import {GeneratorComponent} from '../plugins/GeneratorComponent.ts'
22
+ import {CannonPhysicsPlugin} from '../plugins/cannon/CannonPhysicsPlugin.ts'
23
+ import {
24
+ installGameHooks,
25
+ runtimeCleanupReport,
26
+ type GameValidationFunction,
27
+ type GameValidationReport,
28
+ type RuntimeCleanupReport,
29
+ } from '../authoringValidation.ts'
30
+ import {RuntimeNestedAssetLoader} from './nestedAssets.ts'
31
+ import {
32
+ assetUrlPrefix,
33
+ AssetsJSONManifest,
34
+ ExternalPlugin,
35
+ isDependencyModuleSpecifier,
36
+ parseAssetsJSONManifest,
37
+ parsePackageJSON,
38
+ parsePackageJsonSettingsConfig,
39
+ ProjectPackageJSON,
40
+ ProjectConfigSettings,
41
+ } from './projectFormat.ts'
42
+
43
+ export interface CreateGameOptions {
44
+ base: string
45
+ canvas: HTMLCanvasElement
46
+ onError?: (error: unknown) => void
47
+ /** Content hashes for cache-safe project module imports in development. */
48
+ fileRevisions?: Readonly<Record<string, string>>
49
+ /** Identifies one module-graph load so transitive imports bypass the browser module map together. */
50
+ moduleRevision?: string
51
+ }
52
+
53
+ export interface RuntimeProject {
54
+ packageJson: ProjectPackageJSON
55
+ config: ProjectConfigSettings
56
+ assetsManifest: AssetsJSONManifest
57
+ mainScene: string
58
+ }
59
+
60
+ export interface CreatedGame {
61
+ viewer: ThreeViewer
62
+ project: RuntimeProject
63
+ registerGameValidation(fn: GameValidationFunction): () => void
64
+ publishGameTelemetry(value: object): () => void
65
+ runGameValidation(): Promise<GameValidationReport>
66
+ dispose(): RuntimeCleanupReport
67
+ }
68
+
69
+ type ModuleExports = Record<string, unknown>
70
+ type RuntimeErrorHandler = (error: unknown) => void
71
+
72
+ export function createGame(options: CreateGameOptions): Promise<CreatedGame> {
73
+ return createProjectGame(options, true)
74
+ }
75
+
76
+ /** Load the saved project and generator previews without starting components, physics, the timeline, or main.js. */
77
+ export function createStoppedGame(options: CreateGameOptions): Promise<CreatedGame> {
78
+ return createProjectGame(options, false)
79
+ }
80
+
81
+ async function createProjectGame({
82
+ base,
83
+ canvas,
84
+ onError,
85
+ fileRevisions = {},
86
+ moduleRevision,
87
+ }: CreateGameOptions, start: boolean): Promise<CreatedGame> {
88
+ const reportError = createErrorReporter(onError)
89
+ let viewer: ThreeViewer | undefined
90
+ let nestedAssets: RuntimeNestedAssetLoader | undefined
91
+ let removeURLModifier: (() => void) | undefined
92
+ let gameHooks: ReturnType<typeof installGameHooks> | undefined
93
+
94
+ try {
95
+ const baseUrl = validateBase(base)
96
+ const [packageText, assetsText] = await Promise.all([
97
+ fetchText(new URL('package.json', baseUrl)),
98
+ fetchText(new URL('assets.json', baseUrl)),
99
+ ])
100
+ const packageJson = parsePackageJSON(packageText)
101
+ const config = await parsePackageJsonSettingsConfig(packageJson)
102
+ const assetsManifest = parseAssetsJSONManifest(assetsText)
103
+ const project: RuntimeProject = {
104
+ packageJson,
105
+ config,
106
+ assetsManifest,
107
+ mainScene: packageJson.mainScene,
108
+ }
109
+
110
+ const entityComponents = new EntityComponentPlugin(false)
111
+ const physics = new CannonPhysicsPlugin(true, false)
112
+ EntityComponentPlugin.AddObjectUiConfig = false
113
+ window.MeshoptDecoder = MeshoptDecoder
114
+
115
+ viewer = new ThreeViewer({
116
+ canvas,
117
+ ...config.viewer,
118
+ assetManager: {
119
+ simpleCache: false,
120
+ storage: false,
121
+ },
122
+ plugins: [
123
+ entityComponents,
124
+ new GBufferPlugin(),
125
+ physics,
126
+ new PopmotionPlugin(),
127
+ new GLTFAnimationPlugin(),
128
+ new GLTFMeshOptDecodePlugin(false),
129
+ new KTX2LoadPlugin(),
130
+ new KTXLoadPlugin(),
131
+ new PLYLoadPlugin(),
132
+ new Rhino3dmLoadPlugin(),
133
+ new STLLoadPlugin(),
134
+ new USDZLoadPlugin(),
135
+ ],
136
+ })
137
+ viewer.timeline.endTime = 0
138
+ gameHooks = installGameHooks()
139
+ entityComponents.addComponentType(HtmlUiComponent)
140
+ entityComponents.addComponentType(GeneratorComponent)
141
+ GeneratorComponent.configureViewer(viewer, {base: baseUrl, onError: reportError})
142
+
143
+ // Three's LoadingManager delegates through this importer hook. It covers
144
+ // glTF buffers/textures and nested imports without patching global fetch.
145
+ const urlModifier = createURLModifier(baseUrl, assetsManifest)
146
+ viewer.assetManager.importer.addURLModifier(urlModifier)
147
+ removeURLModifier = () => viewer?.assetManager.importer.removeURLModifier(urlModifier)
148
+
149
+ nestedAssets = new RuntimeNestedAssetLoader(viewer, reportError)
150
+
151
+ await registerProjectScripts(viewer, project, baseUrl, fileRevisions, moduleRevision)
152
+ await registerProjectPlugins(viewer, project, baseUrl, fileRevisions, moduleRevision)
153
+
154
+ const sceneUrl = new URL(project.mainScene, baseUrl).href
155
+ const loadedScene = await viewer.load(sceneUrl, {importAsModelRoot: true})
156
+ if (!loadedScene?.isObject3D) {
157
+ throw new Error(`The main scene did not load as an Object3D: ${sceneUrl}`)
158
+ }
159
+ await nestedAssets.loadObjectDependencies(loadedScene as IObject3D)
160
+ await nestedAssets.waitForPending()
161
+ await GeneratorComponent.waitForViewer(viewer)
162
+
163
+ if (start) {
164
+ viewer.timeline.reset()
165
+ viewer.timeline.start()
166
+ entityComponents.start()
167
+ physics.running = true
168
+
169
+ const mainUrl = versionedProjectUrl('main.js', baseUrl, fileRevisions, moduleRevision)
170
+ const mainModule = await importModule(mainUrl.href)
171
+ if (mainModule.main !== undefined) {
172
+ if (typeof mainModule.main !== 'function') {
173
+ throw new Error('main.js export "main" must be a function')
174
+ }
175
+ await mainModule.main({viewer})
176
+ }
177
+ }
178
+
179
+ const readyViewer = viewer
180
+ let disposed = false
181
+ let cleanupReport: RuntimeCleanupReport | undefined
182
+ return {
183
+ viewer: readyViewer,
184
+ project,
185
+ registerGameValidation: gameHooks.registerGameValidation,
186
+ publishGameTelemetry: gameHooks.publishGameTelemetry,
187
+ runGameValidation: gameHooks.runGameValidation,
188
+ dispose() {
189
+ if (disposed) return cleanupReport!
190
+ disposed = true
191
+ entityComponents.stop()
192
+ physics.running = false
193
+ readyViewer.timeline.stop()
194
+ cleanupReport = runtimeCleanupReport(readyViewer)
195
+ nestedAssets?.dispose()
196
+ removeURLModifier?.()
197
+ gameHooks?.dispose()
198
+ readyViewer.dispose()
199
+ return cleanupReport
200
+ },
201
+ }
202
+ } catch (error) {
203
+ nestedAssets?.dispose()
204
+ removeURLModifier?.()
205
+ gameHooks?.dispose()
206
+ viewer?.dispose()
207
+ reportError(error)
208
+ throw error
209
+ }
210
+ }
211
+
212
+ async function registerProjectPlugins(
213
+ viewer: ThreeViewer,
214
+ project: RuntimeProject,
215
+ base: URL,
216
+ fileRevisions: Readonly<Record<string, string>>,
217
+ moduleRevision?: string,
218
+ ) {
219
+ const {config, packageJson} = project
220
+ for (const definition of config.plugins) {
221
+ if (definition.active === false) continue
222
+ const specifier = resolvePluginSpecifier(definition, packageJson, base, fileRevisions, moduleRevision)
223
+ const module = await importModule(specifier)
224
+ const plugin = findPluginExport(module, definition)
225
+ if (viewer.getPlugin(plugin)) continue
226
+ await viewer.addPlugin(plugin, ...(definition.params || []))
227
+ }
228
+ }
229
+
230
+ async function registerProjectScripts(
231
+ viewer: ThreeViewer,
232
+ project: RuntimeProject,
233
+ base: URL,
234
+ fileRevisions: Readonly<Record<string, string>>,
235
+ moduleRevision?: string,
236
+ ) {
237
+ const {config, packageJson} = project
238
+ const modules: ModuleExports[] = []
239
+ for (const definition of config.scripts) {
240
+ if (definition.active === false) continue
241
+ const specifier = isDependencyModuleSpecifier(definition.import, packageJson)
242
+ ? definition.import
243
+ : versionedProjectUrl(definition.import, base, fileRevisions, moduleRevision).href
244
+ modules.push(await importModule(specifier))
245
+ }
246
+ await registerScripts(viewer, modules)
247
+ }
248
+
249
+ function createURLModifier(base: URL, assets: AssetsJSONManifest) {
250
+ const assetIdPrefix = `${assetUrlPrefix}@`
251
+ return (url: string): string => {
252
+ if (url.startsWith(assetIdPrefix)) {
253
+ const id = url.slice(assetIdPrefix.length).split('/', 1)[0]
254
+ const asset = assets.files[id]
255
+ if (!asset?.path) throw new Error(`Unknown asset id in URL: ${id}`)
256
+ return new URL(asset.path, base).href
257
+ }
258
+ if (url.startsWith(assetUrlPrefix)) {
259
+ return new URL(url.slice(assetUrlPrefix.length), base).href
260
+ }
261
+ return url
262
+ }
263
+ }
264
+
265
+ function resolvePluginSpecifier(
266
+ definition: ExternalPlugin,
267
+ packageJson: ProjectPackageJSON,
268
+ base: URL,
269
+ fileRevisions: Readonly<Record<string, string>>,
270
+ moduleRevision?: string,
271
+ ) {
272
+ const isDependency = isDependencyModuleSpecifier(definition.import, packageJson)
273
+ if (isDependency) return definition.import
274
+ return versionedProjectUrl(definition.import, base, fileRevisions, moduleRevision).href
275
+ }
276
+
277
+ function versionedProjectUrl(
278
+ path: string,
279
+ base: URL,
280
+ fileRevisions: Readonly<Record<string, string>>,
281
+ moduleRevision?: string,
282
+ ): URL {
283
+ const url = assertSameOrigin(new URL(path, base), base)
284
+ const normalized = path.replace(/^\.\//, '')
285
+ const revision = fileRevisions[normalized]
286
+ if (revision) url.searchParams.set('v', revision)
287
+ if (moduleRevision) url.searchParams.set('r', moduleRevision)
288
+ return url
289
+ }
290
+
291
+ function findPluginExport(module: ModuleExports, definition: ExternalPlugin): Class<IViewerPlugin> {
292
+ const requested = definition.className || 'default'
293
+ const selected = module[requested]
294
+ if (isPluginType(selected)) return selected
295
+
296
+ if (!definition.className) {
297
+ const candidates = Object.values(module).filter(isPluginType)
298
+ if (candidates.length === 1) return candidates[0]
299
+ }
300
+ throw new Error(`Cannot find plugin export "${requested}" in ${definition.import}`)
301
+ }
302
+
303
+ function isPluginType(value: unknown): value is Class<IViewerPlugin> {
304
+ return typeof value === 'function'
305
+ && typeof (value as unknown as {PluginType?: unknown}).PluginType === 'string'
306
+ }
307
+
308
+ async function importModule(specifier: string): Promise<ModuleExports> {
309
+ return import(/* @vite-ignore */ specifier) as Promise<ModuleExports>
310
+ }
311
+
312
+ async function fetchText(url: URL): Promise<string> {
313
+ const response = await fetch(url)
314
+ if (!response.ok) throw new Error(`Failed to fetch ${url.href}: ${response.status} ${response.statusText}`)
315
+ return response.text()
316
+ }
317
+
318
+ function validateBase(base: string): URL {
319
+ if (!base.endsWith('/')) throw new Error('createGame base must end in "/"')
320
+ const url = new URL(base)
321
+ if (!url.protocol.startsWith('http')) throw new Error('createGame base must be an absolute HTTP(S) URL')
322
+ return url
323
+ }
324
+
325
+ function assertSameOrigin(url: URL, base: URL): URL {
326
+ if (url.origin !== base.origin) {
327
+ throw new Error(`Project modules must be same-origin: ${url.href}`)
328
+ }
329
+ return url
330
+ }
331
+
332
+ function createErrorReporter(onError?: RuntimeErrorHandler) {
333
+ return (error: unknown) => {
334
+ console.error('[kite3d] Runtime error', error)
335
+ onError?.(error)
336
+ }
337
+ }
@@ -0,0 +1,19 @@
1
+ export {createGame, createStoppedGame} from './createGame.ts'
2
+ export type {CreatedGame, CreateGameOptions, RuntimeProject} from './createGame.ts'
3
+ export {RUNTIME_VERSION} from './version.ts'
4
+ export * from './projectFormat.ts'
5
+ export * from './migrations.ts'
6
+ export {RuntimeNestedAssetLoader} from './nestedAssets.ts'
7
+ export {registerScripts, walkScriptExports} from '../scripts.ts'
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
+ export {CannonPhysicsPlugin} from '../plugins/cannon/CannonPhysicsPlugin.ts'
12
+ export {serializeSceneGltf, serializeSceneGltfDocument} from '../sceneSerialization.ts'
13
+ export type {SerializeSceneGltfOptions, SerializedSceneFile, SerializedSceneGltf} from '../sceneSerialization.ts'
14
+ export * from '../authoring.ts'
15
+ export * from '../authoringValidation.ts'
16
+
17
+ export * from 'threepipe'
18
+ export * from 'uiconfig.js'
19
+ export * from 'ts-browser-helpers'
@@ -0,0 +1,6 @@
1
+ export interface ProjectMigration {
2
+ version: string
3
+ migrate(projectRoot: string): void | Promise<void>
4
+ }
5
+
6
+ export const PROJECT_MIGRATIONS: readonly ProjectMigration[] = []