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

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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/NayanEngine.podspec +28 -0
  3. package/README.md +210 -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 +222 -0
  10. package/lib/module/assets/font.js +136 -0
  11. package/lib/module/assets/loft.js +142 -0
  12. package/lib/module/assets/model.js +74 -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 +36 -0
  19. package/lib/module/assets/storage.web.js +37 -0
  20. package/lib/module/assets/surface.js +47 -0
  21. package/lib/module/assets/texture.js +66 -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 +36 -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 +94 -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 +551 -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 +249 -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/meshes.js +159 -0
  48. package/lib/module/render/quality.js +112 -0
  49. package/lib/module/render/responder.js +4 -0
  50. package/lib/module/render/responder.web.js +97 -0
  51. package/lib/module/render/touch.js +185 -0
  52. package/lib/module/types.js +30 -0
  53. package/lib/module/world/Scoreboard.js +143 -0
  54. package/lib/module/world/TransformBatch.js +126 -0
  55. package/lib/module/world/World.js +690 -0
  56. package/lib/module/world/desc.js +186 -0
  57. package/lib/module/world/effects.js +451 -0
  58. package/lib/module/world/quat.js +86 -0
  59. package/lib/module/world/types.js +4 -0
  60. package/lib/typescript/package.json +1 -0
  61. package/lib/typescript/src/assets/AssetBytesCache.d.ts +7 -0
  62. package/lib/typescript/src/assets/AssetCache.d.ts +9 -0
  63. package/lib/typescript/src/assets/AssetLoader.d.ts +11 -0
  64. package/lib/typescript/src/assets/CacheGeneration.d.ts +3 -0
  65. package/lib/typescript/src/assets/IndexedDbCache.web.d.ts +20 -0
  66. package/lib/typescript/src/assets/ProceduralCache.d.ts +33 -0
  67. package/lib/typescript/src/assets/font.d.ts +53 -0
  68. package/lib/typescript/src/assets/loft.d.ts +31 -0
  69. package/lib/typescript/src/assets/model.d.ts +48 -0
  70. package/lib/typescript/src/assets/modelling.d.ts +157 -0
  71. package/lib/typescript/src/assets/shape.d.ts +203 -0
  72. package/lib/typescript/src/assets/source.d.ts +10 -0
  73. package/lib/typescript/src/assets/source.web.d.ts +8 -0
  74. package/lib/typescript/src/assets/storage.d.ts +2 -0
  75. package/lib/typescript/src/assets/storage.shared.d.ts +7 -0
  76. package/lib/typescript/src/assets/storage.web.d.ts +7 -0
  77. package/lib/typescript/src/assets/surface.d.ts +17 -0
  78. package/lib/typescript/src/assets/texture.d.ts +30 -0
  79. package/lib/typescript/src/drawing/DrawingCanvas.d.ts +5 -0
  80. package/lib/typescript/src/drawing/DrawingCanvas.web.d.ts +4 -0
  81. package/lib/typescript/src/drawing/DrawingDocument.d.ts +210 -0
  82. package/lib/typescript/src/drawing/types.d.ts +12 -0
  83. package/lib/typescript/src/index.d.ts +42 -0
  84. package/lib/typescript/src/input/Joystick.d.ts +20 -0
  85. package/lib/typescript/src/input/Joystick.web.d.ts +13 -0
  86. package/lib/typescript/src/input/keyboard.d.ts +17 -0
  87. package/lib/typescript/src/media/audio.d.ts +46 -0
  88. package/lib/typescript/src/media/haptics.d.ts +22 -0
  89. package/lib/typescript/src/media/output.web.d.ts +4 -0
  90. package/lib/typescript/src/native/BrowserEngine.d.ts +8 -0
  91. package/lib/typescript/src/native/EngineBridge.d.ts +22 -0
  92. package/lib/typescript/src/native/NativeNayanEngine.d.ts +5 -0
  93. package/lib/typescript/src/native/NativeNayanEngine.web.d.ts +10 -0
  94. package/lib/typescript/src/native/prepare.d.ts +3 -0
  95. package/lib/typescript/src/native/prepare.web.d.ts +2 -0
  96. package/lib/typescript/src/native/spec/NativeNayanEngine.d.ts +182 -0
  97. package/lib/typescript/src/render/GameView.d.ts +95 -0
  98. package/lib/typescript/src/render/camera.d.ts +6 -0
  99. package/lib/typescript/src/render/host.d.ts +29 -0
  100. package/lib/typescript/src/render/host.web.d.ts +16 -0
  101. package/lib/typescript/src/render/meshes.d.ts +45 -0
  102. package/lib/typescript/src/render/quality.d.ts +46 -0
  103. package/lib/typescript/src/render/responder.d.ts +2 -0
  104. package/lib/typescript/src/render/responder.web.d.ts +40 -0
  105. package/lib/typescript/src/render/touch.d.ts +26 -0
  106. package/lib/typescript/src/types.d.ts +72 -0
  107. package/lib/typescript/src/world/Scoreboard.d.ts +65 -0
  108. package/lib/typescript/src/world/TransformBatch.d.ts +39 -0
  109. package/lib/typescript/src/world/World.d.ts +199 -0
  110. package/lib/typescript/src/world/desc.d.ts +17 -0
  111. package/lib/typescript/src/world/effects.d.ts +156 -0
  112. package/lib/typescript/src/world/quat.d.ts +31 -0
  113. package/lib/typescript/src/world/types.d.ts +233 -0
  114. package/lib/web/audio-worklet.js +29 -0
  115. package/lib/web/engine_core_bg.wasm +0 -0
  116. package/lib/web/index.js +7974 -0
  117. package/lib/web/types/src/assets/AssetBytesCache.d.ts +6 -0
  118. package/lib/web/types/src/assets/AssetCache.d.ts +8 -0
  119. package/lib/web/types/src/assets/AssetLoader.d.ts +10 -0
  120. package/lib/web/types/src/assets/CacheGeneration.d.ts +2 -0
  121. package/lib/web/types/src/assets/IndexedDbCache.d.ts +19 -0
  122. package/lib/web/types/src/assets/ProceduralCache.d.ts +32 -0
  123. package/lib/web/types/src/assets/font.d.ts +52 -0
  124. package/lib/web/types/src/assets/loft.d.ts +30 -0
  125. package/lib/web/types/src/assets/model.d.ts +47 -0
  126. package/lib/web/types/src/assets/modelling.d.ts +156 -0
  127. package/lib/web/types/src/assets/shape.d.ts +202 -0
  128. package/lib/web/types/src/assets/source.d.ts +7 -0
  129. package/lib/web/types/src/assets/storage.d.ts +6 -0
  130. package/lib/web/types/src/assets/storage.shared.d.ts +6 -0
  131. package/lib/web/types/src/assets/surface.d.ts +16 -0
  132. package/lib/web/types/src/assets/texture.d.ts +29 -0
  133. package/lib/web/types/src/drawing/DrawingCanvas.d.ts +3 -0
  134. package/lib/web/types/src/drawing/DrawingDocument.d.ts +209 -0
  135. package/lib/web/types/src/drawing/types.d.ts +11 -0
  136. package/lib/web/types/src/index.d.ts +41 -0
  137. package/lib/web/types/src/input/Joystick.d.ts +12 -0
  138. package/lib/web/types/src/input/keyboard.d.ts +16 -0
  139. package/lib/web/types/src/media/audio.d.ts +45 -0
  140. package/lib/web/types/src/media/haptics.d.ts +21 -0
  141. package/lib/web/types/src/media/output.d.ts +3 -0
  142. package/lib/web/types/src/native/BrowserEngine.d.ts +7 -0
  143. package/lib/web/types/src/native/EngineBridge.d.ts +21 -0
  144. package/lib/web/types/src/native/NativeNayanEngine.d.ts +9 -0
  145. package/lib/web/types/src/native/prepare.d.ts +1 -0
  146. package/lib/web/types/src/native/spec/NativeNayanEngine.d.ts +181 -0
  147. package/lib/web/types/src/render/GameView.d.ts +94 -0
  148. package/lib/web/types/src/render/camera.d.ts +5 -0
  149. package/lib/web/types/src/render/host.d.ts +15 -0
  150. package/lib/web/types/src/render/meshes.d.ts +44 -0
  151. package/lib/web/types/src/render/quality.d.ts +45 -0
  152. package/lib/web/types/src/render/responder.d.ts +39 -0
  153. package/lib/web/types/src/render/touch.d.ts +25 -0
  154. package/lib/web/types/src/types.d.ts +71 -0
  155. package/lib/web/types/src/world/Scoreboard.d.ts +64 -0
  156. package/lib/web/types/src/world/TransformBatch.d.ts +38 -0
  157. package/lib/web/types/src/world/World.d.ts +198 -0
  158. package/lib/web/types/src/world/desc.d.ts +16 -0
  159. package/lib/web/types/src/world/effects.d.ts +155 -0
  160. package/lib/web/types/src/world/quat.d.ts +30 -0
  161. package/lib/web/types/src/world/types.d.ts +232 -0
  162. package/native/android/CMakeLists.txt +34 -0
  163. package/native/android/NayanEngineSpec.h +15 -0
  164. package/native/android/build.gradle +26 -0
  165. package/native/android/codegen/CMakeLists.txt +3 -0
  166. package/native/android/proguard-rules.pro +2 -0
  167. package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceView.java +234 -0
  168. package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceViewManager.java +45 -0
  169. package/native/android/src/main/java/com/nayanui/engine/NayanEnginePackage.java +24 -0
  170. package/native/core/build/EngineCore.xcframework/Info.plist +43 -0
  171. package/native/core/build/EngineCore.xcframework/ios-arm64/libengine_core.a +0 -0
  172. package/native/core/build/EngineCore.xcframework/ios-arm64-simulator/libengine_core.a +0 -0
  173. package/native/core/build/android/arm64-v8a/libengine_core.a +0 -0
  174. package/native/core/build/android/armeabi-v7a/libengine_core.a +0 -0
  175. package/native/core/build/android/x86/libengine_core.a +0 -0
  176. package/native/core/build/android/x86_64/libengine_core.a +0 -0
  177. package/native/core/include/drawing-licenses.txt +58 -0
  178. package/native/core/include/engine_core.h +322 -0
  179. package/native/cpp/NayanEngineModule.cpp +827 -0
  180. package/native/cpp/NayanEngineModule.h +144 -0
  181. package/native/cpp/generated/NayanEngineSpecJSI.cpp +2 -0
  182. package/native/cpp/generated/NayanEngineSpecJSI.h +921 -0
  183. package/native/ios/NayanEngineProvider.h +5 -0
  184. package/native/ios/NayanEngineProvider.mm +21 -0
  185. package/native/ios/NayanEngineSurface.mm +223 -0
  186. package/native/scripts/check-package.mjs +39 -0
  187. package/package.json +162 -4
  188. package/react-native.config.js +18 -0
  189. package/src/native/spec/NativeNayanEngine.ts +226 -0
@@ -0,0 +1,249 @@
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 { follow, shake } from "./camera.js";
7
+ import { createTouchResponder, pixelRatio, Surface, useActive } from './host';
8
+ import { getMeshIdLimit, uploadBuiltInMeshes } from "./meshes.js";
9
+ import { AutoQuality, median, QUALITY_LEVELS } from "./quality.js";
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ const DEFAULT_BACKGROUND = [0.04, 0.05, 0.09];
12
+ const FRAME_LEN = 26; // ENGINE_FRAME_LEN
13
+ const WHITE = [1, 1, 1];
14
+ let nextSurface = 1;
15
+ const defaultCamera = () => ({
16
+ eye: [0, 40, 60],
17
+ target: [0, 0, 0],
18
+ fov: Math.PI / 3
19
+ });
20
+ const defaultLight = () => ({
21
+ direction: [0.4, 0.8, 0.5],
22
+ ambient: 0.35
23
+ });
24
+ export function GameView(props) {
25
+ const {
26
+ source,
27
+ camera,
28
+ light,
29
+ background,
30
+ fog,
31
+ style
32
+ } = props;
33
+ const active = useActive();
34
+ // Latest props, read from the frame loop and touch handlers without restarting them.
35
+ const live = useRef({
36
+ props,
37
+ camera: camera ?? defaultCamera(),
38
+ light: light ?? defaultLight(),
39
+ environment: {
40
+ background: background ?? DEFAULT_BACKGROUND,
41
+ fog: fog ?? 0
42
+ }
43
+ });
44
+ live.current.props = props;
45
+ if (camera) live.current.camera = camera;
46
+ if (light) live.current.light = light;
47
+ live.current.environment = {
48
+ background: background ?? DEFAULT_BACKGROUND,
49
+ fog: fog ?? 0
50
+ };
51
+ const touch = useMemo(() => createTouchResponder(() => live.current.props), []);
52
+ useEffect(() => touch.dispose, [touch]);
53
+ useEffect(() => {
54
+ if (props.paused || !active) touch.cancel();
55
+ }, [touch, props.paused, active]);
56
+ const surfaceId = useMemo(() => nextSurface++, []);
57
+ const size = useRef({
58
+ width: 0,
59
+ height: 0
60
+ });
61
+ // The automatic quality outlives pauses, settings sheets and resumes; only a new source starts over.
62
+ const autoQuality = useRef(null);
63
+ useEffect(() => {
64
+ if (props.paused || !active) return;
65
+ const native = NativeEngine;
66
+ const report = error => {
67
+ const e = error instanceof Error ? error : new Error(String(error));
68
+ (live.current.props.onError ?? (x => console.error(`GameView: ${x.message}`)))(e);
69
+ };
70
+ if (!native) {
71
+ report(new Error('the native engine is not linked into this build'));
72
+ return;
73
+ }
74
+ uploadBuiltInMeshes();
75
+ let alive = true;
76
+ let raf = 0;
77
+ let errors = 0;
78
+ if (autoQuality.current?.source !== source) autoQuality.current = {
79
+ source,
80
+ auto: new AutoQuality()
81
+ };
82
+ const auto = autoQuality.current.auto;
83
+ const chosen = () => {
84
+ const setting = live.current.props.quality ?? 'auto';
85
+ return setting === 'auto' ? auto.quality : setting;
86
+ };
87
+ const followed = [0, 0, 0];
88
+ const shaken = {
89
+ eye: [0, 0, 0],
90
+ target: [0, 0, 0],
91
+ fov: 1
92
+ };
93
+ const frameData = new Float64Array(FRAME_LEN);
94
+ const viewProj = new Float32Array(16);
95
+ // Sources without texture regions show whole textures.
96
+ const wholeRegions = s => s.regions ?? new Float32Array(Math.max(1, s.capacity) * 4).map((_, i) => i % 4 < 2 ? 0 : 1);
97
+ const regions = wholeRegions(source);
98
+ // Instances actually packed (hidden ones are left out): the furthest range end, at most `count`.
99
+ // Only the mesh ids in use can have instances, in each half (opaque, then see-through). The
100
+ // see-through half starts halfway through the ranges, wherever that is (a native core built for
101
+ // fewer mesh ids has a shorter table).
102
+ const drawn = s => {
103
+ const r = s.ranges;
104
+ const buckets = r.length >> 1;
105
+ const limit = getMeshIdLimit();
106
+ const opaqueStop = Math.min(limit, buckets);
107
+ const half = Math.min(MAX_MESHES, buckets >> 1);
108
+ const clearStop = Math.min(half + limit, buckets);
109
+ let end = 0;
110
+ for (let i = 1, stop = opaqueStop * 2; i < stop; i += 2) {
111
+ const count = r[i];
112
+ if (count > 0 && r[i - 1] + count > end) end = r[i - 1] + count;
113
+ }
114
+ for (let i = half * 2 + 1, stop = clearStop * 2; i < stop; i += 2) {
115
+ const count = r[i];
116
+ if (count > 0 && r[i - 1] + count > end) end = r[i - 1] + count;
117
+ }
118
+ return Math.min(s.count, end);
119
+ };
120
+ let overlay = null;
121
+ let last = 0;
122
+ let frames = 0;
123
+ let fpsStart = 0;
124
+ let updateTime = 0;
125
+ let renderTime = 0;
126
+ // This second's frame intervals and draw times: the quality picker looks at their medians, so a
127
+ // single hiccup (garbage collection, a busy JS frame) never costs quality.
128
+ const intervals = [];
129
+ const draws = [];
130
+ // Game time starts with the first frame actually on screen: until then `onUpdate` gets dt = 0, so
131
+ // intros and animations aren't over before the surface (attached during the screen's transition) shows.
132
+ let shown = false;
133
+ const frame = now => {
134
+ if (!alive) return;
135
+ raf = requestAnimationFrame(frame);
136
+ if (!last) last = fpsStart = now;
137
+ if (now > last) intervals.push(now - last);
138
+ const dt = shown ? Math.min((now - last) / 1000, 0.1) : 0;
139
+ last = now;
140
+ const {
141
+ props: current,
142
+ camera,
143
+ light,
144
+ environment
145
+ } = live.current;
146
+ const t0 = performance.now();
147
+ current.onUpdate?.(dt);
148
+ updateTime += performance.now() - t0;
149
+ follow(camera, source, followed, dt);
150
+ const view = shake(camera, shaken, dt);
151
+ const quality = chosen();
152
+ const level = QUALITY_LEVELS[quality];
153
+ const {
154
+ width,
155
+ height
156
+ } = size.current;
157
+ // Phones' GPUs are fill-rate bound: cap the pixels drawn rather than match a 3x+ screen.
158
+ const ratio = Math.min(pixelRatio(), level.maxPixelRatio);
159
+ const f = frameData;
160
+ f.set(view.eye, 0);
161
+ f.set(view.target, 3);
162
+ f[6] = view.fov;
163
+ f[7] = view.ortho ?? 0;
164
+ f.set(light.direction, 8);
165
+ f[11] = light.ambient;
166
+ f[12] = light.shadows ?? true ? 1 : 0;
167
+ f[13] = light.shadowExtent ?? 30;
168
+ // Exactly three channels each: either color may carry an alpha that must not spill over.
169
+ const lightColor = light.color ?? WHITE;
170
+ f[14] = lightColor[0];
171
+ f[15] = lightColor[1];
172
+ f[16] = lightColor[2];
173
+ const sky = environment.background;
174
+ f[17] = sky[0];
175
+ f[18] = sky[1];
176
+ f[19] = sky[2];
177
+ f[20] = environment.fog;
178
+ f[21] = Math.round(width * ratio);
179
+ f[22] = Math.round(height * ratio);
180
+ f[23] = level.samples;
181
+ f[24] = level.shadowSize;
182
+ f[25] = level.softShadows ? 1 : 0;
183
+ const t1 = performance.now();
184
+ const layer = current.overlay;
185
+ if (layer) {
186
+ if (overlay?.source !== layer) overlay = {
187
+ source: layer,
188
+ regions: wholeRegions(layer)
189
+ };
190
+ native.renderOverlay(surfaceId, layer.matrices.buffer, layer.colors.buffer, overlay.regions.buffer, layer.ranges.buffer, drawn(layer), width, height);
191
+ } else if (overlay) {
192
+ overlay = null;
193
+ native.renderOverlay(surfaceId, regions.buffer, regions.buffer, regions.buffer, regions.buffer, 0, 0, 0); // removes it
194
+ }
195
+ const rendered = native.renderFrame(surfaceId, f.buffer, source.matrices.buffer, source.colors.buffer, regions.buffer, source.ranges.buffer, drawn(source), viewProj.buffer, source.nativeWorld ?? -1);
196
+ const drawMs = performance.now() - t1;
197
+ renderTime += drawMs;
198
+ draws.push(drawMs);
199
+ if (rendered === 1) {
200
+ shown = true;
201
+ source.setView?.(viewProj, width, height);
202
+ } else if (rendered < 0 && errors++ < 3) {
203
+ report(new Error(native.renderError() || 'rendering failed'));
204
+ }
205
+ frames++;
206
+ if (now - fpsStart >= 1000) {
207
+ auto.report(now, {
208
+ frameMs: median(intervals),
209
+ renderMs: median(draws)
210
+ });
211
+ intervals.length = 0;
212
+ draws.length = 0;
213
+ current.onStats?.({
214
+ fps: frames * 1000 / (now - fpsStart),
215
+ updateMs: updateTime / frames,
216
+ renderMs: renderTime / frames,
217
+ quality,
218
+ backend: native.renderBackend()
219
+ });
220
+ frames = 0;
221
+ updateTime = 0;
222
+ renderTime = 0;
223
+ fpsStart = now;
224
+ }
225
+ };
226
+ raf = requestAnimationFrame(frame);
227
+ return () => {
228
+ alive = false;
229
+ cancelAnimationFrame(raf);
230
+ };
231
+ // A new source or resuming a paused view restarts drawing; other props are read live.
232
+ // eslint-disable-next-line react-hooks/exhaustive-deps
233
+ }, [source, props.paused, active]);
234
+ const touchable = props.onTap || props.onSwipe || props.onDrag || props.onLongPress || props.onPinch || props.onOverlayTouch;
235
+ return /*#__PURE__*/_jsx(Surface, {
236
+ surfaceId: surfaceId,
237
+ style: style,
238
+ onSize: (width, height) => {
239
+ size.current = {
240
+ width,
241
+ height
242
+ };
243
+ },
244
+ onError: error => (live.current.props.onError ?? console.error)(error),
245
+ touch: touchable ? touch : undefined,
246
+ onPrimaryAction: props.onPrimaryAction ? () => live.current.props.onPrimaryAction?.() : undefined
247
+ });
248
+ }
249
+ //# 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,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
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+
3
+ // Rendering quality: how many pixels are drawn and how much each costs. Phones' GPUs are mostly
4
+ // limited by fill rate, so resolution and anti-aliasing matter most, then shadow quality.
5
+
6
+ /** `"auto"` starts at "high" and steps down only while the device really can't keep up. */
7
+
8
+ /** From best looking to cheapest; "auto" walks down this list only as far as it must. */
9
+ export const QUALITIES = ['high', 'medium', 'low', 'lowest'];
10
+ export const QUALITY_LEVELS = {
11
+ // Ordered by what each step costs the eye: shadow detail goes first (barely noticed), then
12
+ // anti-aliasing; only the last resort draws fewer pixels, since anything below the screen's own
13
+ // resolution is stretched up and looks soft (text most of all).
14
+ high: {
15
+ maxPixelRatio: 3,
16
+ samples: 4,
17
+ shadowSize: 2048,
18
+ softShadows: true
19
+ },
20
+ medium: {
21
+ maxPixelRatio: 3,
22
+ samples: 4,
23
+ shadowSize: 1024,
24
+ softShadows: true
25
+ },
26
+ low: {
27
+ maxPixelRatio: 3,
28
+ samples: 1,
29
+ shadowSize: 1024,
30
+ softShadows: false
31
+ },
32
+ lowest: {
33
+ maxPixelRatio: 2,
34
+ samples: 1,
35
+ shadowSize: 512,
36
+ softShadows: false
37
+ }
38
+ };
39
+
40
+ /** A typical frame slower than this (under ~50 fps) is a frame the device can't hold. */
41
+ const SLOW_FRAME_MS = 20;
42
+ /** A typical frame this quick (60 fps with room to spare) is one it holds comfortably. */
43
+ const FAST_FRAME_MS = 17.4;
44
+ /** Only a slow second spent mostly drawing is the GPU's fault (a slow game loop isn't helped by drawing less). */
45
+ const RENDER_SHARE = 0.6;
46
+ /** Ignore start-up (loading, shader compiles) and the moments after a change. */
47
+ const STARTUP_MS = 2500;
48
+ const SETTLE_MS = 2000;
49
+ /** Consecutive slow seconds before stepping down, and smooth seconds before trying a step back up. */
50
+ const SLOW_SECONDS = 3;
51
+ const FAST_SECONDS = 10;
52
+ /** Stepping down again this soon after a step up means that level is too much: never return to it. */
53
+ const RELAPSE_MS = 20_000;
54
+
55
+ /** One second of frames, as the frame loop measured it. */
56
+
57
+ /**
58
+ * Picks the level for "auto": starts at the best, steps down one level after `SLOW_SECONDS`
59
+ * consecutive seconds whose typical frame misses ~50 fps while drawing took most of it, and steps
60
+ * back up after `FAST_SECONDS` smooth seconds. A level that fails again soon after being restored
61
+ * becomes the ceiling, so it never bounces.
62
+ */
63
+ export class AutoQuality {
64
+ index = 0;
65
+ /** The best level it may return to (raised past a level that keeps failing). */
66
+ ceiling = 0;
67
+ started = 0;
68
+ changed = 0;
69
+ raisedAt = -Infinity;
70
+ slow = 0;
71
+ fast = 0;
72
+ get quality() {
73
+ return QUALITIES[this.index];
74
+ }
75
+
76
+ /** Feeds one second of frames; returns true when the level changed. */
77
+ report(now, frames) {
78
+ if (!this.started) this.started = this.changed = now;
79
+ if (now - this.started < STARTUP_MS || now - this.changed < SETTLE_MS) return false;
80
+ const gpuBound = frames.frameMs > SLOW_FRAME_MS && frames.renderMs > frames.frameMs * RENDER_SHARE;
81
+ const smooth = frames.frameMs <= FAST_FRAME_MS;
82
+ this.slow = gpuBound ? this.slow + 1 : 0;
83
+ this.fast = smooth ? this.fast + 1 : 0;
84
+ if (this.slow >= SLOW_SECONDS && this.index < QUALITIES.length - 1) {
85
+ // Failing again right after being restored: this level is too much for the device
86
+ if (now - this.raisedAt < RELAPSE_MS) this.ceiling = Math.max(this.ceiling, this.index + 1);
87
+ this.index++;
88
+ return this.moved(now);
89
+ }
90
+ if (this.fast >= FAST_SECONDS && this.index > this.ceiling) {
91
+ this.index--;
92
+ this.raisedAt = now;
93
+ return this.moved(now);
94
+ }
95
+ return false;
96
+ }
97
+ moved(now) {
98
+ this.changed = now;
99
+ this.slow = 0;
100
+ this.fast = 0;
101
+ return true;
102
+ }
103
+ }
104
+
105
+ /** The middle value of `values` (sorted in place). */
106
+ export function median(values) {
107
+ if (!values.length) return 0;
108
+ values.sort((a, b) => a - b);
109
+ const mid = values.length >> 1;
110
+ return values.length % 2 ? values[mid] : (values[mid - 1] + values[mid]) / 2;
111
+ }
112
+ //# sourceMappingURL=quality.js.map