@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,119 @@
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
+ /** Under ~33 fps the game is plainly struggling: one such second is enough to step down. */
43
+ const VERY_SLOW_FRAME_MS = 30;
44
+ /** A typical frame this quick (60 fps with room to spare) is one it holds comfortably. */
45
+ const FAST_FRAME_MS = 17.4;
46
+ /** Only a slow second spent mostly drawing is the GPU's fault (a slow game loop isn't helped by drawing less). */
47
+ const RENDER_SHARE = 0.6;
48
+ /** Ignore start-up (loading, shader compiles) and the moments after a change. */
49
+ const STARTUP_MS = 2500;
50
+ const SETTLE_MS = 1500;
51
+ /** Consecutive slow seconds before stepping down (one when very slow), and smooth seconds before trying a step back up. */
52
+ const SLOW_SECONDS = 3;
53
+ const VERY_SLOW_SECONDS = 1;
54
+ const FAST_SECONDS = 10;
55
+ /** Stepping down again this soon after a step up means that level is too much: never return to it. */
56
+ const RELAPSE_MS = 20_000;
57
+
58
+ /** One second of frames, as the frame loop measured it. */
59
+
60
+ /**
61
+ * Picks the level for "auto": starts at the best, steps down one level after `SLOW_SECONDS`
62
+ * consecutive seconds whose typical frame misses ~50 fps while drawing took most of it (after a
63
+ * single second when it misses ~33 fps, so a struggling phone recovers in seconds rather than
64
+ * stuttering through every level), and steps back up after `FAST_SECONDS` smooth seconds. A level that fails again soon after being restored
65
+ * becomes the ceiling, so it never bounces.
66
+ */
67
+ export class AutoQuality {
68
+ index = 0;
69
+ /** The best level it may return to (raised past a level that keeps failing). */
70
+ ceiling = 0;
71
+ started = 0;
72
+ changed = 0;
73
+ raisedAt = -Infinity;
74
+ slow = 0;
75
+ verySlow = 0;
76
+ fast = 0;
77
+ get quality() {
78
+ return QUALITIES[this.index];
79
+ }
80
+
81
+ /** Feeds one second of frames; returns true when the level changed. */
82
+ report(now, frames) {
83
+ if (!this.started) this.started = this.changed = now;
84
+ if (now - this.started < STARTUP_MS || now - this.changed < SETTLE_MS) return false;
85
+ const gpuBound = frames.frameMs > SLOW_FRAME_MS && frames.renderMs > frames.frameMs * RENDER_SHARE;
86
+ const smooth = frames.frameMs <= FAST_FRAME_MS;
87
+ this.slow = gpuBound ? this.slow + 1 : 0;
88
+ this.verySlow = gpuBound && frames.frameMs > VERY_SLOW_FRAME_MS ? this.verySlow + 1 : 0;
89
+ this.fast = smooth ? this.fast + 1 : 0;
90
+ if ((this.slow >= SLOW_SECONDS || this.verySlow >= VERY_SLOW_SECONDS) && this.index < QUALITIES.length - 1) {
91
+ // Failing again right after being restored: this level is too much for the device
92
+ if (now - this.raisedAt < RELAPSE_MS) this.ceiling = Math.max(this.ceiling, this.index + 1);
93
+ this.index++;
94
+ return this.moved(now);
95
+ }
96
+ if (this.fast >= FAST_SECONDS && this.index > this.ceiling) {
97
+ this.index--;
98
+ this.raisedAt = now;
99
+ return this.moved(now);
100
+ }
101
+ return false;
102
+ }
103
+ moved(now) {
104
+ this.changed = now;
105
+ this.slow = 0;
106
+ this.verySlow = 0;
107
+ this.fast = 0;
108
+ return true;
109
+ }
110
+ }
111
+
112
+ /** The middle value of `values` (sorted in place). */
113
+ export function median(values) {
114
+ if (!values.length) return 0;
115
+ values.sort((a, b) => a - b);
116
+ const mid = values.length >> 1;
117
+ return values.length % 2 ? values[mid] : (values[mid - 1] + values[mid]) / 2;
118
+ }
119
+ //# sourceMappingURL=quality.js.map
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export { PanResponder } from 'react-native';
4
+ //# sourceMappingURL=responder.js.map
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+
3
+ /** Adapt DOM pointer capture to the gesture recognizer used on mobile. Coordinates stay in CSS pixels. */
4
+ export const PanResponder = {
5
+ create(callbacks) {
6
+ const pointers = new Map();
7
+ let origin = {
8
+ x: 0,
9
+ y: 0
10
+ };
11
+ let last = {
12
+ nativeEvent: {
13
+ pageX: 0,
14
+ pageY: 0,
15
+ locationX: 0,
16
+ locationY: 0,
17
+ touches: []
18
+ }
19
+ };
20
+ let gesture = {
21
+ dx: 0,
22
+ dy: 0
23
+ };
24
+ let primary = -1;
25
+ const event = e => {
26
+ const rect = e.currentTarget.getBoundingClientRect();
27
+ const nativeEvent = {
28
+ pageX: e.clientX,
29
+ pageY: e.clientY,
30
+ locationX: e.clientX - rect.left,
31
+ locationY: e.clientY - rect.top,
32
+ touches: [...pointers.values()]
33
+ };
34
+ if (e.pointerId === primary) gesture = {
35
+ dx: e.clientX - origin.x,
36
+ dy: e.clientY - origin.y
37
+ };
38
+ return last = {
39
+ nativeEvent
40
+ };
41
+ };
42
+ const cancel = () => {
43
+ if (pointers.size) callbacks.onPanResponderTerminate(last, gesture);
44
+ pointers.clear();
45
+ primary = -1;
46
+ };
47
+ return {
48
+ cancel,
49
+ panHandlers: {
50
+ onPointerDown(e) {
51
+ if (e.button !== 0) return;
52
+ e.preventDefault();
53
+ // Keyboard pointers already retain their target; only physical pointers can be captured.
54
+ if (e.isTrusted) e.currentTarget.setPointerCapture(e.pointerId);
55
+ pointers.set(e.pointerId, {
56
+ pageX: e.clientX,
57
+ pageY: e.clientY
58
+ });
59
+ if (pointers.size === 1) {
60
+ primary = e.pointerId;
61
+ origin = {
62
+ x: e.clientX,
63
+ y: e.clientY
64
+ };
65
+ gesture = {
66
+ dx: 0,
67
+ dy: 0
68
+ };
69
+ callbacks.onPanResponderGrant(event(e));
70
+ } else callbacks.onPanResponderMove(event(e), gesture);
71
+ },
72
+ onPointerMove(e) {
73
+ if (!pointers.has(e.pointerId)) return;
74
+ pointers.set(e.pointerId, {
75
+ pageX: e.clientX,
76
+ pageY: e.clientY
77
+ });
78
+ callbacks.onPanResponderMove(event(e), gesture);
79
+ },
80
+ onPointerUp(e) {
81
+ if (!pointers.has(e.pointerId)) return;
82
+ const current = event(e);
83
+ if (e.pointerId === primary || pointers.size >= 2) {
84
+ callbacks.onPanResponderRelease(current, gesture);
85
+ pointers.clear();
86
+ primary = -1;
87
+ } else pointers.delete(e.pointerId);
88
+ },
89
+ onPointerCancel: cancel,
90
+ onLostPointerCapture(e) {
91
+ if (pointers.has(e.pointerId)) cancel();
92
+ }
93
+ }
94
+ };
95
+ }
96
+ };
97
+ //# sourceMappingURL=responder.web.js.map
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+
3
+ // Gesture recognition for GameView: tap, swipe, drag, long press, pinch.
4
+ import { PanResponder } from './responder';
5
+ const TAP_SLOP = 10; // points a finger may wander and still tap
6
+ const TAP_TIME = 300; // ms
7
+ const SWIPE_DISTANCE = 30; // points
8
+ const SWIPE_SPEED = 0.3; // points per ms
9
+ const LONG_PRESS_TIME = 500; // ms
10
+
11
+ /**
12
+ * Creates a PanResponder that recognizes taps, swipes, drags, long presses and pinches.
13
+ * `getHandlers` is called on each event so the caller can swap handlers without recreating the responder.
14
+ */
15
+ export function createTouchResponder(getHandlers) {
16
+ let start = {
17
+ x: 0,
18
+ y: 0,
19
+ time: 0
20
+ };
21
+ let origin = {
22
+ x: 0,
23
+ y: 0
24
+ };
25
+ let mode = 'none';
26
+ let pinch0 = {
27
+ distance: 1,
28
+ angle: 0
29
+ };
30
+ let lastPinch = {
31
+ phase: 'end',
32
+ scale: 1,
33
+ rotation: 0,
34
+ x: 0,
35
+ y: 0
36
+ };
37
+ let longPress;
38
+ let longPressed = false;
39
+ const drag = (phase, x, y, cancelled) => getHandlers().onDrag?.({
40
+ phase,
41
+ x,
42
+ y,
43
+ dx: x - start.x,
44
+ dy: y - start.y,
45
+ ...(cancelled && {
46
+ cancelled
47
+ })
48
+ });
49
+ const twoFingers = (touches, phase) => {
50
+ const [a, b] = touches;
51
+ const distance = Math.max(1, Math.hypot(b.pageX - a.pageX, b.pageY - a.pageY));
52
+ const angle = Math.atan2(b.pageY - a.pageY, b.pageX - a.pageX);
53
+ if (phase === 'start') pinch0 = {
54
+ distance,
55
+ angle
56
+ };
57
+ lastPinch = {
58
+ phase,
59
+ scale: distance / pinch0.distance,
60
+ rotation: angle - pinch0.angle,
61
+ x: (a.pageX + b.pageX) / 2 - origin.x,
62
+ y: (a.pageY + b.pageY) / 2 - origin.y
63
+ };
64
+ getHandlers().onPinch?.(lastPinch);
65
+ };
66
+ const stopLongPress = () => {
67
+ clearTimeout(longPress);
68
+ longPress = undefined;
69
+ };
70
+ const responder = PanResponder.create({
71
+ onStartShouldSetPanResponder: () => true,
72
+ onMoveShouldSetPanResponder: () => true,
73
+ onPanResponderGrant: e => {
74
+ const {
75
+ locationX,
76
+ locationY,
77
+ pageX,
78
+ pageY
79
+ } = e.nativeEvent;
80
+ start = {
81
+ x: locationX,
82
+ y: locationY,
83
+ time: Date.now()
84
+ };
85
+ origin = {
86
+ x: pageX - locationX,
87
+ y: pageY - locationY
88
+ };
89
+ if (getHandlers().onOverlayTouch?.('start', start.x, start.y) === true) {
90
+ mode = 'overlay';
91
+ return;
92
+ }
93
+ mode = 'drag';
94
+ longPressed = false;
95
+ drag('start', start.x, start.y);
96
+ if (getHandlers().onLongPress) {
97
+ longPress = setTimeout(() => {
98
+ longPress = undefined;
99
+ if (mode !== 'drag') return;
100
+ longPressed = true;
101
+ getHandlers().onLongPress?.(start.x, start.y);
102
+ }, LONG_PRESS_TIME);
103
+ }
104
+ },
105
+ onPanResponderMove: (e, g) => {
106
+ if (mode === 'overlay') {
107
+ getHandlers().onOverlayTouch?.('move', start.x + g.dx, start.y + g.dy);
108
+ return;
109
+ }
110
+ const touches = e.nativeEvent.touches;
111
+ if (touches.length >= 2 && getHandlers().onPinch) {
112
+ if (mode === 'drag') {
113
+ stopLongPress();
114
+ drag('end', start.x + g.dx, start.y + g.dy, true);
115
+ mode = 'pinch';
116
+ twoFingers(touches, 'start');
117
+ } else if (mode === 'pinch') {
118
+ twoFingers(touches, 'move');
119
+ }
120
+ return;
121
+ }
122
+ if (mode !== 'drag') return;
123
+ if (Math.hypot(g.dx, g.dy) >= TAP_SLOP) stopLongPress();
124
+ drag('move', start.x + g.dx, start.y + g.dy);
125
+ },
126
+ onPanResponderRelease: (_, g) => {
127
+ stopLongPress();
128
+ const was = mode;
129
+ mode = 'none';
130
+ if (was === 'none') return;
131
+ if (was === 'overlay') {
132
+ getHandlers().onOverlayTouch?.('end', start.x + g.dx, start.y + g.dy);
133
+ return;
134
+ }
135
+ if (was === 'pinch') {
136
+ getHandlers().onPinch?.({
137
+ ...lastPinch,
138
+ phase: 'end'
139
+ });
140
+ return;
141
+ }
142
+ const x = start.x + g.dx;
143
+ const y = start.y + g.dy;
144
+ drag('end', x, y);
145
+ if (longPressed) return;
146
+ const {
147
+ onTap,
148
+ onSwipe
149
+ } = getHandlers();
150
+ const distance = Math.hypot(g.dx, g.dy);
151
+ const time = Date.now() - start.time;
152
+ if (distance < TAP_SLOP && time < TAP_TIME) onTap?.(start.x, start.y);else if (distance > SWIPE_DISTANCE && distance / Math.max(1, time) > SWIPE_SPEED) {
153
+ onSwipe?.(Math.abs(g.dx) > Math.abs(g.dy) ? g.dx > 0 ? 'right' : 'left' : g.dy > 0 ? 'down' : 'up');
154
+ }
155
+ },
156
+ onPanResponderTerminate: (_, g) => {
157
+ stopLongPress();
158
+ const was = mode;
159
+ mode = 'none';
160
+ if (was === 'overlay') getHandlers().onOverlayTouch?.('cancel', start.x + g.dx, start.y + g.dy);else if (was === 'pinch') getHandlers().onPinch?.({
161
+ ...lastPinch,
162
+ phase: 'end'
163
+ });else if (was === 'drag') drag('end', start.x + g.dx, start.y + g.dy, true);
164
+ }
165
+ });
166
+ const cancel = () => {
167
+ responder.cancel?.();
168
+ stopLongPress();
169
+ const was = mode;
170
+ mode = 'none';
171
+ if (was === 'overlay') getHandlers().onOverlayTouch?.('cancel', start.x, start.y);else if (was === 'pinch') getHandlers().onPinch?.({
172
+ ...lastPinch,
173
+ phase: 'end'
174
+ });else if (was === 'drag') drag('end', start.x, start.y, true);
175
+ };
176
+ return {
177
+ responder,
178
+ dispose: cancel,
179
+ cancel
180
+ };
181
+ }
182
+
183
+ // Re-export threshold constants for testing.
184
+ export { TAP_SLOP, TAP_TIME, SWIPE_DISTANCE, SWIPE_SPEED, LONG_PRESS_TIME };
185
+ //# sourceMappingURL=touch.js.map
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { isEngineAvailable, World } from "../world/World.js";
5
+ import { warmUpEngine } from "./meshes.js";
6
+
7
+ /** What `useWorld`'s setup may return: a cleanup, run just before the world is disposed. */
8
+
9
+ /**
10
+ * A `World` owned by a component: the engine is warmed up (needed on the web, cheap on mobile), the
11
+ * world is created with `capacity` entity slots and handed to `setup` to fill, and it is disposed on
12
+ * unmount. A change in `deps` replaces it with a fresh one. Returns null until it's ready (one frame),
13
+ * and while the engine is unavailable (Expo Go) or failed to start (`onError`, console by default).
14
+ *
15
+ * ```tsx
16
+ * const world = useWorld(100, (w) => {
17
+ * w.spawn({ mesh: 'plane', scale: [20, 1, 20], color: '#4C9A4C', physics: 'fixed' });
18
+ * w.spawn({ mesh: 'sphere', position: [0, 4, 0], color: '#3399FF', physics: 'dynamic' });
19
+ * });
20
+ * return world && <GameView source={world} style={{ flex: 1 }} />;
21
+ * ```
22
+ */
23
+ export function useWorld(capacity, setup, deps = [], onError) {
24
+ const [world, setWorld] = useState(null);
25
+ // The latest callbacks, so passing inline functions doesn't recreate the world each render.
26
+ const latest = useRef({
27
+ setup,
28
+ onError
29
+ });
30
+ latest.current = {
31
+ setup,
32
+ onError
33
+ };
34
+ useEffect(() => {
35
+ let active = true;
36
+ let created = null;
37
+ let cleanup;
38
+ const report = error => {
39
+ const e = error instanceof Error ? error : new Error(String(error));
40
+ (latest.current.onError ?? (x => console.error(`useWorld: ${x.message}`)))(e);
41
+ };
42
+ if (!isEngineAvailable) {
43
+ report(new Error('the native engine is not linked into this build (a development build is needed, not Expo Go)'));
44
+ return;
45
+ }
46
+ warmUpEngine().then(() => {
47
+ if (!active) return;
48
+ try {
49
+ created = new World(capacity);
50
+ cleanup = latest.current.setup?.(created);
51
+ setWorld(created);
52
+ } catch (error) {
53
+ created?.dispose();
54
+ created = null;
55
+ report(error);
56
+ }
57
+ }, report);
58
+ return () => {
59
+ active = false;
60
+ setWorld(null);
61
+ if (!created) return;
62
+ try {
63
+ cleanup?.();
64
+ } finally {
65
+ created.dispose();
66
+ }
67
+ };
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ }, [capacity, ...deps]);
70
+ return world;
71
+ }
72
+ //# sourceMappingURL=useWorld.js.map
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ /** Number of mesh ids: built-in shapes (0..15), then loaded models, glyphs and textured variants. */
4
+ export const MAX_MESHES = 4096;
5
+
6
+ /** What the renderer draws. `World` implements it; so can any custom simulation. */
7
+
8
+ /**
9
+ * Built-in shapes, each 1 unit across before scaling. "none" isn't drawn (groups, pivots, trigger zones).
10
+ * "puff" is a soft, round sprite that always faces the camera and is always see-through: overlapping
11
+ * puffs blend into smoke, dust, steam and clouds. "flame" is the same sprite drawn additively: it adds
12
+ * light to what's behind it, so overlapping flames build up to a hot white core and stay bright over
13
+ * smoke (fire, sparks, explosions, magic, glows). Use them as a burst's `mesh`.
14
+ */
15
+
16
+ /** Mesh ids of the built-in shapes, as the core and renderer know them. */
17
+ export const SHAPE_MESH = {
18
+ cube: 0,
19
+ sphere: 1,
20
+ plane: 2,
21
+ cylinder: 3,
22
+ cone: 4,
23
+ capsule: 5,
24
+ torus: 6,
25
+ roundedBox: 7,
26
+ puff: 8,
27
+ flame: 9,
28
+ none: 15
29
+ };
30
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+
3
+ import NativeEngine from '../native/NativeNayanEngine';
4
+ const OP = {
5
+ addScore: 0,
6
+ setScore: 1,
7
+ addLives: 2,
8
+ setLives: 3,
9
+ startClock: 4,
10
+ stopClock: 5,
11
+ resumeClock: 6,
12
+ reset: 7,
13
+ addTime: 8,
14
+ timeBonus: 9
15
+ };
16
+ const S = {
17
+ score: 0,
18
+ lives: 1,
19
+ seconds: 2,
20
+ shown: 3,
21
+ flags: 4,
22
+ running: 5,
23
+ rate: 6,
24
+ best: 7
25
+ };
26
+ const FLAG = {
27
+ score: 1,
28
+ lives: 2,
29
+ second: 4,
30
+ timeUp: 8,
31
+ livesOut: 16
32
+ };
33
+ /**
34
+ * Score, lives and the game clock, kept and advanced in Rust by `world.update`: countdowns, elapsed
35
+ * time and time-based points (survival, distance) cost no JS per frame. Handlers run from `update`
36
+ * only when something they show changed. Set `handlers` for the game's own rules, and `listen` for
37
+ * anything else that follows along (a UI showing the score), so neither replaces the other.
38
+ *
39
+ * ```ts
40
+ * world.scoreboard.handlers = { onTimeUp: () => end(true) };
41
+ * const stop = world.scoreboard.listen({ onScore: setScore, onSecond: setTime });
42
+ * world.scoreboard.reset(3);
43
+ * world.scoreboard.startCountdown(60);
44
+ * world.scoreboard.add(10);
45
+ * ```
46
+ */
47
+ export class Scoreboard {
48
+ /** The game's own handlers. `listen` adds more without replacing these. */
49
+ handlers = {};
50
+ listeners = new Set();
51
+ /** @internal */
52
+ constructor(world) {
53
+ this.world = world;
54
+ if (!NativeEngine || typeof NativeEngine.getScoreboard !== 'function') {
55
+ throw new Error("Scoreboard: this app's native core predates the scoreboard: rebuild it");
56
+ }
57
+ this.state = new Float64Array(NativeEngine.getScoreboard(world));
58
+ }
59
+ op(op, a = 0, b = 0) {
60
+ return NativeEngine.scoreboard(this.world, op, a, b);
61
+ }
62
+ get score() {
63
+ return this.state[S.score];
64
+ }
65
+ get best() {
66
+ return this.state[S.best];
67
+ }
68
+ get lives() {
69
+ return this.state[S.lives];
70
+ }
71
+ /** Seconds left (countdown) or elapsed, fractional. */
72
+ get seconds() {
73
+ return this.state[S.seconds];
74
+ }
75
+ /** Whole seconds as shown: rounded up while counting down, down while counting up. */
76
+ get shownSeconds() {
77
+ return this.state[S.shown];
78
+ }
79
+ get running() {
80
+ return this.state[S.running] !== 0;
81
+ }
82
+
83
+ /** Adds points (negative to take away). Returns the score. */
84
+ add(points) {
85
+ return this.op(OP.addScore, points);
86
+ }
87
+ set(score) {
88
+ return this.op(OP.setScore, score);
89
+ }
90
+ addLives(n) {
91
+ this.op(OP.addLives, n);
92
+ return this.lives;
93
+ }
94
+ setLives(n) {
95
+ this.op(OP.setLives, n);
96
+ }
97
+ /** Counts `seconds` down to 0 (then `onTimeUp`), earning `pointsPerSecond` meanwhile. */
98
+ startCountdown(seconds, pointsPerSecond = 0) {
99
+ this.op(OP.startClock, Math.max(1e-6, seconds), pointsPerSecond);
100
+ }
101
+ /** Counts up from 0, earning `pointsPerSecond` (survival and distance scores). */
102
+ startClock(pointsPerSecond = 0) {
103
+ this.op(OP.startClock, 0, pointsPerSecond);
104
+ }
105
+ stop() {
106
+ this.op(OP.stopClock);
107
+ }
108
+ resume() {
109
+ this.op(OP.resumeClock);
110
+ }
111
+ /** Time bonus (or penalty) on the clock. */
112
+ addTime(seconds) {
113
+ this.op(OP.addTime, seconds);
114
+ }
115
+ /**
116
+ * Adds `pointsPerSecond` for each whole second saved: the seconds left on a countdown, or
117
+ * `expectedSeconds` minus the seconds elapsed on a clock counting up. Returns the score.
118
+ */
119
+ awardTimeBonus(pointsPerSecond, expectedSeconds = 0) {
120
+ return this.op(OP.timeBonus, pointsPerSecond, expectedSeconds);
121
+ }
122
+ /**
123
+ * A new run: score 0, `lives`, clock stopped at 0. With `countdownSeconds`, the stopped clock shows
124
+ * that countdown in full (a ready screen); `resume()` starts it.
125
+ */
126
+ reset(lives = 0, countdownSeconds = 0) {
127
+ this.op(OP.reset, lives);
128
+ if (countdownSeconds > 0) {
129
+ this.startCountdown(countdownSeconds);
130
+ this.stop();
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Adds handlers alongside `handlers` (and other listeners); they run after them, in order of
136
+ * listening. Returns a function that removes them.
137
+ */
138
+ listen(handlers) {
139
+ this.listeners.add(handlers);
140
+ return () => {
141
+ this.listeners.delete(handlers);
142
+ };
143
+ }
144
+
145
+ /** @internal Runs handlers for what changed since the last call. */
146
+ dispatch() {
147
+ const flags = this.state[S.flags];
148
+ if (flags === 0) return;
149
+ this.state[S.flags] = 0;
150
+ this.run(this.handlers, flags);
151
+ if (this.listeners.size) for (const h of [...this.listeners]) this.run(h, flags);
152
+ }
153
+ run(h, flags) {
154
+ if (flags & FLAG.score) h.onScore?.(this.score);
155
+ if (flags & FLAG.lives) h.onLives?.(this.lives);
156
+ if (flags & FLAG.second) h.onSecond?.(this.shownSeconds);
157
+ if (flags & FLAG.timeUp) h.onTimeUp?.();
158
+ if (flags & FLAG.livesOut) h.onLivesOut?.();
159
+ }
160
+ }
161
+ //# sourceMappingURL=Scoreboard.js.map