@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,237 @@
1
+ import type { Font, TextAlign } from '../assets/font';
2
+ import type { Model } from '../assets/model';
3
+ import type { Texture } from '../assets/texture';
4
+ import type { Sound } from '../media/audio';
5
+ import type { Vec3 } from '../types';
6
+ import type { ColorInput } from './color';
7
+ /** Opaque entity handle. Handles of despawned entities are safely ignored. */
8
+ export type Entity = number & {
9
+ readonly __entity: unique symbol;
10
+ };
11
+ export type Color = readonly [number, number, number] | readonly [number, number, number, number];
12
+ export type Quat = readonly [number, number, number, number];
13
+ /** Playback for one entity using a skeletal animation embedded in a loaded model. */
14
+ export interface ClipOptions {
15
+ /** Repeat the clip. Default true. */
16
+ loop?: boolean;
17
+ /** Playback rate; zero holds a pose. Default 1. */
18
+ speed?: number;
19
+ /** Crossfade from the current pose, in seconds. Default 0.15. */
20
+ fade?: number;
21
+ /** Seek to this time in seconds. Omit to retain playback when reselecting a clip. */
22
+ time?: number;
23
+ }
24
+ /** Plane used by screenToWorld; defaults to the ground through the origin. */
25
+ export interface ScreenPlane {
26
+ point?: Vec3;
27
+ normal?: Vec3;
28
+ }
29
+ /**
30
+ * - `dynamic`: moved by physics (gravity, collisions, impulses).
31
+ * - `kinematic`: moved by you; pushes dynamic bodies out of the way.
32
+ * - `fixed`: never moves (floors, walls).
33
+ */
34
+ export type BodyType = 'dynamic' | 'kinematic' | 'fixed';
35
+ export type PhysicsOptions = {
36
+ type: BodyType;
37
+ /** Default: matches the mesh (spheres get a ball, cylinders a cylinder, ...; anything else a box). */
38
+ shape?: 'ball' | 'box' | 'cylinder' | 'capsule' | 'cone';
39
+ /** Ball, cylinder, capsule or cone radius. Default: from the entity's size. */
40
+ radius?: number;
41
+ /** Cylinder, capsule or cone height (total). Default: from the entity's size. */
42
+ height?: number;
43
+ /** Box size (full width, height, depth). Default: the entity's size. */
44
+ size?: Vec3;
45
+ /** Layer bits this collider is on. Default 1. */
46
+ layer?: number;
47
+ /** Layers it interacts with. Either side's mask is enough. Default: all. */
48
+ mask?: number;
49
+ /** Reports touches but doesn't push: pickups, trigger zones. */
50
+ sensor?: boolean;
51
+ /** How grippy, default 0.5. */
52
+ friction?: number;
53
+ /** Bounciness 0..1, default 0. */
54
+ bounce?: number;
55
+ /** Mass per volume, default 1. */
56
+ density?: number;
57
+ /** Slows movement over time (air resistance), default 0. */
58
+ drag?: number;
59
+ /** Slows spinning over time, default 0.05. */
60
+ angularDrag?: number;
61
+ /** Multiplier on world gravity, default 1. */
62
+ gravityScale?: number;
63
+ /** Stays upright: never tumbles. */
64
+ upright?: boolean;
65
+ /** Stops fast, small bodies passing through walls. */
66
+ ccd?: boolean;
67
+ /** 2D games: stays in its XY plane (moves in x/y, spins only around z). Keep the camera looking down -z. */
68
+ planar?: boolean;
69
+ };
70
+ /** Sound/haptic the core plays by itself when the entity hits something solid. */
71
+ export type ImpactFeedback = {
72
+ /** A loaded sound, e.g. `sfx.get("bump")`. Omit for haptics only. */
73
+ sound?: Sound;
74
+ /** Slower impacts are silent. Default 1. */
75
+ minSpeed?: number;
76
+ /** Full volume and strength at this speed. Default 10. */
77
+ maxSpeed?: number;
78
+ /** Volume at full strength, 0..2. Default 1. */
79
+ volume?: number;
80
+ /** Vibration at full strength, 0..1. Default 0 (none). */
81
+ haptic?: number;
82
+ };
83
+ /**
84
+ * Everything about an entity. `spawn` and `set` take the same options; in `set`, only what you
85
+ * pass changes, and `null` removes something (physics, parent, follow, bob, lifetime, impact).
86
+ */
87
+ export type EntityOptions = {
88
+ /** A built-in shape or a model from `loadModel`. Default "cube". */
89
+ mesh?: Shape | Model;
90
+ /** An image from `loadTexture`, drawn on the mesh (`null` = none). In `set`, pass `mesh` with it. */
91
+ texture?: Texture | null;
92
+ /** Part of the texture to show, [u0, v0, u1, v1] from 0..1 (sprite sheets, card atlases). */
93
+ textureRegion?: readonly [number, number, number, number];
94
+ /** 3D text instead of a mesh (needs `font`). Letters are 1 unit tall at scale 1. */
95
+ text?: string;
96
+ /** A font from `loadFont`, for `text`. */
97
+ font?: Font;
98
+ /** Text alignment around `position`. Default "center". */
99
+ align?: TextAlign;
100
+ /** Extra space between letters, in ems. Default 0. */
101
+ letterSpacing?: number;
102
+ /** Digits all as wide as the widest, so changing scores and timers don't shift. Default false. */
103
+ tabular?: boolean;
104
+ /** Relative to the parent when attached. */
105
+ position?: Vec3;
106
+ rotation?: Quat;
107
+ /** A number scales uniformly. Children scale with their parent. Visual only after the body exists. */
108
+ scale?: Vec3 | number;
109
+ /**
110
+ * A CSS color (`'#FF4757'`, `'rgba(…)'`) or a 0..1 tuple. Alpha below 1 makes it see-through. Tuple
111
+ * channels above 1 glow: at 2 it's self-lit, ignoring light and shadow (fire, lamps).
112
+ */
113
+ color?: ColorInput;
114
+ /** Units per second. */
115
+ velocity?: Vec3;
116
+ /** Sets horizontal speed [x, z] and keeps the vertical one: steer a body and it still falls. */
117
+ groundVelocity?: readonly [number, number];
118
+ /** Units per second², for entities without a dynamic body (falling debris, thrown items). */
119
+ acceleration?: Vec3;
120
+ /** Radians per second around each axis. */
121
+ spin?: Vec3;
122
+ /** Visual bob: the drawn position moves by amplitude * sin(phase), phase advancing at `speed`. */
123
+ bob?: {
124
+ amplitude: Vec3;
125
+ speed: number;
126
+ phase?: number;
127
+ } | null;
128
+ /** Chase `target` along the ground at `speed`. */
129
+ follow?: {
130
+ target: Entity;
131
+ speed: number;
132
+ } | null;
133
+ /** Seconds until it shrinks away and is despawned. */
134
+ lifetime?: number | null;
135
+ /** Attach to another entity: it moves, turns and scales with the parent and is despawned with it. Visual only. */
136
+ parent?: Entity | null;
137
+ physics?: BodyType | PhysicsOptions | null;
138
+ impact?: ImpactFeedback | null;
139
+ /** Whether `pick` can hit it. Default true. */
140
+ pickable?: boolean;
141
+ };
142
+ export type Easing = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'back' | 'bounce' | 'elastic';
143
+ /** What `animate` changes. Anything left out stays as it is (and keeps any animation it already has). */
144
+ export type AnimateTarget = {
145
+ position?: Vec3;
146
+ rotation?: Quat;
147
+ scale?: Vec3 | number;
148
+ /** A CSS color or a 0..1 tuple. */
149
+ color?: ColorInput;
150
+ /** Move by this offset from where the animation starts (instead of to a `position`). */
151
+ moveBy?: Vec3;
152
+ /** Turn by these angles (radians around x, y, z) from the starting rotation. 2π spins once; more spins more. */
153
+ turn?: Vec3;
154
+ /** Shake by up to this distance, fading out. Only the drawn position shakes (hits, wrong moves). */
155
+ shake?: number;
156
+ };
157
+ /** A step of a keyframed animation: values to pass through, and when (0..1 of the duration; default evenly spaced). */
158
+ export type Keyframe = AnimateTarget & {
159
+ at?: number;
160
+ };
161
+ export type SpringOptions = {
162
+ /** How strongly it pulls toward the target. Default 170. */
163
+ stiffness?: number;
164
+ /** How quickly it calms down. Lower bounces more. Default 26 (no overshoot). */
165
+ damping?: number;
166
+ /** Heavier moves slower and swings further. Default 1. */
167
+ mass?: number;
168
+ };
169
+ export type AnimateOptions = {
170
+ /** Seconds (ignored by springs). Default 0.3. */
171
+ duration?: number;
172
+ /** Seconds before it starts. Default 0. */
173
+ delay?: number;
174
+ /** Default "easeOut". "back" overshoots, "bounce" bounces, "elastic" springs. */
175
+ easing?: Easing;
176
+ /** Extra runs after the first, or "forever". Default 0. */
177
+ repeat?: number | 'forever';
178
+ /** Every other run plays backwards (pulses, ping-pong). */
179
+ yoyo?: boolean;
180
+ /** With several entities: extra delay for each next one, in seconds (waves, board reveals). */
181
+ stagger?: number;
182
+ /** "smooth" moves along a curve through the keyframes (arcs, hops) instead of straight lines. */
183
+ path?: 'linear' | 'smooth';
184
+ /**
185
+ * Physically simulated motion instead of a fixed duration: true (smooth), "bouncy", or your own
186
+ * settings. Interrupting a spring with another keeps its speed, so it never jerks.
187
+ */
188
+ spring?: boolean | 'bouncy' | SpringOptions;
189
+ };
190
+ export type BurstOptions = {
191
+ position: Vec3;
192
+ /** How many particles. Default 16. */
193
+ count?: number;
194
+ /** Main direction. Default up. */
195
+ direction?: Vec3;
196
+ /** Half-angle of the cone around `direction`, in radians. Default π (every direction). */
197
+ spread?: number;
198
+ /** Default "cube". */
199
+ mesh?: Shape | Model;
200
+ /** Particle size. Default 0.15. */
201
+ size?: number;
202
+ /** Top speed. Default 5. */
203
+ speed?: number;
204
+ /** Seconds. Default 0.8. */
205
+ lifetime?: number;
206
+ /** Vertical acceleration. Default -9.81 (falls); 0 floats. */
207
+ gravity?: number;
208
+ /** One color (CSS string or tuple), or up to 4 to pick from at random. Default white. */
209
+ color?: ColorInput | readonly ColorInput[];
210
+ /** Size at the end of each particle's life, as a multiple of its start size: above 1 grows (smoke), below shrinks (embers). Default 1. */
211
+ endSize?: number;
212
+ /** Color every particle shifts to over its life, e.g. flame to dark red; an alpha below 1 fades it out. Default: keeps its color. */
213
+ endColor?: ColorInput;
214
+ };
215
+ export type CollisionInfo = {
216
+ /** True when the touch started; false when it ended. */
217
+ started: boolean;
218
+ /** A sensor was involved. */
219
+ sensor: boolean;
220
+ /** Impact speed (0 when a touch ends). */
221
+ speed: number;
222
+ };
223
+ export type RaycastHit = {
224
+ entity: Entity;
225
+ distance: number;
226
+ normal: [number, number, number];
227
+ point: [number, number, number];
228
+ };
229
+ export type PickHit = {
230
+ entity: Entity;
231
+ /** Where the tap ray hit the entity's bounding box, in world space. */
232
+ point: [number, number, number];
233
+ distance: number;
234
+ };
235
+ export type Bounds = readonly [minX: number, minZ: number, maxX: number, maxZ: number];
236
+ import type { Shape } from '../types';
237
+ export type { Shape };
@@ -0,0 +1,34 @@
1
+ # Android build of the engine's native module. React Native's autolinking adds this directory to the
2
+ # app's native build (see react-native.config.js); there is no Gradle project, so this is a "pure C++"
3
+ # dependency and the codegen header ships in native/cpp/generated (regenerate with `bun run codegen`).
4
+ cmake_minimum_required(VERSION 3.13)
5
+ project(NayanEngine CXX)
6
+
7
+ get_filename_component(NATIVE_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
8
+
9
+ # Rust core, prebuilt per ABI by native/core/scripts/build-android.sh.
10
+ add_library(engine_core STATIC IMPORTED)
11
+ set_target_properties(engine_core PROPERTIES
12
+ IMPORTED_LOCATION "${NATIVE_ROOT}/core/build/android/${ANDROID_ABI}/libengine_core.a")
13
+
14
+ # The codegen target autolinking expects (react_codegen_<codegenConfig.name>). It must be a real
15
+ # library: autolinking links `common_flags` into it with a plain target_link_libraries call.
16
+ add_library(react_codegen_NayanEngineSpec STATIC "${NATIVE_ROOT}/cpp/generated/NayanEngineSpecJSI.cpp")
17
+ # android/ holds NayanEngineSpec.h, the (empty) Java module provider autolinking includes.
18
+ target_include_directories(react_codegen_NayanEngineSpec PUBLIC "${NATIVE_ROOT}/cpp/generated" "${CMAKE_CURRENT_LIST_DIR}")
19
+ target_compile_features(react_codegen_NayanEngineSpec PUBLIC cxx_std_20)
20
+ target_link_libraries(react_codegen_NayanEngineSpec jsi reactnative)
21
+
22
+ # The TurboModule (shared with iOS) and the Rust core it calls.
23
+ add_library(NayanEngine STATIC "${NATIVE_ROOT}/cpp/NayanEngineModule.cpp")
24
+ target_include_directories(NayanEngine PUBLIC "${NATIVE_ROOT}/cpp" "${NATIVE_ROOT}/core/include")
25
+ target_compile_features(NayanEngine PUBLIC cxx_std_20)
26
+ target_link_libraries(NayanEngine
27
+ react_codegen_NayanEngineSpec
28
+ engine_core
29
+ fbjni
30
+ jsi
31
+ reactnative
32
+ aaudio # audio output (cpal's AAudio backend)
33
+ android
34
+ log)
@@ -0,0 +1,15 @@
1
+ // Autolinking includes <NayanEngineSpec.h> and calls this provider for every library with a codegen
2
+ // config. The engine has no Java module (it is registered through the C++ provider in
3
+ // NayanEngineModule.h), so this always declines.
4
+ #pragma once
5
+
6
+ #include <ReactCommon/JavaTurboModule.h>
7
+
8
+ namespace facebook::react {
9
+
10
+ inline std::shared_ptr<TurboModule> NayanEngineSpec_ModuleProvider(
11
+ const std::string&, const JavaTurboModule::InitParams&) {
12
+ return nullptr;
13
+ }
14
+
15
+ } // namespace facebook::react
@@ -0,0 +1,26 @@
1
+ // Java side of the engine: the EngineSurface view the renderer draws into. The native module itself is
2
+ // pure C++ (CMakeLists.txt, built into the app by autolinking).
3
+ apply plugin: "com.android.library"
4
+
5
+ def safeExtGet(prop, fallback) {
6
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
7
+ }
8
+
9
+ android {
10
+ namespace "com.nayanui.engine"
11
+ compileSdkVersion safeExtGet("compileSdkVersion", 35)
12
+
13
+ defaultConfig {
14
+ minSdkVersion safeExtGet("minSdkVersion", 24)
15
+ consumerProguardFiles "proguard-rules.pro"
16
+ }
17
+ }
18
+
19
+ repositories {
20
+ google()
21
+ mavenCentral()
22
+ }
23
+
24
+ dependencies {
25
+ implementation "com.facebook.react:react-android"
26
+ }
@@ -0,0 +1,3 @@
1
+ # Autolinking adds a codegen directory for every library with a Gradle project. The engine runs no
2
+ # Gradle codegen: ../CMakeLists.txt defines react_codegen_NayanEngineSpec from native/cpp/generated.
3
+ cmake_minimum_required(VERSION 3.13)
@@ -0,0 +1,2 @@
1
+ # The native methods are looked up by name from C++ (JNI).
2
+ -keep class com.nayanui.engine.** { *; }
@@ -0,0 +1,295 @@
1
+ package com.nayanui.engine;
2
+
3
+ import android.content.Context;
4
+ import java.nio.charset.StandardCharsets;
5
+ import com.facebook.react.bridge.Arguments;
6
+ import com.facebook.react.bridge.ReactContext;
7
+ import com.facebook.react.bridge.WritableMap;
8
+ import com.facebook.react.uimanager.UIManagerHelper;
9
+ import com.facebook.react.uimanager.events.Event;
10
+ import com.facebook.react.uimanager.events.EventDispatcher;
11
+ import android.graphics.PixelFormat;
12
+ import android.os.Build;
13
+ import android.view.Surface;
14
+ import android.view.SurfaceHolder;
15
+ import android.view.SurfaceView;
16
+ import android.view.MotionEvent;
17
+ import android.view.ScaleGestureDetector;
18
+ import android.view.Choreographer;
19
+
20
+ /**
21
+ * A surface the Rust renderer draws into (Vulkan, or OpenGL ES when Vulkan isn't usable). JS refers
22
+ * to it by `surfaceId`.
23
+ *
24
+ * A SurfaceView, not a TextureView: its frames go straight to the system compositor (often as a
25
+ * hardware overlay), instead of being copied into the app's own view tree by the UI thread's
26
+ * renderer every frame. With a TextureView each game frame also redraws the React Native window and
27
+ * makes the JS thread wait on the UI thread for a free buffer, which is what made heavy 3D scenes
28
+ * stutter and hang on Android. The surface keeps showing its last frame after the renderer detaches,
29
+ * until the view leaves the window, so navigation's exit animation still shows the game.
30
+ */
31
+ public class EngineSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Choreographer.FrameCallback {
32
+ private int surfaceId = -1;
33
+ private Surface surface;
34
+ private int width;
35
+ private int height;
36
+ private int drawingId = -1;
37
+ private int drawingMode;
38
+ private int drawingBackground = 0xffffff;
39
+ private boolean drawingTick;
40
+ private boolean clockRunning;
41
+
42
+ /**
43
+ * The game's display clock: once per refresh while the surface is attached and on screen, the
44
+ * engine is asked for its next frame on the JS thread (see `deliverFrame`). Unlike React Native's
45
+ * requestAnimationFrame, which can tick at half the display's rate, this follows every vsync; the
46
+ * frame itself is drawn on the engine's render thread.
47
+ */
48
+ private final Choreographer.FrameCallback clock = new Choreographer.FrameCallback() {
49
+ @Override public void doFrame(long frameTimeNanos) {
50
+ if (!clockRunning) return;
51
+ if (surfaceId >= 0 && surface != null) nativeVsync(surfaceId, frameTimeNanos);
52
+ Choreographer.getInstance().postFrameCallback(this);
53
+ }
54
+ };
55
+
56
+ private void startClock() {
57
+ if (!clockRunning && surfaceId >= 0 && surface != null && isShown()) {
58
+ clockRunning = true;
59
+ Choreographer.getInstance().postFrameCallback(clock);
60
+ }
61
+ }
62
+
63
+ private void stopClock() {
64
+ clockRunning = false;
65
+ Choreographer.getInstance().removeFrameCallback(clock);
66
+ }
67
+ private long lastDrawingEvent;
68
+ private int drawingPointer = -1;
69
+ private boolean pencil;
70
+ private float lastX, lastY;
71
+ private final float density;
72
+ private final ScaleGestureDetector scaleDetector;
73
+
74
+ public EngineSurfaceView(Context context) {
75
+ super(context);
76
+ getHolder().addCallback(this);
77
+ // Frames are opaque: the compositor can skip blending the layer.
78
+ getHolder().setFormat(PixelFormat.OPAQUE);
79
+ density = context.getResources().getDisplayMetrics().density;
80
+ scaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
81
+ @Override public boolean onScale(ScaleGestureDetector detector) {
82
+ nativeDrawingTransform(drawingId, 0, 0, detector.getScaleFactor());
83
+ return true;
84
+ }
85
+ });
86
+ }
87
+
88
+ public void setSurfaceId(int id) {
89
+ if (id == surfaceId) {
90
+ return;
91
+ }
92
+ if (surfaceId >= 0) {
93
+ nativeDetach(surfaceId);
94
+ }
95
+ surfaceId = id;
96
+ attach();
97
+ }
98
+
99
+ /**
100
+ * Stops the renderer, but leaves the surface (and its last frame) visible until navigation removes
101
+ * the view. Hiding it here would replace the game's last frame with an empty screen during the
102
+ * exit animation.
103
+ */
104
+ public void release() {
105
+ stopClock();
106
+ stopDrawing();
107
+ drawingId = -1;
108
+ if (surfaceId >= 0) {
109
+ nativeDetach(surfaceId);
110
+ surfaceId = -1;
111
+ }
112
+ releaseSurface();
113
+ }
114
+
115
+ /**
116
+ * Games ask for 60 frames a second. A SurfaceView's frames never invalidate the view tree, so on
117
+ * Android 15+ the system otherwise judges the app idle and drops its vsync (and with it React
118
+ * Native's frame callbacks) to 30 or 20 Hz, halving the frame rate and doubling load times.
119
+ */
120
+ private static final float GAME_FRAME_RATE = 60f;
121
+
122
+ private void requestFrameRate() {
123
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && surface != null && surface.isValid()) {
124
+ try {
125
+ surface.setFrameRate(GAME_FRAME_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
126
+ } catch (IllegalArgumentException | IllegalStateException ignored) {
127
+ // A surface being torn down; the next attach asks again.
128
+ }
129
+ }
130
+ if (Build.VERSION.SDK_INT >= 35) setRequestedFrameRate(REQUESTED_FRAME_RATE_CATEGORY_HIGH);
131
+ }
132
+
133
+ private void attach() {
134
+ if (surfaceId >= 0 && surface != null && surface.isValid() && width > 0 && height > 0) {
135
+ nativeAttach(surfaceId, surface, width, height);
136
+ requestFrameRate();
137
+ if (drawingId >= 0) nativeDrawingInvalidate(drawingId);
138
+ startDrawing();
139
+ startClock();
140
+ }
141
+ }
142
+
143
+ @Override
144
+ public void surfaceCreated(SurfaceHolder holder) {
145
+ surface = holder.getSurface();
146
+ }
147
+
148
+ @Override
149
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
150
+ surface = holder.getSurface();
151
+ width = w;
152
+ height = h;
153
+ attach();
154
+ }
155
+
156
+ @Override
157
+ public void surfaceDestroyed(SurfaceHolder holder) {
158
+ stopClock();
159
+ stopDrawing();
160
+ // Wait for the renderer before Android destroys the surface. Keep the id so a screen covered by
161
+ // another route can attach a new surface when it comes back from the back stack.
162
+ if (surfaceId >= 0) {
163
+ nativeDetach(surfaceId);
164
+ }
165
+ releaseSurface();
166
+ }
167
+
168
+ /** The holder owns the surface: only forget it (releasing it here would break the next attach). */
169
+ private void releaseSurface() {
170
+ surface = null;
171
+ }
172
+
173
+ public void setDrawingId(int id) {
174
+ stopDrawing();
175
+ drawingId = id;
176
+ if (id >= 0) nativeDrawingInvalidate(id);
177
+ startDrawing();
178
+ }
179
+ public void setDrawingMode(int mode) {
180
+ if (drawingPointer >= 0) nativeDrawingTouch(drawingId, 3, 0, 0, 1);
181
+ drawingPointer = -1;
182
+ drawingMode = mode;
183
+ }
184
+ public void setDrawingBackground(int color) {
185
+ drawingBackground = color;
186
+ if (drawingId >= 0) nativeDrawingInvalidate(drawingId);
187
+ }
188
+ private void startDrawing() {
189
+ if (!drawingTick && drawingId >= 0 && surface != null && isShown() && hasWindowFocus()) {
190
+ drawingTick = true;
191
+ Choreographer.getInstance().postFrameCallback(this);
192
+ }
193
+ }
194
+ private void stopDrawing() {
195
+ drawingTick = false;
196
+ Choreographer.getInstance().removeFrameCallback(this);
197
+ if (drawingPointer >= 0) nativeDrawingTouch(drawingId, 2, lastX, lastY, 1);
198
+ drawingPointer = -1;
199
+ }
200
+ @Override public void doFrame(long frameTimeNanos) {
201
+ if (!drawingTick || drawingId < 0 || surface == null) return;
202
+ nativeDrawingPresent(drawingId, surfaceId, width / density, height / density, drawingBackground);
203
+ if (frameTimeNanos - lastDrawingEvent >= 100_000_000L && getContext() instanceof ReactContext) {
204
+ lastDrawingEvent = frameTimeNanos;
205
+ byte[] data = nativeDrawingEvent(drawingId);
206
+ if (data != null) {
207
+ String state = new String(data, StandardCharsets.UTF_8);
208
+ ReactContext context = (ReactContext)getContext();
209
+ EventDispatcher dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, getId());
210
+ if (dispatcher != null) dispatcher.dispatchEvent(new DrawingChangeEvent(UIManagerHelper.getSurfaceId(context), getId(), drawingId, state));
211
+ }
212
+ }
213
+ Choreographer.getInstance().postFrameCallback(this);
214
+ }
215
+ private static final class DrawingChangeEvent extends Event<DrawingChangeEvent> {
216
+ private final String state;
217
+ private final int document;
218
+ DrawingChangeEvent(int surface, int view, int document, String state) { super(surface, view); this.document = document; this.state = state; }
219
+ @Override public String getEventName() { return "topDrawingChange"; }
220
+ @Override public boolean canCoalesce() { return true; }
221
+ @Override protected WritableMap getEventData() { WritableMap data = Arguments.createMap(); data.putString("state", state); data.putInt("documentId", document); return data; }
222
+ }
223
+ @Override public void onWindowFocusChanged(boolean focused) {
224
+ super.onWindowFocusChanged(focused);
225
+ if (focused) startDrawing(); else stopDrawing();
226
+ }
227
+ @Override protected void onDetachedFromWindow() {
228
+ stopClock();
229
+ stopDrawing();
230
+ super.onDetachedFromWindow();
231
+ }
232
+ @Override protected void onWindowVisibilityChanged(int visibility) {
233
+ super.onWindowVisibilityChanged(visibility);
234
+ if (visibility == VISIBLE) {
235
+ startDrawing();
236
+ startClock();
237
+ } else {
238
+ stopDrawing();
239
+ stopClock();
240
+ }
241
+ }
242
+ @Override public boolean onTouchEvent(MotionEvent event) {
243
+ if (drawingId < 0) return super.onTouchEvent(event);
244
+ getParent().requestDisallowInterceptTouchEvent(true);
245
+ int action = event.getActionMasked();
246
+ if (action == MotionEvent.ACTION_DOWN) {
247
+ drawingPointer = -1;
248
+ lastX = event.getX() / density;
249
+ lastY = event.getY() / density;
250
+ pencil = event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS || event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER;
251
+ if (drawingMode == 0) {
252
+ drawingPointer = event.getPointerId(0);
253
+ nativeDrawingTouch(drawingId, event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER ? 4 : 0, lastX, lastY, pencil ? event.getPressure() : 1);
254
+ }
255
+ }
256
+ if (!pencil) scaleDetector.onTouchEvent(event);
257
+ if (action == MotionEvent.ACTION_POINTER_DOWN && !pencil) {
258
+ if (drawingPointer >= 0) nativeDrawingTouch(drawingId, 3, 0, 0, 1);
259
+ drawingPointer = -1;
260
+ lastX = event.getX() / density;
261
+ lastY = event.getY() / density;
262
+ }
263
+ if (action == MotionEvent.ACTION_MOVE) {
264
+ int index = drawingPointer < 0 ? -1 : event.findPointerIndex(drawingPointer);
265
+ if (index >= 0) {
266
+ for (int h = 0; h < event.getHistorySize(); h++) nativeDrawingTouch(drawingId, 1, event.getHistoricalX(index, h) / density, event.getHistoricalY(index, h) / density, pencil ? event.getHistoricalPressure(index, h) : 1);
267
+ lastX = event.getX(index) / density;
268
+ lastY = event.getY(index) / density;
269
+ nativeDrawingTouch(drawingId, 1, lastX, lastY, pencil ? event.getPressure(index) : 1);
270
+ } else if (drawingMode != 0 || event.getPointerCount() > 1) {
271
+ float x = event.getX() / density, y = event.getY() / density;
272
+ if (!scaleDetector.isInProgress()) nativeDrawingTransform(drawingId, x - lastX, y - lastY, 1);
273
+ lastX = x;
274
+ lastY = y;
275
+ }
276
+ }
277
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || (action == MotionEvent.ACTION_POINTER_UP && event.getPointerId(event.getActionIndex()) == drawingPointer)) {
278
+ if (drawingPointer >= 0) nativeDrawingTouch(drawingId, action == MotionEvent.ACTION_CANCEL ? 3 : 2, lastX, lastY, 1);
279
+ drawingPointer = -1;
280
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { pencil = false; getParent().requestDisallowInterceptTouchEvent(false); }
281
+ }
282
+ return true;
283
+ }
284
+ private static native void nativeDrawingPresent(int drawing, int surface, float width, float height, int color);
285
+ private static native void nativeDrawingTouch(int drawing, int phase, float x, float y, float pressure);
286
+ private static native void nativeDrawingTransform(int drawing, float dx, float dy, float scale);
287
+ private static native byte[] nativeDrawingEvent(int drawing);
288
+ private static native void nativeDrawingInvalidate(int drawing);
289
+
290
+ private static native boolean nativeAttach(int surfaceId, Surface surface, int width, int height);
291
+
292
+ private static native void nativeDetach(int surfaceId);
293
+
294
+ private static native void nativeVsync(int surfaceId, long frameTimeNanos);
295
+ }
@@ -0,0 +1,45 @@
1
+ package com.nayanui.engine;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import java.util.Map;
5
+ import com.facebook.react.common.MapBuilder;
6
+ import com.facebook.react.uimanager.SimpleViewManager;
7
+ import com.facebook.react.uimanager.ThemedReactContext;
8
+ import com.facebook.react.uimanager.annotations.ReactProp;
9
+
10
+ public class EngineSurfaceViewManager extends SimpleViewManager<EngineSurfaceView> {
11
+ @NonNull
12
+ @Override
13
+ public String getName() {
14
+ return "NayanEngineSurface";
15
+ }
16
+
17
+ @NonNull
18
+ @Override
19
+ protected EngineSurfaceView createViewInstance(@NonNull ThemedReactContext context) {
20
+ return new EngineSurfaceView(context);
21
+ }
22
+
23
+ // React unmounted the screen; stop rendering while its last texture finishes the exit animation.
24
+ @Override
25
+ public void onDropViewInstance(@NonNull EngineSurfaceView view) {
26
+ view.release();
27
+ super.onDropViewInstance(view);
28
+ }
29
+
30
+ @Override
31
+ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
32
+ return MapBuilder.of("topDrawingChange", MapBuilder.of("registrationName", "onDrawingChange"));
33
+ }
34
+
35
+ @ReactProp(name = "surfaceId", defaultInt = -1)
36
+ public void setSurfaceId(EngineSurfaceView view, int surfaceId) {
37
+ view.setSurfaceId(surfaceId);
38
+ }
39
+ @ReactProp(name = "drawingId", defaultInt = -1)
40
+ public void setDrawingId(EngineSurfaceView view, int id) { view.setDrawingId(id); }
41
+ @ReactProp(name = "drawingMode", defaultInt = 0)
42
+ public void setDrawingMode(EngineSurfaceView view, int mode) { view.setDrawingMode(mode); }
43
+ @ReactProp(name = "drawingBackground", defaultInt = 0xffffff)
44
+ public void setDrawingBackground(EngineSurfaceView view, int color) { view.setDrawingBackground(color); }
45
+ }