@nayan-ui/engine 0.0.0-stage → 1.0.0-beta.4

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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/NayanEngine.podspec +28 -0
  3. package/README.md +308 -2
  4. package/lib/module/assets/AssetBytesCache.js +76 -0
  5. package/lib/module/assets/AssetCache.js +19 -0
  6. package/lib/module/assets/AssetLoader.js +52 -0
  7. package/lib/module/assets/CacheGeneration.js +7 -0
  8. package/lib/module/assets/IndexedDbCache.web.js +149 -0
  9. package/lib/module/assets/ProceduralCache.js +249 -0
  10. package/lib/module/assets/font.js +139 -0
  11. package/lib/module/assets/loft.js +142 -0
  12. package/lib/module/assets/model.js +96 -0
  13. package/lib/module/assets/modelling.js +254 -0
  14. package/lib/module/assets/shape.js +926 -0
  15. package/lib/module/assets/source.js +58 -0
  16. package/lib/module/assets/source.web.js +22 -0
  17. package/lib/module/assets/storage.js +4 -0
  18. package/lib/module/assets/storage.shared.js +38 -0
  19. package/lib/module/assets/storage.web.js +38 -0
  20. package/lib/module/assets/surface.js +47 -0
  21. package/lib/module/assets/texture.js +69 -0
  22. package/lib/module/drawing/DrawingCanvas.js +38 -0
  23. package/lib/module/drawing/DrawingCanvas.web.js +206 -0
  24. package/lib/module/drawing/DrawingDocument.js +256 -0
  25. package/lib/module/drawing/types.js +4 -0
  26. package/lib/module/globals.d.ts +12 -0
  27. package/lib/module/index.js +39 -0
  28. package/lib/module/input/Joystick.js +84 -0
  29. package/lib/module/input/Joystick.web.js +93 -0
  30. package/lib/module/input/keyboard.js +250 -0
  31. package/lib/module/media/audio-worklet.js +34 -0
  32. package/lib/module/media/audio.js +97 -0
  33. package/lib/module/media/haptics.js +82 -0
  34. package/lib/module/media/output.web.js +66 -0
  35. package/lib/module/native/BrowserEngine.js +568 -0
  36. package/lib/module/native/EngineBridge.js +4 -0
  37. package/lib/module/native/NativeNayanEngine.js +7 -0
  38. package/lib/module/native/NativeNayanEngine.web.js +62 -0
  39. package/lib/module/native/prepare.js +7 -0
  40. package/lib/module/native/prepare.web.js +17 -0
  41. package/lib/module/native/spec/NativeNayanEngine.js +17 -0
  42. package/lib/module/package.json +1 -0
  43. package/lib/module/render/GameView.js +317 -0
  44. package/lib/module/render/camera.js +34 -0
  45. package/lib/module/render/host.js +39 -0
  46. package/lib/module/render/host.web.js +104 -0
  47. package/lib/module/render/light.js +24 -0
  48. package/lib/module/render/meshes.js +159 -0
  49. package/lib/module/render/quality.js +119 -0
  50. package/lib/module/render/responder.js +4 -0
  51. package/lib/module/render/responder.web.js +97 -0
  52. package/lib/module/render/touch.js +185 -0
  53. package/lib/module/render/useWorld.js +72 -0
  54. package/lib/module/types.js +30 -0
  55. package/lib/module/world/Scoreboard.js +161 -0
  56. package/lib/module/world/TransformBatch.js +128 -0
  57. package/lib/module/world/World.js +704 -0
  58. package/lib/module/world/color.js +80 -0
  59. package/lib/module/world/desc.js +187 -0
  60. package/lib/module/world/effects.js +463 -0
  61. package/lib/module/world/quat.js +86 -0
  62. package/lib/module/world/types.js +4 -0
  63. package/lib/typescript/package.json +1 -0
  64. package/lib/typescript/src/assets/AssetBytesCache.d.ts +7 -0
  65. package/lib/typescript/src/assets/AssetCache.d.ts +9 -0
  66. package/lib/typescript/src/assets/AssetLoader.d.ts +11 -0
  67. package/lib/typescript/src/assets/CacheGeneration.d.ts +3 -0
  68. package/lib/typescript/src/assets/IndexedDbCache.web.d.ts +20 -0
  69. package/lib/typescript/src/assets/ProceduralCache.d.ts +45 -0
  70. package/lib/typescript/src/assets/font.d.ts +53 -0
  71. package/lib/typescript/src/assets/loft.d.ts +31 -0
  72. package/lib/typescript/src/assets/model.d.ts +58 -0
  73. package/lib/typescript/src/assets/modelling.d.ts +157 -0
  74. package/lib/typescript/src/assets/shape.d.ts +203 -0
  75. package/lib/typescript/src/assets/source.d.ts +10 -0
  76. package/lib/typescript/src/assets/source.web.d.ts +8 -0
  77. package/lib/typescript/src/assets/storage.d.ts +2 -0
  78. package/lib/typescript/src/assets/storage.shared.d.ts +7 -0
  79. package/lib/typescript/src/assets/storage.web.d.ts +7 -0
  80. package/lib/typescript/src/assets/surface.d.ts +17 -0
  81. package/lib/typescript/src/assets/texture.d.ts +30 -0
  82. package/lib/typescript/src/drawing/DrawingCanvas.d.ts +5 -0
  83. package/lib/typescript/src/drawing/DrawingCanvas.web.d.ts +4 -0
  84. package/lib/typescript/src/drawing/DrawingDocument.d.ts +210 -0
  85. package/lib/typescript/src/drawing/types.d.ts +12 -0
  86. package/lib/typescript/src/index.d.ts +45 -0
  87. package/lib/typescript/src/input/Joystick.d.ts +20 -0
  88. package/lib/typescript/src/input/Joystick.web.d.ts +13 -0
  89. package/lib/typescript/src/input/keyboard.d.ts +17 -0
  90. package/lib/typescript/src/media/audio.d.ts +46 -0
  91. package/lib/typescript/src/media/haptics.d.ts +22 -0
  92. package/lib/typescript/src/media/output.web.d.ts +4 -0
  93. package/lib/typescript/src/native/BrowserEngine.d.ts +8 -0
  94. package/lib/typescript/src/native/EngineBridge.d.ts +26 -0
  95. package/lib/typescript/src/native/NativeNayanEngine.d.ts +5 -0
  96. package/lib/typescript/src/native/NativeNayanEngine.web.d.ts +10 -0
  97. package/lib/typescript/src/native/prepare.d.ts +3 -0
  98. package/lib/typescript/src/native/prepare.web.d.ts +2 -0
  99. package/lib/typescript/src/native/spec/NativeNayanEngine.d.ts +211 -0
  100. package/lib/typescript/src/render/GameView.d.ts +107 -0
  101. package/lib/typescript/src/render/camera.d.ts +6 -0
  102. package/lib/typescript/src/render/host.d.ts +29 -0
  103. package/lib/typescript/src/render/host.web.d.ts +16 -0
  104. package/lib/typescript/src/render/light.d.ts +12 -0
  105. package/lib/typescript/src/render/meshes.d.ts +45 -0
  106. package/lib/typescript/src/render/quality.d.ts +48 -0
  107. package/lib/typescript/src/render/responder.d.ts +2 -0
  108. package/lib/typescript/src/render/responder.web.d.ts +40 -0
  109. package/lib/typescript/src/render/touch.d.ts +26 -0
  110. package/lib/typescript/src/render/useWorld.d.ts +20 -0
  111. package/lib/typescript/src/types.d.ts +81 -0
  112. package/lib/typescript/src/world/Scoreboard.d.ts +75 -0
  113. package/lib/typescript/src/world/TransformBatch.d.ts +39 -0
  114. package/lib/typescript/src/world/World.d.ts +207 -0
  115. package/lib/typescript/src/world/color.d.ts +18 -0
  116. package/lib/typescript/src/world/desc.d.ts +17 -0
  117. package/lib/typescript/src/world/effects.d.ts +157 -0
  118. package/lib/typescript/src/world/quat.d.ts +31 -0
  119. package/lib/typescript/src/world/types.d.ts +238 -0
  120. package/lib/web/audio-worklet.js +29 -0
  121. package/lib/web/engine_core_bg.wasm +0 -0
  122. package/lib/web/index.js +8240 -0
  123. package/lib/web/types/src/assets/AssetBytesCache.d.ts +6 -0
  124. package/lib/web/types/src/assets/AssetCache.d.ts +8 -0
  125. package/lib/web/types/src/assets/AssetLoader.d.ts +10 -0
  126. package/lib/web/types/src/assets/CacheGeneration.d.ts +2 -0
  127. package/lib/web/types/src/assets/IndexedDbCache.d.ts +19 -0
  128. package/lib/web/types/src/assets/ProceduralCache.d.ts +44 -0
  129. package/lib/web/types/src/assets/font.d.ts +52 -0
  130. package/lib/web/types/src/assets/loft.d.ts +30 -0
  131. package/lib/web/types/src/assets/model.d.ts +57 -0
  132. package/lib/web/types/src/assets/modelling.d.ts +156 -0
  133. package/lib/web/types/src/assets/shape.d.ts +202 -0
  134. package/lib/web/types/src/assets/source.d.ts +7 -0
  135. package/lib/web/types/src/assets/storage.d.ts +6 -0
  136. package/lib/web/types/src/assets/storage.shared.d.ts +6 -0
  137. package/lib/web/types/src/assets/surface.d.ts +16 -0
  138. package/lib/web/types/src/assets/texture.d.ts +29 -0
  139. package/lib/web/types/src/drawing/DrawingCanvas.d.ts +3 -0
  140. package/lib/web/types/src/drawing/DrawingDocument.d.ts +209 -0
  141. package/lib/web/types/src/drawing/types.d.ts +11 -0
  142. package/lib/web/types/src/index.d.ts +44 -0
  143. package/lib/web/types/src/input/Joystick.d.ts +12 -0
  144. package/lib/web/types/src/input/keyboard.d.ts +16 -0
  145. package/lib/web/types/src/media/audio.d.ts +45 -0
  146. package/lib/web/types/src/media/haptics.d.ts +21 -0
  147. package/lib/web/types/src/media/output.d.ts +3 -0
  148. package/lib/web/types/src/native/BrowserEngine.d.ts +7 -0
  149. package/lib/web/types/src/native/EngineBridge.d.ts +25 -0
  150. package/lib/web/types/src/native/NativeNayanEngine.d.ts +9 -0
  151. package/lib/web/types/src/native/prepare.d.ts +1 -0
  152. package/lib/web/types/src/native/spec/NativeNayanEngine.d.ts +210 -0
  153. package/lib/web/types/src/render/GameView.d.ts +106 -0
  154. package/lib/web/types/src/render/camera.d.ts +5 -0
  155. package/lib/web/types/src/render/host.d.ts +15 -0
  156. package/lib/web/types/src/render/light.d.ts +11 -0
  157. package/lib/web/types/src/render/meshes.d.ts +44 -0
  158. package/lib/web/types/src/render/quality.d.ts +47 -0
  159. package/lib/web/types/src/render/responder.d.ts +39 -0
  160. package/lib/web/types/src/render/touch.d.ts +25 -0
  161. package/lib/web/types/src/render/useWorld.d.ts +19 -0
  162. package/lib/web/types/src/types.d.ts +80 -0
  163. package/lib/web/types/src/world/Scoreboard.d.ts +74 -0
  164. package/lib/web/types/src/world/TransformBatch.d.ts +38 -0
  165. package/lib/web/types/src/world/World.d.ts +206 -0
  166. package/lib/web/types/src/world/color.d.ts +17 -0
  167. package/lib/web/types/src/world/desc.d.ts +16 -0
  168. package/lib/web/types/src/world/effects.d.ts +156 -0
  169. package/lib/web/types/src/world/quat.d.ts +30 -0
  170. package/lib/web/types/src/world/types.d.ts +237 -0
  171. package/native/android/CMakeLists.txt +34 -0
  172. package/native/android/NayanEngineSpec.h +15 -0
  173. package/native/android/build.gradle +26 -0
  174. package/native/android/codegen/CMakeLists.txt +3 -0
  175. package/native/android/proguard-rules.pro +2 -0
  176. package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceView.java +295 -0
  177. package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceViewManager.java +45 -0
  178. package/native/android/src/main/java/com/nayanui/engine/NayanEnginePackage.java +24 -0
  179. package/native/core/build/EngineCore.xcframework/Info.plist +43 -0
  180. package/native/core/build/EngineCore.xcframework/ios-arm64/libengine_core.a +0 -0
  181. package/native/core/build/EngineCore.xcframework/ios-arm64-simulator/libengine_core.a +0 -0
  182. package/native/core/build/android/arm64-v8a/libengine_core.a +0 -0
  183. package/native/core/build/android/armeabi-v7a/libengine_core.a +0 -0
  184. package/native/core/build/android/x86/libengine_core.a +0 -0
  185. package/native/core/build/android/x86_64/libengine_core.a +0 -0
  186. package/native/core/include/drawing-licenses.txt +58 -0
  187. package/native/core/include/engine_core.h +331 -0
  188. package/native/cpp/NayanEngineModule.cpp +998 -0
  189. package/native/cpp/NayanEngineModule.h +174 -0
  190. package/native/cpp/generated/NayanEngineSpecJSI.cpp +2 -0
  191. package/native/cpp/generated/NayanEngineSpecJSI.h +1026 -0
  192. package/native/ios/NayanEngineProvider.h +5 -0
  193. package/native/ios/NayanEngineProvider.mm +21 -0
  194. package/native/ios/NayanEngineSurface.mm +252 -0
  195. package/native/scripts/check-package.mjs +39 -0
  196. package/package.json +162 -4
  197. package/react-native.config.js +18 -0
  198. package/src/native/spec/NativeNayanEngine.ts +265 -0
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+
3
+ import NativeEngine from '../native/NativeNayanEngine';
4
+ import { meshInfo } from "../render/meshes.js";
5
+ import { registerModelMesh } from "./model.js";
6
+ import { proceduralStorage } from './storage';
7
+
8
+ /** Optional storage override. The engine selects a best-effort platform cache by default. */
9
+
10
+ const MAGIC = 0x3143504e; // NPC1: static vertices (11 floats) and u32 indices, little endian.
11
+ const LIMIT = 32 * 1024 * 1024;
12
+ // JSON is escaped to ASCII so Hermes hosts need no TextEncoder/TextDecoder polyfill.
13
+ function encodeJson(value) {
14
+ const text = JSON.stringify(value).replace(/[\u007f-\uffff]/g, char => '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0'));
15
+ const bytes = new Uint8Array(text.length);
16
+ for (let i = 0; i < text.length; i++) bytes[i] = text.charCodeAt(i);
17
+ return bytes;
18
+ }
19
+ function decodeJson(bytes) {
20
+ let text = '';
21
+ for (let i = 0; i < bytes.length; i += 4096) text += String.fromCharCode(...bytes.subarray(i, i + 4096));
22
+ return JSON.parse(text);
23
+ }
24
+ function checksum(bytes) {
25
+ let hash = 2166136261;
26
+ for (let i = 0; i < bytes.length; i++) hash = Math.imul(hash ^ bytes[i], 16777619);
27
+ return hash >>> 0;
28
+ }
29
+
30
+ /**
31
+ * Persists the output of a deterministic model factory, including nested model arrays/objects.
32
+ * Hits register saved vertices directly: no procedural tessellation, painting or optimization.
33
+ * Imported/skinned/textured models are intentionally excluded; their own loaders retain their rigs.
34
+ */
35
+ export class ProceduralCache {
36
+ memory = new Map();
37
+ hits = 0;
38
+ builds = 0;
39
+ invalid = 0;
40
+ failures = 0;
41
+ writes = new Set();
42
+ constructor(revision, storage = proceduralStorage(revision)) {
43
+ this.revision = revision;
44
+ this.storage = storage;
45
+ }
46
+ get stats() {
47
+ return {
48
+ hits: this.hits,
49
+ builds: this.builds,
50
+ invalid: this.invalid,
51
+ failures: this.failures,
52
+ pendingWrites: this.writes.size
53
+ };
54
+ }
55
+ async flush() {
56
+ await Promise.all([...this.writes]);
57
+ }
58
+
59
+ /**
60
+ * A value already cached (in memory, or saved by an earlier launch), without building anything:
61
+ * undefined when there is none yet. Lets work that's spread over many frames (a level generated
62
+ * step by step) skip itself entirely when a saved result exists, then save with `get`.
63
+ */
64
+ peek(key) {
65
+ if (this.memory.has(key)) return this.memory.get(key);
66
+ if (!this.storage) return undefined;
67
+ try {
68
+ const bytes = this.storage.read(key);
69
+ if (!bytes) return undefined;
70
+ const value = this.restore(bytes, key);
71
+ this.memory.set(key, value);
72
+ this.hits++;
73
+ return value;
74
+ } catch {
75
+ this.invalid++;
76
+ return undefined;
77
+ }
78
+ }
79
+ get(key, build) {
80
+ if (this.memory.has(key)) return this.memory.get(key);
81
+ if (this.storage) {
82
+ try {
83
+ const bytes = this.storage.read(key);
84
+ if (bytes) {
85
+ try {
86
+ const value = this.restore(bytes, key);
87
+ this.memory.set(key, value);
88
+ this.hits++;
89
+ return value;
90
+ } catch {
91
+ this.invalid++;
92
+ }
93
+ }
94
+ } catch {
95
+ this.failures++;
96
+ }
97
+ }
98
+ const value = build();
99
+ this.builds++;
100
+ this.memory.set(key, value);
101
+ if (this.storage) {
102
+ try {
103
+ const result = this.storage.write(key, this.encode(value, key));
104
+ if (result) {
105
+ const pending = Promise.resolve(result).catch(() => {
106
+ this.failures++;
107
+ }).finally(() => {
108
+ this.writes.delete(pending);
109
+ });
110
+ this.writes.add(pending);
111
+ }
112
+ } catch {
113
+ this.failures++;
114
+ } // Storage/unsupported values never prevent a game from running.
115
+ }
116
+ return value;
117
+ }
118
+ encode(value, key) {
119
+ const models = [];
120
+ const ids = new Map();
121
+ const visit = v => {
122
+ if (v === undefined) return {
123
+ $undefined: true
124
+ };
125
+ if (v === null || typeof v === 'string' || typeof v === 'boolean') return v;
126
+ if (typeof v === 'number' && Number.isFinite(v)) return v;
127
+ if (Array.isArray(v)) return v.map(visit);
128
+ if (typeof v !== 'object' || !v || Object.getPrototypeOf(v) !== Object.prototype) throw Error('not a plain procedural asset');
129
+ const obj = v;
130
+ if (Number.isSafeInteger(obj.id) && Array.isArray(obj.size) && obj.size.length === 3) {
131
+ const model = v;
132
+ const info = meshInfo(model.id);
133
+ if (!info || info.texture !== undefined || 'jointCount' in obj || 'animations' in obj) throw Error('not a static procedural model');
134
+ let index = ids.get(model.id);
135
+ if (index === undefined) {
136
+ index = models.length;
137
+ ids.set(model.id, index);
138
+ models.push(model);
139
+ }
140
+ return {
141
+ $mesh: index
142
+ };
143
+ }
144
+ if (Number.isSafeInteger(obj.id)) throw Error('native handles cannot be persisted as plain data');
145
+ const result = {};
146
+ for (const name of Object.keys(obj)) {
147
+ if (name.startsWith('$')) throw Error('reserved cache key');
148
+ if (Object.getOwnPropertyDescriptor(obj, name)?.get) throw Error('lazy values must be cached separately');
149
+ result[name] = visit(obj[name]);
150
+ }
151
+ return result;
152
+ };
153
+ const root = visit(value);
154
+ const infos = models.map(model => meshInfo(model.id));
155
+ const header = {
156
+ revision: this.revision,
157
+ key,
158
+ value: root,
159
+ meshes: infos.map(info => ({
160
+ floats: info.geometry.vertices.length,
161
+ indices: info.geometry.indices.length,
162
+ shine: info.shine ?? 0
163
+ }))
164
+ };
165
+ const json = encodeJson(header);
166
+ const start = 16 + json.length + 3 & ~3;
167
+ const length = start + infos.reduce((sum, info) => sum + info.geometry.vertices.byteLength + info.geometry.indices.byteLength, 0);
168
+ if (length > LIMIT) throw Error('procedural asset too large');
169
+ const bytes = new Uint8Array(length),
170
+ view = new DataView(bytes.buffer);
171
+ view.setUint32(0, MAGIC, true);
172
+ view.setUint32(4, json.length, true);
173
+ view.setUint32(8, length, true);
174
+ bytes.set(json, 16);
175
+ let offset = start;
176
+ for (const {
177
+ geometry
178
+ } of infos) for (const data of [geometry.vertices, geometry.indices]) {
179
+ bytes.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), offset);
180
+ offset += data.byteLength;
181
+ }
182
+ // A verifying store checksums the record itself: hashing every byte again in JS cost more than
183
+ // building small models.
184
+ view.setUint32(12, this.storage?.verified ? 0 : checksum(bytes.subarray(16)), true);
185
+ return bytes;
186
+ }
187
+ restore(raw, key) {
188
+ if (raw.length < 16 || raw.length > LIMIT) throw Error('cache size');
189
+ const bytes = raw.byteOffset % 4 ? raw.slice() : raw;
190
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
191
+ const jsonLength = view.getUint32(4, true);
192
+ if (view.getUint32(0, true) !== MAGIC || view.getUint32(8, true) !== bytes.length || jsonLength > bytes.length - 16 || !this.storage?.verified && view.getUint32(12, true) !== checksum(bytes.subarray(16))) throw Error('cache integrity');
193
+ const header = decodeJson(bytes.subarray(16, 16 + jsonLength));
194
+ if (!('value' in header) || header.revision !== this.revision || header.key !== key || !Array.isArray(header.meshes)) throw Error('cache revision');
195
+ let offset = 16 + jsonLength + 3 & ~3;
196
+ const payloads = [];
197
+ // Validate the complete record before registering any mesh (including the value's references).
198
+ for (const mesh of header.meshes) {
199
+ if (!Number.isSafeInteger(mesh.floats) || mesh.floats <= 0 || mesh.floats % 11 || !Number.isSafeInteger(mesh.indices) || mesh.indices <= 0 || mesh.indices % 3 || !Number.isFinite(mesh.shine) || mesh.shine < 0 || mesh.shine > 1) throw Error('cache geometry');
200
+ const end = offset + (mesh.floats + mesh.indices) * 4;
201
+ if (end > bytes.length) throw Error('cache truncated');
202
+ const vertices = new Float32Array(bytes.buffer, bytes.byteOffset + offset, mesh.floats);
203
+ const indices = new Uint32Array(bytes.buffer, bytes.byteOffset + offset + mesh.floats * 4, mesh.indices);
204
+ // A verified record is the one this cache wrote, and the core rejects bad geometry anyway:
205
+ // scanning millions of floats in JS was most of a relaunch's loading time.
206
+ if (!this.storage?.verified) {
207
+ for (const value of vertices) if (!Number.isFinite(value)) throw Error('cache vertex');
208
+ for (const index of indices) if (index >= mesh.floats / 11) throw Error('cache index');
209
+ }
210
+ payloads.push({
211
+ vertices,
212
+ indices,
213
+ shine: mesh.shine
214
+ });
215
+ offset = end;
216
+ }
217
+ if (offset !== bytes.length) throw Error('cache length');
218
+ const mapValue = (v, models) => {
219
+ if (v === null || typeof v !== 'object') return v;
220
+ if (Array.isArray(v)) return v.map(item => mapValue(item, models));
221
+ if ('$mesh' in v) {
222
+ const index = v.$mesh;
223
+ if (!Number.isSafeInteger(index) || typeof index !== 'number' || index < 0 || index >= header.meshes.length || Object.keys(v).length !== 1) throw Error('cache model reference');
224
+ return models?.[index];
225
+ }
226
+ if ('$undefined' in v) return undefined;
227
+ const result = {};
228
+ for (const name of Object.keys(v)) {
229
+ if (name.startsWith('$') || name === '__proto__') throw Error('cache value');
230
+ result[name] = mapValue(v[name], models);
231
+ }
232
+ return result;
233
+ };
234
+ mapValue(header.value);
235
+ if (!NativeEngine) throw Error('native engine unavailable');
236
+ const restored = payloads.map(mesh => {
237
+ let id = NativeEngine.meshCreate(mesh.vertices.slice().buffer, mesh.indices.slice().buffer);
238
+ if (id < 0) throw Error('cache registration');
239
+ id = registerModelMesh(id, mesh.shine);
240
+ const [x, y, z] = NativeEngine.modelSize(id);
241
+ return {
242
+ id,
243
+ size: [x, y, z]
244
+ };
245
+ });
246
+ return mapValue(header.value, restored);
247
+ }
248
+ }
249
+ //# sourceMappingURL=ProceduralCache.js.map
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+
3
+ // Fonts for 3D text: each character becomes an extruded mesh, built by the Rust core.
4
+
5
+ import { AssetLoader } from "./AssetLoader.js";
6
+ import { registerCoreMesh } from "./model.js";
7
+ /** Printable ASCII plus common arithmetic and typography, prewarmed for ordinary labels. */
8
+ export const DEFAULT_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~×÷±−°·•–—…‘’“”';
9
+
10
+ /** A loaded font. Use it on an entity with `text`: `{ text: "2048", font }`. */
11
+
12
+ const loader = new AssetLoader('loadFont');
13
+ const extend = new WeakMap();
14
+
15
+ // Format controls affect presentation, not glyph count or spacing. In particular, a text/emoji
16
+ // variation selector must not append a second fallback question mark to a symbol.
17
+ function isInvisible(ch) {
18
+ const code = ch.codePointAt(0);
19
+ return code >= 0x200b && code <= 0x200f || code >= 0x202a && code <= 0x202e || code >= 0x2060 && code <= 0x206f || code >= 0xfe00 && code <= 0xfe0f || code === 0xfeff || code >= 0xe0100 && code <= 0xe01ef;
20
+ }
21
+ const WHITESPACE = /\s/u;
22
+
23
+ /**
24
+ * Initializes the engine automatically. Loads a TrueType / OpenType font (.ttf / .otf) for 3D text. The source/depth/weight are cached independently of the requested prewarm characters. Letters are real meshes: lit, shadowed,
25
+ * and every copy of a letter on screen is drawn in one call. At scale 1, letters are 1 unit tall (em).
26
+ * Additional Unicode glyphs are built on demand from the same font and cached. The font must contain
27
+ * an outline for the character; missing characters still use ?, including unsupported emoji.
28
+ *
29
+ * ```ts
30
+ * const font = await loadFont(require("./assets/Inter-Bold.ttf"), { chars: "0123456789" });
31
+ * world.spawn({ text: "2048", font, scale: 0.5, color: [1, 1, 1] });
32
+ * ```
33
+ */
34
+ export function loadFont(source, options = {}) {
35
+ const depth = options.depth ?? 0.2;
36
+ const chars = [...new Set(`${options.chars ?? DEFAULT_CHARS} ?`.normalize('NFC'))].filter(ch => !isInvisible(ch)).join('');
37
+ const weight = options.weight ?? 0;
38
+ const pending = loader.load(source, (native, bytes) => {
39
+ // Outlines and glyph meshes are built on a background native thread where the app's build supports it.
40
+ if (typeof native.fontLoadAsync === 'function') return native.fontLoadAsync(bytes, depth, chars, weight).then(id => finish(native, bytes, id));
41
+ return finish(native, bytes, native.fontLoad(bytes, depth, chars, weight));
42
+ }, [depth, weight]);
43
+ /** The loaded font's glyphs, registered for drawing in Rust, and its on-demand extension. */
44
+ function finish(native, bytes, id) {
45
+ if (id < 0) throw new Error(native.loadError() || 'not a TrueType / OpenType font');
46
+ const glyphs = new Map();
47
+ const registered = new Set();
48
+ const read = fontId => {
49
+ const data = native.fontGlyphs(fontId);
50
+ for (let i = 0; i + 3 <= data.length - 1; i += 3) {
51
+ const mesh = data[i + 1];
52
+ if (mesh >= 0 && !registered.has(mesh)) {
53
+ registerCoreMesh(mesh);
54
+ registered.add(mesh);
55
+ }
56
+ glyphs.set(String.fromCodePoint(data[i]), {
57
+ mesh,
58
+ advance: data[i + 2]
59
+ });
60
+ }
61
+ return data[data.length - 1];
62
+ };
63
+ const font = {
64
+ id,
65
+ capHeight: read(id),
66
+ glyphs
67
+ };
68
+ const attempted = new Set(chars);
69
+ extend.set(font, text => {
70
+ let missing = '';
71
+ for (const ch of text) {
72
+ if (isInvisible(ch) || attempted.has(ch)) continue;
73
+ attempted.add(ch);
74
+ missing += ch;
75
+ }
76
+ if (!missing) return;
77
+ // One native call per group of new codepoints, never one per letter or repeated label.
78
+ try {
79
+ const extra = native.fontLoad(bytes, depth, missing, weight);
80
+ if (extra < 0) throw new Error(native.loadError());
81
+ read(extra);
82
+ } catch (error) {
83
+ for (const ch of missing) attempted.delete(ch);
84
+ throw new Error(`loadFont: couldn't build additional glyphs: ${error instanceof Error ? error.message : String(error)}`, {
85
+ cause: error
86
+ });
87
+ }
88
+ });
89
+ return font;
90
+ }
91
+ return pending.then(font => {
92
+ extend.get(font)?.(chars);
93
+ return font;
94
+ });
95
+ }
96
+ const DIGIT = /[0-9]/;
97
+ /** Walk one normalized line for both measuring and placing, so fallback widths always agree. */
98
+ function walkText(font, text, options, placed) {
99
+ text = text.normalize('NFC');
100
+ extend.get(font)?.(options.tabular ? `${text}0123456789` : text);
101
+ const spacing = options.letterSpacing ?? 0;
102
+ let digit = 0;
103
+ if (options.tabular) for (const d of '0123456789') digit = Math.max(digit, font.glyphs.get(d)?.advance ?? 0);
104
+ let width = 0;
105
+ let letters = 0;
106
+ for (const ch of text) {
107
+ if (isInvisible(ch)) continue;
108
+ const glyph = font.glyphs.get(ch) ?? (WHITESPACE.test(ch) ? font.glyphs.get(' ') : undefined) ?? font.glyphs.get('?');
109
+ if (!glyph) continue;
110
+ const advance = digit > 0 && DIGIT.test(ch) ? digit : glyph.advance;
111
+ if (letters++ > 0) width += spacing;
112
+ if (placed && glyph.mesh >= 0) placed.push({
113
+ mesh: glyph.mesh,
114
+ x: width + (advance - glyph.advance) / 2,
115
+ y: 0
116
+ });
117
+ width += advance;
118
+ }
119
+ return width;
120
+ }
121
+
122
+ /** A line's advance in ems, with the same Unicode glyph loading, spacing and fallbacks as rendering. */
123
+ export function measureText(font, text, options = {}) {
124
+ return walkText(font, text, options);
125
+ }
126
+
127
+ /** Glyph placements for a line of text: centered vertically on capitals, aligned horizontally. */
128
+ export function layoutText(font, text, align, options = {}) {
129
+ const placed = [];
130
+ const width = walkText(font, text, options, placed);
131
+ const shift = align === 'left' ? 0 : align === 'right' ? -width : -width / 2;
132
+ const lift = -font.capHeight / 2;
133
+ for (const p of placed) {
134
+ p.x += shift;
135
+ p.y = lift;
136
+ }
137
+ return placed;
138
+ }
139
+ //# sourceMappingURL=font.js.map
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+
3
+ import { lathe, merge, recolor } from "./shape.js";
4
+ import { parametricSurface } from "./surface.js";
5
+ /**
6
+ * Smooth closed body through rounded rectangular cross sections along Z. Useful for vehicle
7
+ * shells, cabins, shoes and fairings. Caps have separate flat normals and planar UVs; sides have
8
+ * continuous longitudinal UVs and a welded seam. Input sections may increase or decrease in Z.
9
+ */
10
+ export function loft(sections, options = {}) {
11
+ if (sections.length < 2 || sections.some(s => ![s.z, s.bottom, s.top, s.width, s.topWidth ?? s.width, s.square ?? 4].every(Number.isFinite) || s.width < 0 || (s.topWidth ?? s.width) < 0 || s.top <= s.bottom || (s.square ?? 4) < 1)) throw new Error('loft: needs at least two valid cross sections');
12
+ const ordered = sections[0].z > sections[sections.length - 1].z ? [...sections].reverse() : sections;
13
+ if (ordered.some((s, i) => i > 0 && s.z <= ordered[i - 1].z)) throw new Error('loft: sections must be ordered along Z');
14
+ const n = options.around ?? 32,
15
+ steps = options.smoothness ?? 4;
16
+ if (!Number.isInteger(n) || n < 6 || n > 512 || !Number.isInteger(steps) || steps < 1 || steps > 64) throw new Error('loft: invalid around/smoothness');
17
+ const values = ordered.map(s => [s.z, s.bottom, s.top, s.width, s.topWidth ?? s.width, s.square ?? 4]);
18
+ const at = i => values[Math.max(0, Math.min(values.length - 1, i))];
19
+ const row = v => {
20
+ const cursor = v * (values.length - 1),
21
+ i = Math.min(values.length - 2, Math.floor(cursor)),
22
+ t = cursor - i;
23
+ const a = at(i - 1),
24
+ b = at(i),
25
+ c = at(i + 1),
26
+ d = at(i + 2);
27
+ const f = b.map((_, k) => 0.5 * (2 * b[k] + (-a[k] + c[k]) * t + (2 * a[k] - 5 * b[k] + 4 * c[k] - d[k]) * t * t + (-a[k] + 3 * b[k] - 3 * c[k] + d[k]) * t * t * t));
28
+ f[2] = Math.max(f[1] + 1e-6, f[2]);
29
+ f[3] = Math.max(0, f[3]);
30
+ f[4] = Math.max(0, f[4]);
31
+ f[5] = Math.max(1, f[5]);
32
+ return f;
33
+ };
34
+ const rowCount = (sections.length - 1) * steps;
35
+ const rows = Array.from({
36
+ length: rowCount + 1
37
+ }, (_, i) => row(i / rowCount));
38
+ const point = (u, v) => {
39
+ const [z, bottom, top, width, topWidth, square] = rows[Math.round(v * rowCount)],
40
+ angle = -Math.PI / 2 + u * Math.PI * 2;
41
+ const c = Math.cos(angle),
42
+ s = Math.sin(angle),
43
+ exponent = 2 / square;
44
+ const x = Math.sign(c) * Math.abs(c) ** exponent,
45
+ y = Math.sign(s) * Math.abs(s) ** exponent;
46
+ return [x * (y > 0 ? width + (topWidth - width) * y : width), (bottom + top) / 2 + y * (top - bottom) / 2, z];
47
+ };
48
+ const side = parametricSurface(point, {
49
+ uSegments: n,
50
+ vSegments: rowCount,
51
+ wrapU: true
52
+ });
53
+ const parts = [{
54
+ geometry: side
55
+ }];
56
+ for (const [v, facing] of [[0, -1], [1, 1]]) {
57
+ const section = rows[v * rowCount],
58
+ cy = (section[1] + section[2]) / 2;
59
+ const vertices = new Float32Array((n + 1) * 11),
60
+ indices = [];
61
+ vertices.set([0, cy, section[0], 0, 0, facing, 1, 1, 1, 0.5, 0.5]);
62
+ for (let j = 0; j < n; j++) {
63
+ const p = point(j / n, v),
64
+ width = Math.max(section[3], section[4], 1e-6);
65
+ vertices.set([p[0], p[1], p[2], 0, 0, facing, 1, 1, 1, 0.5 + p[0] / (2 * width), (p[1] - section[1]) / (section[2] - section[1])], (j + 1) * 11);
66
+ const a = j + 1,
67
+ b = (j + 1) % n + 1;
68
+ if (facing === 1) indices.push(0, a, b);else indices.push(0, b, a);
69
+ }
70
+ parts.push({
71
+ geometry: {
72
+ vertices,
73
+ indices: new Uint32Array(indices)
74
+ }
75
+ });
76
+ }
77
+ return paint(merge(parts), options.color);
78
+ }
79
+ function paint(geometry, color) {
80
+ return color ? recolor(geometry, typeof color === 'function' ? ({
81
+ position,
82
+ normal
83
+ }) => color(position, normal) : () => color) : geometry;
84
+ }
85
+
86
+ /** A rounded rectangular cross-section with flat front/back caps, centered at the origin. */
87
+ export function block(w, h, d, square = 6, color) {
88
+ return loft([{
89
+ z: -d / 2,
90
+ bottom: -h / 2,
91
+ top: h / 2,
92
+ width: w / 2,
93
+ square
94
+ }, {
95
+ z: d / 2,
96
+ bottom: -h / 2,
97
+ top: h / 2,
98
+ width: w / 2,
99
+ square
100
+ }], {
101
+ around: 16,
102
+ smoothness: 1,
103
+ color
104
+ });
105
+ }
106
+
107
+ /** An exact ellipsoid with analytic normals, rather than capped/truncated poles. */
108
+ export function ellipsoid(radii, color, around = 16) {
109
+ if (radii.some(r => !Number.isFinite(r) || r <= 0)) throw new Error('ellipsoid: radii must be positive');
110
+ const profile = Array.from({
111
+ length: 9
112
+ }, (_, i) => [i === 0 || i === 8 ? 0 : Math.sin(i * Math.PI / 8), Math.cos(i * Math.PI / 8)]);
113
+ return paint(merge([{
114
+ geometry: lathe(profile, {
115
+ segments: around
116
+ }),
117
+ scale: radii
118
+ }]), color);
119
+ }
120
+
121
+ /** Smooth Hermite interpolation through increasing [x,value] points; clamped beyond the ends. */
122
+ export function smoothCurve(points) {
123
+ if (points.length < 2 || points.some((p, i) => !p.every(Number.isFinite) || i > 0 && p[0] <= points[i - 1][0])) throw new Error('smoothCurve: needs increasing finite points');
124
+ const n = points.length;
125
+ const slope = i => {
126
+ const a = points[Math.max(0, i - 1)],
127
+ b = points[Math.min(n - 1, i + 1)];
128
+ return (b[1] - a[1]) / (b[0] - a[0]);
129
+ };
130
+ return x => {
131
+ if (x <= points[0][0]) return points[0][1];
132
+ if (x >= points[n - 1][0]) return points[n - 1][1];
133
+ let i = 0;
134
+ while (points[i + 1][0] < x) i++;
135
+ const [x0, y0] = points[i],
136
+ [x1, y1] = points[i + 1],
137
+ h = x1 - x0,
138
+ t = (x - x0) / h;
139
+ return (2 * t * t * t - 3 * t * t + 1) * y0 + (t * t * t - 2 * t * t + t) * h * slope(i) + (-2 * t * t * t + 3 * t * t) * y1 + (t * t * t - t * t) * h * slope(i + 1);
140
+ };
141
+ }
142
+ //# sourceMappingURL=loft.js.map
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+
3
+ import NativeEngine from '../native/NativeNayanEngine';
4
+ import { meshInfo, registerMesh } from "../render/meshes.js";
5
+ import { AssetLoader } from "./AssetLoader.js";
6
+ // External 3D models: glTF 2.0 (.glb, or .gltf with embedded buffers), parsed by the Rust core.
7
+
8
+ /** A loaded model: pass it as an entity's `mesh`. Colliders sized from the mesh fit its bounding box. */
9
+
10
+ /** An animation embedded in a loaded GLB. */
11
+
12
+ const loader = new AssetLoader('loadModel');
13
+
14
+ /** A registered mesh's geometry, aliasing native memory. */
15
+ export function nativeGeometry(mesh) {
16
+ return {
17
+ vertices: new Float32Array(NativeEngine.modelVertices(mesh)),
18
+ indices: new Uint32Array(NativeEngine.modelIndices(mesh))
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Registers a mesh the core already holds (a created, cached or loaded model, a glyph, an alias):
24
+ * the renderer copies it inside Rust, and JS reads its geometry only if something asks for it
25
+ * (merging, caching). Apps built before `renderModel` upload it through JS as before.
26
+ */
27
+ export function registerCoreMesh(mesh, info = {}, geometry = () => nativeGeometry(mesh)) {
28
+ const native = NativeEngine;
29
+ let cached;
30
+ const inRust = typeof native.renderModel === 'function' && native.renderModel(mesh, info.texture ?? -1, info.shine ?? 0);
31
+ registerMesh(mesh, {
32
+ get geometry() {
33
+ return cached ??= geometry();
34
+ },
35
+ texture: info.texture,
36
+ shine: info.shine
37
+ }, !inRust);
38
+ }
39
+ const materials = new Map();
40
+
41
+ /** Independent materials share core geometry without overwriting an existing model's appearance. */
42
+ export function registerModelMesh(mesh, shine) {
43
+ const info = meshInfo(mesh);
44
+ if (info && (info.shine ?? 0) === shine) return mesh;
45
+ const key = `${mesh}:${shine}`;
46
+ const cached = materials.get(key);
47
+ if (cached !== undefined) return cached;
48
+ const id = info ? NativeEngine.meshAlias(mesh) : mesh;
49
+ if (id < 0) throw new Error(NativeEngine.loadError() || "couldn't register a material variant");
50
+ registerCoreMesh(id, {
51
+ texture: info?.texture,
52
+ shine
53
+ }, info ? () => info.geometry : undefined);
54
+ materials.set(key, id);
55
+ return id;
56
+ }
57
+
58
+ /**
59
+ * Initializes the engine automatically. Loads a glTF model once; pass it as an entity's `mesh`. Meshes, node transforms, material colors
60
+ * and the base color texture are kept (one texture per model); skeletal animation clips are available through `animations` and `World.playClip`.
61
+ * Every entity using the model is drawn in one instanced draw call.
62
+ *
63
+ * ```ts
64
+ * const tree = await loadModel(require("./assets/tree.glb"), { fit: 2 });
65
+ * world.spawn({ mesh: tree, position: [0, 1, 0], physics: "fixed" });
66
+ * ```
67
+ */
68
+ export function loadModel(source, options = {}) {
69
+ const center = options.center ?? true;
70
+ const fit = options.fit ?? 0;
71
+ const shine = Math.max(0, Math.min(1, options.shine ?? 0));
72
+ return loader.load(source, (native, bytes) => {
73
+ // Parsed on a background native thread where the app's build supports it.
74
+ if (typeof native.modelLoadAsync === 'function') return native.modelLoadAsync(bytes, center, fit).then(mesh => finishModel(native, mesh, shine));
75
+ return finishModel(native, native.modelLoad(bytes, center, fit), shine);
76
+ }, [center, fit, shine]);
77
+ }
78
+
79
+ /** A parsed glTF's handle: registered for drawing (in Rust) with its rig's clips. */
80
+ function finishModel(native, mesh, shine) {
81
+ if (mesh < 0) throw new Error(native.loadError() || 'not a glTF model');
82
+ const texture = native.modelTexture(mesh);
83
+ registerCoreMesh(mesh, {
84
+ texture: texture >= 0 ? texture : undefined,
85
+ shine
86
+ });
87
+ const [x, y, z] = native.modelSize(mesh);
88
+ const rig = JSON.parse(native.modelRig(mesh));
89
+ return {
90
+ animations: rig.clips,
91
+ jointCount: rig.joints,
92
+ id: mesh,
93
+ size: [x, y, z]
94
+ };
95
+ }
96
+ //# sourceMappingURL=model.js.map