@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.
- package/dist/authoring.d.ts +54 -0
- package/dist/authoring.js +165 -0
- package/dist/authoring.js.map +1 -0
- package/dist/authoringValidation.d.ts +110 -0
- package/dist/authoringValidation.js +488 -0
- package/dist/authoringValidation.js.map +1 -0
- package/dist/defaults.d.ts +2 -0
- package/dist/fileTypes.d.ts +5 -0
- package/dist/fileTypes.js +51 -0
- package/dist/fileTypes.js.map +1 -0
- package/dist/importMap.d.ts +15 -0
- package/dist/importMap.js +62 -0
- package/dist/importMap.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +2756 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.js +5 -0
- package/dist/migrations.js.map +1 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +13 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugins/GeneratorComponent.d.ts +40 -0
- package/dist/plugins/HtmlUiComponent.d.ts +52 -0
- package/dist/plugins/HtmlUiComponent.example.d.ts +0 -0
- package/dist/plugins/cannon/Cannon3DBodyComponent.d.ts +27 -0
- package/dist/plugins/cannon/Cannon3DShapeComponent.d.ts +57 -0
- package/dist/plugins/cannon/CannonPhysicsPlugin.d.ts +63 -0
- package/dist/plugins/cannon/CannonRagdollComponent.d.ts +148 -0
- package/dist/plugins/cannon/helper.d.ts +24 -0
- package/dist/plugins/cannon/threeToCannon.d.ts +63 -0
- package/dist/plugins/cannon/utils.d.ts +9 -0
- package/dist/projectFormat.js +108 -0
- package/dist/projectFormat.js.map +1 -0
- package/dist/runtime/createGame.d.ts +29 -0
- package/dist/runtime/index.d.ts +18 -0
- package/dist/runtime/migrations.d.ts +5 -0
- package/dist/runtime/nestedAssets.d.ts +26 -0
- package/dist/runtime/projectFormat.d.ts +83 -0
- package/dist/runtime/version.d.ts +1 -0
- package/dist/runtime.js +72835 -0
- package/dist/runtime.js.map +1 -0
- package/dist/sceneSerialization.d.ts +17 -0
- package/dist/sceneSerialization.js +174 -0
- package/dist/sceneSerialization.js.map +1 -0
- package/dist/scripts.d.ts +17 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +86 -0
- package/src/authoring.ts +293 -0
- package/src/authoringValidation.ts +815 -0
- package/src/defaults.ts +277 -0
- package/src/fileTypes.ts +53 -0
- package/src/importMap.ts +105 -0
- package/src/index.ts +15 -0
- package/src/paths.ts +9 -0
- package/src/plugins/GeneratorComponent.ts +275 -0
- package/src/plugins/HtmlUiComponent.example.ts +202 -0
- package/src/plugins/HtmlUiComponent.md +219 -0
- package/src/plugins/HtmlUiComponent.ts +320 -0
- package/src/plugins/cannon/Cannon3DBodyComponent.ts +227 -0
- package/src/plugins/cannon/Cannon3DShapeComponent.ts +258 -0
- package/src/plugins/cannon/CannonPhysicsPlugin.ts +409 -0
- package/src/plugins/cannon/CannonRagdollComponent.ts +1170 -0
- package/src/plugins/cannon/helper.ts +255 -0
- package/src/plugins/cannon/threeToCannon.ts +441 -0
- package/src/plugins/cannon/utils.ts +185 -0
- package/src/runtime/createGame.ts +337 -0
- package/src/runtime/index.ts +19 -0
- package/src/runtime/migrations.ts +6 -0
- package/src/runtime/nestedAssets.ts +226 -0
- package/src/runtime/projectFormat.ts +233 -0
- package/src/runtime/version.ts +3 -0
- package/src/sceneSerialization.ts +289 -0
- 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'
|