@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,317 @@
1
+ "use strict";
2
+
3
+ import { useEffect, useMemo, useRef } from 'react';
4
+ import NativeEngine from '../native/NativeNayanEngine';
5
+ import { MAX_MESHES } from "../types.js";
6
+ import { resolveColor } from "../world/color.js";
7
+ import { follow, shake } from "./camera.js";
8
+ import { createTouchResponder, pixelRatio, Surface, useActive } from './host';
9
+ import { getMeshIdLimit, uploadBuiltInMeshes } from "./meshes.js";
10
+ import { AutoQuality, median, QUALITY_LEVELS } from "./quality.js";
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ const DEFAULT_BACKGROUND = [0.04, 0.05, 0.09];
13
+ const FRAME_LEN = 26; // ENGINE_FRAME_LEN
14
+ const WHITE = [1, 1, 1];
15
+ let nextSurface = 1;
16
+ const defaultCamera = () => ({
17
+ eye: [0, 40, 60],
18
+ target: [0, 0, 0],
19
+ fov: Math.PI / 3
20
+ });
21
+ const defaultLight = () => ({
22
+ direction: [0.4, 0.8, 0.5],
23
+ ambient: 0.35
24
+ });
25
+
26
+ /** Callbacks waiting for the next frame any GameView finishes (see `afterNextFrame`). */
27
+ let afterFrame = [];
28
+ /** GameView frame loops running now. */
29
+ let runningLoops = 0;
30
+ function runAfterFrame() {
31
+ if (afterFrame.length === 0) return;
32
+ const due = afterFrame;
33
+ afterFrame = [];
34
+ for (const fn of due) fn();
35
+ }
36
+
37
+ /**
38
+ * Runs `fn` right after the next frame a GameView hands to the screen, on the same display clock
39
+ * (the native one where the platform has it): the time a frame leaves is the best moment for
40
+ * background work, such as loading in slices. With no GameView running it falls back to
41
+ * requestAnimationFrame. Returns a function that cancels it.
42
+ *
43
+ * ```ts
44
+ * const cancel = afterNextFrame(() => buildNextChunk());
45
+ * ```
46
+ */
47
+ export function afterNextFrame(fn) {
48
+ let cancelled = false;
49
+ const run = () => {
50
+ if (!cancelled) fn();
51
+ };
52
+ if (runningLoops > 0) afterFrame.push(run);else {
53
+ const raf = requestAnimationFrame(run);
54
+ return () => {
55
+ cancelled = true;
56
+ cancelAnimationFrame(raf);
57
+ };
58
+ }
59
+ return () => {
60
+ cancelled = true;
61
+ };
62
+ }
63
+ export function GameView(props) {
64
+ const {
65
+ source,
66
+ camera,
67
+ light,
68
+ background,
69
+ fog,
70
+ style
71
+ } = props;
72
+ const active = useActive();
73
+ // Latest props, read from the frame loop and touch handlers without restarting them.
74
+ const live = useRef({
75
+ props,
76
+ camera: camera ?? defaultCamera(),
77
+ light: light ?? defaultLight(),
78
+ environment: {
79
+ background: background ?? DEFAULT_BACKGROUND,
80
+ fog: fog ?? 0
81
+ }
82
+ });
83
+ live.current.props = props;
84
+ if (camera) live.current.camera = camera;
85
+ if (light) live.current.light = light;
86
+ live.current.environment = {
87
+ background: background ?? DEFAULT_BACKGROUND,
88
+ fog: fog ?? 0
89
+ };
90
+ const touch = useMemo(() => createTouchResponder(() => live.current.props), []);
91
+ useEffect(() => touch.dispose, [touch]);
92
+ useEffect(() => {
93
+ if (props.paused || !active) touch.cancel();
94
+ }, [touch, props.paused, active]);
95
+ const surfaceId = useMemo(() => nextSurface++, []);
96
+ const size = useRef({
97
+ width: 0,
98
+ height: 0
99
+ });
100
+ // The automatic quality outlives pauses, settings sheets and resumes; only a new source starts over.
101
+ const autoQuality = useRef(null);
102
+ useEffect(() => {
103
+ if (props.paused || !active) return;
104
+ const native = NativeEngine;
105
+ const report = error => {
106
+ const e = error instanceof Error ? error : new Error(String(error));
107
+ (live.current.props.onError ?? (x => console.error(`GameView: ${x.message}`)))(e);
108
+ };
109
+ if (!native) {
110
+ report(new Error('the native engine is not linked into this build'));
111
+ return;
112
+ }
113
+ uploadBuiltInMeshes();
114
+ let alive = true;
115
+ let raf = 0;
116
+ let errors = 0;
117
+ // Where the platform has them (Android), frames come from the display's own clock and are drawn
118
+ // on the engine's render thread: this loop only runs the game and publishes what to draw.
119
+ const native2 = native;
120
+ const threaded = native2.hasFrameClock?.() === true && typeof native2.publishFrame === 'function' && typeof native2.startFrames === 'function';
121
+ let presentedAt = 0;
122
+ if (autoQuality.current?.source !== source) autoQuality.current = {
123
+ source,
124
+ auto: new AutoQuality()
125
+ };
126
+ const auto = autoQuality.current.auto;
127
+ const chosen = () => {
128
+ const setting = live.current.props.quality ?? 'auto';
129
+ return setting === 'auto' ? auto.quality : setting;
130
+ };
131
+ const followed = [0, 0, 0];
132
+ const shaken = {
133
+ eye: [0, 0, 0],
134
+ target: [0, 0, 0],
135
+ fov: 1
136
+ };
137
+ const frameData = new Float64Array(FRAME_LEN);
138
+ const viewProj = new Float32Array(16);
139
+ // Sources without texture regions show whole textures.
140
+ const wholeRegions = s => s.regions ?? new Float32Array(Math.max(1, s.capacity) * 4).map((_, i) => i % 4 < 2 ? 0 : 1);
141
+ const regions = wholeRegions(source);
142
+ // Instances actually packed (hidden ones are left out): the furthest range end, at most `count`.
143
+ // Only the mesh ids in use can have instances, in each half (opaque, then see-through). The
144
+ // see-through half starts halfway through the ranges, wherever that is (a native core built for
145
+ // fewer mesh ids has a shorter table).
146
+ const drawn = s => {
147
+ const r = s.ranges;
148
+ const buckets = r.length >> 1;
149
+ const limit = getMeshIdLimit();
150
+ const opaqueStop = Math.min(limit, buckets);
151
+ const half = Math.min(MAX_MESHES, buckets >> 1);
152
+ const clearStop = Math.min(half + limit, buckets);
153
+ let end = 0;
154
+ for (let i = 1, stop = opaqueStop * 2; i < stop; i += 2) {
155
+ const count = r[i];
156
+ if (count > 0 && r[i - 1] + count > end) end = r[i - 1] + count;
157
+ }
158
+ for (let i = half * 2 + 1, stop = clearStop * 2; i < stop; i += 2) {
159
+ const count = r[i];
160
+ if (count > 0 && r[i - 1] + count > end) end = r[i - 1] + count;
161
+ }
162
+ return Math.min(s.count, end);
163
+ };
164
+ let overlay = null;
165
+ let last = 0;
166
+ let frames = 0;
167
+ let fpsStart = 0;
168
+ let updateTime = 0;
169
+ let renderTime = 0;
170
+ // This second's frame intervals and draw times: the quality picker looks at their medians, so a
171
+ // single hiccup (garbage collection, a busy JS frame) never costs quality.
172
+ const intervals = [];
173
+ const draws = [];
174
+ // Game time starts with the first frame actually on screen: until then `onUpdate` gets dt = 0, so
175
+ // intros and animations aren't over before the surface (attached during the screen's transition) shows.
176
+ let shown = false;
177
+ const frame = now => {
178
+ if (!alive) return;
179
+ if (!threaded) raf = requestAnimationFrame(frame);
180
+ if (!last) last = fpsStart = now;
181
+ if (now > last) intervals.push(now - last);
182
+ const dt = shown ? Math.min((now - last) / 1000, 0.1) : 0;
183
+ last = now;
184
+ const {
185
+ props: current,
186
+ camera,
187
+ light,
188
+ environment
189
+ } = live.current;
190
+ const t0 = performance.now();
191
+ const layer = current.overlay;
192
+ const sourceSteps = source.updateCount;
193
+ const layerSteps = layer?.updateCount;
194
+ current.onUpdate?.(dt);
195
+ // Step what onUpdate didn't (a World counts its own updates), so the loop needs no boilerplate.
196
+ if (source.update && !source.isDisposed && source.updateCount === sourceSteps) source.update(dt);
197
+ if (layer?.update && !layer.isDisposed && layer.updateCount === layerSteps) layer.update(dt);
198
+ updateTime += performance.now() - t0;
199
+ follow(camera, source, followed, dt);
200
+ const view = shake(camera, shaken, dt);
201
+ const quality = chosen();
202
+ const level = QUALITY_LEVELS[quality];
203
+ const {
204
+ width,
205
+ height
206
+ } = size.current;
207
+ // Phones' GPUs are fill-rate bound: cap the pixels drawn rather than match a 3x+ screen.
208
+ const ratio = Math.min(pixelRatio(), level.maxPixelRatio);
209
+ const f = frameData;
210
+ f.set(view.eye, 0);
211
+ f.set(view.target, 3);
212
+ f[6] = view.fov;
213
+ f[7] = view.ortho ?? 0;
214
+ f.set(light.direction, 8);
215
+ f[11] = light.ambient;
216
+ f[12] = light.shadows ?? true ? 1 : 0;
217
+ f[13] = light.shadowExtent ?? 30;
218
+ // Exactly three channels each: either color may carry an alpha that must not spill over.
219
+ const lightColor = light.color === undefined ? WHITE : resolveColor(light.color);
220
+ f[14] = lightColor[0];
221
+ f[15] = lightColor[1];
222
+ f[16] = lightColor[2];
223
+ const sky = resolveColor(environment.background);
224
+ f[17] = sky[0];
225
+ f[18] = sky[1];
226
+ f[19] = sky[2];
227
+ f[20] = environment.fog;
228
+ f[21] = Math.round(width * ratio);
229
+ f[22] = Math.round(height * ratio);
230
+ f[23] = level.samples;
231
+ f[24] = level.shadowSize;
232
+ f[25] = level.softShadows ? 1 : 0;
233
+ const t1 = performance.now();
234
+ if (layer) {
235
+ if (overlay?.source !== layer) overlay = {
236
+ source: layer,
237
+ regions: wholeRegions(layer)
238
+ };
239
+ native.renderOverlay(surfaceId, layer.matrices.buffer, layer.colors.buffer, overlay.regions.buffer, layer.ranges.buffer, drawn(layer), width, height);
240
+ } else if (overlay) {
241
+ overlay = null;
242
+ native.renderOverlay(surfaceId, regions.buffer, regions.buffer, regions.buffer, regions.buffer, 0, 0, 0); // removes it
243
+ }
244
+ const render = threaded ? native2.publishFrame : native.renderFrame;
245
+ const rendered = render(surfaceId, f.buffer, source.matrices.buffer, source.colors.buffer, regions.buffer, source.ranges.buffer, drawn(source), viewProj.buffer, source.nativeWorld ?? -1);
246
+ const drawMs = performance.now() - t1;
247
+ if (!threaded) {
248
+ renderTime += drawMs;
249
+ draws.push(drawMs);
250
+ }
251
+ // A published frame's camera is known at once (picking needs it), whether or not it's presented yet.
252
+ if (rendered === 1 || threaded && rendered === 0) source.setView?.(viewProj, width, height);
253
+ if (rendered === 1) shown = true;else if (rendered < 0 && errors++ < 3) report(new Error(native.renderError() || 'rendering failed'));
254
+ frames++;
255
+ if (now - fpsStart >= 1000) {
256
+ const elapsed = now - fpsStart;
257
+ let fps = frames * 1000 / elapsed;
258
+ let frameMs = median(intervals);
259
+ let renderMs = median(draws);
260
+ let meanRenderMs = renderTime / frames;
261
+ if (threaded) {
262
+ // What the display actually got: frames presented by the render thread, and how long each took to draw.
263
+ const [presented = 0, nativeMs = 0] = native2.renderStats?.(surfaceId) ?? [];
264
+ const shownFrames = presented - presentedAt;
265
+ presentedAt = presented;
266
+ fps = shownFrames * 1000 / elapsed;
267
+ frameMs = shownFrames > 0 ? Math.max(frameMs, elapsed / shownFrames) : elapsed;
268
+ renderMs = meanRenderMs = nativeMs;
269
+ }
270
+ auto.report(now, {
271
+ frameMs,
272
+ renderMs
273
+ });
274
+ intervals.length = 0;
275
+ draws.length = 0;
276
+ current.onStats?.({
277
+ fps,
278
+ updateMs: updateTime / frames,
279
+ renderMs: meanRenderMs,
280
+ quality,
281
+ backend: native.renderBackend()
282
+ });
283
+ frames = 0;
284
+ updateTime = 0;
285
+ renderTime = 0;
286
+ fpsStart = now;
287
+ }
288
+ runAfterFrame();
289
+ };
290
+ if (threaded) native2.startFrames(surfaceId, frame);else raf = requestAnimationFrame(frame);
291
+ runningLoops++;
292
+ return () => {
293
+ alive = false;
294
+ runningLoops--;
295
+ // Work waiting on this loop's frames moves to requestAnimationFrame if it was the last one.
296
+ if (runningLoops === 0 && afterFrame.length) requestAnimationFrame(runAfterFrame);
297
+ if (threaded) native2.stopFrames(surfaceId);else cancelAnimationFrame(raf);
298
+ };
299
+ // A new source or resuming a paused view restarts drawing; other props are read live.
300
+ // eslint-disable-next-line react-hooks/exhaustive-deps
301
+ }, [source, props.paused, active]);
302
+ const touchable = props.onTap || props.onSwipe || props.onDrag || props.onLongPress || props.onPinch || props.onOverlayTouch;
303
+ return /*#__PURE__*/_jsx(Surface, {
304
+ surfaceId: surfaceId,
305
+ style: style,
306
+ onSize: (width, height) => {
307
+ size.current = {
308
+ width,
309
+ height
310
+ };
311
+ },
312
+ onError: error => (live.current.props.onError ?? console.error)(error),
313
+ touch: touchable ? touch : undefined,
314
+ onPrimaryAction: props.onPrimaryAction ? () => live.current.props.onPrimaryAction?.() : undefined
315
+ });
316
+ }
317
+ //# sourceMappingURL=GameView.js.map
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ // Camera helpers: follow an entity, and shake the camera on impacts.
4
+
5
+ /** `camera.follow`: ease the target toward the entity (where it was last drawn) and keep the eye at the offset. */
6
+ export function follow(camera, source, out, dt) {
7
+ const f = camera.follow;
8
+ if (!f || !('worldPosition' in source) || !source.worldPosition(f.target, out)) return;
9
+ const smoothing = f.smoothing ?? 0.15;
10
+ const k = smoothing > 0 ? 1 - Math.exp(-dt / smoothing) : 1;
11
+ for (let i = 0; i < 3; i++) {
12
+ camera.target[i] = camera.target[i] + (out[i] - camera.target[i]) * k;
13
+ camera.eye[i] = camera.target[i] + f.offset[i];
14
+ }
15
+ }
16
+
17
+ /** `camera.shake`: a jittered copy of the camera; the strength fades by itself. */
18
+ export function shake(camera, out, dt) {
19
+ const strength = camera.shake ?? 0;
20
+ if (strength <= 0.001) {
21
+ if (strength) camera.shake = 0;
22
+ return camera;
23
+ }
24
+ camera.shake = strength * Math.exp(-dt * 8);
25
+ out.fov = camera.fov;
26
+ out.ortho = camera.ortho;
27
+ for (let i = 0; i < 3; i++) {
28
+ const jitter = (Math.random() * 2 - 1) * strength;
29
+ out.eye[i] = camera.eye[i] + jitter;
30
+ out.target[i] = camera.target[i] + jitter;
31
+ }
32
+ return out;
33
+ }
34
+ //# sourceMappingURL=camera.js.map
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { AppState, PixelRatio, requireNativeComponent, StyleSheet, View } from 'react-native';
5
+ import { createTouchResponder } from "./touch.js";
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ export const EngineSurface = requireNativeComponent('NayanEngineSurface');
8
+ export { createTouchResponder };
9
+ export const pixelRatio = () => PixelRatio.get();
10
+ export function useActive() {
11
+ const [active, setActive] = useState(AppState.currentState !== 'background' && AppState.currentState !== 'inactive');
12
+ useEffect(() => {
13
+ const subscription = AppState.addEventListener('change', state => setActive(state === 'active'));
14
+ return () => subscription.remove();
15
+ }, []);
16
+ return active;
17
+ }
18
+ export function Surface({
19
+ surfaceId,
20
+ style,
21
+ onSize,
22
+ touch
23
+ }) {
24
+ return /*#__PURE__*/_jsx(View, {
25
+ style: [styles.canvas, style],
26
+ onLayout: e => onSize(e.nativeEvent.layout.width, e.nativeEvent.layout.height),
27
+ ...touch?.responder.panHandlers,
28
+ children: /*#__PURE__*/_jsx(EngineSurface, {
29
+ surfaceId: surfaceId,
30
+ style: styles.canvas
31
+ })
32
+ });
33
+ }
34
+ const styles = StyleSheet.create({
35
+ canvas: {
36
+ flex: 1
37
+ }
38
+ });
39
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { PRIMARY_INPUT_EVENT } from "../input/keyboard.js";
5
+ import { attachCanvas, detachCanvas } from "../native/NativeNayanEngine.web.js";
6
+ import { createTouchResponder } from "./touch.js";
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ export { createTouchResponder };
9
+ export const pixelRatio = () => typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1;
10
+ export function useActive() {
11
+ const [active, setActive] = useState(typeof document === 'undefined' || !document.hidden);
12
+ useEffect(() => {
13
+ const changed = () => setActive(!document.hidden);
14
+ document.addEventListener('visibilitychange', changed);
15
+ return () => document.removeEventListener('visibilitychange', changed);
16
+ }, []);
17
+ return active;
18
+ }
19
+ export function Surface({
20
+ surfaceId,
21
+ style,
22
+ onSize,
23
+ onError,
24
+ touch,
25
+ onPrimaryAction
26
+ }) {
27
+ const parent = useRef(null);
28
+ const canvas = useRef(null);
29
+ const live = useRef({
30
+ onSize,
31
+ onError,
32
+ touch,
33
+ onPrimaryAction
34
+ });
35
+ live.current = {
36
+ onSize,
37
+ onError,
38
+ touch,
39
+ onPrimaryAction
40
+ };
41
+ useEffect(() => {
42
+ let alive = true;
43
+ const measure = () => {
44
+ if (!alive || !parent.current) return;
45
+ const rect = parent.current.getBoundingClientRect();
46
+ live.current.onSize(rect.width, rect.height);
47
+ };
48
+ measure();
49
+ const observer = new ResizeObserver(measure);
50
+ observer.observe(parent.current);
51
+ void attachCanvas(surfaceId, canvas.current).then(() => {
52
+ if (!alive) detachCanvas(surfaceId);
53
+ }).catch(error => {
54
+ if (alive) live.current.onError(error instanceof Error ? error : new Error(String(error)));
55
+ });
56
+ const cancel = () => live.current.touch?.cancel();
57
+ const primary = event => {
58
+ if (live.current.onPrimaryAction) {
59
+ event.preventDefault();
60
+ live.current.onPrimaryAction();
61
+ }
62
+ };
63
+ const element = parent.current;
64
+ element.addEventListener(PRIMARY_INPUT_EVENT, primary);
65
+ const visibility = () => {
66
+ if (document.hidden) cancel();
67
+ };
68
+ window.addEventListener('blur', cancel);
69
+ document.addEventListener('visibilitychange', visibility);
70
+ return () => {
71
+ alive = false;
72
+ observer.disconnect();
73
+ cancel();
74
+ element.removeEventListener(PRIMARY_INPUT_EVENT, primary);
75
+ window.removeEventListener('blur', cancel);
76
+ document.removeEventListener('visibilitychange', visibility);
77
+ detachCanvas(surfaceId);
78
+ };
79
+ }, [surfaceId]);
80
+ return /*#__PURE__*/_jsx("div", {
81
+ ref: parent,
82
+ style: {
83
+ position: 'relative',
84
+ flex: 1,
85
+ minWidth: 0,
86
+ minHeight: 0,
87
+ overflow: 'hidden',
88
+ touchAction: 'none',
89
+ ...style
90
+ },
91
+ ...touch?.responder.panHandlers,
92
+ children: /*#__PURE__*/_jsx("canvas", {
93
+ ref: canvas,
94
+ style: {
95
+ position: 'absolute',
96
+ inset: 0,
97
+ width: '100%',
98
+ height: '100%',
99
+ display: 'block'
100
+ }
101
+ })
102
+ });
103
+ }
104
+ //# sourceMappingURL=host.web.js.map
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * A light for flat, front-facing scenes (boards, puzzles, 2D games looking down -z): surfaces facing
5
+ * the camera (+z) are lit at exactly 1, so flat panels show their color unchanged, while rounded
6
+ * shapes keep their shading (lighter toward the light, darker away). Shadows are off.
7
+ *
8
+ * ```tsx
9
+ * <GameView source={world} light={frontLight([-0.45, 0.6, 1], 0.5)} />
10
+ * ```
11
+ */
12
+ export function frontLight(direction, ambient) {
13
+ const [x, y, z] = direction;
14
+ const facing = z / Math.hypot(x, y, z);
15
+ // Mirrors the renderer's shading: ambient × hemisphere (0.8 for a sideways normal) plus diffuse.
16
+ const k = 1 / (ambient * 0.8 + (1 - ambient) * facing);
17
+ return {
18
+ direction,
19
+ ambient,
20
+ shadows: false,
21
+ color: [k, k, k]
22
+ };
23
+ }
24
+ //# sourceMappingURL=light.js.map
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+
3
+ import NativeEngine from '../native/NativeNayanEngine';
4
+ import { prepareEngine } from '../native/prepare';
5
+ import { SHAPE_MESH } from "../types.js";
6
+
7
+ // Interleaved position(3) + normal(3) + color(3) + uv(2), the same layout loaded models use.
8
+ // Built-in shapes are unit-sized, centered at the origin and white (the instance color tints them).
9
+ export const VERTEX_FLOATS = 11;
10
+ export class Builder {
11
+ vertices = [];
12
+ indices = [];
13
+
14
+ /** Adds a vertex, returns its index. */
15
+ vertex(p, n, u, v) {
16
+ this.vertices.push(p[0], p[1], p[2], n[0], n[1], n[2], 1, 1, 1, u, v);
17
+ return this.vertices.length / VERTEX_FLOATS - 1;
18
+ }
19
+
20
+ /** Two triangles, counter-clockwise a b c d as seen from the front. */
21
+ quad(a, b, c, d) {
22
+ this.indices.push(a, b, c, a, c, d);
23
+ }
24
+
25
+ /**
26
+ * A (rows + 1) x (cols + 1) vertex grid; `at(u, v)` gives position and normal. u runs along a row and
27
+ * v down the rows; the grid faces the side from which u goes right and v goes down.
28
+ */
29
+ grid(rows, cols, at) {
30
+ const first = this.vertices.length / VERTEX_FLOATS;
31
+ for (let i = 0; i <= rows; i++) {
32
+ for (let j = 0; j <= cols; j++) {
33
+ const [p, n] = at(j / cols, i / rows);
34
+ this.vertex(p, n, j / cols, i / rows);
35
+ }
36
+ }
37
+ for (let i = 0; i < rows; i++) {
38
+ for (let j = 0; j < cols; j++) {
39
+ const a = first + i * (cols + 1) + j;
40
+ const b = a + cols + 1;
41
+ this.indices.push(a, b, a + 1, a + 1, b, b + 1);
42
+ }
43
+ }
44
+ }
45
+
46
+ /** A flat disc at height y facing up (+1) or down (-1). */
47
+ disc(y, radius, facing, segments) {
48
+ const center = this.vertex([0, y, 0], [0, facing, 0], 0.5, 0.5);
49
+ const first = center + 1;
50
+ for (let j = 0; j <= segments; j++) {
51
+ const t = j / segments * TAU;
52
+ const x = Math.cos(t);
53
+ const z = Math.sin(t);
54
+ this.vertex([x * radius, y, z * radius], [0, facing, 0], 0.5 + x / 2, 0.5 + z / 2);
55
+ }
56
+ for (let j = 0; j < segments; j++) {
57
+ if (facing === 1) this.indices.push(center, first + j + 1, first + j);else this.indices.push(center, first + j, first + j + 1);
58
+ }
59
+ }
60
+ }
61
+ const TAU = Math.PI * 2;
62
+
63
+ /** Render-ready geometry: VERTEX_FLOATS floats per vertex, u32 triangle indices. */
64
+
65
+ /** What the renderer draws for a mesh id: geometry, and a texture id (see `loadTexture`) if any. */
66
+ /** `shine` 0..1: how glossy (sun highlight and sky reflection), 0 = matte. */
67
+
68
+ const registered = new Map();
69
+ /** One past the highest mesh id registered (the 16 built-in shapes at least). */
70
+ let idLimit = 16;
71
+
72
+ /** Registers what a mesh id draws (loaded models, glyphs, textured variants). */
73
+ export function registerMesh(mesh, info, uploadGeometry = true) {
74
+ registered.set(mesh, info);
75
+ idLimit = Math.max(idLimit, mesh + 1);
76
+ if (uploadGeometry) upload(mesh, info);
77
+ }
78
+
79
+ /** One past the highest mesh id in use: per-frame scans of a source's ranges stop here. */
80
+ export const getMeshIdLimit = () => idLimit;
81
+
82
+ /** A typed array's bytes as an ArrayBuffer of exactly its length. */
83
+ function exact(data) {
84
+ const {
85
+ buffer,
86
+ byteOffset,
87
+ byteLength
88
+ } = data;
89
+ if (buffer instanceof ArrayBuffer && byteOffset === 0 && byteLength === buffer.byteLength) return buffer;
90
+ return data.slice().buffer;
91
+ }
92
+
93
+ /** Hands a mesh's geometry and material to the native renderer (which copies them). */
94
+ function upload(mesh, info) {
95
+ const {
96
+ vertices,
97
+ indices
98
+ } = info.geometry;
99
+ NativeEngine?.renderMesh(mesh, exact(vertices), exact(indices), info.texture ?? -1, info.shine ?? 0);
100
+ }
101
+ let builtInsUploaded = false;
102
+
103
+ /** Generates the built-in shapes in the Rust core and registers them with the native renderer (once). */
104
+ export function uploadBuiltInMeshes() {
105
+ if (builtInsUploaded) return;
106
+ NativeEngine?.generateBuiltinMeshes();
107
+ builtInsUploaded = true;
108
+ }
109
+
110
+ /**
111
+ * Initializes the shared core and built-in meshes. Await before using the engine on the web.
112
+ * Mobile initialization remains synchronous and also warms the GPU on a background thread.
113
+ * Repeat calls reuse initialization and mesh registration.
114
+ */
115
+ export function warmUpEngine() {
116
+ const pending = prepareEngine();
117
+ const warm = () => {
118
+ const native = NativeEngine;
119
+ if (!native) return;
120
+ uploadBuiltInMeshes();
121
+ // Apps built before this method existed draw as before: the first GameView makes the device.
122
+ if (typeof native.renderWarmUp === 'function') native.renderWarmUp();
123
+ };
124
+ if (pending) return pending.then(warm);
125
+ warm();
126
+ return Promise.resolve();
127
+ }
128
+
129
+ /** What to draw for a mesh id, or undefined ("none", or unknown). Built-in shapes use native geometry. */
130
+ export function meshInfo(mesh) {
131
+ const info = registered.get(mesh);
132
+ if (info || mesh === SHAPE_MESH.none) return info;
133
+ // Built-in shapes are generated in Rust; read their geometry from the renderer.
134
+ const shape = Object.keys(SHAPE_MESH).find(s => SHAPE_MESH[s] === mesh);
135
+ if (!shape || shape === 'none') return undefined;
136
+ const native = NativeEngine;
137
+ if (!native) return undefined;
138
+ let vertices = new Float32Array(native.renderMeshVertices(mesh));
139
+ // Generate the built-in shapes on first use, so shapeGeometry works before warmUpEngine() on mobile.
140
+ if (!vertices.length && !builtInsUploaded && typeof native.generateBuiltinMeshes === 'function') {
141
+ try {
142
+ uploadBuiltInMeshes();
143
+ } catch {
144
+ return undefined; // Browser core not loaded yet: callers report that warmUpEngine() is needed.
145
+ }
146
+ vertices = new Float32Array(native.renderMeshVertices(mesh));
147
+ }
148
+ const indices = new Uint32Array(native.renderMeshIndices(mesh));
149
+ if (!vertices.length) return undefined;
150
+ const made = {
151
+ geometry: {
152
+ vertices,
153
+ indices
154
+ }
155
+ };
156
+ registered.set(mesh, made);
157
+ return made;
158
+ }
159
+ //# sourceMappingURL=meshes.js.map