@nayan-ui/engine 1.0.0-beta.3 → 1.0.0-beta.5
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/README.md +229 -113
- package/lib/module/assets/AssetLoader.js +1 -1
- package/lib/module/assets/ProceduralCache.js +31 -4
- package/lib/module/assets/font.js +10 -7
- package/lib/module/assets/model.js +41 -19
- package/lib/module/assets/storage.shared.js +2 -0
- package/lib/module/assets/storage.web.js +1 -0
- package/lib/module/assets/texture.js +14 -11
- package/lib/module/index.js +4 -1
- package/lib/module/media/audio.js +6 -3
- package/lib/module/native/BrowserEngine.js +17 -0
- package/lib/module/render/GameView.js +86 -18
- package/lib/module/render/light.js +24 -0
- package/lib/module/render/quality.js +12 -5
- package/lib/module/render/useWorld.js +72 -0
- package/lib/module/world/Scoreboard.js +21 -3
- package/lib/module/world/TransformBatch.js +5 -3
- package/lib/module/world/World.js +27 -13
- package/lib/module/world/color.js +80 -0
- package/lib/module/world/desc.js +6 -5
- package/lib/module/world/effects.js +20 -8
- package/lib/typescript/src/assets/AssetLoader.d.ts +1 -1
- package/lib/typescript/src/assets/ProceduralCache.d.ts +12 -0
- package/lib/typescript/src/assets/model.d.ts +10 -0
- package/lib/typescript/src/index.d.ts +4 -1
- package/lib/typescript/src/native/EngineBridge.d.ts +6 -2
- package/lib/typescript/src/native/spec/NativeNayanEngine.d.ts +29 -0
- package/lib/typescript/src/render/GameView.d.ts +19 -7
- package/lib/typescript/src/render/light.d.ts +12 -0
- package/lib/typescript/src/render/quality.d.ts +4 -2
- package/lib/typescript/src/render/useWorld.d.ts +20 -0
- package/lib/typescript/src/types.d.ts +13 -4
- package/lib/typescript/src/world/Scoreboard.d.ts +12 -2
- package/lib/typescript/src/world/TransformBatch.d.ts +2 -2
- package/lib/typescript/src/world/World.d.ts +10 -2
- package/lib/typescript/src/world/color.d.ts +18 -0
- package/lib/typescript/src/world/effects.d.ts +13 -12
- package/lib/typescript/src/world/types.d.ts +11 -6
- package/lib/web/engine_core_bg.wasm +0 -0
- package/lib/web/index.js +521 -255
- package/lib/web/types/src/assets/AssetLoader.d.ts +1 -1
- package/lib/web/types/src/assets/ProceduralCache.d.ts +12 -0
- package/lib/web/types/src/assets/model.d.ts +10 -0
- package/lib/web/types/src/index.d.ts +4 -1
- package/lib/web/types/src/native/EngineBridge.d.ts +6 -2
- package/lib/web/types/src/native/spec/NativeNayanEngine.d.ts +29 -0
- package/lib/web/types/src/render/GameView.d.ts +19 -7
- package/lib/web/types/src/render/light.d.ts +11 -0
- package/lib/web/types/src/render/quality.d.ts +4 -2
- package/lib/web/types/src/render/useWorld.d.ts +19 -0
- package/lib/web/types/src/types.d.ts +13 -4
- package/lib/web/types/src/world/Scoreboard.d.ts +12 -2
- package/lib/web/types/src/world/TransformBatch.d.ts +2 -2
- package/lib/web/types/src/world/World.d.ts +10 -2
- package/lib/web/types/src/world/color.d.ts +17 -0
- package/lib/web/types/src/world/effects.d.ts +13 -12
- package/lib/web/types/src/world/types.d.ts +11 -6
- package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceView.java +87 -26
- 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/engine_core.h +9 -0
- package/native/cpp/NayanEngineModule.cpp +171 -0
- package/native/cpp/NayanEngineModule.h +30 -0
- package/native/cpp/generated/NayanEngineSpecJSI.h +105 -0
- package/native/ios/NayanEngineSurface.mm +30 -1
- package/package.json +2 -2
- package/src/native/spec/NativeNayanEngine.ts +39 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Colors: every engine option that takes a color accepts either a linear 0..1 tuple or a CSS color
|
|
4
|
+
// string, so theme colors go straight in: `world.spawn({ color: '#FF4757' })`.
|
|
5
|
+
|
|
6
|
+
/** A color as an option takes it: `[r, g, b]` / `[r, g, b, a]` (0..1, channels above 1 glow) or a CSS string. */
|
|
7
|
+
|
|
8
|
+
const HEX = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
9
|
+
const RGB = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+%?)\s*)?\)$/i;
|
|
10
|
+
const NAMED = {
|
|
11
|
+
transparent: [0, 0, 0, 0],
|
|
12
|
+
black: [0, 0, 0, 1],
|
|
13
|
+
white: [1, 1, 1, 1],
|
|
14
|
+
red: [1, 0, 0, 1],
|
|
15
|
+
green: [0, 128 / 255, 0, 1],
|
|
16
|
+
blue: [0, 0, 1, 1],
|
|
17
|
+
yellow: [1, 1, 0, 1],
|
|
18
|
+
orange: [1, 165 / 255, 0, 1],
|
|
19
|
+
purple: [128 / 255, 0, 128 / 255, 1],
|
|
20
|
+
gray: [128 / 255, 128 / 255, 128 / 255, 1],
|
|
21
|
+
grey: [128 / 255, 128 / 255, 128 / 255, 1]
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Theme colors repeat endlessly; parse each string once.
|
|
25
|
+
const parsed = new Map();
|
|
26
|
+
const PARSED_LIMIT = 1024;
|
|
27
|
+
function parse(css) {
|
|
28
|
+
const text = css.trim();
|
|
29
|
+
const named = NAMED[text.toLowerCase()];
|
|
30
|
+
if (named) return named;
|
|
31
|
+
const hex = HEX.exec(text);
|
|
32
|
+
if (hex) {
|
|
33
|
+
let digits = hex[1];
|
|
34
|
+
if (digits.length <= 4) digits = [...digits].map(d => d + d).join('');
|
|
35
|
+
const n = i => parseInt(digits.slice(i, i + 2), 16) / 255;
|
|
36
|
+
return [n(0), n(2), n(4), digits.length === 8 ? n(6) : 1];
|
|
37
|
+
}
|
|
38
|
+
const rgb = RGB.exec(text);
|
|
39
|
+
if (rgb) {
|
|
40
|
+
const alpha = rgb[4] === undefined ? 1 : rgb[4].endsWith('%') ? parseFloat(rgb[4]) / 100 : Number(rgb[4]);
|
|
41
|
+
return [Number(rgb[1]) / 255, Number(rgb[2]) / 255, Number(rgb[3]) / 255, alpha];
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`@nayan-ui/engine: "${css}" is not a color (use '#RRGGBB', '#RGB', '#RRGGBBAA', 'rgb(…)', 'rgba(…)' or a 0..1 tuple)`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Any color input as an `[r, g, b, a]` tuple (0..1), with its alpha multiplied by `alpha`. Use it when
|
|
48
|
+
* you need the numbers (to brighten, mix or fade a theme color); options take strings directly.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* toColor('#FF4757'); // [1, 0.278, 0.341, 1]
|
|
52
|
+
* toColor('#FF4757', 0.5); // half see-through
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function toColor(color, alpha = 1) {
|
|
56
|
+
let c;
|
|
57
|
+
if (typeof color === 'string') {
|
|
58
|
+
let hit = parsed.get(color);
|
|
59
|
+
if (!hit) {
|
|
60
|
+
hit = parse(color);
|
|
61
|
+
if (parsed.size >= PARSED_LIMIT) parsed.clear();
|
|
62
|
+
parsed.set(color, hit);
|
|
63
|
+
}
|
|
64
|
+
c = hit;
|
|
65
|
+
} else c = color;
|
|
66
|
+
return [c[0], c[1], c[2], (c[3] ?? 1) * alpha];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A color input as a tuple, without copying tuples (the hot path of `spawn`, `set` and `animate`). */
|
|
70
|
+
export function resolveColor(color) {
|
|
71
|
+
return typeof color === 'string' ? toColor(color) : color;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** One color input, or a list to pick from, as tuples. */
|
|
75
|
+
export function resolveColors(colors) {
|
|
76
|
+
if (typeof colors === 'string') return [toColor(colors)];
|
|
77
|
+
if (typeof colors[0] === 'number') return [colors];
|
|
78
|
+
return colors.map(resolveColor);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=color.js.map
|
package/lib/module/world/desc.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Encodes entity options into the flat layout the Rust core decodes (native/core/include/engine_core.h,
|
|
4
4
|
// native/core/src/world/desc.rs). One reusable Float64Array per world: spawning or changing an entity is
|
|
5
5
|
// a single native call, and doubles keep entity ids and layer bits exact.
|
|
6
|
-
|
|
6
|
+
import { resolveColor } from "./color.js";
|
|
7
7
|
export const DESC_LEN = 64;
|
|
8
8
|
const FLAG = {
|
|
9
9
|
MESH: 1 << 0,
|
|
@@ -102,10 +102,11 @@ export function encode(d, o, mesh) {
|
|
|
102
102
|
}
|
|
103
103
|
if (o.color) {
|
|
104
104
|
flags |= FLAG.COLOR;
|
|
105
|
-
|
|
106
|
-
d[SLOT.COLOR
|
|
107
|
-
d[SLOT.COLOR +
|
|
108
|
-
d[SLOT.COLOR +
|
|
105
|
+
const c = resolveColor(o.color);
|
|
106
|
+
d[SLOT.COLOR] = c[0];
|
|
107
|
+
d[SLOT.COLOR + 1] = c[1];
|
|
108
|
+
d[SLOT.COLOR + 2] = c[2];
|
|
109
|
+
d[SLOT.COLOR + 3] = c[3] ?? 1;
|
|
109
110
|
}
|
|
110
111
|
if (o.velocity) {
|
|
111
112
|
flags |= FLAG.VELOCITY;
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
// Every particle takes a world slot, out of the capacity shared with the game's own entities, so the
|
|
10
10
|
// counts stay close to what was passed in. Effects run entirely in the Rust core after one call each.
|
|
11
11
|
|
|
12
|
+
import { resolveColor, resolveColors } from "./color.js";
|
|
13
|
+
|
|
12
14
|
/** Shared options of the layered effects (confetti, sparkle, shatter, splash, sparks, explosion). */
|
|
13
15
|
|
|
14
16
|
/** A soft cloud of puffs that swell and fade: landing dust, kicked-up sand, sawdust, steam, muzzle smoke. */
|
|
@@ -17,10 +19,16 @@
|
|
|
17
19
|
|
|
18
20
|
/** The cleared-piece pop: swell, flash white, settle, shrink away. */
|
|
19
21
|
|
|
20
|
-
const rgba =
|
|
21
|
-
const
|
|
22
|
+
const rgba = input => {
|
|
23
|
+
const c = resolveColor(input);
|
|
24
|
+
return [c[0], c[1], c[2], c[3] ?? 1];
|
|
25
|
+
};
|
|
26
|
+
const list = colors => resolveColors(colors).map(rgba);
|
|
22
27
|
const part = (count, share) => Math.max(1, Math.round(count * share));
|
|
23
|
-
const fade =
|
|
28
|
+
const fade = input => {
|
|
29
|
+
const c = resolveColor(input);
|
|
30
|
+
return [c[0], c[1], c[2], 0];
|
|
31
|
+
};
|
|
24
32
|
|
|
25
33
|
/** The colors' average. */
|
|
26
34
|
function mean(colors) {
|
|
@@ -30,13 +38,17 @@ function mean(colors) {
|
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
/** The same hue, self-lit: its brightest channel lifted to `peak` (above 1 glows, through light, shadow and fog). */
|
|
33
|
-
function hot(
|
|
41
|
+
function hot(input, peak = 2.2, alpha) {
|
|
42
|
+
const c = resolveColor(input);
|
|
43
|
+
alpha ??= c[3] ?? 1;
|
|
34
44
|
const k = peak / Math.max(c[0], c[1], c[2], 0.25);
|
|
35
45
|
return [c[0] * k, c[1] * k, c[2] * k, alpha];
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
/** Part way (`t`, 0..1) to white, with `alpha`. */
|
|
39
|
-
function pale(
|
|
49
|
+
function pale(input, t, alpha) {
|
|
50
|
+
const c = resolveColor(input);
|
|
51
|
+
alpha ??= c[3] ?? 1;
|
|
40
52
|
return [c[0] + (1 - c[0]) * t, c[1] + (1 - c[1]) * t, c[2] + (1 - c[2]) * t, alpha];
|
|
41
53
|
}
|
|
42
54
|
function aim(e) {
|
|
@@ -70,7 +82,7 @@ function glowFlash(world, e, colors, size) {
|
|
|
70
82
|
});
|
|
71
83
|
}
|
|
72
84
|
function dust(world, o) {
|
|
73
|
-
const
|
|
85
|
+
const colors = resolveColors(o.color);
|
|
74
86
|
return world.burst({
|
|
75
87
|
position: o.position,
|
|
76
88
|
count: o.count,
|
|
@@ -81,9 +93,9 @@ function dust(world, o) {
|
|
|
81
93
|
gravity: o.gravity ?? 0,
|
|
82
94
|
direction: o.direction,
|
|
83
95
|
spread: o.spread,
|
|
84
|
-
color:
|
|
96
|
+
color: colors.length === 1 ? colors[0] : colors,
|
|
85
97
|
endSize: o.endSize ?? 2,
|
|
86
|
-
endColor: o.endColor ?? (
|
|
98
|
+
endColor: o.endColor ?? (colors.length === 1 ? fade(colors[0]) : undefined)
|
|
87
99
|
});
|
|
88
100
|
}
|
|
89
101
|
|
|
@@ -5,7 +5,7 @@ export declare class AssetLoader<T> {
|
|
|
5
5
|
private readonly name;
|
|
6
6
|
private readonly loads;
|
|
7
7
|
constructor(name: string);
|
|
8
|
-
load(source: AssetSource, decode: (native: EngineBridge, bytes: ArrayBuffer) => T
|
|
8
|
+
load(source: AssetSource, decode: (native: EngineBridge, bytes: ArrayBuffer) => T | Promise<T>, variant?: readonly (string | number | boolean)[]): Promise<T>;
|
|
9
9
|
private error;
|
|
10
10
|
}
|
|
11
11
|
//# sourceMappingURL=AssetLoader.d.ts.map
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
export interface ProceduralStorage {
|
|
3
3
|
read(key: string): Uint8Array | null;
|
|
4
4
|
write(key: string, bytes: Uint8Array): void | Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* The store itself verifies every record's integrity (the engine's own Rust store checksums each
|
|
7
|
+
* one with SHA-256), so the cache needn't checksum and scan them again in JS. Geometry is still
|
|
8
|
+
* validated by the core when it's registered.
|
|
9
|
+
*/
|
|
10
|
+
readonly verified?: boolean;
|
|
5
11
|
}
|
|
6
12
|
/**
|
|
7
13
|
* Persists the output of a deterministic model factory, including nested model arrays/objects.
|
|
@@ -26,6 +32,12 @@ export declare class ProceduralCache {
|
|
|
26
32
|
pendingWrites: number;
|
|
27
33
|
};
|
|
28
34
|
flush(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* A value already cached (in memory, or saved by an earlier launch), without building anything:
|
|
37
|
+
* undefined when there is none yet. Lets work that's spread over many frames (a level generated
|
|
38
|
+
* step by step) skip itself entirely when a saved result exists, then save with `get`.
|
|
39
|
+
*/
|
|
40
|
+
peek<T>(key: string): T | undefined;
|
|
29
41
|
get<T>(key: string, build: () => T): T;
|
|
30
42
|
private encode;
|
|
31
43
|
private restore;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MeshGeometry } from '../render/meshes.js';
|
|
1
2
|
import type { Vec3 } from '../types.js';
|
|
2
3
|
import type { AssetSource } from './source';
|
|
3
4
|
export type ModelOptions = {
|
|
@@ -32,6 +33,15 @@ export declare function nativeGeometry(mesh: number): {
|
|
|
32
33
|
vertices: Float32Array<ArrayBuffer>;
|
|
33
34
|
indices: Uint32Array<ArrayBuffer>;
|
|
34
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Registers a mesh the core already holds (a created, cached or loaded model, a glyph, an alias):
|
|
38
|
+
* the renderer copies it inside Rust, and JS reads its geometry only if something asks for it
|
|
39
|
+
* (merging, caching). Apps built before `renderModel` upload it through JS as before.
|
|
40
|
+
*/
|
|
41
|
+
export declare function registerCoreMesh(mesh: number, info?: {
|
|
42
|
+
texture?: number;
|
|
43
|
+
shine?: number;
|
|
44
|
+
}, geometry?: () => MeshGeometry): void;
|
|
35
45
|
/** Independent materials share core geometry without overwriting an existing model's appearance. */
|
|
36
46
|
export declare function registerModelMesh(mesh: number, shine: number): number;
|
|
37
47
|
/**
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export { World, isEngineAvailable } from './world/World.js';
|
|
2
2
|
export { quat, type MutableQuat } from './world/quat.js';
|
|
3
|
+
export { toColor, type ColorInput } from './world/color.js';
|
|
3
4
|
export { effects, type DustOptions, type EffectOptions, type FlashOptions, type PopOptions } from './world/effects.js';
|
|
4
5
|
export { Scoreboard, type ScoreboardHandlers } from './world/Scoreboard.js';
|
|
5
6
|
export type { AnimateOptions, AnimateTarget, BodyType, Bounds, BurstOptions, CollisionInfo, ClipOptions, ScreenPlane, Color, Easing, Entity, EntityOptions, ImpactFeedback, Keyframe, PhysicsOptions, PickHit, Quat, RaycastHit, SpringOptions } from './world/types.js';
|
|
6
|
-
export { GameView } from './render/GameView.js';
|
|
7
|
+
export { afterNextFrame, GameView } from './render/GameView.js';
|
|
8
|
+
export { useWorld, type WorldSetup } from './render/useWorld.js';
|
|
9
|
+
export { frontLight } from './render/light.js';
|
|
7
10
|
export { warmUpEngine, getMeshIdLimit } from './render/meshes.js';
|
|
8
11
|
export type { TransformBatch, TransformOptions } from './world/TransformBatch.js';
|
|
9
12
|
export type { DragEvent, GameViewProps, GameViewStats, OverlayTouchPhase, PinchEvent, SwipeDirection } from './render/GameView.js';
|
|
@@ -4,11 +4,13 @@ export interface NativeGeometry {
|
|
|
4
4
|
vertices: ArrayBuffer;
|
|
5
5
|
indices: ArrayBuffer;
|
|
6
6
|
}
|
|
7
|
-
type BufferValue<T, Buffer> = T extends readonly number[] ? T : T extends object ? Buffer : T;
|
|
7
|
+
type BufferValue<T, Buffer> = T extends readonly number[] ? T : T extends Promise<unknown> ? T : T extends (...args: never[]) => unknown ? T : T extends object ? Buffer : T;
|
|
8
8
|
type BufferMethod<T> = T extends (...args: infer A) => infer R ? (...args: {
|
|
9
9
|
[K in keyof A]: BufferValue<A[K], ArrayBufferLike>;
|
|
10
10
|
}) => BufferValue<R, ArrayBuffer> : T;
|
|
11
|
-
|
|
11
|
+
/** Native-only (a display clock and a render thread): absent on the web core and on older app builds. */
|
|
12
|
+
type NativeFrames = 'publishFrame' | 'renderStats' | 'hasFrameClock' | 'startFrames' | 'stopFrames';
|
|
13
|
+
type BinarySpec = Omit<Spec, 'geometrySolid' | 'geometryOptimize' | 'modelSize' | 'textureSize' | NativeFrames>;
|
|
12
14
|
/** The same runtime ABI on JSI and WASM, with precise TypeScript buffer and tuple contracts. */
|
|
13
15
|
export type EngineBridge = {
|
|
14
16
|
[K in keyof BinarySpec]: BufferMethod<BinarySpec[K]>;
|
|
@@ -17,6 +19,8 @@ export type EngineBridge = {
|
|
|
17
19
|
geometryOptimize(vertices: ArrayBufferLike, indices: ArrayBufferLike, ratio: number, error: number, lockBorders: boolean): NativeGeometry;
|
|
18
20
|
modelSize(mesh: number): [x: number, y: number, z: number];
|
|
19
21
|
textureSize(texture: number): [width: number, height: number];
|
|
22
|
+
} & {
|
|
23
|
+
[K in NativeFrames]?: BufferMethod<Spec[K]>;
|
|
20
24
|
};
|
|
21
25
|
export {};
|
|
22
26
|
//# sourceMappingURL=EngineBridge.d.ts.map
|
|
@@ -90,6 +90,8 @@ export interface Spec extends TurboModule {
|
|
|
90
90
|
shapePrepare(vertices: Object, indices: Object, options: string): number;
|
|
91
91
|
solidPrepare(recipe: string, options: string): number;
|
|
92
92
|
modelLoad(data: Object, center: boolean, fit: number): number;
|
|
93
|
+
/** `modelLoad` on a background native thread (the data is copied first): resolves to the mesh id, or -1. */
|
|
94
|
+
modelLoadAsync(data: Object, center: boolean, fit: number): Promise<number>;
|
|
93
95
|
/** 11 floats per vertex (position, normal, color, uv); aliases native memory. */
|
|
94
96
|
modelVertices(mesh: number): Object;
|
|
95
97
|
/** u32 triangle indices; aliases native memory. */
|
|
@@ -102,6 +104,11 @@ export interface Spec extends TurboModule {
|
|
|
102
104
|
meshAlias(base: number): number;
|
|
103
105
|
/** Registers geometry (copied): 11 floats per vertex, u32 triangle indices. Returns the mesh id, or -1. */
|
|
104
106
|
meshCreate(vertices: Object, indices: Object): number;
|
|
107
|
+
/**
|
|
108
|
+
* Hands a core model's own geometry (from `meshCreate`, `modelLoad`, a font) to the renderer, in
|
|
109
|
+
* Rust: nothing is copied through JS. `texture` -1 for none. False if the mesh is unknown.
|
|
110
|
+
*/
|
|
111
|
+
renderModel(mesh: number, texture: number, shine: number): boolean;
|
|
105
112
|
/**
|
|
106
113
|
* Fills 2D contours ([x, y] f32 pairs, `counts[k]` points per contour) by the non-zero or even-odd
|
|
107
114
|
* rule and extrudes them `depth` deep. Returns an ArrayBuffer of 11 floats per vertex (unindexed).
|
|
@@ -122,12 +129,16 @@ export interface Spec extends TurboModule {
|
|
|
122
129
|
geometryPlace(vertices: Object, indices: Object, placement: Array<number>, first: number, outVertices: Object, vertexOffset: number, outIndices: Object, indexOffset: number): void;
|
|
123
130
|
/** Decodes a PNG/JPEG ArrayBuffer. Returns the texture id, or -1. */
|
|
124
131
|
textureLoad(data: Object): number;
|
|
132
|
+
/** `textureLoad` on a background native thread: resolves to the texture id, or -1. */
|
|
133
|
+
textureLoadAsync(data: Object): Promise<number>;
|
|
125
134
|
/** RGBA8 pixels, top row first; aliases native memory. */
|
|
126
135
|
texturePixels(texture: number): Object;
|
|
127
136
|
/** [width, height]. */
|
|
128
137
|
textureSize(texture: number): Array<number>;
|
|
129
138
|
/** Builds extruded glyph meshes for `chars`. Returns the font id, or -1. */
|
|
130
139
|
fontLoad(data: Object, depth: number, chars: string, weight: number): number;
|
|
140
|
+
/** `fontLoad` (outlines and glyph meshes) on a background native thread: resolves to the font id, or -1. */
|
|
141
|
+
fontLoadAsync(data: Object, depth: number, chars: string, weight: number): Promise<number>;
|
|
131
142
|
/** [codepoint, mesh (-1 = nothing drawn), advance, ...] followed by the cap height. */
|
|
132
143
|
fontGlyphs(font: number): Array<number>;
|
|
133
144
|
/** Why the last model / texture / font / alias load failed ("" after a success). */
|
|
@@ -151,6 +162,22 @@ export interface Spec extends TurboModule {
|
|
|
151
162
|
* Returns 1 (drawn), 0 (surface not ready yet / frame skipped) or -1 (see `renderError`).
|
|
152
163
|
*/
|
|
153
164
|
renderFrame(surface: number, frame: Object, matrices: Object, colors: Object, regions: Object, ranges: Object, count: number, viewProj: Object, world: number): number;
|
|
165
|
+
/**
|
|
166
|
+
* `renderFrame`'s arguments, but the frame is copied and drawn on the native render thread: returns
|
|
167
|
+
* at once, with `viewProj` written. 1 once the surface has presented a frame, 0 before, -1 if the
|
|
168
|
+
* last frame failed (see `renderError`).
|
|
169
|
+
*/
|
|
170
|
+
publishFrame(surface: number, frame: Object, matrices: Object, colors: Object, regions: Object, ranges: Object, count: number, viewProj: Object, world: number): number;
|
|
171
|
+
/** [frames presented, smoothed render milliseconds] for a surface drawn by the render thread. */
|
|
172
|
+
renderStats(surface: number): Array<number>;
|
|
173
|
+
/** True where the platform drives frames from its display clock (`startFrames`); else use requestAnimationFrame. */
|
|
174
|
+
hasFrameClock(): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* Calls `onFrame` on the JS thread once per display refresh while the surface is attached and
|
|
177
|
+
* visible, with the frame time in milliseconds. A frame JS hasn't started yet isn't queued again.
|
|
178
|
+
*/
|
|
179
|
+
startFrames(surface: number, onFrame: (timeMs: number) => void): void;
|
|
180
|
+
stopFrames(surface: number): void;
|
|
154
181
|
/**
|
|
155
182
|
* The screen-space overlay drawn over surface `surface` from its next frame on, copied from a world's
|
|
156
183
|
* buffers. In points: x right and y up from the view's top-left (on screen y <= 0), z toward the
|
|
@@ -167,6 +194,8 @@ export interface Spec extends TurboModule {
|
|
|
167
194
|
drawingDestroy(document: number): void;
|
|
168
195
|
/** Decodes a WAV ArrayBuffer. Returns a sound id, or -1. */
|
|
169
196
|
audioLoad(data: Object): number;
|
|
197
|
+
/** `audioLoad` on a background native thread: resolves to the sound id, or -1. */
|
|
198
|
+
audioLoadAsync(data: Object): Promise<number>;
|
|
170
199
|
/** Returns a voice id (> 0), or 0 if nothing played. */
|
|
171
200
|
audioPlay(sound: number, volume: number, pan: number, pitch: number, loop: boolean): number;
|
|
172
201
|
audioStop(voice: number): void;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { type Camera, type Light, type RenderSource } from '../types.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type ColorInput } from '../world/color.js';
|
|
3
3
|
import { type ViewStyle } from './host';
|
|
4
4
|
import { type RenderQuality, type RenderQualitySetting } from './quality.js';
|
|
5
|
+
/**
|
|
6
|
+
* Runs `fn` right after the next frame a GameView hands to the screen, on the same display clock
|
|
7
|
+
* (the native one where the platform has it): the time a frame leaves is the best moment for
|
|
8
|
+
* background work, such as loading in slices. With no GameView running it falls back to
|
|
9
|
+
* requestAnimationFrame. Returns a function that cancels it.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const cancel = afterNextFrame(() => buildNextChunk());
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function afterNextFrame(fn: () => void): () => void;
|
|
5
16
|
export type GameViewStats = {
|
|
6
17
|
fps: number;
|
|
7
18
|
/** Mean time spent in `onUpdate` per frame, in milliseconds. */
|
|
@@ -41,15 +52,16 @@ export type GameViewProps = {
|
|
|
41
52
|
source: RenderSource;
|
|
42
53
|
/**
|
|
43
54
|
* Your game loop, called once per frame before drawing, with the elapsed seconds (capped at 0.1;
|
|
44
|
-
* 0 until the first frame is on screen, so nothing plays out unseen)
|
|
45
|
-
*
|
|
55
|
+
* 0 until the first frame is on screen, so nothing plays out unseen): read input, spawn, move the
|
|
56
|
+
* camera. The world (and `overlay`) is stepped for you right after; call `world.update(dt)` here
|
|
57
|
+
* yourself only to step it at a different point or with a different `dt`.
|
|
46
58
|
*/
|
|
47
59
|
onUpdate?: (dt: number) => void;
|
|
48
60
|
/** Mutate this object (e.g. `camera.eye`) from `onUpdate`; it is read every frame. */
|
|
49
61
|
camera?: Camera;
|
|
50
62
|
light?: Light;
|
|
51
|
-
/** Sky / clear color
|
|
52
|
-
background?:
|
|
63
|
+
/** Sky / clear color: a CSS color or linear 0..1 RGB (alpha ignored). Also the fog color. Default dark navy. */
|
|
64
|
+
background?: ColorInput;
|
|
53
65
|
/** Distance fog density towards `background` (e.g. 0.02). Default 0 (off). */
|
|
54
66
|
fog?: number;
|
|
55
67
|
/**
|
|
@@ -76,8 +88,8 @@ export type GameViewProps = {
|
|
|
76
88
|
/**
|
|
77
89
|
* A screen-space layer drawn over the scene by the renderer (scoreboards, buttons): usually a second
|
|
78
90
|
* `World`, in points from the view's top-left with y up (so on screen y <= 0) and z toward the viewer
|
|
79
|
-
* within -100..100. Unlit (flat faces show their exact color) and always in front.
|
|
80
|
-
*
|
|
91
|
+
* within -100..100. Unlit (flat faces show their exact color) and always in front. Stepped each
|
|
92
|
+
* frame like `source`.
|
|
81
93
|
*/
|
|
82
94
|
overlay?: RenderSource;
|
|
83
95
|
/**
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Light } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A light for flat, front-facing scenes (boards, puzzles, 2D games looking down -z): surfaces facing
|
|
4
|
+
* the camera (+z) are lit at exactly 1, so flat panels show their color unchanged, while rounded
|
|
5
|
+
* shapes keep their shading (lighter toward the light, darker away). Shadows are off.
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <GameView source={world} light={frontLight([-0.45, 0.6, 1], 0.5)} />
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare function frontLight(direction: [number, number, number], ambient: number): Light;
|
|
12
|
+
//# sourceMappingURL=light.d.ts.map
|
|
@@ -23,8 +23,9 @@ export type FrameReport = {
|
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
25
|
* Picks the level for "auto": starts at the best, steps down one level after `SLOW_SECONDS`
|
|
26
|
-
* consecutive seconds whose typical frame misses ~50 fps while drawing took most of it
|
|
27
|
-
*
|
|
26
|
+
* consecutive seconds whose typical frame misses ~50 fps while drawing took most of it (after a
|
|
27
|
+
* single second when it misses ~33 fps, so a struggling phone recovers in seconds rather than
|
|
28
|
+
* stuttering through every level), and steps back up after `FAST_SECONDS` smooth seconds. A level that fails again soon after being restored
|
|
28
29
|
* becomes the ceiling, so it never bounces.
|
|
29
30
|
*/
|
|
30
31
|
export declare class AutoQuality {
|
|
@@ -35,6 +36,7 @@ export declare class AutoQuality {
|
|
|
35
36
|
private changed;
|
|
36
37
|
private raisedAt;
|
|
37
38
|
private slow;
|
|
39
|
+
private verySlow;
|
|
38
40
|
private fast;
|
|
39
41
|
get quality(): RenderQuality;
|
|
40
42
|
/** Feeds one second of frames; returns true when the level changed. */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DependencyList } from 'react';
|
|
2
|
+
import { World } from '../world/World.js';
|
|
3
|
+
/** What `useWorld`'s setup may return: a cleanup, run just before the world is disposed. */
|
|
4
|
+
export type WorldSetup = (world: World) => void | (() => void);
|
|
5
|
+
/**
|
|
6
|
+
* A `World` owned by a component: the engine is warmed up (needed on the web, cheap on mobile), the
|
|
7
|
+
* world is created with `capacity` entity slots and handed to `setup` to fill, and it is disposed on
|
|
8
|
+
* unmount. A change in `deps` replaces it with a fresh one. Returns null until it's ready (one frame),
|
|
9
|
+
* and while the engine is unavailable (Expo Go) or failed to start (`onError`, console by default).
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const world = useWorld(100, (w) => {
|
|
13
|
+
* w.spawn({ mesh: 'plane', scale: [20, 1, 20], color: '#4C9A4C', physics: 'fixed' });
|
|
14
|
+
* w.spawn({ mesh: 'sphere', position: [0, 4, 0], color: '#3399FF', physics: 'dynamic' });
|
|
15
|
+
* });
|
|
16
|
+
* return world && <GameView source={world} style={{ flex: 1 }} />;
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function useWorld(capacity: number, setup?: WorldSetup, deps?: DependencyList, onError?: (error: Error) => void): World | null;
|
|
20
|
+
//# sourceMappingURL=useWorld.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ColorInput } from './world/color.js';
|
|
2
|
+
import type { Entity } from './world/types.js';
|
|
2
3
|
export type Vec3 = readonly [number, number, number];
|
|
3
4
|
/** Number of mesh ids: built-in shapes (0..15), then loaded models, glyphs and textured variants. */
|
|
4
5
|
export declare const MAX_MESHES = 4096;
|
|
@@ -23,6 +24,14 @@ export interface RenderSource {
|
|
|
23
24
|
readonly capacity: number;
|
|
24
25
|
/** Called by GameView after each frame with the camera it drew with (for picking and `toScreen`). */
|
|
25
26
|
setView?(viewProj: Float32Array, width: number, height: number): void;
|
|
27
|
+
/**
|
|
28
|
+
* Steps the source. When present, GameView calls it once a frame after `onUpdate`, unless
|
|
29
|
+
* `onUpdate` already did (`updateCount` changed) or the source is disposed.
|
|
30
|
+
*/
|
|
31
|
+
update?(dt: number): void;
|
|
32
|
+
/** How many times `update` has run. */
|
|
33
|
+
readonly updateCount?: number;
|
|
34
|
+
readonly isDisposed?: boolean;
|
|
26
35
|
}
|
|
27
36
|
/**
|
|
28
37
|
* Built-in shapes, each 1 unit across before scaling. "none" isn't drawn (groups, pivots, trigger zones).
|
|
@@ -64,9 +73,9 @@ export type Light = {
|
|
|
64
73
|
shadowExtent?: number;
|
|
65
74
|
/**
|
|
66
75
|
* Color and brightness of the light, multiplying both sun and ambient: e.g. [0.35, 0.38, 0.6] for
|
|
67
|
-
* moonlight. Glowing colors (channels above 1) aren't affected. Alpha, if given, is
|
|
68
|
-
* Default [1, 1, 1].
|
|
76
|
+
* moonlight, or a CSS color. Glowing colors (channels above 1) aren't affected. Alpha, if given, is
|
|
77
|
+
* ignored. Default [1, 1, 1].
|
|
69
78
|
*/
|
|
70
|
-
color?:
|
|
79
|
+
color?: ColorInput;
|
|
71
80
|
};
|
|
72
81
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -12,10 +12,12 @@ export type ScoreboardHandlers = {
|
|
|
12
12
|
/**
|
|
13
13
|
* Score, lives and the game clock, kept and advanced in Rust by `world.update`: countdowns, elapsed
|
|
14
14
|
* time and time-based points (survival, distance) cost no JS per frame. Handlers run from `update`
|
|
15
|
-
* only when something they show changed.
|
|
15
|
+
* only when something they show changed. Set `handlers` for the game's own rules, and `listen` for
|
|
16
|
+
* anything else that follows along (a UI showing the score), so neither replaces the other.
|
|
16
17
|
*
|
|
17
18
|
* ```ts
|
|
18
|
-
* world.scoreboard.handlers = {
|
|
19
|
+
* world.scoreboard.handlers = { onTimeUp: () => end(true) };
|
|
20
|
+
* const stop = world.scoreboard.listen({ onScore: setScore, onSecond: setTime });
|
|
19
21
|
* world.scoreboard.reset(3);
|
|
20
22
|
* world.scoreboard.startCountdown(60);
|
|
21
23
|
* world.scoreboard.add(10);
|
|
@@ -23,7 +25,9 @@ export type ScoreboardHandlers = {
|
|
|
23
25
|
*/
|
|
24
26
|
export declare class Scoreboard {
|
|
25
27
|
private readonly world;
|
|
28
|
+
/** The game's own handlers. `listen` adds more without replacing these. */
|
|
26
29
|
handlers: ScoreboardHandlers;
|
|
30
|
+
private readonly listeners;
|
|
27
31
|
private readonly state;
|
|
28
32
|
/** @internal */
|
|
29
33
|
constructor(world: number);
|
|
@@ -59,7 +63,13 @@ export declare class Scoreboard {
|
|
|
59
63
|
* that countdown in full (a ready screen); `resume()` starts it.
|
|
60
64
|
*/
|
|
61
65
|
reset(lives?: number, countdownSeconds?: number): void;
|
|
66
|
+
/**
|
|
67
|
+
* Adds handlers alongside `handlers` (and other listeners); they run after them, in order of
|
|
68
|
+
* listening. Returns a function that removes them.
|
|
69
|
+
*/
|
|
70
|
+
listen(handlers: ScoreboardHandlers): () => void;
|
|
62
71
|
/** @internal Runs handlers for what changed since the last call. */
|
|
63
72
|
dispatch(): void;
|
|
73
|
+
private run;
|
|
64
74
|
}
|
|
65
75
|
//# sourceMappingURL=Scoreboard.d.ts.map
|
|
@@ -2,8 +2,8 @@ import type { Entity, EntityOptions } from './types.js';
|
|
|
2
2
|
export type TransformOptions = Pick<EntityOptions, 'position' | 'rotation' | 'scale' | 'color'>;
|
|
3
3
|
export declare const TRANSFORM_STRIDE = 16;
|
|
4
4
|
/**
|
|
5
|
-
* Explicit draw-only updates, applied in one native call by `flush()`.
|
|
6
|
-
*
|
|
5
|
+
* Explicit draw-only updates, applied in one native call by `flush()`. `world.update` flushes
|
|
6
|
+
* `world.transforms` before stepping; flush yourself before reading positions or animating. Records and scratch storage are reused; values are
|
|
7
7
|
* copied when queued, never retained by reference. The scalar methods (`position`, `rotation`,
|
|
8
8
|
* `scale`, `color`, `transform`) allocate nothing; consecutive calls for the same entity share one
|
|
9
9
|
* record.
|
|
@@ -44,6 +44,7 @@ export declare class World implements RenderSource {
|
|
|
44
44
|
private gravityValue;
|
|
45
45
|
private boundsValue;
|
|
46
46
|
private listenerValue;
|
|
47
|
+
private updates;
|
|
47
48
|
constructor(capacity: number);
|
|
48
49
|
private get native();
|
|
49
50
|
/** True once `dispose()` has run. Cleanup calls (despawn, set, has, queries, stopping animations
|
|
@@ -61,7 +62,8 @@ export declare class World implements RenderSource {
|
|
|
61
62
|
set(e: Entity, options: EntityOptions): boolean;
|
|
62
63
|
/**
|
|
63
64
|
* This world's shared draw-only transform batch (created on first use). Queue per-frame moves with
|
|
64
|
-
* its allocation-free methods
|
|
65
|
+
* its allocation-free methods; the next `update()` applies them first (or `flush()` them yourself
|
|
66
|
+
* before reading positions or animating).
|
|
65
67
|
*/
|
|
66
68
|
get transforms(): TransformBatch;
|
|
67
69
|
/** Reusable draw-only transforms; use set() for text entities and flush before animations/queries. */
|
|
@@ -131,6 +133,8 @@ export declare class World implements RenderSource {
|
|
|
131
133
|
cancelTimers(): void;
|
|
132
134
|
/** Score, lives and the game clock, kept in Rust (created on first use). */
|
|
133
135
|
get scoreboard(): Scoreboard;
|
|
136
|
+
/** How many times `update` has run. GameView reads it to step the world only when `onUpdate` didn't. */
|
|
137
|
+
get updateCount(): number;
|
|
134
138
|
/** Timers waiting to fire. */
|
|
135
139
|
get timerCount(): number;
|
|
136
140
|
/** Resolves the promises of animations the core reports as ended, then runs due timers. */
|
|
@@ -183,7 +187,11 @@ export declare class World implements RenderSource {
|
|
|
183
187
|
/** Impact sounds pan and fade relative to this entity (usually the player). */
|
|
184
188
|
get listener(): Entity | null;
|
|
185
189
|
set listener(e: Entity | null);
|
|
186
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* Advances the simulation (fixed 60 Hz steps), resolves finished animations, runs due timers and
|
|
192
|
+
* scoreboard handlers, and refreshes what GameView draws. `GameView` calls it for you each frame;
|
|
193
|
+
* call it yourself from `onUpdate` only to step with a different `dt` (it is then not stepped twice).
|
|
194
|
+
*/
|
|
187
195
|
update(dt: number): void;
|
|
188
196
|
/**
|
|
189
197
|
* Calls `fn` for each touch that started or ended during the last `update`. `info` is reused:
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Color } from './types.js';
|
|
2
|
+
/** A color as an option takes it: `[r, g, b]` / `[r, g, b, a]` (0..1, channels above 1 glow) or a CSS string. */
|
|
3
|
+
export type ColorInput = Color | string;
|
|
4
|
+
/**
|
|
5
|
+
* Any color input as an `[r, g, b, a]` tuple (0..1), with its alpha multiplied by `alpha`. Use it when
|
|
6
|
+
* you need the numbers (to brighten, mix or fade a theme color); options take strings directly.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* toColor('#FF4757'); // [1, 0.278, 0.341, 1]
|
|
10
|
+
* toColor('#FF4757', 0.5); // half see-through
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function toColor(color: ColorInput, alpha?: number): [number, number, number, number];
|
|
14
|
+
/** A color input as a tuple, without copying tuples (the hot path of `spawn`, `set` and `animate`). */
|
|
15
|
+
export declare function resolveColor(color: ColorInput): Color;
|
|
16
|
+
/** One color input, or a list to pick from, as tuples. */
|
|
17
|
+
export declare function resolveColors(colors: ColorInput | readonly ColorInput[]): readonly Color[];
|
|
18
|
+
//# sourceMappingURL=color.d.ts.map
|