nova64 0.2.1

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +786 -0
  3. package/index.html +651 -0
  4. package/package.json +255 -0
  5. package/public/os9-shell/assets/index-B1Uvacma.js +32825 -0
  6. package/public/os9-shell/assets/index-B1Uvacma.js.map +1 -0
  7. package/public/os9-shell/assets/index-DIHfrTaW.css +1 -0
  8. package/public/os9-shell/index.html +14 -0
  9. package/public/os9-shell/nova-icon.svg +12 -0
  10. package/runtime/api-2d.js +878 -0
  11. package/runtime/api-3d/camera.js +73 -0
  12. package/runtime/api-3d/instancing.js +180 -0
  13. package/runtime/api-3d/lights.js +51 -0
  14. package/runtime/api-3d/materials.js +47 -0
  15. package/runtime/api-3d/models.js +84 -0
  16. package/runtime/api-3d/pbr.js +69 -0
  17. package/runtime/api-3d/primitives.js +304 -0
  18. package/runtime/api-3d/scene.js +169 -0
  19. package/runtime/api-3d/transforms.js +161 -0
  20. package/runtime/api-3d.js +154 -0
  21. package/runtime/api-effects.js +753 -0
  22. package/runtime/api-presets.js +85 -0
  23. package/runtime/api-skybox.js +178 -0
  24. package/runtime/api-sprites.js +100 -0
  25. package/runtime/api-voxel.js +601 -0
  26. package/runtime/api.js +201 -0
  27. package/runtime/assets.js +27 -0
  28. package/runtime/audio.js +114 -0
  29. package/runtime/collision.js +47 -0
  30. package/runtime/console.js +101 -0
  31. package/runtime/editor.js +233 -0
  32. package/runtime/font.js +233 -0
  33. package/runtime/framebuffer.js +28 -0
  34. package/runtime/fullscreen-button.js +185 -0
  35. package/runtime/gpu-canvas2d.js +47 -0
  36. package/runtime/gpu-threejs.js +639 -0
  37. package/runtime/gpu-webgl2.js +310 -0
  38. package/runtime/index.js +22 -0
  39. package/runtime/input.js +225 -0
  40. package/runtime/logger.js +60 -0
  41. package/runtime/physics.js +101 -0
  42. package/runtime/screens.js +213 -0
  43. package/runtime/storage.js +38 -0
  44. package/runtime/store.js +151 -0
  45. package/runtime/textinput.js +68 -0
  46. package/runtime/ui/buttons.js +124 -0
  47. package/runtime/ui/panels.js +105 -0
  48. package/runtime/ui/text.js +86 -0
  49. package/runtime/ui/widgets.js +141 -0
  50. package/runtime/ui.js +111 -0
  51. package/src/main.js +474 -0
  52. package/vite.config.js +63 -0
@@ -0,0 +1,304 @@
1
+ // runtime/api-3d/primitives.js
2
+ // Mesh creation (createMesh) and all shape primitives
3
+
4
+ import * as THREE from 'three';
5
+ import { logger } from '../logger.js';
6
+
7
+ export function primitivesModule({
8
+ scene,
9
+ gpu,
10
+ meshes,
11
+ mixers,
12
+ modelAnimations,
13
+ counters,
14
+ getCachedMaterial,
15
+ }) {
16
+ function createMesh(geometry, material, position = [0, 0, 0]) {
17
+ if (!geometry || !material) {
18
+ logger.error('createMesh: geometry and material are required');
19
+ return null;
20
+ }
21
+
22
+ let mesh;
23
+ if (geometry.type && !geometry.isBufferGeometry) {
24
+ // Mock object path (used in tests)
25
+ mesh = {
26
+ type: 'Mesh',
27
+ geometry,
28
+ material,
29
+ position: {
30
+ x: 0,
31
+ y: 0,
32
+ z: 0,
33
+ set(x, y, z) {
34
+ this.x = x;
35
+ this.y = y;
36
+ this.z = z;
37
+ },
38
+ },
39
+ rotation: {
40
+ x: 0,
41
+ y: 0,
42
+ z: 0,
43
+ set(x, y, z) {
44
+ this.x = x;
45
+ this.y = y;
46
+ this.z = z;
47
+ },
48
+ },
49
+ scale: {
50
+ x: 1,
51
+ y: 1,
52
+ z: 1,
53
+ set(x, y, z) {
54
+ this.x = x;
55
+ this.y = y;
56
+ this.z = z;
57
+ },
58
+ setScalar(s) {
59
+ this.x = s;
60
+ this.y = s;
61
+ this.z = s;
62
+ },
63
+ },
64
+ castShadow: true,
65
+ receiveShadow: true,
66
+ };
67
+ } else {
68
+ mesh = new THREE.Mesh(geometry, material);
69
+ mesh.castShadow = true;
70
+ mesh.receiveShadow = true;
71
+ }
72
+
73
+ if (Array.isArray(position) && position.length >= 3) {
74
+ mesh.position.set(position[0], position[1], position[2]);
75
+ } else if (typeof position === 'object' && position.x !== undefined) {
76
+ mesh.position.set(position.x, position.y || 0, position.z || 0);
77
+ } else {
78
+ mesh.position.set(0, 0, 0);
79
+ }
80
+
81
+ if (scene.add) scene.add(mesh);
82
+ if (material?.userData?.animated && gpu.registerAnimatedMesh) {
83
+ gpu.registerAnimatedMesh(mesh);
84
+ }
85
+
86
+ const id = ++counters.mesh;
87
+ meshes.set(id, mesh);
88
+ return id;
89
+ }
90
+
91
+ function destroyMesh(id) {
92
+ const mesh = meshes.get(id);
93
+ if (mesh) {
94
+ scene.remove(mesh);
95
+ mesh.geometry?.dispose();
96
+ if (mesh.material?.map) mesh.material.map.dispose();
97
+ mesh.material?.dispose();
98
+ meshes.delete(id);
99
+ mixers.delete(id);
100
+ modelAnimations.delete(id);
101
+ }
102
+ }
103
+
104
+ function getMesh(id) {
105
+ return meshes.get(id);
106
+ }
107
+
108
+ function createCube(size = 1, color = 0xffffff, position = [0, 0, 0], options = {}) {
109
+ try {
110
+ if (typeof size !== 'number' || size <= 0) {
111
+ logger.warn('createCube: invalid size, using default');
112
+ size = 1;
113
+ }
114
+ if (typeof color !== 'number') {
115
+ logger.warn('createCube: invalid color, using white');
116
+ color = 0xffffff;
117
+ }
118
+ return createMesh(
119
+ gpu.createBoxGeometry(size, size, size),
120
+ getCachedMaterial({ color, ...options }),
121
+ position
122
+ );
123
+ } catch (e) {
124
+ logger.error('createCube failed:', e);
125
+ return null;
126
+ }
127
+ }
128
+
129
+ function createAdvancedCube(size = 1, materialOptions = {}, position = [0, 0, 0]) {
130
+ try {
131
+ if (typeof size !== 'number' || size <= 0) size = 1;
132
+ return createMesh(
133
+ gpu.createBoxGeometry(size, size, size),
134
+ getCachedMaterial(materialOptions),
135
+ position
136
+ );
137
+ } catch (e) {
138
+ logger.error('createAdvancedCube failed:', e);
139
+ return null;
140
+ }
141
+ }
142
+
143
+ function createSphere(
144
+ radius = 1,
145
+ color = 0xffffff,
146
+ position = [0, 0, 0],
147
+ segments = 8,
148
+ options = {}
149
+ ) {
150
+ try {
151
+ if (typeof radius !== 'number' || radius <= 0) {
152
+ logger.warn('createSphere: invalid radius, using default');
153
+ radius = 1;
154
+ }
155
+ if (typeof segments !== 'number' || segments < 3) {
156
+ logger.warn('createSphere: invalid segments, using default');
157
+ segments = 8;
158
+ }
159
+ return createMesh(
160
+ gpu.createSphereGeometry(radius, segments),
161
+ getCachedMaterial({ color, ...options }),
162
+ position
163
+ );
164
+ } catch (e) {
165
+ logger.error('createSphere failed:', e);
166
+ return null;
167
+ }
168
+ }
169
+
170
+ function createAdvancedSphere(
171
+ radius = 1,
172
+ materialOptions = {},
173
+ position = [0, 0, 0],
174
+ segments = 16
175
+ ) {
176
+ try {
177
+ if (typeof radius !== 'number' || radius <= 0) radius = 1;
178
+ if (typeof segments !== 'number' || segments < 3) segments = 16;
179
+ return createMesh(
180
+ gpu.createSphereGeometry(radius, segments),
181
+ getCachedMaterial(materialOptions),
182
+ position
183
+ );
184
+ } catch (e) {
185
+ logger.error('createAdvancedSphere failed:', e);
186
+ return null;
187
+ }
188
+ }
189
+
190
+ function createPlane(width = 1, height = 1, color = 0xffffff, position = [0, 0, 0]) {
191
+ try {
192
+ if (typeof width !== 'number' || width <= 0) {
193
+ logger.warn('createPlane: invalid width, using default');
194
+ width = 1;
195
+ }
196
+ if (typeof height !== 'number' || height <= 0) {
197
+ logger.warn('createPlane: invalid height, using default');
198
+ height = 1;
199
+ }
200
+ return createMesh(
201
+ gpu.createPlaneGeometry(width, height),
202
+ getCachedMaterial({ color }),
203
+ position
204
+ );
205
+ } catch (e) {
206
+ logger.error('createPlane failed:', e);
207
+ return null;
208
+ }
209
+ }
210
+
211
+ function createCylinder(
212
+ radiusTop = 1,
213
+ radiusBottom = 1,
214
+ height = 1,
215
+ color = 0xffffff,
216
+ position = [0, 0, 0],
217
+ options = {}
218
+ ) {
219
+ try {
220
+ const geom = gpu.createCylinderGeometry
221
+ ? gpu.createCylinderGeometry(radiusTop, radiusBottom, height, options.segments || 16)
222
+ : gpu.createBoxGeometry(radiusTop, height, radiusTop);
223
+ return createMesh(geom, getCachedMaterial({ ...options, color }), position);
224
+ } catch (e) {
225
+ logger.error('createCylinder err', e);
226
+ return createCube(radiusTop * 2, color, position);
227
+ }
228
+ }
229
+
230
+ function createTorus(
231
+ radius = 1,
232
+ tube = 0.3,
233
+ color = 0xffffff,
234
+ position = [0, 0, 0],
235
+ options = {}
236
+ ) {
237
+ try {
238
+ const geometry = new THREE.TorusGeometry(
239
+ radius,
240
+ tube,
241
+ options.radialSegments || 8,
242
+ options.tubularSegments || 16
243
+ );
244
+ return createMesh(geometry, gpu.createN64Material({ color, ...options }), position);
245
+ } catch (e) {
246
+ logger.error('createTorus failed:', e);
247
+ return null;
248
+ }
249
+ }
250
+
251
+ function createCone(
252
+ radius = 1,
253
+ height = 2,
254
+ color = 0xffffff,
255
+ position = [0, 0, 0],
256
+ options = {}
257
+ ) {
258
+ try {
259
+ return createMesh(
260
+ gpu.createConeGeometry(radius, height, options.segments || 16),
261
+ getCachedMaterial({ color, ...options }),
262
+ position
263
+ );
264
+ } catch (e) {
265
+ logger.error('createCone failed:', e);
266
+ return null;
267
+ }
268
+ }
269
+
270
+ function createCapsule(
271
+ radius = 0.5,
272
+ height = 1,
273
+ color = 0xffffff,
274
+ position = [0, 0, 0],
275
+ options = {}
276
+ ) {
277
+ try {
278
+ return createMesh(
279
+ gpu.createCapsuleGeometry(radius, height, options.segments || 8),
280
+ getCachedMaterial({ color, ...options }),
281
+ position
282
+ );
283
+ } catch (e) {
284
+ logger.error('createCapsule failed:', e);
285
+ return null;
286
+ }
287
+ }
288
+
289
+ return {
290
+ createMesh,
291
+ destroyMesh,
292
+ getMesh,
293
+ removeMesh: destroyMesh,
294
+ createCube,
295
+ createAdvancedCube,
296
+ createSphere,
297
+ createAdvancedSphere,
298
+ createPlane,
299
+ createCylinder,
300
+ createTorus,
301
+ createCone,
302
+ createCapsule,
303
+ };
304
+ }
@@ -0,0 +1,169 @@
1
+ // runtime/api-3d/scene.js
2
+ // clearScene, setupScene, raycasting, stats, N64 effects
3
+
4
+ import * as THREE from 'three';
5
+ import { logger } from '../logger.js';
6
+
7
+ export function sceneModule(ctx) {
8
+ const {
9
+ scene,
10
+ camera,
11
+ renderer,
12
+ gpu,
13
+ meshes,
14
+ cartLights,
15
+ materialCache,
16
+ instancedMeshes,
17
+ lodObjects,
18
+ } = ctx;
19
+
20
+ // These come from other modules — resolved at call-time via ctx
21
+ function getDisposeMaterial() {
22
+ return ctx.disposeMaterial;
23
+ }
24
+ function getRemoveLight() {
25
+ return ctx.removeLight;
26
+ }
27
+
28
+ function clearScene() {
29
+ const disposeMaterial = getDisposeMaterial();
30
+ const removeLight = getRemoveLight();
31
+
32
+ logger.info(
33
+ '🧹 Clearing 3D scene... meshes:',
34
+ meshes.size,
35
+ 'lights:',
36
+ cartLights.size,
37
+ 'materials:',
38
+ materialCache.size
39
+ );
40
+
41
+ // Dispose all cart meshes
42
+ for (const [, mesh] of meshes) {
43
+ scene.remove(mesh);
44
+ if (mesh.geometry) mesh.geometry.dispose();
45
+ if (mesh.material) {
46
+ if (Array.isArray(mesh.material)) mesh.material.forEach(disposeMaterial);
47
+ else disposeMaterial(mesh.material);
48
+ }
49
+ }
50
+ meshes.clear();
51
+
52
+ // Remove dynamic lights
53
+ cartLights.forEach((_, id) => removeLight(id));
54
+ cartLights.clear();
55
+
56
+ // Flush material cache
57
+ materialCache.forEach(disposeMaterial);
58
+ materialCache.clear();
59
+
60
+ // Remove any remaining scene children (e.g. from loadModel / direct additions)
61
+ while (scene.children.length > 0) {
62
+ const child = scene.children[0];
63
+ scene.remove(child);
64
+ if (child.geometry) child.geometry.dispose();
65
+ if (child.material) {
66
+ if (Array.isArray(child.material)) child.material.forEach(disposeMaterial);
67
+ else disposeMaterial(child.material);
68
+ }
69
+ }
70
+
71
+ // Re-add minimal default lighting so scenes aren't black
72
+ scene.add(new THREE.AmbientLight(0xffffff, 0.5));
73
+ const dir = new THREE.DirectionalLight(0xffffff, 0.8);
74
+ dir.position.set(5, 10, 7.5);
75
+ scene.add(dir);
76
+
77
+ // Reset animated mesh registry in GPU backend
78
+ if (gpu.clearAnimatedMeshes) gpu.clearAnimatedMeshes();
79
+
80
+ // Dispose instanced meshes
81
+ for (const { mesh } of instancedMeshes.values()) {
82
+ scene.remove(mesh);
83
+ mesh.geometry?.dispose();
84
+ mesh.material?.dispose();
85
+ }
86
+ instancedMeshes.clear();
87
+
88
+ // Dispose LOD objects
89
+ for (const lod of lodObjects.values()) {
90
+ scene.remove(lod);
91
+ for (const { object } of lod.levels) {
92
+ object.geometry?.dispose();
93
+ object.material?.dispose();
94
+ }
95
+ }
96
+ lodObjects.clear();
97
+
98
+ logger.info('✅ Scene cleared completely');
99
+ }
100
+
101
+ /**
102
+ * setupScene(opts) — One-call scene configuration.
103
+ * opts: { camera, light, fog, skybox, effects }
104
+ */
105
+ function setupScene(opts = {}) {
106
+ const cam = opts.camera ?? {};
107
+ ctx.setCameraPosition(cam.x ?? 0, cam.y ?? 5, cam.z ?? 10);
108
+ ctx.setCameraTarget(cam.targetX ?? 0, cam.targetY ?? 0, cam.targetZ ?? 0);
109
+ if (cam.fov) ctx.setCameraFOV(cam.fov);
110
+
111
+ const light = opts.light ?? {};
112
+ if (light.direction) {
113
+ const d = light.direction;
114
+ ctx.setLightDirection(d[0] ?? -0.3, d[1] ?? -1, d[2] ?? -0.5);
115
+ }
116
+ if (light.color !== undefined) ctx.setLightColor(light.color);
117
+ if (light.ambient !== undefined) ctx.setAmbientLight(light.ambient);
118
+
119
+ if (opts.fog && opts.fog !== false) {
120
+ ctx.setFog(opts.fog.color ?? 0x000000, opts.fog.near ?? 10, opts.fog.far ?? 50);
121
+ }
122
+
123
+ if (opts.skybox && typeof globalThis.createSpaceSkybox === 'function') {
124
+ globalThis.createSpaceSkybox(opts.skybox);
125
+ }
126
+
127
+ if (opts.effects && typeof globalThis.enableRetroEffects === 'function') {
128
+ globalThis.enableRetroEffects(typeof opts.effects === 'object' ? opts.effects : {});
129
+ }
130
+ }
131
+
132
+ function raycastFromCamera(x, y) {
133
+ const raycaster = new THREE.Raycaster();
134
+ const mouse = new THREE.Vector2(
135
+ (x / renderer.domElement.clientWidth) * 2 - 1,
136
+ -(y / renderer.domElement.clientHeight) * 2 + 1
137
+ );
138
+ raycaster.setFromCamera(mouse, camera);
139
+ const objects = Array.from(meshes.values()).filter(m => m.type === 'Mesh');
140
+ const hits = raycaster.intersectObjects(objects);
141
+ if (hits.length > 0) {
142
+ for (const [id, mesh] of meshes) {
143
+ if (mesh === hits[0].object) {
144
+ return { meshId: id, point: hits[0].point, distance: hits[0].distance };
145
+ }
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+
151
+ function get3DStats() {
152
+ return gpu.getStats();
153
+ }
154
+ function enablePixelation(factor = 2) {
155
+ gpu.enablePixelation(factor);
156
+ }
157
+ function enableDithering(enabled = true) {
158
+ gpu.enableDithering(enabled);
159
+ }
160
+
161
+ return {
162
+ clearScene,
163
+ setupScene,
164
+ raycastFromCamera,
165
+ get3DStats,
166
+ enablePixelation,
167
+ enableDithering,
168
+ };
169
+ }
@@ -0,0 +1,161 @@
1
+ // runtime/api-3d/transforms.js
2
+ // Mesh position, rotation, scale, visibility, and shadow controls
3
+
4
+ import { logger } from '../logger.js';
5
+
6
+ export function transformsModule({ getMesh }) {
7
+ function setPosition(meshId, x, y, z) {
8
+ try {
9
+ const mesh = getMesh(meshId);
10
+ if (!mesh) {
11
+ logger.warn(`setPosition: mesh with id ${meshId} not found`);
12
+ return false;
13
+ }
14
+ mesh.position.set(
15
+ typeof x === 'number' ? x : 0,
16
+ typeof y === 'number' ? y : 0,
17
+ typeof z === 'number' ? z : 0
18
+ );
19
+ return true;
20
+ } catch (e) {
21
+ logger.error('setPosition failed:', e);
22
+ return false;
23
+ }
24
+ }
25
+
26
+ function setRotation(meshId, x, y, z) {
27
+ try {
28
+ const mesh = getMesh(meshId);
29
+ if (!mesh) {
30
+ logger.warn(`setRotation: mesh with id ${meshId} not found`);
31
+ return false;
32
+ }
33
+ mesh.rotation.set(
34
+ typeof x === 'number' ? x : 0,
35
+ typeof y === 'number' ? y : 0,
36
+ typeof z === 'number' ? z : 0
37
+ );
38
+ return true;
39
+ } catch (e) {
40
+ logger.error('setRotation failed:', e);
41
+ return false;
42
+ }
43
+ }
44
+
45
+ function setScale(meshId, x, y, z) {
46
+ try {
47
+ const mesh = getMesh(meshId);
48
+ if (!mesh) {
49
+ logger.warn(`setScale: mesh with id ${meshId} not found`);
50
+ return false;
51
+ }
52
+ x = typeof x === 'number' && x > 0 ? x : 1;
53
+ if (typeof y === 'undefined') {
54
+ mesh.scale.setScalar(x);
55
+ } else {
56
+ mesh.scale.set(
57
+ x,
58
+ typeof y === 'number' && y > 0 ? y : 1,
59
+ typeof z === 'number' && z > 0 ? z : 1
60
+ );
61
+ }
62
+ return true;
63
+ } catch (e) {
64
+ logger.error('setScale failed:', e);
65
+ return false;
66
+ }
67
+ }
68
+
69
+ function getPosition(meshId) {
70
+ const mesh = getMesh(meshId);
71
+ return mesh ? [mesh.position.x, mesh.position.y, mesh.position.z] : null;
72
+ }
73
+
74
+ function getRotation(meshId) {
75
+ const mesh = getMesh(meshId);
76
+ return mesh ? [mesh.rotation.x, mesh.rotation.y, mesh.rotation.z] : null;
77
+ }
78
+
79
+ function rotateMesh(meshId, dX, dY, dZ) {
80
+ try {
81
+ const mesh = getMesh(meshId);
82
+ if (!mesh) {
83
+ logger.warn(`rotateMesh: mesh with id ${meshId} not found`);
84
+ return false;
85
+ }
86
+ mesh.rotation.x += typeof dX === 'number' ? dX : 0;
87
+ mesh.rotation.y += typeof dY === 'number' ? dY : 0;
88
+ mesh.rotation.z += typeof dZ === 'number' ? dZ : 0;
89
+ return true;
90
+ } catch (e) {
91
+ logger.error('rotateMesh failed:', e);
92
+ return false;
93
+ }
94
+ }
95
+
96
+ function moveMesh(meshId, x, y, z) {
97
+ const mesh = getMesh(meshId);
98
+ if (mesh) {
99
+ mesh.position.x += x;
100
+ mesh.position.y += y;
101
+ mesh.position.z += z;
102
+ }
103
+ }
104
+
105
+ function setFlatShading(meshId, enabled = true) {
106
+ const mesh = getMesh(meshId);
107
+ if (!mesh) return false;
108
+ if (mesh.material) {
109
+ mesh.material.flatShading = enabled;
110
+ mesh.material.needsUpdate = true;
111
+ }
112
+ return true;
113
+ }
114
+
115
+ function setMeshVisible(meshId, visible) {
116
+ const mesh = getMesh(meshId);
117
+ if (!mesh) return false;
118
+ mesh.visible = visible;
119
+ return true;
120
+ }
121
+
122
+ function setMeshOpacity(meshId, opacity) {
123
+ const mesh = getMesh(meshId);
124
+ if (!mesh) return false;
125
+ if (mesh.material) {
126
+ mesh.material.transparent = opacity < 1;
127
+ mesh.material.opacity = opacity;
128
+ mesh.material.needsUpdate = true;
129
+ }
130
+ return true;
131
+ }
132
+
133
+ function setCastShadow(meshId, cast) {
134
+ const mesh = getMesh(meshId);
135
+ if (!mesh) return false;
136
+ mesh.castShadow = cast;
137
+ return true;
138
+ }
139
+
140
+ function setReceiveShadow(meshId, receive) {
141
+ const mesh = getMesh(meshId);
142
+ if (!mesh) return false;
143
+ mesh.receiveShadow = receive;
144
+ return true;
145
+ }
146
+
147
+ return {
148
+ setPosition,
149
+ setRotation,
150
+ setScale,
151
+ getPosition,
152
+ getRotation,
153
+ rotateMesh,
154
+ moveMesh,
155
+ setFlatShading,
156
+ setMeshVisible,
157
+ setMeshOpacity,
158
+ setCastShadow,
159
+ setReceiveShadow,
160
+ };
161
+ }