@multiplekex/shallot 0.1.6 → 0.1.9
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 +2 -2
- package/src/core/component.ts +7 -17
- package/src/core/index.ts +2 -1
- package/src/core/math.ts +33 -16
- package/src/core/state.ts +2 -2
- package/src/core/types.ts +2 -2
- package/src/core/xml.ts +83 -33
- package/src/extras/arrows/index.ts +73 -95
- package/src/extras/lines/index.ts +97 -56
- package/src/extras/orbit/index.ts +3 -2
- package/src/extras/text/index.ts +245 -82
- package/src/standard/compute/index.ts +26 -2
- package/src/standard/compute/inspect.ts +15 -1
- package/src/standard/render/camera.ts +8 -1
- package/src/standard/render/forward.ts +54 -3
- package/src/standard/render/index.ts +52 -3
- package/src/standard/render/material/index.ts +92 -0
- package/src/standard/render/mesh/index.ts +66 -10
- package/src/standard/render/opaque.ts +44 -0
- package/src/standard/render/postprocess.ts +10 -2
- package/src/standard/render/scene.ts +18 -1
- package/src/standard/render/transparent.ts +94 -0
- package/src/standard/tween/sequence.ts +2 -2
- package/src/standard/tween/tween.ts +31 -13
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { resource, type State } from "../../core";
|
|
2
|
+
import type { ComputeNode, ExecutionContext } from "../compute";
|
|
3
|
+
import { MASK_FORMAT } from "./scene";
|
|
4
|
+
|
|
5
|
+
export interface DrawContext {
|
|
6
|
+
readonly device: GPUDevice;
|
|
7
|
+
readonly format: GPUTextureFormat;
|
|
8
|
+
readonly maskFormat: GPUTextureFormat;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DrawContributor {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly order: number;
|
|
14
|
+
draw(pass: GPURenderPassEncoder, ctx: DrawContext): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TransparentPassState {
|
|
18
|
+
contributors: Map<string, DrawContributor>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const TransparentPass = resource<TransparentPassState>("transparent-pass");
|
|
22
|
+
|
|
23
|
+
export function registerDrawContributor(state: State, contributor: DrawContributor): void {
|
|
24
|
+
const pass = TransparentPass.from(state);
|
|
25
|
+
if (pass) {
|
|
26
|
+
pass.contributors.set(contributor.id, contributor);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function unregisterDrawContributor(state: State, id: string): void {
|
|
31
|
+
const pass = TransparentPass.from(state);
|
|
32
|
+
if (pass) {
|
|
33
|
+
pass.contributors.delete(id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TransparentNodeConfig {
|
|
38
|
+
getContributors: () => DrawContributor[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createTransparentNode(config: TransparentNodeConfig): ComputeNode {
|
|
42
|
+
return {
|
|
43
|
+
id: "transparent",
|
|
44
|
+
phase: "transparent",
|
|
45
|
+
inputs: [{ id: "depth", access: "read" }],
|
|
46
|
+
outputs: [
|
|
47
|
+
{ id: "scene", access: "write" },
|
|
48
|
+
{ id: "mask", access: "write" },
|
|
49
|
+
],
|
|
50
|
+
|
|
51
|
+
execute(ctx: ExecutionContext) {
|
|
52
|
+
const contributors = config.getContributors();
|
|
53
|
+
if (contributors.length === 0) return;
|
|
54
|
+
|
|
55
|
+
const targetView = ctx.getTextureView("scene") ?? ctx.canvasView;
|
|
56
|
+
const depthView = ctx.getTextureView("depth")!;
|
|
57
|
+
const maskView = ctx.getTextureView("mask")!;
|
|
58
|
+
|
|
59
|
+
const pass = ctx.encoder.beginRenderPass({
|
|
60
|
+
colorAttachments: [
|
|
61
|
+
{
|
|
62
|
+
view: targetView,
|
|
63
|
+
loadOp: "load" as const,
|
|
64
|
+
storeOp: "store" as const,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
view: maskView,
|
|
68
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
69
|
+
loadOp: "clear" as const,
|
|
70
|
+
storeOp: "store" as const,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
depthStencilAttachment: {
|
|
74
|
+
view: depthView,
|
|
75
|
+
depthLoadOp: "load" as const,
|
|
76
|
+
depthStoreOp: "store" as const,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const drawCtx: DrawContext = {
|
|
81
|
+
device: ctx.device,
|
|
82
|
+
format: ctx.format,
|
|
83
|
+
maskFormat: MASK_FORMAT,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const sorted = [...contributors].sort((a, b) => a.order - b.order);
|
|
87
|
+
for (const contributor of sorted) {
|
|
88
|
+
contributor.draw(pass, drawCtx);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pass.end();
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Pair } from "bitecs";
|
|
2
2
|
import { setTraits } from "../../core/component";
|
|
3
|
-
import { ChildOf, type State, type
|
|
3
|
+
import { ChildOf, type State, type PostLoadContext } from "../../core";
|
|
4
4
|
import { Tween, TweenState, ensureResolved, captureFromValue } from "./tween";
|
|
5
5
|
|
|
6
6
|
const compareNumbers = (a: number, b: number) => a - b;
|
|
@@ -35,7 +35,7 @@ setTraits(Sequence, {
|
|
|
35
35
|
}),
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
export function finalizeSequences(_state: State, _context:
|
|
38
|
+
export function finalizeSequences(_state: State, _context: PostLoadContext): void {}
|
|
39
39
|
|
|
40
40
|
function getChildrenSorted(state: State, parentEid: number): number[] {
|
|
41
41
|
childrenBuffer.length = 0;
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
registerPostLoadHook,
|
|
4
4
|
type State,
|
|
5
5
|
type System,
|
|
6
|
-
type ParseContext,
|
|
7
6
|
type Plugin,
|
|
7
|
+
type PostLoadContext,
|
|
8
8
|
} from "../../core";
|
|
9
9
|
import { setTraits } from "../../core/component";
|
|
10
10
|
import { getEasingIndex, getEasing } from "./easing";
|
|
@@ -57,7 +57,7 @@ setTraits(Tween, {
|
|
|
57
57
|
delay: 0,
|
|
58
58
|
easingIndex: 0,
|
|
59
59
|
}),
|
|
60
|
-
adapter: (attrs: Record<string, string>,
|
|
60
|
+
adapter: (attrs: Record<string, string>, eid: number) => {
|
|
61
61
|
const parsed = parseTweenAttrs(attrs);
|
|
62
62
|
const result: Record<string, number> = {};
|
|
63
63
|
|
|
@@ -66,7 +66,7 @@ setTraits(Tween, {
|
|
|
66
66
|
if (parsed.easing) result.easingIndex = getEasingIndex(parsed.easing);
|
|
67
67
|
|
|
68
68
|
if (parsed.target) {
|
|
69
|
-
setupTweenFromXml(
|
|
69
|
+
setupTweenFromXml(parsed, eid);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
return result;
|
|
@@ -110,12 +110,7 @@ interface PendingTween {
|
|
|
110
110
|
|
|
111
111
|
let pendingXmlTweens: PendingTween[] = [];
|
|
112
112
|
|
|
113
|
-
function setupTweenFromXml(
|
|
114
|
-
_state: State,
|
|
115
|
-
_context: ParseContext,
|
|
116
|
-
attrs: Record<string, string>,
|
|
117
|
-
tweenEid: number
|
|
118
|
-
): void {
|
|
113
|
+
function setupTweenFromXml(attrs: Record<string, string>, tweenEid: number): void {
|
|
119
114
|
pendingXmlTweens.push({
|
|
120
115
|
tweenEid,
|
|
121
116
|
target: attrs.target,
|
|
@@ -123,7 +118,7 @@ function setupTweenFromXml(
|
|
|
123
118
|
});
|
|
124
119
|
}
|
|
125
120
|
|
|
126
|
-
export function finalizePendingTweens(state: State, context:
|
|
121
|
+
export function finalizePendingTweens(state: State, context: PostLoadContext): void {
|
|
127
122
|
for (const pending of pendingXmlTweens) {
|
|
128
123
|
const parsed = parseTargetPath(pending.target);
|
|
129
124
|
if (!parsed) continue;
|
|
@@ -135,7 +130,14 @@ export function finalizePendingTweens(state: State, context: ParseContext): void
|
|
|
135
130
|
if (!binding) continue;
|
|
136
131
|
|
|
137
132
|
state.addRelation(pending.tweenEid, TweenTarget, targetEid);
|
|
138
|
-
|
|
133
|
+
const toValue =
|
|
134
|
+
pending.to.startsWith("0x") || pending.to.startsWith("0X")
|
|
135
|
+
? parseInt(pending.to, 16)
|
|
136
|
+
: parseFloat(pending.to);
|
|
137
|
+
if (!Number.isFinite(toValue)) {
|
|
138
|
+
throw new Error(`Tween has invalid 'to' value: "${pending.to}" (parsed as ${toValue})`);
|
|
139
|
+
}
|
|
140
|
+
Tween.to[pending.tweenEid] = toValue;
|
|
139
141
|
}
|
|
140
142
|
pendingXmlTweens = [];
|
|
141
143
|
}
|
|
@@ -159,8 +161,12 @@ export function ensureResolved(state: State, tweenEid: number): void {
|
|
|
159
161
|
const binding = state.getFieldAccessor(tweenEid);
|
|
160
162
|
|
|
161
163
|
if (binding && targetEid >= 0) {
|
|
164
|
+
const toValue = Tween.to[tweenEid];
|
|
165
|
+
if (!Number.isFinite(toValue)) {
|
|
166
|
+
throw new Error(`Tween ${tweenEid} has invalid to value: ${toValue}`);
|
|
167
|
+
}
|
|
162
168
|
Tween.from[tweenEid] = binding.get(targetEid) ?? 0;
|
|
163
|
-
binding.set(targetEid,
|
|
169
|
+
binding.set(targetEid, toValue);
|
|
164
170
|
}
|
|
165
171
|
|
|
166
172
|
Tween.elapsed[tweenEid] = duration;
|
|
@@ -188,7 +194,13 @@ function updateTweens(state: State, dt: number): void {
|
|
|
188
194
|
|
|
189
195
|
const elapsed = Tween.elapsed[tweenEid];
|
|
190
196
|
const duration = Tween.duration[tweenEid];
|
|
191
|
-
const rawProgress = Math.min(elapsed / duration, 1);
|
|
197
|
+
const rawProgress = duration <= 0 ? 1 : Math.min(elapsed / duration, 1);
|
|
198
|
+
|
|
199
|
+
if (!Number.isFinite(rawProgress)) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Tween ${tweenEid} invalid progress: elapsed=${elapsed}, duration=${duration}, dt=${dt}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
192
204
|
|
|
193
205
|
const easingFn = getEasing(Tween.easingIndex[tweenEid]);
|
|
194
206
|
const easedProgress = easingFn(rawProgress);
|
|
@@ -197,6 +209,12 @@ function updateTweens(state: State, dt: number): void {
|
|
|
197
209
|
const to = Tween.to[tweenEid];
|
|
198
210
|
const value = from + (to - from) * easedProgress;
|
|
199
211
|
|
|
212
|
+
if (!Number.isFinite(value)) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Tween ${tweenEid} computed NaN: from=${from}, to=${to}, eased=${easedProgress}, raw=${rawProgress}`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
200
218
|
if (binding && targetEid >= 0) {
|
|
201
219
|
binding.set(targetEid, value);
|
|
202
220
|
}
|