nova64 0.2.5 → 0.2.7
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 +25 -8
- package/bin/nova64.js +165 -0
- package/dist/assets/console-CY_kygm3.js +14 -0
- package/dist/assets/console-CY_kygm3.js.map +1 -0
- package/dist/assets/main-l0sNRNKZ.js.map +1 -0
- package/dist/assets/sky/studio/nx.png +0 -0
- package/dist/assets/sky/studio/ny.png +0 -0
- package/dist/assets/sky/studio/nz.png +0 -0
- package/dist/assets/sky/studio/px.png +0 -0
- package/dist/assets/sky/studio/py.png +0 -0
- package/dist/assets/sky/studio/pz.png +0 -0
- package/dist/assets/vanilla-Dcuy32gi.js +2 -0
- package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
- package/dist/console.html +899 -0
- package/dist/docs/BENCHMARK.md +77 -0
- package/dist/docs/CHEATSHEET.md +255 -0
- package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
- package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
- package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
- package/dist/docs/FREE_GLB_ASSETS.md +330 -0
- package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
- package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
- package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
- package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
- package/dist/docs/MIGRATION_GUIDE.md +553 -0
- package/dist/docs/NOVA64_3D_API.md +356 -0
- package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
- package/dist/docs/NOVA64_UI_API.md +503 -0
- package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
- package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
- package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
- package/dist/docs/api-3d.html +750 -0
- package/dist/docs/api-effects.html +385 -0
- package/dist/docs/api-improvements.md +121 -0
- package/dist/docs/api-skybox.html +407 -0
- package/dist/docs/api-sprites.html +321 -0
- package/dist/docs/api-voxel.html +337 -0
- package/dist/docs/api.html +543 -0
- package/dist/docs/assets.html +306 -0
- package/dist/docs/audio.html +340 -0
- package/dist/docs/blogs.html +286 -0
- package/dist/docs/collision.html +316 -0
- package/dist/docs/console.html +247 -0
- package/dist/docs/editor.html +297 -0
- package/dist/docs/font.html +247 -0
- package/dist/docs/framebuffer.html +247 -0
- package/dist/docs/fullscreen-button.html +297 -0
- package/dist/docs/gpu-systems.html +247 -0
- package/dist/docs/index.html +580 -0
- package/dist/docs/input.html +491 -0
- package/dist/docs/physics.html +311 -0
- package/dist/docs/screens.html +311 -0
- package/dist/docs/storage.html +311 -0
- package/dist/docs/textinput.html +332 -0
- package/dist/docs/ui.html +488 -0
- package/dist/examples/3d-advanced/code.js +695 -0
- package/dist/examples/adventure-comic-3d/code.js +342 -0
- package/dist/examples/audio-lab/code.js +150 -0
- package/dist/examples/boids-flocking/code.js +270 -0
- package/dist/examples/crystal-cathedral-3d/code.js +706 -0
- package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
- package/dist/examples/demoscene/README.md +192 -0
- package/dist/examples/demoscene/code.js +1081 -0
- package/dist/examples/demoscene/meta.json +21 -0
- package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
- package/dist/examples/f-zero-nova-3d/code.js +865 -0
- package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
- package/dist/examples/fps-demo-3d/code.js +744 -0
- package/dist/examples/game-of-life-3d/code.js +338 -0
- package/dist/examples/generative-art/code.js +632 -0
- package/dist/examples/hello-3d/code.js +325 -0
- package/dist/examples/hello-skybox/code.js +183 -0
- package/dist/examples/hello-world/code.js +19 -0
- package/dist/examples/input-showcase/code.js +109 -0
- package/dist/examples/instancing-demo/code.js +315 -0
- package/dist/examples/minecraft-demo/code.js +387 -0
- package/dist/examples/model-viewer-3d/code.js +114 -0
- package/dist/examples/mystical-realm-3d/code.js +1203 -0
- package/dist/examples/nature-explorer-3d/code.js +1318 -0
- package/dist/examples/particles-demo/code.js +522 -0
- package/dist/examples/pbr-showcase/code.js +140 -0
- package/dist/examples/physics-demo-3d/code.js +948 -0
- package/dist/examples/screen-demo/code.js +267 -0
- package/dist/examples/shooter-demo-3d/code.js +1286 -0
- package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
- package/dist/examples/space-combat-3d/README.md +135 -0
- package/dist/examples/space-combat-3d/code.js +1332 -0
- package/dist/examples/space-harrier-3d/code.js +923 -0
- package/dist/examples/star-fox-nova-3d/code.js +1116 -0
- package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
- package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
- package/dist/examples/storage-quest/code.js +209 -0
- package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
- package/dist/examples/strider-demo-3d/cache-test.html +132 -0
- package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
- package/dist/examples/strider-demo-3d/code-old.js +1537 -0
- package/dist/examples/strider-demo-3d/code.js +1462 -0
- package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
- package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
- package/dist/examples/super-plumber-64/README.md +128 -0
- package/dist/examples/super-plumber-64/code.js +1185 -0
- package/dist/examples/super-plumber-64/index.html +88 -0
- package/dist/examples/test-2d-overlay/code.js +32 -0
- package/dist/examples/test-font/code.js +51 -0
- package/dist/examples/test-minimal/code.js +21 -0
- package/dist/examples/ui-demo/code.js +306 -0
- package/dist/examples/wing-commander-space/README.md +180 -0
- package/dist/examples/wing-commander-space/code.js +1285 -0
- package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
- package/dist/examples/wizardry-3d/code.js +3928 -0
- package/dist/index.html +666 -0
- package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/dist/os9-shell/index.html +23 -0
- package/dist/os9-shell/nova-icon.svg +12 -0
- package/dist/runtime/api-2d.js +1158 -0
- package/dist/runtime/api-3d/camera.js +73 -0
- package/dist/runtime/api-3d/instancing.js +180 -0
- package/dist/runtime/api-3d/lights.js +51 -0
- package/dist/runtime/api-3d/materials.js +47 -0
- package/dist/runtime/api-3d/models.js +84 -0
- package/dist/runtime/api-3d/particles.js +296 -0
- package/dist/runtime/api-3d/pbr.js +113 -0
- package/dist/runtime/api-3d/primitives.js +304 -0
- package/dist/runtime/api-3d/scene.js +169 -0
- package/dist/runtime/api-3d/transforms.js +161 -0
- package/dist/runtime/api-3d.js +166 -0
- package/dist/runtime/api-effects.js +840 -0
- package/dist/runtime/api-gameutils.js +476 -0
- package/dist/runtime/api-generative.js +610 -0
- package/dist/runtime/api-presets.js +85 -0
- package/dist/runtime/api-skybox.js +232 -0
- package/dist/runtime/api-sprites.js +100 -0
- package/dist/runtime/api-voxel.js +712 -0
- package/dist/runtime/api.js +201 -0
- package/dist/runtime/assets.js +27 -0
- package/dist/runtime/audio.js +114 -0
- package/dist/runtime/collision.js +47 -0
- package/dist/runtime/console.js +101 -0
- package/dist/runtime/editor.js +233 -0
- package/dist/runtime/font.js +233 -0
- package/dist/runtime/framebuffer.js +28 -0
- package/dist/runtime/fullscreen-button.js +185 -0
- package/dist/runtime/gpu-canvas2d.js +47 -0
- package/dist/runtime/gpu-threejs.js +643 -0
- package/dist/runtime/gpu-webgl2.js +310 -0
- package/dist/runtime/index.d.ts +682 -0
- package/dist/runtime/index.js +22 -0
- package/dist/runtime/input.js +225 -0
- package/dist/runtime/logger.js +60 -0
- package/dist/runtime/physics.js +101 -0
- package/dist/runtime/screens.js +213 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/store.js +151 -0
- package/dist/runtime/textinput.js +68 -0
- package/dist/runtime/ui/buttons.js +124 -0
- package/dist/runtime/ui/panels.js +105 -0
- package/dist/runtime/ui/text.js +86 -0
- package/dist/runtime/ui/widgets.js +141 -0
- package/dist/runtime/ui.js +111 -0
- package/index.html +6 -1
- package/package.json +9 -2
- package/public/assets/sky/studio/nx.png +0 -0
- package/public/assets/sky/studio/ny.png +0 -0
- package/public/assets/sky/studio/nz.png +0 -0
- package/public/assets/sky/studio/px.png +0 -0
- package/public/assets/sky/studio/py.png +0 -0
- package/public/assets/sky/studio/pz.png +0 -0
- package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/public/os9-shell/index.html +10 -1
- package/runtime/api-2d.js +301 -21
- package/runtime/api-3d/pbr.js +45 -1
- package/runtime/api-3d.js +1 -0
- package/runtime/api-effects.js +90 -3
- package/runtime/api-gameutils.js +476 -0
- package/runtime/api-generative.js +610 -0
- package/runtime/api-skybox.js +54 -0
- package/runtime/api-voxel.js +139 -28
- package/runtime/gpu-threejs.js +13 -9
- package/runtime/ui.js +2 -2
- package/src/main.js +20 -0
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// runtime/api-3d/camera.js
|
|
2
|
+
// Camera controls, fog, and scene lighting helpers
|
|
3
|
+
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
export function cameraModule({ scene, camera, gpu }) {
|
|
7
|
+
function setCameraPosition(x, y, z) {
|
|
8
|
+
gpu.setCameraPosition(x, y, z);
|
|
9
|
+
}
|
|
10
|
+
function setCameraTarget(x, y, z) {
|
|
11
|
+
gpu.setCameraTarget(x, y, z);
|
|
12
|
+
}
|
|
13
|
+
function setCameraFOV(fov) {
|
|
14
|
+
gpu.setCameraFOV(fov);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setCameraLookAt(direction) {
|
|
18
|
+
if (Array.isArray(direction) && direction.length >= 3) {
|
|
19
|
+
camera.lookAt(
|
|
20
|
+
camera.position.x + direction[0],
|
|
21
|
+
camera.position.y + direction[1],
|
|
22
|
+
camera.position.z + direction[2]
|
|
23
|
+
);
|
|
24
|
+
} else if (typeof direction === 'object' && direction.x !== undefined) {
|
|
25
|
+
camera.lookAt(
|
|
26
|
+
camera.position.x + direction.x,
|
|
27
|
+
camera.position.y + direction.y,
|
|
28
|
+
camera.position.z + direction.z
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setFog(color, near = 10, far = 50) {
|
|
34
|
+
gpu.setFog(color, near, far);
|
|
35
|
+
}
|
|
36
|
+
function clearFog() {
|
|
37
|
+
if (scene) scene.fog = null;
|
|
38
|
+
}
|
|
39
|
+
function setLightDirection(x, y, z) {
|
|
40
|
+
gpu.setLightDirection(x, y, z);
|
|
41
|
+
}
|
|
42
|
+
function setLightColor(color) {
|
|
43
|
+
if (gpu.setLightColor) gpu.setLightColor(color);
|
|
44
|
+
}
|
|
45
|
+
function setAmbientLight(color, intensity) {
|
|
46
|
+
if (gpu.setAmbientLight) gpu.setAmbientLight(color, intensity);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setDirectionalLight(direction, color = 0xffffff, intensity = 1.0) {
|
|
50
|
+
const toRemove = scene.children.filter(c => c.type === 'DirectionalLight');
|
|
51
|
+
toRemove.forEach(l => scene.remove(l));
|
|
52
|
+
const light = new THREE.DirectionalLight(color, intensity);
|
|
53
|
+
if (Array.isArray(direction) && direction.length >= 3) {
|
|
54
|
+
light.position.set(direction[0], direction[1], direction[2]);
|
|
55
|
+
} else {
|
|
56
|
+
light.position.set(1, 2, 1);
|
|
57
|
+
}
|
|
58
|
+
scene.add(light);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
setCameraPosition,
|
|
63
|
+
setCameraTarget,
|
|
64
|
+
setCameraFOV,
|
|
65
|
+
setCameraLookAt,
|
|
66
|
+
setFog,
|
|
67
|
+
clearFog,
|
|
68
|
+
setLightDirection,
|
|
69
|
+
setLightColor,
|
|
70
|
+
setAmbientLight,
|
|
71
|
+
setDirectionalLight,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// runtime/api-3d/instancing.js
|
|
2
|
+
// GPU instancing (InstancedMesh) and LOD (Level of Detail) system
|
|
3
|
+
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
export function instancingModule({
|
|
7
|
+
scene,
|
|
8
|
+
camera,
|
|
9
|
+
gpu,
|
|
10
|
+
instancedMeshes,
|
|
11
|
+
lodObjects,
|
|
12
|
+
counters,
|
|
13
|
+
getCachedMaterial,
|
|
14
|
+
}) {
|
|
15
|
+
const _iDummy = new THREE.Object3D();
|
|
16
|
+
|
|
17
|
+
// --- Instancing ---
|
|
18
|
+
|
|
19
|
+
function createInstancedMesh(shape = 'cube', count = 100, color = 0xffffff, options = {}) {
|
|
20
|
+
let geometry;
|
|
21
|
+
switch (shape) {
|
|
22
|
+
case 'sphere':
|
|
23
|
+
geometry = gpu.createSphereGeometry(options.size || 1, options.segments || 6);
|
|
24
|
+
break;
|
|
25
|
+
case 'plane':
|
|
26
|
+
geometry = gpu.createPlaneGeometry(options.width || 1, options.height || 1);
|
|
27
|
+
break;
|
|
28
|
+
case 'cylinder':
|
|
29
|
+
geometry = gpu.createCylinderGeometry(
|
|
30
|
+
options.size || 0.5,
|
|
31
|
+
options.size || 0.5,
|
|
32
|
+
options.height || 2,
|
|
33
|
+
8
|
|
34
|
+
);
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
geometry = gpu.createBoxGeometry(options.size || 1, options.size || 1, options.size || 1);
|
|
38
|
+
}
|
|
39
|
+
const material = getCachedMaterial({ color, ...options });
|
|
40
|
+
const mesh = new THREE.InstancedMesh(geometry, material, count);
|
|
41
|
+
mesh.castShadow = mesh.receiveShadow = true;
|
|
42
|
+
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
|
|
43
|
+
scene.add(mesh);
|
|
44
|
+
const id = ++counters.instance;
|
|
45
|
+
instancedMeshes.set(id, { mesh, count });
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setInstanceTransform(
|
|
50
|
+
instancedId,
|
|
51
|
+
index,
|
|
52
|
+
x = 0,
|
|
53
|
+
y = 0,
|
|
54
|
+
z = 0,
|
|
55
|
+
rx = 0,
|
|
56
|
+
ry = 0,
|
|
57
|
+
rz = 0,
|
|
58
|
+
sx = 1,
|
|
59
|
+
sy = 1,
|
|
60
|
+
sz = 1
|
|
61
|
+
) {
|
|
62
|
+
const entry = instancedMeshes.get(instancedId);
|
|
63
|
+
if (!entry || index < 0 || index >= entry.count) return false;
|
|
64
|
+
_iDummy.position.set(x, y, z);
|
|
65
|
+
_iDummy.rotation.set(rx, ry, rz);
|
|
66
|
+
_iDummy.scale.set(sx, sy, sz);
|
|
67
|
+
_iDummy.updateMatrix();
|
|
68
|
+
entry.mesh.setMatrixAt(index, _iDummy.matrix);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function setInstanceColor(instancedId, index, color) {
|
|
73
|
+
const entry = instancedMeshes.get(instancedId);
|
|
74
|
+
if (!entry) return false;
|
|
75
|
+
entry.mesh.setColorAt(index, new THREE.Color(color));
|
|
76
|
+
if (entry.mesh.instanceColor) entry.mesh.instanceColor.needsUpdate = true;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function finalizeInstances(instancedId) {
|
|
81
|
+
const entry = instancedMeshes.get(instancedId);
|
|
82
|
+
if (!entry) return false;
|
|
83
|
+
entry.mesh.instanceMatrix.needsUpdate = true;
|
|
84
|
+
try {
|
|
85
|
+
entry.mesh.computeBoundingSphere();
|
|
86
|
+
} catch {
|
|
87
|
+
/* mock or incomplete geometry — skip */
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function removeInstancedMesh(instancedId) {
|
|
93
|
+
const entry = instancedMeshes.get(instancedId);
|
|
94
|
+
if (!entry) return false;
|
|
95
|
+
scene.remove(entry.mesh);
|
|
96
|
+
entry.mesh.geometry?.dispose();
|
|
97
|
+
entry.mesh.material?.dispose();
|
|
98
|
+
instancedMeshes.delete(instancedId);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- LOD ---
|
|
103
|
+
|
|
104
|
+
function createLODMesh(levels = [], position = [0, 0, 0]) {
|
|
105
|
+
const lod = new THREE.LOD();
|
|
106
|
+
for (const {
|
|
107
|
+
shape = 'cube',
|
|
108
|
+
size = 1,
|
|
109
|
+
color = 0xffffff,
|
|
110
|
+
distance = 0,
|
|
111
|
+
options = {},
|
|
112
|
+
} of levels) {
|
|
113
|
+
let geometry;
|
|
114
|
+
switch (shape) {
|
|
115
|
+
case 'sphere':
|
|
116
|
+
geometry = gpu.createSphereGeometry(size, options.segments || 8);
|
|
117
|
+
break;
|
|
118
|
+
case 'plane':
|
|
119
|
+
geometry = gpu.createPlaneGeometry(size, options.height || size);
|
|
120
|
+
break;
|
|
121
|
+
case 'cylinder':
|
|
122
|
+
geometry = gpu.createCylinderGeometry(
|
|
123
|
+
size * 0.5,
|
|
124
|
+
size * 0.5,
|
|
125
|
+
options.height || size * 2,
|
|
126
|
+
options.segments || 8
|
|
127
|
+
);
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
geometry = gpu.createBoxGeometry(size, size, size);
|
|
131
|
+
}
|
|
132
|
+
const mat = getCachedMaterial({ color, ...options });
|
|
133
|
+
const mesh = new THREE.Mesh(geometry, mat);
|
|
134
|
+
mesh.castShadow = mesh.receiveShadow = true;
|
|
135
|
+
lod.addLevel(mesh, distance);
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(position) && position.length >= 3) {
|
|
138
|
+
lod.position.set(position[0], position[1], position[2]);
|
|
139
|
+
}
|
|
140
|
+
scene.add(lod);
|
|
141
|
+
const id = ++counters.lod;
|
|
142
|
+
lodObjects.set(id, lod);
|
|
143
|
+
return id;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function setLODPosition(lodId, x, y, z) {
|
|
147
|
+
const lod = lodObjects.get(lodId);
|
|
148
|
+
if (!lod) return false;
|
|
149
|
+
lod.position.set(x, y, z);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function removeLODMesh(lodId) {
|
|
154
|
+
const lod = lodObjects.get(lodId);
|
|
155
|
+
if (!lod) return false;
|
|
156
|
+
scene.remove(lod);
|
|
157
|
+
for (const { object } of lod.levels) {
|
|
158
|
+
object.geometry?.dispose();
|
|
159
|
+
object.material?.dispose();
|
|
160
|
+
}
|
|
161
|
+
lodObjects.delete(lodId);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function updateLODs() {
|
|
166
|
+
for (const lod of lodObjects.values()) lod.update(camera);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
createInstancedMesh,
|
|
171
|
+
setInstanceTransform,
|
|
172
|
+
setInstanceColor,
|
|
173
|
+
finalizeInstances,
|
|
174
|
+
removeInstancedMesh,
|
|
175
|
+
createLODMesh,
|
|
176
|
+
setLODPosition,
|
|
177
|
+
removeLODMesh,
|
|
178
|
+
updateLODs,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// runtime/api-3d/lights.js
|
|
2
|
+
// Dynamic point lights created by carts at runtime
|
|
3
|
+
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
export function lightsModule({ scene, cartLights, counters }) {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a dynamic point light in the 3D scene.
|
|
9
|
+
* @param {number} [color=0xffffff] - Hex color
|
|
10
|
+
* @param {number} [intensity=2] - Brightness multiplier
|
|
11
|
+
* @param {number} [distance=20] - Max range (0 = infinite)
|
|
12
|
+
* @param {number} [x=0] - World X position
|
|
13
|
+
* @param {number} [y=0] - World Y position
|
|
14
|
+
* @param {number} [z=0] - World Z position
|
|
15
|
+
* @returns {number} Light ID
|
|
16
|
+
*/
|
|
17
|
+
function createPointLight(color = 0xffffff, intensity = 2, distance = 20, x = 0, y = 0, z = 0) {
|
|
18
|
+
const light = new THREE.PointLight(color, intensity, distance);
|
|
19
|
+
light.position.set(x, y, z);
|
|
20
|
+
scene.add(light);
|
|
21
|
+
const id = ++counters.light;
|
|
22
|
+
cartLights.set(id, light);
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function setPointLightPosition(lightId, x, y, z) {
|
|
27
|
+
const light = cartLights.get(lightId);
|
|
28
|
+
if (!light) return false;
|
|
29
|
+
light.position.set(x, y, z);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setPointLightColor(lightId, color) {
|
|
34
|
+
const light = cartLights.get(lightId);
|
|
35
|
+
if (!light) return false;
|
|
36
|
+
light.color.setHex(color);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function removeLight(lightId) {
|
|
41
|
+
const light = cartLights.get(lightId);
|
|
42
|
+
if (!light) return false;
|
|
43
|
+
scene.remove(light);
|
|
44
|
+
if (light.shadow?.map) light.shadow.map.dispose();
|
|
45
|
+
if (light.dispose) light.dispose();
|
|
46
|
+
cartLights.delete(lightId);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { createPointLight, setPointLightPosition, setPointLightColor, removeLight };
|
|
51
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// runtime/api-3d/materials.js
|
|
2
|
+
// Material cache and full disposal utility
|
|
3
|
+
|
|
4
|
+
export function materialsModule({ gpu, materialCache }) {
|
|
5
|
+
function getMaterialKey(options) {
|
|
6
|
+
return JSON.stringify({
|
|
7
|
+
material: options.material || 'standard',
|
|
8
|
+
color: options.color || 0xffffff,
|
|
9
|
+
emissive: options.emissive || 0x000000,
|
|
10
|
+
metalness: options.metalness,
|
|
11
|
+
roughness: options.roughness,
|
|
12
|
+
transparent: options.transparent,
|
|
13
|
+
opacity: options.opacity,
|
|
14
|
+
wireframe: options.wireframe,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getCachedMaterial(options) {
|
|
19
|
+
const key = getMaterialKey(options);
|
|
20
|
+
if (materialCache.has(key)) return materialCache.get(key);
|
|
21
|
+
const material = gpu.createN64Material(options);
|
|
22
|
+
materialCache.set(key, material);
|
|
23
|
+
return material;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function disposeMaterial(material) {
|
|
27
|
+
const textureProps = [
|
|
28
|
+
'map',
|
|
29
|
+
'normalMap',
|
|
30
|
+
'roughnessMap',
|
|
31
|
+
'metalnessMap',
|
|
32
|
+
'aoMap',
|
|
33
|
+
'emissiveMap',
|
|
34
|
+
'bumpMap',
|
|
35
|
+
'displacementMap',
|
|
36
|
+
'alphaMap',
|
|
37
|
+
'lightMap',
|
|
38
|
+
'envMap',
|
|
39
|
+
];
|
|
40
|
+
textureProps.forEach(prop => {
|
|
41
|
+
if (material[prop]?.dispose) material[prop].dispose();
|
|
42
|
+
});
|
|
43
|
+
material.dispose();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { getMaterialKey, getCachedMaterial, disposeMaterial };
|
|
47
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// runtime/api-3d/models.js
|
|
2
|
+
// GLTF model loading, skeletal animation, and texture loading
|
|
3
|
+
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
6
|
+
|
|
7
|
+
export function modelsModule({ scene, gpu, meshes, mixers, modelAnimations, counters }) {
|
|
8
|
+
async function loadModel(url, position = [0, 0, 0], scale = 1) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const loader = new GLTFLoader();
|
|
11
|
+
loader.load(
|
|
12
|
+
url,
|
|
13
|
+
gltf => {
|
|
14
|
+
const model = gltf.scene;
|
|
15
|
+
model.position.set(...position);
|
|
16
|
+
model.scale.setScalar(scale);
|
|
17
|
+
model.traverse(child => {
|
|
18
|
+
if (child.isMesh) {
|
|
19
|
+
child.castShadow = true;
|
|
20
|
+
child.receiveShadow = true;
|
|
21
|
+
if (child.material) {
|
|
22
|
+
child.material = gpu.createN64Material({
|
|
23
|
+
color: child.material.color,
|
|
24
|
+
texture: child.material.map,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
scene.add(model);
|
|
30
|
+
const id = ++counters.mesh;
|
|
31
|
+
meshes.set(id, model);
|
|
32
|
+
if (gltf.animations?.length > 0) {
|
|
33
|
+
const mixer = new THREE.AnimationMixer(model);
|
|
34
|
+
mixers.set(id, mixer);
|
|
35
|
+
modelAnimations.set(id, gltf.animations);
|
|
36
|
+
mixer.clipAction(gltf.animations[0]).play();
|
|
37
|
+
}
|
|
38
|
+
resolve(id);
|
|
39
|
+
},
|
|
40
|
+
undefined,
|
|
41
|
+
reject
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function playAnimation(meshId, nameOrIndex = 0, loop = true, timeScale = 1.0) {
|
|
47
|
+
const mixer = mixers.get(meshId);
|
|
48
|
+
const animations = modelAnimations.get(meshId);
|
|
49
|
+
if (!mixer || !animations) return;
|
|
50
|
+
const clip =
|
|
51
|
+
typeof nameOrIndex === 'number'
|
|
52
|
+
? animations[nameOrIndex]
|
|
53
|
+
: THREE.AnimationClip.findByName(animations, nameOrIndex);
|
|
54
|
+
if (clip) {
|
|
55
|
+
mixer.stopAllAction();
|
|
56
|
+
const action = mixer.clipAction(clip);
|
|
57
|
+
action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce);
|
|
58
|
+
action.timeScale = timeScale;
|
|
59
|
+
action.play();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function updateAnimations(dt) {
|
|
64
|
+
mixers.forEach(mixer => mixer.update(dt));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function loadTexture(url) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
new THREE.TextureLoader().load(
|
|
70
|
+
url,
|
|
71
|
+
tex => {
|
|
72
|
+
tex.magFilter = THREE.NearestFilter;
|
|
73
|
+
tex.minFilter = THREE.NearestFilter;
|
|
74
|
+
tex.generateMipmaps = false;
|
|
75
|
+
resolve(tex);
|
|
76
|
+
},
|
|
77
|
+
undefined,
|
|
78
|
+
reject
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { loadModel, playAnimation, updateAnimations, loadTexture };
|
|
84
|
+
}
|