@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.
- package/LICENSE +21 -0
- package/NayanEngine.podspec +28 -0
- package/README.md +308 -2
- package/lib/module/assets/AssetBytesCache.js +76 -0
- package/lib/module/assets/AssetCache.js +19 -0
- package/lib/module/assets/AssetLoader.js +52 -0
- package/lib/module/assets/CacheGeneration.js +7 -0
- package/lib/module/assets/IndexedDbCache.web.js +149 -0
- package/lib/module/assets/ProceduralCache.js +249 -0
- package/lib/module/assets/font.js +139 -0
- package/lib/module/assets/loft.js +142 -0
- package/lib/module/assets/model.js +96 -0
- package/lib/module/assets/modelling.js +254 -0
- package/lib/module/assets/shape.js +926 -0
- package/lib/module/assets/source.js +58 -0
- package/lib/module/assets/source.web.js +22 -0
- package/lib/module/assets/storage.js +4 -0
- package/lib/module/assets/storage.shared.js +38 -0
- package/lib/module/assets/storage.web.js +38 -0
- package/lib/module/assets/surface.js +47 -0
- package/lib/module/assets/texture.js +69 -0
- package/lib/module/drawing/DrawingCanvas.js +38 -0
- package/lib/module/drawing/DrawingCanvas.web.js +206 -0
- package/lib/module/drawing/DrawingDocument.js +256 -0
- package/lib/module/drawing/types.js +4 -0
- package/lib/module/globals.d.ts +12 -0
- package/lib/module/index.js +39 -0
- package/lib/module/input/Joystick.js +84 -0
- package/lib/module/input/Joystick.web.js +93 -0
- package/lib/module/input/keyboard.js +250 -0
- package/lib/module/media/audio-worklet.js +34 -0
- package/lib/module/media/audio.js +97 -0
- package/lib/module/media/haptics.js +82 -0
- package/lib/module/media/output.web.js +66 -0
- package/lib/module/native/BrowserEngine.js +568 -0
- package/lib/module/native/EngineBridge.js +4 -0
- package/lib/module/native/NativeNayanEngine.js +7 -0
- package/lib/module/native/NativeNayanEngine.web.js +62 -0
- package/lib/module/native/prepare.js +7 -0
- package/lib/module/native/prepare.web.js +17 -0
- package/lib/module/native/spec/NativeNayanEngine.js +17 -0
- package/lib/module/package.json +1 -0
- package/lib/module/render/GameView.js +317 -0
- package/lib/module/render/camera.js +34 -0
- package/lib/module/render/host.js +39 -0
- package/lib/module/render/host.web.js +104 -0
- package/lib/module/render/light.js +24 -0
- package/lib/module/render/meshes.js +159 -0
- package/lib/module/render/quality.js +119 -0
- package/lib/module/render/responder.js +4 -0
- package/lib/module/render/responder.web.js +97 -0
- package/lib/module/render/touch.js +185 -0
- package/lib/module/render/useWorld.js +72 -0
- package/lib/module/types.js +30 -0
- package/lib/module/world/Scoreboard.js +161 -0
- package/lib/module/world/TransformBatch.js +128 -0
- package/lib/module/world/World.js +704 -0
- package/lib/module/world/color.js +80 -0
- package/lib/module/world/desc.js +187 -0
- package/lib/module/world/effects.js +463 -0
- package/lib/module/world/quat.js +86 -0
- package/lib/module/world/types.js +4 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/assets/AssetBytesCache.d.ts +7 -0
- package/lib/typescript/src/assets/AssetCache.d.ts +9 -0
- package/lib/typescript/src/assets/AssetLoader.d.ts +11 -0
- package/lib/typescript/src/assets/CacheGeneration.d.ts +3 -0
- package/lib/typescript/src/assets/IndexedDbCache.web.d.ts +20 -0
- package/lib/typescript/src/assets/ProceduralCache.d.ts +45 -0
- package/lib/typescript/src/assets/font.d.ts +53 -0
- package/lib/typescript/src/assets/loft.d.ts +31 -0
- package/lib/typescript/src/assets/model.d.ts +58 -0
- package/lib/typescript/src/assets/modelling.d.ts +157 -0
- package/lib/typescript/src/assets/shape.d.ts +203 -0
- package/lib/typescript/src/assets/source.d.ts +10 -0
- package/lib/typescript/src/assets/source.web.d.ts +8 -0
- package/lib/typescript/src/assets/storage.d.ts +2 -0
- package/lib/typescript/src/assets/storage.shared.d.ts +7 -0
- package/lib/typescript/src/assets/storage.web.d.ts +7 -0
- package/lib/typescript/src/assets/surface.d.ts +17 -0
- package/lib/typescript/src/assets/texture.d.ts +30 -0
- package/lib/typescript/src/drawing/DrawingCanvas.d.ts +5 -0
- package/lib/typescript/src/drawing/DrawingCanvas.web.d.ts +4 -0
- package/lib/typescript/src/drawing/DrawingDocument.d.ts +210 -0
- package/lib/typescript/src/drawing/types.d.ts +12 -0
- package/lib/typescript/src/index.d.ts +45 -0
- package/lib/typescript/src/input/Joystick.d.ts +20 -0
- package/lib/typescript/src/input/Joystick.web.d.ts +13 -0
- package/lib/typescript/src/input/keyboard.d.ts +17 -0
- package/lib/typescript/src/media/audio.d.ts +46 -0
- package/lib/typescript/src/media/haptics.d.ts +22 -0
- package/lib/typescript/src/media/output.web.d.ts +4 -0
- package/lib/typescript/src/native/BrowserEngine.d.ts +8 -0
- package/lib/typescript/src/native/EngineBridge.d.ts +26 -0
- package/lib/typescript/src/native/NativeNayanEngine.d.ts +5 -0
- package/lib/typescript/src/native/NativeNayanEngine.web.d.ts +10 -0
- package/lib/typescript/src/native/prepare.d.ts +3 -0
- package/lib/typescript/src/native/prepare.web.d.ts +2 -0
- package/lib/typescript/src/native/spec/NativeNayanEngine.d.ts +211 -0
- package/lib/typescript/src/render/GameView.d.ts +107 -0
- package/lib/typescript/src/render/camera.d.ts +6 -0
- package/lib/typescript/src/render/host.d.ts +29 -0
- package/lib/typescript/src/render/host.web.d.ts +16 -0
- package/lib/typescript/src/render/light.d.ts +12 -0
- package/lib/typescript/src/render/meshes.d.ts +45 -0
- package/lib/typescript/src/render/quality.d.ts +48 -0
- package/lib/typescript/src/render/responder.d.ts +2 -0
- package/lib/typescript/src/render/responder.web.d.ts +40 -0
- package/lib/typescript/src/render/touch.d.ts +26 -0
- package/lib/typescript/src/render/useWorld.d.ts +20 -0
- package/lib/typescript/src/types.d.ts +81 -0
- package/lib/typescript/src/world/Scoreboard.d.ts +75 -0
- package/lib/typescript/src/world/TransformBatch.d.ts +39 -0
- package/lib/typescript/src/world/World.d.ts +207 -0
- package/lib/typescript/src/world/color.d.ts +18 -0
- package/lib/typescript/src/world/desc.d.ts +17 -0
- package/lib/typescript/src/world/effects.d.ts +157 -0
- package/lib/typescript/src/world/quat.d.ts +31 -0
- package/lib/typescript/src/world/types.d.ts +238 -0
- package/lib/web/audio-worklet.js +29 -0
- package/lib/web/engine_core_bg.wasm +0 -0
- package/lib/web/index.js +8240 -0
- package/lib/web/types/src/assets/AssetBytesCache.d.ts +6 -0
- package/lib/web/types/src/assets/AssetCache.d.ts +8 -0
- package/lib/web/types/src/assets/AssetLoader.d.ts +10 -0
- package/lib/web/types/src/assets/CacheGeneration.d.ts +2 -0
- package/lib/web/types/src/assets/IndexedDbCache.d.ts +19 -0
- package/lib/web/types/src/assets/ProceduralCache.d.ts +44 -0
- package/lib/web/types/src/assets/font.d.ts +52 -0
- package/lib/web/types/src/assets/loft.d.ts +30 -0
- package/lib/web/types/src/assets/model.d.ts +57 -0
- package/lib/web/types/src/assets/modelling.d.ts +156 -0
- package/lib/web/types/src/assets/shape.d.ts +202 -0
- package/lib/web/types/src/assets/source.d.ts +7 -0
- package/lib/web/types/src/assets/storage.d.ts +6 -0
- package/lib/web/types/src/assets/storage.shared.d.ts +6 -0
- package/lib/web/types/src/assets/surface.d.ts +16 -0
- package/lib/web/types/src/assets/texture.d.ts +29 -0
- package/lib/web/types/src/drawing/DrawingCanvas.d.ts +3 -0
- package/lib/web/types/src/drawing/DrawingDocument.d.ts +209 -0
- package/lib/web/types/src/drawing/types.d.ts +11 -0
- package/lib/web/types/src/index.d.ts +44 -0
- package/lib/web/types/src/input/Joystick.d.ts +12 -0
- package/lib/web/types/src/input/keyboard.d.ts +16 -0
- package/lib/web/types/src/media/audio.d.ts +45 -0
- package/lib/web/types/src/media/haptics.d.ts +21 -0
- package/lib/web/types/src/media/output.d.ts +3 -0
- package/lib/web/types/src/native/BrowserEngine.d.ts +7 -0
- package/lib/web/types/src/native/EngineBridge.d.ts +25 -0
- package/lib/web/types/src/native/NativeNayanEngine.d.ts +9 -0
- package/lib/web/types/src/native/prepare.d.ts +1 -0
- package/lib/web/types/src/native/spec/NativeNayanEngine.d.ts +210 -0
- package/lib/web/types/src/render/GameView.d.ts +106 -0
- package/lib/web/types/src/render/camera.d.ts +5 -0
- package/lib/web/types/src/render/host.d.ts +15 -0
- package/lib/web/types/src/render/light.d.ts +11 -0
- package/lib/web/types/src/render/meshes.d.ts +44 -0
- package/lib/web/types/src/render/quality.d.ts +47 -0
- package/lib/web/types/src/render/responder.d.ts +39 -0
- package/lib/web/types/src/render/touch.d.ts +25 -0
- package/lib/web/types/src/render/useWorld.d.ts +19 -0
- package/lib/web/types/src/types.d.ts +80 -0
- package/lib/web/types/src/world/Scoreboard.d.ts +74 -0
- package/lib/web/types/src/world/TransformBatch.d.ts +38 -0
- package/lib/web/types/src/world/World.d.ts +206 -0
- package/lib/web/types/src/world/color.d.ts +17 -0
- package/lib/web/types/src/world/desc.d.ts +16 -0
- package/lib/web/types/src/world/effects.d.ts +156 -0
- package/lib/web/types/src/world/quat.d.ts +30 -0
- package/lib/web/types/src/world/types.d.ts +237 -0
- package/native/android/CMakeLists.txt +34 -0
- package/native/android/NayanEngineSpec.h +15 -0
- package/native/android/build.gradle +26 -0
- package/native/android/codegen/CMakeLists.txt +3 -0
- package/native/android/proguard-rules.pro +2 -0
- package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceView.java +295 -0
- package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceViewManager.java +45 -0
- package/native/android/src/main/java/com/nayanui/engine/NayanEnginePackage.java +24 -0
- package/native/core/build/EngineCore.xcframework/Info.plist +43 -0
- package/native/core/build/EngineCore.xcframework/ios-arm64/libengine_core.a +0 -0
- package/native/core/build/EngineCore.xcframework/ios-arm64-simulator/libengine_core.a +0 -0
- package/native/core/build/android/arm64-v8a/libengine_core.a +0 -0
- package/native/core/build/android/armeabi-v7a/libengine_core.a +0 -0
- package/native/core/build/android/x86/libengine_core.a +0 -0
- package/native/core/build/android/x86_64/libengine_core.a +0 -0
- package/native/core/include/drawing-licenses.txt +58 -0
- package/native/core/include/engine_core.h +331 -0
- package/native/cpp/NayanEngineModule.cpp +998 -0
- package/native/cpp/NayanEngineModule.h +174 -0
- package/native/cpp/generated/NayanEngineSpecJSI.cpp +2 -0
- package/native/cpp/generated/NayanEngineSpecJSI.h +1026 -0
- package/native/ios/NayanEngineProvider.h +5 -0
- package/native/ios/NayanEngineProvider.mm +21 -0
- package/native/ios/NayanEngineSurface.mm +252 -0
- package/native/scripts/check-package.mjs +39 -0
- package/package.json +162 -4
- package/react-native.config.js +18 -0
- package/src/native/spec/NativeNayanEngine.ts +265 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export const PRIMARY_INPUT_EVENT = 'nayan-engine-primary-action';
|
|
4
|
+
/** Browser keyboard adapter. Reuses pointer input, including engine gestures and raw contact surfaces. */
|
|
5
|
+
export function createKeyboardInput(surface, options = {}) {
|
|
6
|
+
const mode = options.mode ?? 'pointer';
|
|
7
|
+
const keys = new Set();
|
|
8
|
+
const directions = {
|
|
9
|
+
ArrowLeft: [-1, 0],
|
|
10
|
+
KeyA: [-1, 0],
|
|
11
|
+
ArrowRight: [1, 0],
|
|
12
|
+
KeyD: [1, 0],
|
|
13
|
+
ArrowUp: [0, -1],
|
|
14
|
+
KeyW: [0, -1],
|
|
15
|
+
ArrowDown: [0, 1],
|
|
16
|
+
KeyS: [0, 1]
|
|
17
|
+
};
|
|
18
|
+
let point = {
|
|
19
|
+
x: surface.clientWidth / 2,
|
|
20
|
+
y: surface.clientHeight / 2
|
|
21
|
+
};
|
|
22
|
+
let held;
|
|
23
|
+
let frame = 0;
|
|
24
|
+
let last = 0;
|
|
25
|
+
let disposed = false;
|
|
26
|
+
const cursor = document.createElement('div');
|
|
27
|
+
cursor.setAttribute('aria-hidden', 'true');
|
|
28
|
+
cursor.dataset.engineKeyboardCursor = '';
|
|
29
|
+
Object.assign(cursor.style, {
|
|
30
|
+
position: 'absolute',
|
|
31
|
+
width: '22px',
|
|
32
|
+
height: '22px',
|
|
33
|
+
border: '2px solid white',
|
|
34
|
+
borderRadius: '50%',
|
|
35
|
+
boxShadow: '0 0 0 2px #17122b, 0 2px 8px #000a',
|
|
36
|
+
background: '#6f3ef460',
|
|
37
|
+
pointerEvents: 'none',
|
|
38
|
+
zIndex: '20',
|
|
39
|
+
display: 'none',
|
|
40
|
+
transform: 'translate(-50%, -50%)'
|
|
41
|
+
});
|
|
42
|
+
surface.appendChild(cursor);
|
|
43
|
+
const show = () => {
|
|
44
|
+
point.x = Math.max(2, Math.min(surface.clientWidth - 2, point.x));
|
|
45
|
+
point.y = Math.max(2, Math.min(surface.clientHeight - 2, point.y));
|
|
46
|
+
cursor.style.display = 'block';
|
|
47
|
+
cursor.style.left = `${point.x}px`;
|
|
48
|
+
cursor.style.top = `${point.y}px`;
|
|
49
|
+
};
|
|
50
|
+
const target = () => {
|
|
51
|
+
const rect = surface.getBoundingClientRect();
|
|
52
|
+
const element = document.elementFromPoint(rect.left + point.x, rect.top + point.y);
|
|
53
|
+
return element && surface.contains(element) ? element : null;
|
|
54
|
+
};
|
|
55
|
+
const dispatch = (element, type, id) => {
|
|
56
|
+
const rect = surface.getBoundingClientRect();
|
|
57
|
+
element.dispatchEvent(new PointerEvent(type, {
|
|
58
|
+
bubbles: true,
|
|
59
|
+
cancelable: true,
|
|
60
|
+
pointerId: id,
|
|
61
|
+
pointerType: 'mouse',
|
|
62
|
+
isPrimary: id === -1,
|
|
63
|
+
button: 0,
|
|
64
|
+
buttons: type === 'pointerup' || type === 'pointercancel' ? 0 : 1,
|
|
65
|
+
clientX: rect.left + point.x,
|
|
66
|
+
clientY: rect.top + point.y
|
|
67
|
+
}));
|
|
68
|
+
};
|
|
69
|
+
const down = () => {
|
|
70
|
+
const element = target();
|
|
71
|
+
if (!element || held) return;
|
|
72
|
+
held = {
|
|
73
|
+
target: element,
|
|
74
|
+
id: -1
|
|
75
|
+
};
|
|
76
|
+
dispatch(element, 'pointerdown', -1);
|
|
77
|
+
};
|
|
78
|
+
const up = (cancelled = false) => {
|
|
79
|
+
if (!held) return;
|
|
80
|
+
dispatch(held.target, cancelled ? 'pointercancel' : 'pointerup', held.id);
|
|
81
|
+
held = undefined;
|
|
82
|
+
};
|
|
83
|
+
const tap = (id = -2) => {
|
|
84
|
+
const element = target();
|
|
85
|
+
if (!element) return;
|
|
86
|
+
dispatch(element, 'pointerdown', id);
|
|
87
|
+
dispatch(element, 'pointerup', id);
|
|
88
|
+
};
|
|
89
|
+
const cancel = () => {
|
|
90
|
+
cancelAnimationFrame(frame);
|
|
91
|
+
frame = 0;
|
|
92
|
+
keys.clear();
|
|
93
|
+
up(true);
|
|
94
|
+
cursor.style.display = 'none';
|
|
95
|
+
};
|
|
96
|
+
const vector = () => {
|
|
97
|
+
let x = 0,
|
|
98
|
+
y = 0;
|
|
99
|
+
for (const key of keys) {
|
|
100
|
+
const direction = directions[key];
|
|
101
|
+
if (direction) {
|
|
102
|
+
x += direction[0];
|
|
103
|
+
y += direction[1];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const length = Math.max(1, Math.hypot(x, y));
|
|
107
|
+
return {
|
|
108
|
+
x: x / length,
|
|
109
|
+
y: y / length
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
const tick = now => {
|
|
113
|
+
frame = 0;
|
|
114
|
+
if (disposed || options.enabled?.() === false) {
|
|
115
|
+
cancel();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const dt = Math.min(0.05, Math.max(0, (now - last) / 1000));
|
|
119
|
+
last = now;
|
|
120
|
+
const direction = vector();
|
|
121
|
+
point.x = Math.max(2, Math.min(surface.clientWidth - 2, point.x + direction.x * 420 * dt));
|
|
122
|
+
point.y = Math.max(2, Math.min(surface.clientHeight - 2, point.y + direction.y * 420 * dt));
|
|
123
|
+
show();
|
|
124
|
+
if (held) dispatch(held.target, 'pointermove', held.id);
|
|
125
|
+
if (keys.size) frame = requestAnimationFrame(tick);
|
|
126
|
+
};
|
|
127
|
+
const editable = event => {
|
|
128
|
+
const element = event.target;
|
|
129
|
+
return element instanceof Element && !!element.closest('input,textarea,select,button,a,[role=button],[role=switch],[contenteditable=true]');
|
|
130
|
+
};
|
|
131
|
+
const keyDown = event => {
|
|
132
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.defaultPrevented || editable(event)) return;
|
|
133
|
+
if (options.enabled?.() === false) {
|
|
134
|
+
cancel();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const focused = document.activeElement;
|
|
138
|
+
if (focused && focused !== document.body && focused !== document.documentElement && !surface.contains(focused)) return;
|
|
139
|
+
const direction = directions[event.code];
|
|
140
|
+
if (!direction && event.code !== 'Space' && event.code !== 'Enter') return;
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
if (event.code === 'Enter') {
|
|
143
|
+
if (!event.repeat) {
|
|
144
|
+
cancel();
|
|
145
|
+
const action = new CustomEvent(PRIMARY_INPUT_EVENT, {
|
|
146
|
+
bubbles: true,
|
|
147
|
+
cancelable: true
|
|
148
|
+
});
|
|
149
|
+
if (!(surface.querySelector('canvas') ?? surface).dispatchEvent(action)) return;
|
|
150
|
+
const previous = point;
|
|
151
|
+
point = options.primary?.() ?? point;
|
|
152
|
+
tap();
|
|
153
|
+
point = previous;
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (event.code === 'Space') {
|
|
158
|
+
if (event.repeat) return;
|
|
159
|
+
show();
|
|
160
|
+
if (mode === 'pointer') down();else {
|
|
161
|
+
const resume = !!held;
|
|
162
|
+
up();
|
|
163
|
+
tap();
|
|
164
|
+
if (resume) down();
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (mode === 'swipe') {
|
|
169
|
+
const previous = point;
|
|
170
|
+
point = {
|
|
171
|
+
x: surface.clientWidth / 2,
|
|
172
|
+
y: surface.clientHeight / 2
|
|
173
|
+
};
|
|
174
|
+
down();
|
|
175
|
+
point = {
|
|
176
|
+
x: point.x + direction[0] * 80,
|
|
177
|
+
y: point.y + direction[1] * 80
|
|
178
|
+
};
|
|
179
|
+
if (held) dispatch(held.target, 'pointermove', held.id);
|
|
180
|
+
up();
|
|
181
|
+
point = previous;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!keys.size && mode === 'drag') {
|
|
185
|
+
point = {
|
|
186
|
+
x: surface.clientWidth / 2,
|
|
187
|
+
y: surface.clientHeight / 2
|
|
188
|
+
};
|
|
189
|
+
down();
|
|
190
|
+
}
|
|
191
|
+
keys.add(event.code);
|
|
192
|
+
show();
|
|
193
|
+
if (!frame) {
|
|
194
|
+
last = performance.now();
|
|
195
|
+
frame = requestAnimationFrame(tick);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const keyUp = event => {
|
|
199
|
+
if (event.code === 'Space' && mode === 'pointer') up();
|
|
200
|
+
if (!keys.delete(event.code)) return;
|
|
201
|
+
if (!keys.size) {
|
|
202
|
+
cancelAnimationFrame(frame);
|
|
203
|
+
frame = 0;
|
|
204
|
+
if (mode === 'drag') up();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const pointer = event => {
|
|
208
|
+
if (!event.isTrusted) return;
|
|
209
|
+
if (event.type === 'pointerdown') {
|
|
210
|
+
cancel();
|
|
211
|
+
const control = event.target instanceof Element && event.target.closest('input,textarea,select,button,a,[role=button],[role=switch],[contenteditable=true]');
|
|
212
|
+
if (!control) surface.focus({
|
|
213
|
+
preventScroll: true
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
// Keep the keyboard cursor independent of mouse clicks on HUD controls.
|
|
217
|
+
if (!held) cursor.style.display = 'none';
|
|
218
|
+
};
|
|
219
|
+
const visibility = () => {
|
|
220
|
+
if (document.hidden) cancel();
|
|
221
|
+
};
|
|
222
|
+
const focus = () => {
|
|
223
|
+
const element = document.activeElement;
|
|
224
|
+
if (element && element !== document.body && (!surface.contains(element) || element.closest('input,textarea,select,button,a,[role=button],[role=switch],[contenteditable=true]'))) cancel();
|
|
225
|
+
};
|
|
226
|
+
window.addEventListener('keydown', keyDown);
|
|
227
|
+
window.addEventListener('keyup', keyUp);
|
|
228
|
+
window.addEventListener('blur', cancel);
|
|
229
|
+
document.addEventListener('visibilitychange', visibility);
|
|
230
|
+
document.addEventListener('focusin', focus);
|
|
231
|
+
surface.addEventListener('pointerdown', pointer, true);
|
|
232
|
+
surface.addEventListener('pointermove', pointer, true);
|
|
233
|
+
return {
|
|
234
|
+
cancel,
|
|
235
|
+
dispose() {
|
|
236
|
+
if (disposed) return;
|
|
237
|
+
disposed = true;
|
|
238
|
+
cancel();
|
|
239
|
+
window.removeEventListener('keydown', keyDown);
|
|
240
|
+
window.removeEventListener('keyup', keyUp);
|
|
241
|
+
window.removeEventListener('blur', cancel);
|
|
242
|
+
document.removeEventListener('visibilitychange', visibility);
|
|
243
|
+
document.removeEventListener('focusin', focus);
|
|
244
|
+
surface.removeEventListener('pointerdown', pointer, true);
|
|
245
|
+
surface.removeEventListener('pointermove', pointer, true);
|
|
246
|
+
cursor.remove();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=keyboard.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Runs on the audio rendering thread. PCM is produced by the shared Rust mixer on the main thread.
|
|
4
|
+
class NayanMixer extends AudioWorkletProcessor {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
this.blocks = [];
|
|
8
|
+
this.offset = 0;
|
|
9
|
+
this.port.onmessage = ({
|
|
10
|
+
data
|
|
11
|
+
}) => {
|
|
12
|
+
if (this.blocks.length < 8) this.blocks.push(data);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
process(_inputs, outputs) {
|
|
16
|
+
const channels = outputs[0];
|
|
17
|
+
for (let i = 0; i < channels[0].length; i++) {
|
|
18
|
+
const block = this.blocks[0];
|
|
19
|
+
channels[0][i] = block ? block[this.offset] : 0;
|
|
20
|
+
channels[1][i] = block ? block[this.offset + 1] : 0;
|
|
21
|
+
if (block) {
|
|
22
|
+
this.offset += 2;
|
|
23
|
+
if (this.offset >= block.length) {
|
|
24
|
+
this.blocks.shift();
|
|
25
|
+
this.offset = 0;
|
|
26
|
+
this.port.postMessage(null);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
registerProcessor('nayan-mixer', NayanMixer);
|
|
34
|
+
//# sourceMappingURL=audio-worklet.js.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { AssetLoader } from "../assets/AssetLoader.js";
|
|
4
|
+
// Sound, played by the Rust core's mixer (no Expo or other native audio dependency).
|
|
5
|
+
|
|
6
|
+
import NativeEngine from '../native/NativeNayanEngine';
|
|
7
|
+
|
|
8
|
+
/** A decoded sound in the core's mixer. */
|
|
9
|
+
|
|
10
|
+
/** A playing instance of a sound (for `audio.stop`). */
|
|
11
|
+
|
|
12
|
+
/** A `require("./file.wav")` asset, or a URI. WAV, PCM or float, mono or stereo. */
|
|
13
|
+
|
|
14
|
+
const loader = new AssetLoader('audio');
|
|
15
|
+
function loadSound(source) {
|
|
16
|
+
return loader.load(source, (native, bytes) => {
|
|
17
|
+
const finish = id => {
|
|
18
|
+
if (id < 0) throw new Error(native.loadError() || 'not a supported WAV file');
|
|
19
|
+
return id;
|
|
20
|
+
};
|
|
21
|
+
// Decoded on a background native thread where the app's build supports it.
|
|
22
|
+
return typeof native.audioLoadAsync === 'function' ? native.audioLoadAsync(bytes).then(finish) : finish(native.audioLoad(bytes));
|
|
23
|
+
}).catch(error => {
|
|
24
|
+
console.warn(error instanceof Error ? error.message : String(error));
|
|
25
|
+
return null;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Named sounds. Plays are ignored until `ready` resolves (usually a few milliseconds). */
|
|
30
|
+
export class SoundBank {
|
|
31
|
+
/** Resolves when every sound has loaded (failures are logged and skipped). */
|
|
32
|
+
|
|
33
|
+
ids = {};
|
|
34
|
+
constructor(sources) {
|
|
35
|
+
this.ready = Promise.all(Object.keys(sources).map(async name => {
|
|
36
|
+
const id = await loadSound(sources[name]);
|
|
37
|
+
if (id !== null) this.ids[name] = id;
|
|
38
|
+
})).then(() => undefined);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The sound, once loaded (e.g. for an entity's `impact.sound`). */
|
|
42
|
+
get(name) {
|
|
43
|
+
return this.ids[name];
|
|
44
|
+
}
|
|
45
|
+
play(name, options) {
|
|
46
|
+
const id = this.ids[name];
|
|
47
|
+
return id === undefined ? null : audio.play(id, options);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
let muted = false;
|
|
51
|
+
let volume = 1;
|
|
52
|
+
export const audio = {
|
|
53
|
+
/** Initializes the engine and loads named sounds into its mixer. Each asset decodes once per session. */
|
|
54
|
+
load(sources) {
|
|
55
|
+
return new SoundBank(sources);
|
|
56
|
+
},
|
|
57
|
+
/** Plays a sound (low latency: mixed in Rust on the audio thread). Returns null if it couldn't play. */
|
|
58
|
+
play(sound, {
|
|
59
|
+
volume: v = 1,
|
|
60
|
+
pan = 0,
|
|
61
|
+
pitch = 1,
|
|
62
|
+
loop = false
|
|
63
|
+
} = {}) {
|
|
64
|
+
const voice = NativeEngine?.audioPlay(sound, v, pan, pitch, loop) ?? 0;
|
|
65
|
+
return voice > 0 ? voice : null;
|
|
66
|
+
},
|
|
67
|
+
stop(voice) {
|
|
68
|
+
if (voice) NativeEngine?.audioStop(voice);
|
|
69
|
+
},
|
|
70
|
+
get muted() {
|
|
71
|
+
return muted;
|
|
72
|
+
},
|
|
73
|
+
/** The inverse of `muted`, matching `haptics.enabled` (e.g. `audio.enabled = settings.enableSounds`). */
|
|
74
|
+
get enabled() {
|
|
75
|
+
return !muted;
|
|
76
|
+
},
|
|
77
|
+
set enabled(value) {
|
|
78
|
+
this.muted = !value;
|
|
79
|
+
},
|
|
80
|
+
set muted(value) {
|
|
81
|
+
muted = value;
|
|
82
|
+
NativeEngine?.audioSetMuted(value);
|
|
83
|
+
},
|
|
84
|
+
/** Master volume, 0..2. */
|
|
85
|
+
get volume() {
|
|
86
|
+
return volume;
|
|
87
|
+
},
|
|
88
|
+
set volume(value) {
|
|
89
|
+
volume = value;
|
|
90
|
+
NativeEngine?.audioSetVolume(value);
|
|
91
|
+
},
|
|
92
|
+
/** True once the device's audio output is running. */
|
|
93
|
+
get running() {
|
|
94
|
+
return NativeEngine?.audioIsRunning() ?? false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=audio.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Haptics, driven by the Rust core (Core Haptics on iOS).
|
|
4
|
+
import NativeEngine from '../native/NativeNayanEngine';
|
|
5
|
+
const PATTERNS = {
|
|
6
|
+
success: [{
|
|
7
|
+
time: 0,
|
|
8
|
+
intensity: 0.55,
|
|
9
|
+
sharpness: 0.5
|
|
10
|
+
}, {
|
|
11
|
+
time: 0.1,
|
|
12
|
+
intensity: 1,
|
|
13
|
+
sharpness: 0.7
|
|
14
|
+
}],
|
|
15
|
+
warning: [{
|
|
16
|
+
time: 0,
|
|
17
|
+
intensity: 0.8,
|
|
18
|
+
sharpness: 0.4
|
|
19
|
+
}, {
|
|
20
|
+
time: 0.16,
|
|
21
|
+
intensity: 0.8,
|
|
22
|
+
sharpness: 0.4
|
|
23
|
+
}],
|
|
24
|
+
error: [{
|
|
25
|
+
time: 0,
|
|
26
|
+
intensity: 1,
|
|
27
|
+
sharpness: 0.8
|
|
28
|
+
}, {
|
|
29
|
+
time: 0.09,
|
|
30
|
+
intensity: 0.9,
|
|
31
|
+
sharpness: 0.8
|
|
32
|
+
}, {
|
|
33
|
+
time: 0.18,
|
|
34
|
+
intensity: 0.7,
|
|
35
|
+
sharpness: 0.8
|
|
36
|
+
}]
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** One throttled tap (collisions call this often): a reused [time, intensity, sharpness] array. */
|
|
40
|
+
const single = [0, 0, 0];
|
|
41
|
+
function playTap(intensity, sharpness) {
|
|
42
|
+
if (!haptics.enabled || !NativeEngine) return;
|
|
43
|
+
single[1] = intensity;
|
|
44
|
+
single[2] = sharpness;
|
|
45
|
+
NativeEngine.hapticsPlay(single, true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Flattened patterns, built once per tap list (presets are constant; custom lists by identity). */
|
|
49
|
+
const flattened = new WeakMap();
|
|
50
|
+
function playTaps(taps, throttle) {
|
|
51
|
+
if (!haptics.enabled || !NativeEngine) return;
|
|
52
|
+
let flat = flattened.get(taps);
|
|
53
|
+
if (!flat) {
|
|
54
|
+
flat = taps.flatMap(t => [t.time, t.intensity, t.sharpness]);
|
|
55
|
+
flattened.set(taps, flat);
|
|
56
|
+
}
|
|
57
|
+
NativeEngine.hapticsPlay(flat, throttle);
|
|
58
|
+
}
|
|
59
|
+
export const haptics = {
|
|
60
|
+
/** Set false to silence all haptics (e.g. a settings toggle). */
|
|
61
|
+
enabled: true,
|
|
62
|
+
/** True if the device has a haptic actuator (false on simulators). */
|
|
63
|
+
get supported() {
|
|
64
|
+
return NativeEngine?.hapticsSupported() ?? false;
|
|
65
|
+
},
|
|
66
|
+
/** A single tap. Rapid repeats (< 35 ms apart) are dropped so collisions don't buzz. */
|
|
67
|
+
impact(intensity = 0.6, sharpness = 0.5) {
|
|
68
|
+
playTap(intensity, sharpness);
|
|
69
|
+
},
|
|
70
|
+
/** A light, crisp tick for UI selection. */
|
|
71
|
+
selection() {
|
|
72
|
+
playTap(0.35, 0.9);
|
|
73
|
+
},
|
|
74
|
+
notify(type) {
|
|
75
|
+
playTaps(PATTERNS[type], false);
|
|
76
|
+
},
|
|
77
|
+
/** Any sequence of taps. */
|
|
78
|
+
play(taps) {
|
|
79
|
+
playTaps(taps, false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=haptics.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/** The Rust mixer remains the source of all audio, including native collision feedback. */
|
|
4
|
+
export function installAudioOutput(core) {
|
|
5
|
+
if (typeof window === 'undefined') return () => false;
|
|
6
|
+
let context;
|
|
7
|
+
let pending;
|
|
8
|
+
const start = () => {
|
|
9
|
+
if (pending) {
|
|
10
|
+
void context?.resume().catch(() => {});
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
let release;
|
|
14
|
+
pending = (async () => {
|
|
15
|
+
context = new AudioContext({
|
|
16
|
+
sampleRate: 48000
|
|
17
|
+
});
|
|
18
|
+
await context.audioWorklet.addModule(new URL('./audio-worklet.js', import.meta.url));
|
|
19
|
+
const output = new AudioWorkletNode(context, 'nayan-mixer', {
|
|
20
|
+
numberOfInputs: 0,
|
|
21
|
+
numberOfOutputs: 1,
|
|
22
|
+
outputChannelCount: [2]
|
|
23
|
+
});
|
|
24
|
+
const allocate = core.engine_web_alloc;
|
|
25
|
+
const free = core.engine_web_free;
|
|
26
|
+
const render = core.engine_web_audio_render;
|
|
27
|
+
const floats = 2048;
|
|
28
|
+
const pointer = allocate(floats * 4);
|
|
29
|
+
const current = context;
|
|
30
|
+
const visibility = () => {
|
|
31
|
+
void (document.hidden ? current.suspend() : current.resume()).catch(() => {});
|
|
32
|
+
};
|
|
33
|
+
release = () => {
|
|
34
|
+
document.removeEventListener('visibilitychange', visibility);
|
|
35
|
+
output.port.onmessage = null;
|
|
36
|
+
output.disconnect();
|
|
37
|
+
free(pointer, floats * 4);
|
|
38
|
+
};
|
|
39
|
+
const send = () => {
|
|
40
|
+
render(pointer, floats);
|
|
41
|
+
const samples = new Float32Array(core.memory.buffer, pointer, floats).slice();
|
|
42
|
+
output.port.postMessage(samples, [samples.buffer]);
|
|
43
|
+
};
|
|
44
|
+
output.port.onmessage = send;
|
|
45
|
+
send();
|
|
46
|
+
send();
|
|
47
|
+
send();
|
|
48
|
+
output.connect(context.destination);
|
|
49
|
+
document.addEventListener('visibilitychange', visibility);
|
|
50
|
+
await context.resume();
|
|
51
|
+
})().catch(() => {
|
|
52
|
+
release?.();
|
|
53
|
+
void context?.close().catch(() => {});
|
|
54
|
+
context = undefined;
|
|
55
|
+
pending = undefined; // retry on the next gesture if audio is unavailable or blocked
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
window.addEventListener('pointerdown', start, {
|
|
59
|
+
capture: true
|
|
60
|
+
});
|
|
61
|
+
window.addEventListener('keydown', start, {
|
|
62
|
+
capture: true
|
|
63
|
+
});
|
|
64
|
+
return () => context?.state === 'running';
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=output.web.js.map
|