@directivegames/genesys.sdk 3.2.2 → 3.2.5
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/README.md +60 -60
- package/dist/src/asset-pack/eslint.config.js +4 -4
- package/dist/src/core/cli.js +22 -22
- package/dist/src/core/common.js +2 -2
- package/dist/src/core/tools/build-project.js +1 -1
- package/dist/src/core/tools/new-asset-pack.js +1 -1
- package/dist/src/core/tools/new-project.js +1 -1
- package/dist/src/dependencies.js +1 -1
- package/dist/src/electron/backend/tools/const.js +1 -1
- package/dist/src/electron/backend/tools/open-project.js +1 -1
- package/dist/src/templates/eslint.config.js +4 -4
- package/dist/src/templates/scripts/genesys/calc-bounding-box.js +2 -2
- package/dist/src/templates/scripts/genesys/dev/dump-default-scene.js +1 -1
- package/dist/src/templates/scripts/genesys/dev/generate-manifest.js +1 -1
- package/dist/src/templates/scripts/genesys/dev/launcher.js +1 -1
- package/dist/src/templates/scripts/genesys/dev/storage-provider.js +4 -4
- package/dist/src/templates/scripts/genesys/genesys-mcp.js +26 -26
- package/dist/src/templates/scripts/genesys/mcp/doc-tools.js +2 -2
- package/dist/src/templates/scripts/genesys/mcp/editor-functions.js +5 -5
- package/dist/src/templates/scripts/genesys/mcp/search-actors.js +1 -1
- package/dist/src/templates/scripts/genesys/mcp/search-assets.js +1 -1
- package/dist/src/templates/scripts/genesys/mcp/utils.js +1 -1
- package/dist/src/templates/scripts/genesys/misc.js +1 -1
- package/dist/src/templates/scripts/genesys/mock.js +1 -1
- package/dist/src/templates/scripts/genesys/place-actors.js +1 -1
- package/dist/src/templates/scripts/genesys/post-install.js +1 -1
- package/dist/src/templates/scripts/genesys/storageProvider.js +2 -2
- package/dist/src/templates/src/index.js +1 -1
- package/dist/src/templates/src/templates/firstPerson/src/game.js +1 -1
- package/dist/src/templates/src/templates/firstPerson/src/player.js +1 -1
- package/dist/src/templates/src/templates/fps/src/game.js +1 -1
- package/dist/src/templates/src/templates/fps/src/player.js +1 -1
- package/dist/src/templates/src/templates/fps/src/weapon.js +1 -1
- package/dist/src/templates/src/templates/freeCamera/src/game.js +1 -1
- package/dist/src/templates/src/templates/freeCamera/src/player.js +1 -1
- package/dist/src/templates/src/templates/sideScroller/src/game.js +1 -1
- package/dist/src/templates/src/templates/sideScroller/src/level-generator.js +1 -1
- package/dist/src/templates/src/templates/sideScroller/src/player.js +1 -1
- package/dist/src/templates/src/templates/thirdPerson/src/game.js +1 -1
- package/dist/src/templates/src/templates/thirdPerson/src/player.js +1 -1
- package/dist/src/templates/src/templates/vehicle/src/base-vehicle.js +1 -1
- package/dist/src/templates/src/templates/vehicle/src/game.js +1 -1
- package/dist/src/templates/src/templates/vehicle/src/mesh-vehicle.js +1 -1
- package/dist/src/templates/src/templates/vehicle/src/player.js +1 -1
- package/dist/src/templates/src/templates/vehicle/src/primitive-vehicle.js +1 -1
- package/dist/src/templates/src/templates/vehicle/src/ui-hints.js +31 -31
- package/dist/src/templates/src/templates/vr-game/src/game.js +1 -1
- package/dist/src/templates/src/templates/vr-game/src/sample-vr-actor.js +1 -1
- package/package.json +176 -176
- package/scripts/post-install.ts +143 -143
- package/src/asset-pack/.gitattributes +88 -88
- package/src/asset-pack/eslint.config.js +45 -45
- package/src/asset-pack/gitignore +11 -11
- package/src/asset-pack/scripts/postinstall.ts +81 -81
- package/src/asset-pack/tsconfig.json +33 -33
- package/src/templates/.cursor/mcp.json +20 -20
- package/src/templates/.cursorignore +2 -2
- package/src/templates/.gitattributes +88 -88
- package/src/templates/.vscode/settings.json +6 -6
- package/src/templates/AGENTS.md +86 -86
- package/src/templates/README.md +24 -24
- package/src/templates/eslint.config.js +45 -45
- package/src/templates/gitignore +11 -11
- package/src/templates/index.html +34 -34
- package/src/templates/pnpm-lock.yaml +3676 -3676
- package/src/templates/scripts/genesys/build-project.ts +51 -51
- package/src/templates/scripts/genesys/calc-bounding-box.ts +272 -272
- package/src/templates/scripts/genesys/common.ts +46 -46
- package/src/templates/scripts/genesys/const.ts +9 -9
- package/src/templates/scripts/genesys/dev/dump-default-scene.ts +11 -11
- package/src/templates/scripts/genesys/dev/generate-manifest.ts +146 -146
- package/src/templates/scripts/genesys/dev/launcher.ts +46 -46
- package/src/templates/scripts/genesys/dev/storage-provider.ts +229 -229
- package/src/templates/scripts/genesys/dev/update-template-scenes.ts +84 -84
- package/src/templates/scripts/genesys/doc-server.ts +16 -16
- package/src/templates/scripts/genesys/genesys-mcp.ts +526 -526
- package/src/templates/scripts/genesys/mcp/doc-tools.ts +86 -86
- package/src/templates/scripts/genesys/mcp/editor-functions.ts +151 -151
- package/src/templates/scripts/genesys/mcp/editor-tools.ts +73 -73
- package/src/templates/scripts/genesys/mcp/get-scene-state.ts +35 -35
- package/src/templates/scripts/genesys/mcp/run-subprocess.ts +30 -30
- package/src/templates/scripts/genesys/mcp/search-actors.ts +858 -858
- package/src/templates/scripts/genesys/mcp/search-assets.ts +380 -380
- package/src/templates/scripts/genesys/mcp/utils.ts +281 -281
- package/src/templates/scripts/genesys/misc.ts +42 -42
- package/src/templates/scripts/genesys/mock.ts +6 -6
- package/src/templates/scripts/genesys/place-actors.ts +179 -179
- package/src/templates/scripts/genesys/post-install.ts +30 -30
- package/src/templates/scripts/genesys/prefab.schema.json +84 -84
- package/src/templates/scripts/genesys/remove-engine-comments.ts +134 -134
- package/src/templates/scripts/genesys/run-mcp-inspector.bat +4 -4
- package/src/templates/scripts/genesys/storageProvider.ts +182 -182
- package/src/templates/scripts/genesys/validate-prefabs.ts +138 -138
- package/src/templates/src/index.ts +22 -22
- package/src/templates/src/templates/firstPerson/assets/default.genesys-scene +165 -165
- package/src/templates/src/templates/firstPerson/src/game.ts +39 -39
- package/src/templates/src/templates/firstPerson/src/player.ts +63 -63
- package/src/templates/src/templates/fps/assets/default.genesys-scene +9459 -9459
- package/src/templates/src/templates/fps/src/game.ts +39 -39
- package/src/templates/src/templates/fps/src/player.ts +69 -69
- package/src/templates/src/templates/fps/src/weapon.ts +54 -54
- package/src/templates/src/templates/freeCamera/assets/default.genesys-scene +165 -165
- package/src/templates/src/templates/freeCamera/src/game.ts +39 -39
- package/src/templates/src/templates/freeCamera/src/player.ts +45 -45
- package/src/templates/src/templates/sideScroller/assets/default.genesys-scene +121 -121
- package/src/templates/src/templates/sideScroller/src/const.ts +45 -45
- package/src/templates/src/templates/sideScroller/src/game.ts +122 -122
- package/src/templates/src/templates/sideScroller/src/level-generator.ts +361 -361
- package/src/templates/src/templates/sideScroller/src/player.ts +125 -125
- package/src/templates/src/templates/thirdPerson/assets/default.genesys-scene +165 -165
- package/src/templates/src/templates/thirdPerson/src/game.ts +39 -39
- package/src/templates/src/templates/thirdPerson/src/player.ts +61 -61
- package/src/templates/src/templates/vehicle/assets/default.genesys-scene +225 -225
- package/src/templates/src/templates/vehicle/src/base-vehicle.ts +145 -145
- package/src/templates/src/templates/vehicle/src/game.ts +43 -43
- package/src/templates/src/templates/vehicle/src/mesh-vehicle.ts +191 -191
- package/src/templates/src/templates/vehicle/src/player.ts +109 -109
- package/src/templates/src/templates/vehicle/src/primitive-vehicle.ts +266 -266
- package/src/templates/src/templates/vehicle/src/ui-hints.ts +101 -101
- package/src/templates/src/templates/vr-game/assets/default.genesys-scene +246 -246
- package/src/templates/src/templates/vr-game/src/auto-imports.ts +1 -1
- package/src/templates/src/templates/vr-game/src/game.ts +66 -66
- package/src/templates/src/templates/vr-game/src/sample-vr-actor.ts +26 -26
- package/src/templates/tsconfig.json +34 -34
- package/src/templates/vite.config.ts +52 -52
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { getProjectRoot, isDev } from './common.js';
|
|
2
|
-
|
|
3
|
-
const fileServerPort = !isDev ? 4000 : 4001;
|
|
4
|
-
|
|
5
|
-
async function main() {
|
|
6
|
-
try {
|
|
7
|
-
const rootResponse = await fetch(`http://localhost:${fileServerPort}/`);
|
|
8
|
-
|
|
9
|
-
if (!rootResponse.ok) {
|
|
10
|
-
console.log('❌ Failed to talk to the Genesys SDK App, please make sure it is running and open the project in it!');
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const projectPath = getProjectRoot();
|
|
15
|
-
|
|
16
|
-
console.log(`🔨 Building project: ${projectPath} ...`);
|
|
17
|
-
|
|
18
|
-
const buildResponse = await fetch(`http://localhost:${fileServerPort}/api/build-project`, {
|
|
19
|
-
method: 'POST',
|
|
20
|
-
headers: {
|
|
21
|
-
'Content-Type': 'application/json',
|
|
22
|
-
},
|
|
23
|
-
body: JSON.stringify({
|
|
24
|
-
projectPath,
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
const responseJson = await buildResponse.json();
|
|
28
|
-
if (!buildResponse.ok) {
|
|
29
|
-
console.log('❌ Failed to build project, please check the console for errors!');
|
|
30
|
-
console.log(responseJson);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/*
|
|
35
|
-
success: result.success,
|
|
36
|
-
message: result.message,
|
|
37
|
-
error: result.error
|
|
38
|
-
*/
|
|
39
|
-
if (!responseJson.success) {
|
|
40
|
-
console.log(`❌ Failed to build project:\n - ${responseJson.error ?? responseJson.message}`);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
console.log(`✅ ${responseJson.message}`);
|
|
45
|
-
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.log('❌ Failed to talk to the Genesys SDK App, please make sure it is running and open the project in it!');
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
main();
|
|
1
|
+
import { getProjectRoot, isDev } from './common.js';
|
|
2
|
+
|
|
3
|
+
const fileServerPort = !isDev ? 4000 : 4001;
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
try {
|
|
7
|
+
const rootResponse = await fetch(`http://localhost:${fileServerPort}/`);
|
|
8
|
+
|
|
9
|
+
if (!rootResponse.ok) {
|
|
10
|
+
console.log('❌ Failed to talk to the Genesys SDK App, please make sure it is running and open the project in it!');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const projectPath = getProjectRoot();
|
|
15
|
+
|
|
16
|
+
console.log(`🔨 Building project: ${projectPath} ...`);
|
|
17
|
+
|
|
18
|
+
const buildResponse = await fetch(`http://localhost:${fileServerPort}/api/build-project`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
projectPath,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
const responseJson = await buildResponse.json();
|
|
28
|
+
if (!buildResponse.ok) {
|
|
29
|
+
console.log('❌ Failed to build project, please check the console for errors!');
|
|
30
|
+
console.log(responseJson);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
success: result.success,
|
|
36
|
+
message: result.message,
|
|
37
|
+
error: result.error
|
|
38
|
+
*/
|
|
39
|
+
if (!responseJson.success) {
|
|
40
|
+
console.log(`❌ Failed to build project:\n - ${responseJson.error ?? responseJson.message}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`✅ ${responseJson.message}`);
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.log('❌ Failed to talk to the Genesys SDK App, please make sure it is running and open the project in it!');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
main();
|
|
@@ -1,272 +1,272 @@
|
|
|
1
|
-
import fs, { readFileSync } from 'fs';
|
|
2
|
-
import path, { resolve } from 'path';
|
|
3
|
-
|
|
4
|
-
import * as ENGINE from 'genesys.js';
|
|
5
|
-
import * as THREE from 'three';
|
|
6
|
-
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
|
|
7
|
-
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
8
|
-
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
9
|
-
|
|
10
|
-
import { mockBrowserEnvironment } from './mock.js';
|
|
11
|
-
|
|
12
|
-
import type { Transform } from './common.js';
|
|
13
|
-
|
|
14
|
-
mockBrowserEnvironment();
|
|
15
|
-
|
|
16
|
-
// Custom error types for better error handling
|
|
17
|
-
class GLBLoadError extends Error {
|
|
18
|
-
constructor(message: string, public readonly filePath: string) {
|
|
19
|
-
super(message);
|
|
20
|
-
this.name = 'GLBLoadError';
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class BoundingBoxCalculationError extends Error {
|
|
25
|
-
constructor(message: string) {
|
|
26
|
-
super(message);
|
|
27
|
-
this.name = 'BoundingBoxCalculationError';
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Interface for manifest entry
|
|
32
|
-
interface ManifestEntry {
|
|
33
|
-
readonly bounding_box: ENGINE.TBoundingBox;
|
|
34
|
-
readonly timestamp: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Interface for the entire manifest
|
|
38
|
-
interface BoundingBoxManifest {
|
|
39
|
-
[filePath: string]: ManifestEntry;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function verifyPath(path: string) {
|
|
43
|
-
if (path.startsWith(ENGINE.PROJECT_PATH_PREFIX) || path.startsWith(ENGINE.ENGINE_PATH_PREFIX)) {
|
|
44
|
-
throw new Error(`Expecting a valid path, not a project or engine path: ${path}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Helper function to load and parse a GLB file
|
|
50
|
-
* @param glbFilePath - Path to the GLB file
|
|
51
|
-
* @returns Promise resolving to the loaded GLTF scene
|
|
52
|
-
*/
|
|
53
|
-
async function loadGLBFile(glbFilePath: string): Promise<any> {
|
|
54
|
-
verifyPath(glbFilePath);
|
|
55
|
-
|
|
56
|
-
// Resolve the absolute path
|
|
57
|
-
const absolutePath = resolve(glbFilePath);
|
|
58
|
-
try {
|
|
59
|
-
// Read the GLB file
|
|
60
|
-
const glbData = readFileSync(absolutePath);
|
|
61
|
-
const arrayBuffer = glbData.buffer.slice(
|
|
62
|
-
glbData.byteOffset,
|
|
63
|
-
glbData.byteOffset + glbData.byteLength
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// Load the GLB using GLTFLoader
|
|
67
|
-
const loader = new GLTFLoader();
|
|
68
|
-
loader.setMeshoptDecoder(MeshoptDecoder);
|
|
69
|
-
const dracoLoader = new DRACOLoader();
|
|
70
|
-
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
|
|
71
|
-
loader.setDRACOLoader(dracoLoader);
|
|
72
|
-
const gltf = await loader.parseAsync(arrayBuffer, '');
|
|
73
|
-
|
|
74
|
-
if (!gltf.scene) {
|
|
75
|
-
throw new BoundingBoxCalculationError('No scene found in GLB file');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return gltf;
|
|
79
|
-
} catch (error) {
|
|
80
|
-
if (error instanceof Error) {
|
|
81
|
-
if (error.name === 'GLBLoadError' || error.name === 'BoundingBoxCalculationError') {
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
throw new GLBLoadError(`Failed to load GLB file: ${error.message}`, glbFilePath);
|
|
85
|
-
}
|
|
86
|
-
throw new GLBLoadError('Unknown error loading GLB file', glbFilePath);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Helper function to extract bounding box data from a Three.js Box3
|
|
92
|
-
* @param boundingBox - Three.js Box3 object
|
|
93
|
-
* @returns BoundingBox interface object
|
|
94
|
-
*/
|
|
95
|
-
function extractBoundingBoxData(boundingBox: THREE.Box3): ENGINE.TBoundingBox {
|
|
96
|
-
return ENGINE.DescriptionHelper.dumpBoundingBox(boundingBox);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Calculates the bounding box of a GLB mesh with applied transformations
|
|
101
|
-
* @param glbFilePath - Path to the GLB file
|
|
102
|
-
* @param transform - Transformation to apply (position, rotation, scale)
|
|
103
|
-
* @returns Promise resolving to the calculated bounding box
|
|
104
|
-
*/
|
|
105
|
-
async function calculateGLBBoundingBox(
|
|
106
|
-
glbFilePath: string,
|
|
107
|
-
transform: Transform
|
|
108
|
-
): Promise<ENGINE.TBoundingBox> {
|
|
109
|
-
const gltf = await loadGLBFile(glbFilePath);
|
|
110
|
-
|
|
111
|
-
// Create a new object to apply transformations
|
|
112
|
-
const transformedObject = new THREE.Object3D();
|
|
113
|
-
transformedObject.add(gltf.scene);
|
|
114
|
-
|
|
115
|
-
// Apply transformations
|
|
116
|
-
transform.position && transformedObject.position.set(...transform.position as [number, number, number]);
|
|
117
|
-
transform.rotation && transformedObject.rotation.set(...transform.rotation as [number, number, number]);
|
|
118
|
-
transform.scale && transformedObject.scale.set(...transform.scale as [number, number, number]);
|
|
119
|
-
|
|
120
|
-
// Update world matrix to ensure transformations are applied
|
|
121
|
-
transformedObject.updateMatrixWorld(true);
|
|
122
|
-
|
|
123
|
-
const boundingBox = ENGINE.GLTFMeshComponent.calcBoundingBoxFromGLTF(gltf);
|
|
124
|
-
return extractBoundingBoxData(boundingBox);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Calculates the original bounding box of a GLB mesh without any transformations
|
|
129
|
-
* @param glbFilePath - Path to the GLB file
|
|
130
|
-
* @returns Promise resolving to the original mesh bounding box
|
|
131
|
-
*/
|
|
132
|
-
async function calculateGLBOriginalBoundingBox(glbFilePath: string): Promise<ENGINE.TBoundingBox> {
|
|
133
|
-
const gltf = await loadGLBFile(glbFilePath);
|
|
134
|
-
|
|
135
|
-
const boundingBox = ENGINE.GLTFMeshComponent.calcBoundingBoxFromGLTF(gltf);
|
|
136
|
-
return extractBoundingBoxData(boundingBox);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Helper function to create a default transform
|
|
141
|
-
*/
|
|
142
|
-
function createTransform(
|
|
143
|
-
position: [number, number, number] = [0, 0, 0],
|
|
144
|
-
rotation: [number, number, number] = [0, 0, 0],
|
|
145
|
-
scale: [number, number, number] = [1, 1, 1]
|
|
146
|
-
): Transform {
|
|
147
|
-
return { position, rotation, scale };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Utility function to convert degrees to radians
|
|
152
|
-
*/
|
|
153
|
-
function degreesToRadians(degrees: number): number {
|
|
154
|
-
return degrees * (Math.PI / 180);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Fetches the bounding box data from the manifest file, and returns the bounding box data
|
|
160
|
-
* It internally keeps a bounding box manifest as a cache, and only recalculates the bounding box data if the file has been modified since last calculation
|
|
161
|
-
* @param manifestFile - Path to the manifest JSON file, must be a valid path, not `@project` or `@engine` path
|
|
162
|
-
* @param gltfPaths - Dictionary of gltf key to GLTF file paths, key in theory can be any ID of a gltf mesh.
|
|
163
|
-
*/
|
|
164
|
-
async function fetchBoundingBoxData(manifestFile: string, gltfPaths: {[key: string]: string}): Promise<{[filePath: string]: ENGINE.TBoundingBox}> {
|
|
165
|
-
verifyPath(manifestFile);
|
|
166
|
-
manifestFile = resolve(manifestFile);
|
|
167
|
-
try {
|
|
168
|
-
// Read existing manifest or create empty one
|
|
169
|
-
let manifest: BoundingBoxManifest = {};
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
if (fs.existsSync(manifestFile)) {
|
|
173
|
-
const manifestContent = fs.readFileSync(manifestFile, 'utf-8');
|
|
174
|
-
manifest = JSON.parse(manifestContent) as BoundingBoxManifest;
|
|
175
|
-
console.log(`Loaded existing manifest with ${Object.keys(manifest).length} entries`);
|
|
176
|
-
} else {
|
|
177
|
-
console.log('Creating new manifest file');
|
|
178
|
-
}
|
|
179
|
-
} catch (error) {
|
|
180
|
-
console.warn(`Failed to read existing manifest, starting fresh: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
-
manifest = {};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Process each GLTF file
|
|
185
|
-
const updatedManifest: BoundingBoxManifest = { ...manifest };
|
|
186
|
-
let processedCount = 0;
|
|
187
|
-
let skippedCount = 0;
|
|
188
|
-
|
|
189
|
-
for (const [key, gltfPath] of Object.entries(gltfPaths)) {
|
|
190
|
-
try {
|
|
191
|
-
// Resolve absolute path for consistent comparison
|
|
192
|
-
const absolutePath = resolve(gltfPath);
|
|
193
|
-
|
|
194
|
-
// Check if file exists
|
|
195
|
-
if (!fs.existsSync(absolutePath)) {
|
|
196
|
-
console.warn(`Warning: File not found: ${gltfPath}`);
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Get file stats to check modification time
|
|
201
|
-
const stats = fs.statSync(absolutePath);
|
|
202
|
-
const currentTimestamp = stats.mtime.toISOString();
|
|
203
|
-
|
|
204
|
-
// Check if we need to update this file
|
|
205
|
-
const existingEntry = manifest[key];
|
|
206
|
-
if (existingEntry && existingEntry.timestamp === currentTimestamp) {
|
|
207
|
-
console.log(`Skipping ${gltfPath} (unchanged)`);
|
|
208
|
-
skippedCount++;
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Calculate bounding box for new/modified file
|
|
213
|
-
console.log(`Processing ${gltfPath}...`);
|
|
214
|
-
const boundingBox = await calculateGLBOriginalBoundingBox(absolutePath);
|
|
215
|
-
|
|
216
|
-
// Update manifest entry
|
|
217
|
-
updatedManifest[key] = {
|
|
218
|
-
bounding_box: boundingBox,
|
|
219
|
-
timestamp: currentTimestamp
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
processedCount++;
|
|
223
|
-
console.log(`✓ Updated bounding box for ${gltfPath}`);
|
|
224
|
-
|
|
225
|
-
} catch (error) {
|
|
226
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
227
|
-
console.error(`Failed to process ${gltfPath}: ${errorMessage}`);
|
|
228
|
-
// Continue processing other files even if one fails
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Save updated manifest if any changes were made
|
|
233
|
-
if (processedCount > 0) {
|
|
234
|
-
// Ensure directory exists
|
|
235
|
-
const manifestDir = path.dirname(manifestFile);
|
|
236
|
-
if (!fs.existsSync(manifestDir)) {
|
|
237
|
-
fs.mkdirSync(manifestDir, { recursive: true });
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Write manifest with pretty formatting
|
|
241
|
-
fs.writeFileSync(manifestFile, JSON.stringify(updatedManifest, null, 2), 'utf-8');
|
|
242
|
-
console.log(`\n✓ Manifest updated: ${processedCount} files processed, ${skippedCount} files skipped`);
|
|
243
|
-
console.log(`Manifest saved to: ${manifestFile}`);
|
|
244
|
-
} else {
|
|
245
|
-
console.log(`\n✓ No updates needed: ${skippedCount} files already up to date`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return Object.fromEntries(
|
|
249
|
-
Object.entries(gltfPaths).map(([key, absPath]) => [key, updatedManifest[key].bounding_box])
|
|
250
|
-
);
|
|
251
|
-
} catch (error) {
|
|
252
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
253
|
-
throw new Error(`Failed to update bounding box manifest: ${errorMessage}`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Export the main functions for use as a module
|
|
258
|
-
export {
|
|
259
|
-
calculateGLBBoundingBox,
|
|
260
|
-
calculateGLBOriginalBoundingBox,
|
|
261
|
-
createTransform,
|
|
262
|
-
degreesToRadians,
|
|
263
|
-
fetchBoundingBoxData,
|
|
264
|
-
extractBoundingBoxData,
|
|
265
|
-
GLBLoadError,
|
|
266
|
-
BoundingBoxCalculationError
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
export type { Transform, ManifestEntry, BoundingBoxManifest };
|
|
270
|
-
export { BoundingBoxSchema } from 'genesys.js';
|
|
271
|
-
|
|
272
|
-
|
|
1
|
+
import fs, { readFileSync } from 'fs';
|
|
2
|
+
import path, { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
import * as ENGINE from '@directivegames/genesys.js';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
|
|
7
|
+
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
8
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
9
|
+
|
|
10
|
+
import { mockBrowserEnvironment } from './mock.js';
|
|
11
|
+
|
|
12
|
+
import type { Transform } from './common.js';
|
|
13
|
+
|
|
14
|
+
mockBrowserEnvironment();
|
|
15
|
+
|
|
16
|
+
// Custom error types for better error handling
|
|
17
|
+
class GLBLoadError extends Error {
|
|
18
|
+
constructor(message: string, public readonly filePath: string) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'GLBLoadError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class BoundingBoxCalculationError extends Error {
|
|
25
|
+
constructor(message: string) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'BoundingBoxCalculationError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Interface for manifest entry
|
|
32
|
+
interface ManifestEntry {
|
|
33
|
+
readonly bounding_box: ENGINE.TBoundingBox;
|
|
34
|
+
readonly timestamp: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Interface for the entire manifest
|
|
38
|
+
interface BoundingBoxManifest {
|
|
39
|
+
[filePath: string]: ManifestEntry;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function verifyPath(path: string) {
|
|
43
|
+
if (path.startsWith(ENGINE.PROJECT_PATH_PREFIX) || path.startsWith(ENGINE.ENGINE_PATH_PREFIX)) {
|
|
44
|
+
throw new Error(`Expecting a valid path, not a project or engine path: ${path}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Helper function to load and parse a GLB file
|
|
50
|
+
* @param glbFilePath - Path to the GLB file
|
|
51
|
+
* @returns Promise resolving to the loaded GLTF scene
|
|
52
|
+
*/
|
|
53
|
+
async function loadGLBFile(glbFilePath: string): Promise<any> {
|
|
54
|
+
verifyPath(glbFilePath);
|
|
55
|
+
|
|
56
|
+
// Resolve the absolute path
|
|
57
|
+
const absolutePath = resolve(glbFilePath);
|
|
58
|
+
try {
|
|
59
|
+
// Read the GLB file
|
|
60
|
+
const glbData = readFileSync(absolutePath);
|
|
61
|
+
const arrayBuffer = glbData.buffer.slice(
|
|
62
|
+
glbData.byteOffset,
|
|
63
|
+
glbData.byteOffset + glbData.byteLength
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Load the GLB using GLTFLoader
|
|
67
|
+
const loader = new GLTFLoader();
|
|
68
|
+
loader.setMeshoptDecoder(MeshoptDecoder);
|
|
69
|
+
const dracoLoader = new DRACOLoader();
|
|
70
|
+
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
|
|
71
|
+
loader.setDRACOLoader(dracoLoader);
|
|
72
|
+
const gltf = await loader.parseAsync(arrayBuffer, '');
|
|
73
|
+
|
|
74
|
+
if (!gltf.scene) {
|
|
75
|
+
throw new BoundingBoxCalculationError('No scene found in GLB file');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return gltf;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error instanceof Error) {
|
|
81
|
+
if (error.name === 'GLBLoadError' || error.name === 'BoundingBoxCalculationError') {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
throw new GLBLoadError(`Failed to load GLB file: ${error.message}`, glbFilePath);
|
|
85
|
+
}
|
|
86
|
+
throw new GLBLoadError('Unknown error loading GLB file', glbFilePath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Helper function to extract bounding box data from a Three.js Box3
|
|
92
|
+
* @param boundingBox - Three.js Box3 object
|
|
93
|
+
* @returns BoundingBox interface object
|
|
94
|
+
*/
|
|
95
|
+
function extractBoundingBoxData(boundingBox: THREE.Box3): ENGINE.TBoundingBox {
|
|
96
|
+
return ENGINE.DescriptionHelper.dumpBoundingBox(boundingBox);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Calculates the bounding box of a GLB mesh with applied transformations
|
|
101
|
+
* @param glbFilePath - Path to the GLB file
|
|
102
|
+
* @param transform - Transformation to apply (position, rotation, scale)
|
|
103
|
+
* @returns Promise resolving to the calculated bounding box
|
|
104
|
+
*/
|
|
105
|
+
async function calculateGLBBoundingBox(
|
|
106
|
+
glbFilePath: string,
|
|
107
|
+
transform: Transform
|
|
108
|
+
): Promise<ENGINE.TBoundingBox> {
|
|
109
|
+
const gltf = await loadGLBFile(glbFilePath);
|
|
110
|
+
|
|
111
|
+
// Create a new object to apply transformations
|
|
112
|
+
const transformedObject = new THREE.Object3D();
|
|
113
|
+
transformedObject.add(gltf.scene);
|
|
114
|
+
|
|
115
|
+
// Apply transformations
|
|
116
|
+
transform.position && transformedObject.position.set(...transform.position as [number, number, number]);
|
|
117
|
+
transform.rotation && transformedObject.rotation.set(...transform.rotation as [number, number, number]);
|
|
118
|
+
transform.scale && transformedObject.scale.set(...transform.scale as [number, number, number]);
|
|
119
|
+
|
|
120
|
+
// Update world matrix to ensure transformations are applied
|
|
121
|
+
transformedObject.updateMatrixWorld(true);
|
|
122
|
+
|
|
123
|
+
const boundingBox = ENGINE.GLTFMeshComponent.calcBoundingBoxFromGLTF(gltf);
|
|
124
|
+
return extractBoundingBoxData(boundingBox);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Calculates the original bounding box of a GLB mesh without any transformations
|
|
129
|
+
* @param glbFilePath - Path to the GLB file
|
|
130
|
+
* @returns Promise resolving to the original mesh bounding box
|
|
131
|
+
*/
|
|
132
|
+
async function calculateGLBOriginalBoundingBox(glbFilePath: string): Promise<ENGINE.TBoundingBox> {
|
|
133
|
+
const gltf = await loadGLBFile(glbFilePath);
|
|
134
|
+
|
|
135
|
+
const boundingBox = ENGINE.GLTFMeshComponent.calcBoundingBoxFromGLTF(gltf);
|
|
136
|
+
return extractBoundingBoxData(boundingBox);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Helper function to create a default transform
|
|
141
|
+
*/
|
|
142
|
+
function createTransform(
|
|
143
|
+
position: [number, number, number] = [0, 0, 0],
|
|
144
|
+
rotation: [number, number, number] = [0, 0, 0],
|
|
145
|
+
scale: [number, number, number] = [1, 1, 1]
|
|
146
|
+
): Transform {
|
|
147
|
+
return { position, rotation, scale };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Utility function to convert degrees to radians
|
|
152
|
+
*/
|
|
153
|
+
function degreesToRadians(degrees: number): number {
|
|
154
|
+
return degrees * (Math.PI / 180);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Fetches the bounding box data from the manifest file, and returns the bounding box data
|
|
160
|
+
* It internally keeps a bounding box manifest as a cache, and only recalculates the bounding box data if the file has been modified since last calculation
|
|
161
|
+
* @param manifestFile - Path to the manifest JSON file, must be a valid path, not `@project` or `@engine` path
|
|
162
|
+
* @param gltfPaths - Dictionary of gltf key to GLTF file paths, key in theory can be any ID of a gltf mesh.
|
|
163
|
+
*/
|
|
164
|
+
async function fetchBoundingBoxData(manifestFile: string, gltfPaths: {[key: string]: string}): Promise<{[filePath: string]: ENGINE.TBoundingBox}> {
|
|
165
|
+
verifyPath(manifestFile);
|
|
166
|
+
manifestFile = resolve(manifestFile);
|
|
167
|
+
try {
|
|
168
|
+
// Read existing manifest or create empty one
|
|
169
|
+
let manifest: BoundingBoxManifest = {};
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
if (fs.existsSync(manifestFile)) {
|
|
173
|
+
const manifestContent = fs.readFileSync(manifestFile, 'utf-8');
|
|
174
|
+
manifest = JSON.parse(manifestContent) as BoundingBoxManifest;
|
|
175
|
+
console.log(`Loaded existing manifest with ${Object.keys(manifest).length} entries`);
|
|
176
|
+
} else {
|
|
177
|
+
console.log('Creating new manifest file');
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.warn(`Failed to read existing manifest, starting fresh: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
+
manifest = {};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Process each GLTF file
|
|
185
|
+
const updatedManifest: BoundingBoxManifest = { ...manifest };
|
|
186
|
+
let processedCount = 0;
|
|
187
|
+
let skippedCount = 0;
|
|
188
|
+
|
|
189
|
+
for (const [key, gltfPath] of Object.entries(gltfPaths)) {
|
|
190
|
+
try {
|
|
191
|
+
// Resolve absolute path for consistent comparison
|
|
192
|
+
const absolutePath = resolve(gltfPath);
|
|
193
|
+
|
|
194
|
+
// Check if file exists
|
|
195
|
+
if (!fs.existsSync(absolutePath)) {
|
|
196
|
+
console.warn(`Warning: File not found: ${gltfPath}`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Get file stats to check modification time
|
|
201
|
+
const stats = fs.statSync(absolutePath);
|
|
202
|
+
const currentTimestamp = stats.mtime.toISOString();
|
|
203
|
+
|
|
204
|
+
// Check if we need to update this file
|
|
205
|
+
const existingEntry = manifest[key];
|
|
206
|
+
if (existingEntry && existingEntry.timestamp === currentTimestamp) {
|
|
207
|
+
console.log(`Skipping ${gltfPath} (unchanged)`);
|
|
208
|
+
skippedCount++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Calculate bounding box for new/modified file
|
|
213
|
+
console.log(`Processing ${gltfPath}...`);
|
|
214
|
+
const boundingBox = await calculateGLBOriginalBoundingBox(absolutePath);
|
|
215
|
+
|
|
216
|
+
// Update manifest entry
|
|
217
|
+
updatedManifest[key] = {
|
|
218
|
+
bounding_box: boundingBox,
|
|
219
|
+
timestamp: currentTimestamp
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
processedCount++;
|
|
223
|
+
console.log(`✓ Updated bounding box for ${gltfPath}`);
|
|
224
|
+
|
|
225
|
+
} catch (error) {
|
|
226
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
227
|
+
console.error(`Failed to process ${gltfPath}: ${errorMessage}`);
|
|
228
|
+
// Continue processing other files even if one fails
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Save updated manifest if any changes were made
|
|
233
|
+
if (processedCount > 0) {
|
|
234
|
+
// Ensure directory exists
|
|
235
|
+
const manifestDir = path.dirname(manifestFile);
|
|
236
|
+
if (!fs.existsSync(manifestDir)) {
|
|
237
|
+
fs.mkdirSync(manifestDir, { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Write manifest with pretty formatting
|
|
241
|
+
fs.writeFileSync(manifestFile, JSON.stringify(updatedManifest, null, 2), 'utf-8');
|
|
242
|
+
console.log(`\n✓ Manifest updated: ${processedCount} files processed, ${skippedCount} files skipped`);
|
|
243
|
+
console.log(`Manifest saved to: ${manifestFile}`);
|
|
244
|
+
} else {
|
|
245
|
+
console.log(`\n✓ No updates needed: ${skippedCount} files already up to date`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return Object.fromEntries(
|
|
249
|
+
Object.entries(gltfPaths).map(([key, absPath]) => [key, updatedManifest[key].bounding_box])
|
|
250
|
+
);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
253
|
+
throw new Error(`Failed to update bounding box manifest: ${errorMessage}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Export the main functions for use as a module
|
|
258
|
+
export {
|
|
259
|
+
calculateGLBBoundingBox,
|
|
260
|
+
calculateGLBOriginalBoundingBox,
|
|
261
|
+
createTransform,
|
|
262
|
+
degreesToRadians,
|
|
263
|
+
fetchBoundingBoxData,
|
|
264
|
+
extractBoundingBoxData,
|
|
265
|
+
GLBLoadError,
|
|
266
|
+
BoundingBoxCalculationError
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export type { Transform, ManifestEntry, BoundingBoxManifest };
|
|
270
|
+
export { BoundingBoxSchema } from '@directivegames/genesys.js';
|
|
271
|
+
|
|
272
|
+
|