@multiplekex/shallot 0.1.12 → 0.2.1
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/package.json +3 -4
- package/src/core/builder.ts +71 -32
- package/src/core/component.ts +25 -11
- package/src/core/index.ts +14 -13
- package/src/core/math.ts +135 -0
- package/src/core/runtime.ts +0 -1
- package/src/core/state.ts +9 -68
- package/src/core/xml.ts +381 -265
- package/src/editor/format.ts +5 -0
- package/src/editor/index.ts +101 -0
- package/src/extras/arrows/index.ts +28 -69
- package/src/extras/gradient/index.ts +36 -52
- package/src/extras/lines/index.ts +53 -123
- package/src/extras/orbit/index.ts +40 -15
- package/src/extras/text/font.ts +546 -0
- package/src/extras/text/index.ts +250 -239
- package/src/extras/text/sdf.ts +429 -0
- package/src/standard/activity/index.ts +172 -0
- package/src/standard/compute/graph.ts +23 -23
- package/src/standard/compute/index.ts +76 -61
- package/src/standard/defaults.ts +8 -5
- package/src/standard/index.ts +1 -0
- package/src/standard/input/index.ts +30 -19
- package/src/standard/loading/index.ts +18 -13
- package/src/standard/render/bvh/blas.ts +752 -0
- package/src/standard/render/bvh/radix.ts +476 -0
- package/src/standard/render/bvh/structs.ts +167 -0
- package/src/standard/render/bvh/tlas.ts +886 -0
- package/src/standard/render/bvh/traverse.ts +467 -0
- package/src/standard/render/camera.ts +302 -27
- package/src/standard/render/data.ts +93 -0
- package/src/standard/render/depth.ts +117 -0
- package/src/standard/render/forward/index.ts +259 -0
- package/src/standard/render/forward/raster.ts +228 -0
- package/src/standard/render/index.ts +443 -70
- package/src/standard/render/indirect.ts +40 -0
- package/src/standard/render/instance.ts +214 -0
- package/src/standard/render/intersection.ts +72 -0
- package/src/standard/render/light.ts +16 -16
- package/src/standard/render/mesh/index.ts +67 -75
- package/src/standard/render/mesh/unified.ts +96 -0
- package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
- package/src/standard/render/pass.ts +10 -4
- package/src/standard/render/postprocess.ts +142 -64
- package/src/standard/render/ray.ts +61 -0
- package/src/standard/render/scene.ts +38 -164
- package/src/standard/render/shaders.ts +484 -0
- package/src/standard/render/surface/compile.ts +3 -10
- package/src/standard/render/surface/index.ts +60 -30
- package/src/standard/render/surface/noise.ts +45 -0
- package/src/standard/render/surface/structs.ts +60 -19
- package/src/standard/render/surface/wgsl.ts +573 -0
- package/src/standard/render/triangle.ts +84 -0
- package/src/standard/transforms/index.ts +4 -6
- package/src/standard/tween/index.ts +10 -1
- package/src/standard/tween/sequence.ts +24 -16
- package/src/standard/tween/tween.ts +67 -16
- package/src/core/types.ts +0 -37
- package/src/standard/compute/inspect.ts +0 -201
- package/src/standard/compute/pass.ts +0 -23
- package/src/standard/compute/timing.ts +0 -139
- package/src/standard/render/forward.ts +0 -273
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplekex/shallot",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts",
|
|
9
|
+
"./editor": "./src/editor/index.ts",
|
|
9
10
|
"./extras": "./src/extras/index.ts"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
@@ -17,9 +18,7 @@
|
|
|
17
18
|
"test": "bun test"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"
|
|
21
|
-
"bitecs": "^0.4.0",
|
|
22
|
-
"fast-xml-parser": "^5.3.3"
|
|
21
|
+
"bitecs": "^0.4.0"
|
|
23
22
|
},
|
|
24
23
|
"devDependencies": {
|
|
25
24
|
"@napi-rs/canvas": "^0.1.88",
|
package/src/core/builder.ts
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
import { State } from "./state";
|
|
2
|
-
import type { Plugin, Loading } from "./types";
|
|
3
2
|
import { toposort, type System } from "./scheduler";
|
|
4
|
-
import { registerComponent } from "./component";
|
|
3
|
+
import { registerComponent, type ComponentLike } from "./component";
|
|
5
4
|
import { initRuntime } from "./runtime";
|
|
5
|
+
import type { RelationDef } from "./relation";
|
|
6
|
+
|
|
7
|
+
export interface Plugin {
|
|
8
|
+
readonly systems?: readonly System[];
|
|
9
|
+
readonly components?: Record<string, ComponentLike>;
|
|
10
|
+
readonly relations?: readonly RelationDef[];
|
|
11
|
+
readonly dependencies?: readonly Plugin[];
|
|
12
|
+
readonly initialize?: (
|
|
13
|
+
state: State,
|
|
14
|
+
onProgress?: (progress: number) => void
|
|
15
|
+
) => void | Promise<void>;
|
|
16
|
+
readonly warm?: (state: State, onProgress?: (progress: number) => void) => void | Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Loading {
|
|
20
|
+
show(): (() => void) | void;
|
|
21
|
+
update(progress: number): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const NoLoading: Loading = { show: () => {}, update: () => {} };
|
|
6
25
|
|
|
7
26
|
export class StateBuilder {
|
|
8
27
|
static defaultPlugins: readonly Plugin[] = [];
|
|
9
|
-
static
|
|
28
|
+
static initCanvas: ((state: State, canvas: HTMLCanvasElement) => void) | null = null;
|
|
29
|
+
static defaultLoading: ((canvas: HTMLCanvasElement) => Loading) | null = null;
|
|
10
30
|
|
|
11
31
|
private readonly _plugins: Plugin[] = [];
|
|
12
32
|
private readonly _systems: System[] = [];
|
|
@@ -14,13 +34,18 @@ export class StateBuilder {
|
|
|
14
34
|
private readonly _excludedPlugins = new Set<Plugin>();
|
|
15
35
|
private _useDefaultPlugins = true;
|
|
16
36
|
private _canvas: HTMLCanvasElement | null = null;
|
|
17
|
-
private _loading: Loading | null
|
|
37
|
+
private _loading: Loading | null = null;
|
|
18
38
|
|
|
19
39
|
withCanvas(canvas: HTMLCanvasElement): this {
|
|
20
40
|
this._canvas = canvas;
|
|
21
41
|
return this;
|
|
22
42
|
}
|
|
23
43
|
|
|
44
|
+
withLoading(loading: Loading): this {
|
|
45
|
+
this._loading = loading;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
24
49
|
withPlugins(...plugins: Plugin[]): this {
|
|
25
50
|
this._plugins.push(...plugins);
|
|
26
51
|
return this;
|
|
@@ -51,20 +76,16 @@ export class StateBuilder {
|
|
|
51
76
|
return this;
|
|
52
77
|
}
|
|
53
78
|
|
|
54
|
-
withLoading(loading: Loading | null): this {
|
|
55
|
-
this._loading = loading;
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
79
|
async build(): Promise<State> {
|
|
60
|
-
const
|
|
61
|
-
this._loading === undefined && this._canvas
|
|
62
|
-
? (StateBuilder.loading?.(this._canvas) ?? null)
|
|
63
|
-
: this._loading;
|
|
80
|
+
const state = new State();
|
|
64
81
|
|
|
65
|
-
|
|
82
|
+
if (this._canvas && StateBuilder.initCanvas) {
|
|
83
|
+
StateBuilder.initCanvas(state, this._canvas);
|
|
84
|
+
}
|
|
66
85
|
|
|
67
|
-
const
|
|
86
|
+
const loading =
|
|
87
|
+
this._loading ?? (this._canvas && StateBuilder.defaultLoading?.(this._canvas));
|
|
88
|
+
const cleanup = loading?.show();
|
|
68
89
|
|
|
69
90
|
const pluginSet = new Set<Plugin>();
|
|
70
91
|
|
|
@@ -105,13 +126,15 @@ export class StateBuilder {
|
|
|
105
126
|
}
|
|
106
127
|
const sorted = toposort(allPlugins, edges);
|
|
107
128
|
|
|
108
|
-
|
|
109
|
-
const total = sorted.length;
|
|
129
|
+
const total = sorted.length * 2 + this._scenes.length;
|
|
110
130
|
|
|
111
|
-
for (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
132
|
+
const plugin = sorted[i];
|
|
133
|
+
const onProgress = loading
|
|
134
|
+
? (progress: number) => loading.update((i + progress) / total)
|
|
135
|
+
: undefined;
|
|
136
|
+
await plugin.initialize?.(state, onProgress);
|
|
137
|
+
loading?.update((i + 1) / total);
|
|
115
138
|
}
|
|
116
139
|
|
|
117
140
|
for (const system of this._systems) {
|
|
@@ -119,21 +142,37 @@ export class StateBuilder {
|
|
|
119
142
|
}
|
|
120
143
|
|
|
121
144
|
if (this._scenes.length > 0) {
|
|
122
|
-
const {
|
|
145
|
+
const { parse, load } = await import("./xml");
|
|
123
146
|
const runtime = await initRuntime();
|
|
124
|
-
for (
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
if (result.errors.length > 0) {
|
|
131
|
-
console.warn(`Scene "${scenePath}" loaded with errors:`, result.errors);
|
|
132
|
-
}
|
|
147
|
+
for (let i = 0; i < this._scenes.length; i++) {
|
|
148
|
+
const xml = await runtime.readFile(this._scenes[i]);
|
|
149
|
+
const nodes = parse(xml);
|
|
150
|
+
load(nodes, state);
|
|
151
|
+
loading?.update((sorted.length + i + 1) / total);
|
|
133
152
|
}
|
|
134
153
|
}
|
|
135
154
|
|
|
136
|
-
|
|
155
|
+
const warmable = sorted.filter((p) => p.warm);
|
|
156
|
+
let warmDone = 0;
|
|
157
|
+
const warmBase = sorted.length + this._scenes.length;
|
|
158
|
+
|
|
159
|
+
const warmPromises = warmable.map(async (plugin) => {
|
|
160
|
+
await plugin.warm!(state, (progress: number) => {
|
|
161
|
+
if (loading) {
|
|
162
|
+
loading.update((warmBase + warmDone + progress) / total);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
warmDone++;
|
|
166
|
+
loading?.update((warmBase + warmDone) / total);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await Promise.all(warmPromises);
|
|
170
|
+
|
|
171
|
+
if (cleanup) {
|
|
172
|
+
loading?.update(1);
|
|
173
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
174
|
+
cleanup();
|
|
175
|
+
}
|
|
137
176
|
|
|
138
177
|
return state;
|
|
139
178
|
}
|
package/src/core/component.ts
CHANGED
|
@@ -12,7 +12,6 @@ export interface FieldAccessor {
|
|
|
12
12
|
export interface ComponentTraits {
|
|
13
13
|
defaults?: () => Record<string, number>;
|
|
14
14
|
adapter?: (attrs: Record<string, string>, eid: number) => Record<string, number>;
|
|
15
|
-
accessors?: Record<string, FieldAccessor>;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
const traitsMap = new WeakMap<ComponentLike, ComponentTraits>();
|
|
@@ -47,15 +46,30 @@ export function clearRegistry(): void {
|
|
|
47
46
|
registry.clear();
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
export interface FieldProxy extends Array<number>, FieldAccessor {}
|
|
50
|
+
|
|
51
|
+
export function createFieldProxy(data: Float32Array, stride: number, offset: number): FieldProxy {
|
|
52
|
+
function getValue(eid: number): number {
|
|
53
|
+
return data[eid * stride + offset];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setValue(eid: number, value: number): void {
|
|
57
|
+
data[eid * stride + offset] = value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new Proxy([] as unknown as FieldProxy, {
|
|
61
|
+
get(_, prop) {
|
|
62
|
+
if (prop === "get") return getValue;
|
|
63
|
+
if (prop === "set") return setValue;
|
|
64
|
+
const eid = Number(prop);
|
|
65
|
+
if (Number.isNaN(eid)) return undefined;
|
|
66
|
+
return getValue(eid);
|
|
67
|
+
},
|
|
68
|
+
set(_, prop, value) {
|
|
69
|
+
const eid = Number(prop);
|
|
70
|
+
if (Number.isNaN(eid)) return false;
|
|
71
|
+
setValue(eid, value);
|
|
72
|
+
return true;
|
|
59
73
|
},
|
|
60
|
-
};
|
|
74
|
+
});
|
|
61
75
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ export {
|
|
|
24
24
|
} from "bitecs";
|
|
25
25
|
|
|
26
26
|
export { State, MAX_ENTITIES } from "./state";
|
|
27
|
-
export { StateBuilder } from "./builder";
|
|
27
|
+
export { StateBuilder, NoLoading, type Plugin, type Loading } from "./builder";
|
|
28
28
|
export { resource, type ResourceKey } from "./resource";
|
|
29
29
|
export {
|
|
30
30
|
Scheduler,
|
|
@@ -42,12 +42,13 @@ export {
|
|
|
42
42
|
clearRegistry,
|
|
43
43
|
getTraits,
|
|
44
44
|
setTraits,
|
|
45
|
-
|
|
45
|
+
createFieldProxy,
|
|
46
46
|
type ComponentTraits,
|
|
47
47
|
type ComponentData,
|
|
48
48
|
type ComponentLike,
|
|
49
49
|
type RegisteredComponent,
|
|
50
50
|
type FieldAccessor,
|
|
51
|
+
type FieldProxy,
|
|
51
52
|
} from "./component";
|
|
52
53
|
|
|
53
54
|
export {
|
|
@@ -59,8 +60,6 @@ export {
|
|
|
59
60
|
type RelationOptions,
|
|
60
61
|
} from "./relation";
|
|
61
62
|
|
|
62
|
-
export type { Plugin, Loading, InputState, MouseState, RenderContext } from "./types";
|
|
63
|
-
|
|
64
63
|
export {
|
|
65
64
|
clamp,
|
|
66
65
|
lerp,
|
|
@@ -69,6 +68,11 @@ export {
|
|
|
69
68
|
eulerToQuaternion,
|
|
70
69
|
quaternionToEuler,
|
|
71
70
|
lookAt,
|
|
71
|
+
perspective,
|
|
72
|
+
orthographic,
|
|
73
|
+
multiply,
|
|
74
|
+
invert,
|
|
75
|
+
extractFrustumPlanes,
|
|
72
76
|
} from "./math";
|
|
73
77
|
|
|
74
78
|
export { toKebabCase, toCamelCase } from "./strings";
|
|
@@ -76,18 +80,15 @@ export { toKebabCase, toCamelCase } from "./strings";
|
|
|
76
80
|
export { initRuntime, getRuntime, resetRuntime, type Runtime, type RuntimeTarget } from "./runtime";
|
|
77
81
|
|
|
78
82
|
export {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
parse,
|
|
84
|
+
serialize,
|
|
85
|
+
load,
|
|
82
86
|
registerPostLoadHook,
|
|
83
87
|
unregisterPostLoadHook,
|
|
84
|
-
type
|
|
85
|
-
type
|
|
86
|
-
type
|
|
87
|
-
type ComponentDef,
|
|
88
|
-
type ParseResult,
|
|
88
|
+
type Node,
|
|
89
|
+
type Attr,
|
|
90
|
+
type Ref,
|
|
89
91
|
type ParseError,
|
|
90
|
-
type LoadResult,
|
|
91
92
|
type PostLoadHook,
|
|
92
93
|
type PostLoadContext,
|
|
93
94
|
} from "./xml";
|
package/src/core/math.ts
CHANGED
|
@@ -149,6 +149,141 @@ export function quaternionToEuler(
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
export function perspective(fov: number, aspect: number, near: number, far: number): Float32Array {
|
|
153
|
+
if (fov <= 0) throw new Error(`Invalid FOV: ${fov} (must be > 0)`);
|
|
154
|
+
if (aspect <= 0) throw new Error(`Invalid aspect ratio: ${aspect} (must be > 0)`);
|
|
155
|
+
if (near === far) throw new Error(`Invalid depth planes: near === far (${near})`);
|
|
156
|
+
const f = 1 / Math.tan((fov * Math.PI) / 360);
|
|
157
|
+
const nf = 1 / (near - far);
|
|
158
|
+
return new Float32Array([
|
|
159
|
+
f / aspect,
|
|
160
|
+
0,
|
|
161
|
+
0,
|
|
162
|
+
0,
|
|
163
|
+
0,
|
|
164
|
+
f,
|
|
165
|
+
0,
|
|
166
|
+
0,
|
|
167
|
+
0,
|
|
168
|
+
0,
|
|
169
|
+
far * nf,
|
|
170
|
+
-1,
|
|
171
|
+
0,
|
|
172
|
+
0,
|
|
173
|
+
far * near * nf,
|
|
174
|
+
0,
|
|
175
|
+
]);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function orthographic(
|
|
179
|
+
size: number,
|
|
180
|
+
aspect: number,
|
|
181
|
+
near: number,
|
|
182
|
+
far: number
|
|
183
|
+
): Float32Array {
|
|
184
|
+
if (size <= 0) throw new Error(`Invalid orthographic size: ${size} (must be > 0)`);
|
|
185
|
+
if (aspect <= 0) throw new Error(`Invalid aspect ratio: ${aspect} (must be > 0)`);
|
|
186
|
+
if (near === far) throw new Error(`Invalid depth planes: near === far (${near})`);
|
|
187
|
+
const lr = 1 / (size * aspect);
|
|
188
|
+
const bt = 1 / size;
|
|
189
|
+
const nf = 1 / (near - far);
|
|
190
|
+
return new Float32Array([lr, 0, 0, 0, 0, bt, 0, 0, 0, 0, nf, 0, 0, 0, near * nf, 1]);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function multiply(a: Float32Array, b: Float32Array): Float32Array {
|
|
194
|
+
const out = new Float32Array(16);
|
|
195
|
+
for (let i = 0; i < 4; i++) {
|
|
196
|
+
for (let j = 0; j < 4; j++) {
|
|
197
|
+
out[j * 4 + i] =
|
|
198
|
+
a[i] * b[j * 4] +
|
|
199
|
+
a[i + 4] * b[j * 4 + 1] +
|
|
200
|
+
a[i + 8] * b[j * 4 + 2] +
|
|
201
|
+
a[i + 12] * b[j * 4 + 3];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function invert(m: Float32Array): Float32Array {
|
|
208
|
+
const out = new Float32Array(16);
|
|
209
|
+
const r00 = m[0],
|
|
210
|
+
r01 = m[1],
|
|
211
|
+
r02 = m[2];
|
|
212
|
+
const r10 = m[4],
|
|
213
|
+
r11 = m[5],
|
|
214
|
+
r12 = m[6];
|
|
215
|
+
const r20 = m[8],
|
|
216
|
+
r21 = m[9],
|
|
217
|
+
r22 = m[10];
|
|
218
|
+
const tx = m[12],
|
|
219
|
+
ty = m[13],
|
|
220
|
+
tz = m[14];
|
|
221
|
+
|
|
222
|
+
out[0] = r00;
|
|
223
|
+
out[1] = r10;
|
|
224
|
+
out[2] = r20;
|
|
225
|
+
out[3] = 0;
|
|
226
|
+
out[4] = r01;
|
|
227
|
+
out[5] = r11;
|
|
228
|
+
out[6] = r21;
|
|
229
|
+
out[7] = 0;
|
|
230
|
+
out[8] = r02;
|
|
231
|
+
out[9] = r12;
|
|
232
|
+
out[10] = r22;
|
|
233
|
+
out[11] = 0;
|
|
234
|
+
out[12] = -(r00 * tx + r01 * ty + r02 * tz);
|
|
235
|
+
out[13] = -(r10 * tx + r11 * ty + r12 * tz);
|
|
236
|
+
out[14] = -(r20 * tx + r21 * ty + r22 * tz);
|
|
237
|
+
out[15] = 1;
|
|
238
|
+
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function extractFrustumPlanes(viewProj: Float32Array): Float32Array {
|
|
243
|
+
const planes = new Float32Array(24);
|
|
244
|
+
const m = viewProj;
|
|
245
|
+
|
|
246
|
+
planes[0] = m[3] + m[0];
|
|
247
|
+
planes[1] = m[7] + m[4];
|
|
248
|
+
planes[2] = m[11] + m[8];
|
|
249
|
+
planes[3] = m[15] + m[12];
|
|
250
|
+
|
|
251
|
+
planes[4] = m[3] - m[0];
|
|
252
|
+
planes[5] = m[7] - m[4];
|
|
253
|
+
planes[6] = m[11] - m[8];
|
|
254
|
+
planes[7] = m[15] - m[12];
|
|
255
|
+
|
|
256
|
+
planes[8] = m[3] + m[1];
|
|
257
|
+
planes[9] = m[7] + m[5];
|
|
258
|
+
planes[10] = m[11] + m[9];
|
|
259
|
+
planes[11] = m[15] + m[13];
|
|
260
|
+
|
|
261
|
+
planes[12] = m[3] - m[1];
|
|
262
|
+
planes[13] = m[7] - m[5];
|
|
263
|
+
planes[14] = m[11] - m[9];
|
|
264
|
+
planes[15] = m[15] - m[13];
|
|
265
|
+
|
|
266
|
+
planes[16] = m[2];
|
|
267
|
+
planes[17] = m[6];
|
|
268
|
+
planes[18] = m[10];
|
|
269
|
+
planes[19] = m[14];
|
|
270
|
+
|
|
271
|
+
planes[20] = m[3] - m[2];
|
|
272
|
+
planes[21] = m[7] - m[6];
|
|
273
|
+
planes[22] = m[11] - m[10];
|
|
274
|
+
planes[23] = m[15] - m[14];
|
|
275
|
+
for (let i = 0; i < 6; i++) {
|
|
276
|
+
const len = Math.hypot(planes[i * 4], planes[i * 4 + 1], planes[i * 4 + 2]);
|
|
277
|
+
if (len > 0) {
|
|
278
|
+
planes[i * 4] /= len;
|
|
279
|
+
planes[i * 4 + 1] /= len;
|
|
280
|
+
planes[i * 4 + 2] /= len;
|
|
281
|
+
planes[i * 4 + 3] /= len;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return planes;
|
|
285
|
+
}
|
|
286
|
+
|
|
152
287
|
export function lookAt(
|
|
153
288
|
eyeX: number,
|
|
154
289
|
eyeY: number,
|
package/src/core/runtime.ts
CHANGED
|
@@ -99,7 +99,6 @@ function isWebviewResource(path: string): boolean {
|
|
|
99
99
|
return path.startsWith("/") || path.startsWith("./") || path.startsWith("../");
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
// Dynamic import path to prevent bundler static analysis
|
|
103
102
|
const TAURI_FS_MODULE = "@tauri-apps/" + "plugin-fs";
|
|
104
103
|
|
|
105
104
|
function createStandaloneRuntime(): Runtime {
|
package/src/core/state.ts
CHANGED
|
@@ -17,34 +17,23 @@ import {
|
|
|
17
17
|
} from "bitecs";
|
|
18
18
|
import { Scheduler, Time, type GameTime, type System } from "./scheduler";
|
|
19
19
|
import type { RelationDef } from "./relation";
|
|
20
|
-
import type { Plugin } from "./
|
|
21
|
-
import type { StateBuilder } from "./builder";
|
|
20
|
+
import type { Plugin, StateBuilder } from "./builder";
|
|
22
21
|
import { initRuntime, type Runtime } from "./runtime";
|
|
23
|
-
import {
|
|
24
|
-
registerComponent,
|
|
25
|
-
getTraits,
|
|
26
|
-
getRegisteredComponent,
|
|
27
|
-
directAccessor,
|
|
28
|
-
type ComponentData,
|
|
29
|
-
type FieldAccessor,
|
|
30
|
-
} from "./component";
|
|
22
|
+
import { registerComponent, getTraits, type ComponentData } from "./component";
|
|
31
23
|
import { type ResourceKey } from "./resource";
|
|
32
|
-
import { toCamelCase } from "./strings";
|
|
33
24
|
|
|
34
25
|
export const MAX_ENTITIES = 65536;
|
|
35
26
|
|
|
36
27
|
export class State {
|
|
37
28
|
readonly world: World;
|
|
38
29
|
readonly scheduler = new Scheduler();
|
|
39
|
-
readonly canvas: HTMLCanvasElement | null;
|
|
40
|
-
maxEid = 0;
|
|
41
30
|
|
|
42
31
|
private _resources = new Map<symbol, unknown>();
|
|
43
32
|
private _disposed = false;
|
|
44
33
|
private _running = false;
|
|
45
34
|
private _runtime: Runtime | null = null;
|
|
46
35
|
private _lastTime = 0;
|
|
47
|
-
private
|
|
36
|
+
private _maxEid = 0;
|
|
48
37
|
|
|
49
38
|
get time(): Readonly<GameTime> {
|
|
50
39
|
return this.scheduler.time;
|
|
@@ -54,6 +43,10 @@ export class State {
|
|
|
54
43
|
return this._running;
|
|
55
44
|
}
|
|
56
45
|
|
|
46
|
+
get maxEid(): number {
|
|
47
|
+
return this._maxEid;
|
|
48
|
+
}
|
|
49
|
+
|
|
57
50
|
static Builder: (new () => StateBuilder) | null = null;
|
|
58
51
|
|
|
59
52
|
static new(): StateBuilder {
|
|
@@ -63,9 +56,8 @@ export class State {
|
|
|
63
56
|
return new State.Builder();
|
|
64
57
|
}
|
|
65
58
|
|
|
66
|
-
constructor(
|
|
59
|
+
constructor() {
|
|
67
60
|
this.world = createWorld();
|
|
68
|
-
this.canvas = canvas;
|
|
69
61
|
}
|
|
70
62
|
|
|
71
63
|
setResource<T>(key: ResourceKey<T>, value: T): void {
|
|
@@ -141,12 +133,11 @@ export class State {
|
|
|
141
133
|
if (eid >= MAX_ENTITIES) {
|
|
142
134
|
throw new Error(`Entity limit exceeded: ${eid} >= ${MAX_ENTITIES}`);
|
|
143
135
|
}
|
|
144
|
-
if (eid > this.
|
|
136
|
+
if (eid > this._maxEid) this._maxEid = eid;
|
|
145
137
|
return eid;
|
|
146
138
|
}
|
|
147
139
|
|
|
148
140
|
removeEntity(eid: number): void {
|
|
149
|
-
this._fieldAccessors?.delete(eid);
|
|
150
141
|
removeEntity(this.world, eid);
|
|
151
142
|
}
|
|
152
143
|
|
|
@@ -236,59 +227,9 @@ export class State {
|
|
|
236
227
|
return targets.length > 0 ? targets[0] : -1;
|
|
237
228
|
}
|
|
238
229
|
|
|
239
|
-
private ensureFieldAccessors(): Map<number, FieldAccessor> {
|
|
240
|
-
if (!this._fieldAccessors) {
|
|
241
|
-
this._fieldAccessors = new Map();
|
|
242
|
-
}
|
|
243
|
-
return this._fieldAccessors;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
resolveFieldPath(path: string): { component: string; field: string } | null {
|
|
247
|
-
const dotIndex = path.lastIndexOf(".");
|
|
248
|
-
if (dotIndex === -1) return null;
|
|
249
|
-
return {
|
|
250
|
-
component: path.slice(0, dotIndex),
|
|
251
|
-
field: path.slice(dotIndex + 1),
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
bindFieldAccessor(
|
|
256
|
-
bindingId: number,
|
|
257
|
-
componentName: string,
|
|
258
|
-
fieldPath: string
|
|
259
|
-
): FieldAccessor | null {
|
|
260
|
-
const registered = getRegisteredComponent(componentName);
|
|
261
|
-
if (!registered) return null;
|
|
262
|
-
|
|
263
|
-
const camelPath = toCamelCase(fieldPath);
|
|
264
|
-
|
|
265
|
-
const traits = getTraits(registered.component);
|
|
266
|
-
if (traits?.accessors?.[camelPath]) {
|
|
267
|
-
const accessor = traits.accessors[camelPath];
|
|
268
|
-
this.ensureFieldAccessors().set(bindingId, accessor);
|
|
269
|
-
return accessor;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const array = registered.component[camelPath];
|
|
273
|
-
if (array == null || !(ArrayBuffer.isView(array) || Array.isArray(array))) return null;
|
|
274
|
-
|
|
275
|
-
const accessor = directAccessor(array as number[], 1, 0);
|
|
276
|
-
this.ensureFieldAccessors().set(bindingId, accessor);
|
|
277
|
-
return accessor;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
getFieldAccessor(bindingId: number): FieldAccessor | undefined {
|
|
281
|
-
return this._fieldAccessors?.get(bindingId);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
removeFieldAccessor(bindingId: number): boolean {
|
|
285
|
-
return this._fieldAccessors?.delete(bindingId) ?? false;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
230
|
dispose(): void {
|
|
289
231
|
if (this._disposed) return;
|
|
290
232
|
this.stop();
|
|
291
|
-
this._fieldAccessors = null;
|
|
292
233
|
for (const system of this.scheduler.systems) {
|
|
293
234
|
system.dispose?.(this);
|
|
294
235
|
}
|