@pascal-app/viewer 1.0.4 → 1.0.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/dist/capture/capture-runtime.js +1 -1
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.d.ts +1 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +4 -1
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/glb-interactive.js +1 -1
- package/dist/components/viewer/glb-reference-nodes.js +1 -1
- package/dist/components/viewer/glb-replace-instances.js +1 -1
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +2 -3
- package/dist/components/viewer/glb-walkthrough-controller.js +1 -1
- package/dist/components/viewer/index.d.ts +10 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +28 -12
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +107 -21
- package/dist/components/viewer/perf-panel.d.ts.map +1 -1
- package/dist/components/viewer/perf-panel.js +1 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +96 -33
- package/dist/components/viewer/registered-systems.js +1 -1
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
- package/dist/components/viewer/scene-bvh.js +1 -1
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +31 -11
- package/dist/components/viewer/selection-manager.js +15 -7
- package/dist/components/viewer/underground-ambiance.d.ts +9 -0
- package/dist/components/viewer/underground-ambiance.d.ts.map +1 -0
- package/dist/components/viewer/underground-ambiance.js +105 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +1 -1
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/index.d.ts +16 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- package/dist/lib/backdrop.d.ts +17 -0
- package/dist/lib/backdrop.d.ts.map +1 -1
- package/dist/lib/backdrop.js +18 -1
- package/dist/lib/construction-dust.d.ts +57 -0
- package/dist/lib/construction-dust.d.ts.map +1 -0
- package/dist/lib/construction-dust.js +187 -0
- package/dist/lib/csg-utils.js +1 -1
- package/dist/lib/foundation-materials.d.ts +26 -0
- package/dist/lib/foundation-materials.d.ts.map +1 -0
- package/dist/lib/foundation-materials.js +65 -0
- package/dist/lib/gltf-unlit.d.ts +10 -0
- package/dist/lib/gltf-unlit.d.ts.map +1 -0
- package/dist/lib/gltf-unlit.js +23 -0
- package/dist/lib/ground-window.d.ts +33 -0
- package/dist/lib/ground-window.d.ts.map +1 -0
- package/dist/lib/ground-window.js +114 -0
- package/dist/lib/hero-pose.d.ts +1 -1
- package/dist/lib/hero-pose.d.ts.map +1 -1
- package/dist/lib/ink-edges.d.ts +2 -1
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.d.ts +7 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +35 -0
- package/dist/lib/layer-pass.d.ts.map +1 -1
- package/dist/lib/layers.d.ts +11 -7
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +16 -6
- package/dist/lib/materials.d.ts +5 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +34 -4
- package/dist/lib/merged-outline-node.d.ts +5 -0
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +80 -2
- package/dist/lib/pointer-events.d.ts +20 -4
- package/dist/lib/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +78 -14
- package/dist/lib/post-processing-resources.d.ts.map +1 -1
- package/dist/lib/reveal-candidates.d.ts +80 -0
- package/dist/lib/reveal-candidates.d.ts.map +1 -0
- package/dist/lib/reveal-candidates.js +524 -0
- package/dist/lib/reveal-ghost.d.ts +9 -0
- package/dist/lib/reveal-ghost.d.ts.map +1 -0
- package/dist/lib/reveal-ghost.js +62 -0
- package/dist/lib/reveal-pending.d.ts +12 -0
- package/dist/lib/reveal-pending.d.ts.map +1 -0
- package/dist/lib/reveal-pending.js +12 -0
- package/dist/lib/reveal-pose.d.ts +70 -0
- package/dist/lib/reveal-pose.d.ts.map +1 -0
- package/dist/lib/reveal-pose.js +248 -0
- package/dist/lib/reveal-schedule.d.ts +208 -0
- package/dist/lib/reveal-schedule.d.ts.map +1 -0
- package/dist/lib/reveal-schedule.js +410 -0
- package/dist/lib/reveal-texture-scale.d.ts +5 -0
- package/dist/lib/reveal-texture-scale.d.ts.map +1 -0
- package/dist/lib/reveal-texture-scale.js +113 -0
- package/dist/lib/reveal-windows.d.ts +23 -0
- package/dist/lib/reveal-windows.d.ts.map +1 -0
- package/dist/lib/reveal-windows.js +31 -0
- package/dist/lib/reveal-wipe.d.ts +71 -0
- package/dist/lib/reveal-wipe.d.ts.map +1 -0
- package/dist/lib/reveal-wipe.js +105 -0
- package/dist/lib/scene-pass-mrt.d.ts +12 -0
- package/dist/lib/scene-pass-mrt.d.ts.map +1 -0
- package/dist/lib/scene-pass-mrt.js +23 -0
- package/dist/lib/scene-themes.d.ts +30 -8
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +36 -0
- package/dist/lib/scene-visibility.d.ts +11 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +38 -0
- package/dist/lib/site-surfaces.d.ts +32 -0
- package/dist/lib/site-surfaces.d.ts.map +1 -0
- package/dist/lib/site-surfaces.js +70 -0
- package/dist/lib/snapshot-pipeline.d.ts +2 -1
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +35 -15
- package/dist/lib/tsl-compat.d.ts +2 -2
- package/dist/lib/tsl-compat.d.ts.map +1 -1
- package/dist/lib/underground-ambiance.d.ts +168 -0
- package/dist/lib/underground-ambiance.d.ts.map +1 -0
- package/dist/lib/underground-ambiance.js +334 -0
- package/dist/store/use-viewer.d.ts +184 -27
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +155 -19
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +2 -0
- package/dist/systems/construction-reveal/construction-dust.d.ts +16 -0
- package/dist/systems/construction-reveal/construction-dust.d.ts.map +1 -0
- package/dist/systems/construction-reveal/construction-dust.js +92 -0
- package/dist/systems/construction-reveal/construction-reveal.d.ts +276 -0
- package/dist/systems/construction-reveal/construction-reveal.d.ts.map +1 -0
- package/dist/systems/construction-reveal/construction-reveal.js +2383 -0
- package/dist/systems/construction-reveal/dust-material.d.ts +10 -0
- package/dist/systems/construction-reveal/dust-material.d.ts.map +1 -0
- package/dist/systems/construction-reveal/dust-material.js +17 -0
- package/dist/systems/construction-reveal/ghost-dress.d.ts +10 -0
- package/dist/systems/construction-reveal/ghost-dress.d.ts.map +1 -0
- package/dist/systems/construction-reveal/ghost-dress.js +24 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts +7 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-interaction-system.js +18 -1
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/interactive/control-widget.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +16 -6
- package/dist/systems/interactive/scripted-clips.js +1 -1
- package/dist/systems/item-light/item-light-system.js +1 -1
- package/dist/systems/level/level-hold.d.ts +4 -0
- package/dist/systems/level/level-hold.d.ts.map +1 -0
- package/dist/systems/level/level-hold.js +14 -0
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +16 -1
- package/dist/systems/level/level-utils.d.ts +13 -3
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +56 -8
- package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts +103 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts.map +1 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.js +317 -0
- package/dist/systems/roof/roof-materials.d.ts +4 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +18 -24
- package/dist/systems/roof/roof-system.d.ts +8 -1
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +195 -45
- package/dist/systems/wall/wall-cutout-cache.d.ts +32 -3
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout-cache.js +95 -2
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +39 -4
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.d.ts +5 -0
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +42 -19
- package/dist/systems/window/corner-frame.d.ts +10 -0
- package/dist/systems/window/corner-frame.d.ts.map +1 -0
- package/dist/systems/window/corner-frame.js +17 -0
- package/dist/systems/window/window-system.d.ts +1 -1
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +31 -6
- package/package.json +8 -8
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { REVEAL_ARRIVAL } from './reveal-pose';
|
|
2
|
+
export const REVEAL_PHASE_ORDER = [
|
|
3
|
+
'foundation',
|
|
4
|
+
'structure',
|
|
5
|
+
'circulation',
|
|
6
|
+
'openings',
|
|
7
|
+
'roof',
|
|
8
|
+
'furnishing',
|
|
9
|
+
];
|
|
10
|
+
/** The one place to tune the reveal's pace; the heights live in the kinds' declarations. */
|
|
11
|
+
export const REVEAL_TIMING = {
|
|
12
|
+
nodeGapMs: 70,
|
|
13
|
+
groupGapMs: 280,
|
|
14
|
+
capMs: 6000,
|
|
15
|
+
durationMs: { rise: 650, scale: 400, settle: 380, drop: 620, cut: 260, assemble: 0 },
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* How long `style` moves its node in `table`. Only the Framing level's table has a `cover` time (it
|
|
19
|
+
* lays a roof's covering where Full drops the roof whole); a plan made on a Full table or the reverse
|
|
20
|
+
* play of a framing build gives a covering twice what the whole roof takes to come or go.
|
|
21
|
+
*/
|
|
22
|
+
export function styleMs(table, style) {
|
|
23
|
+
if (style !== 'cover')
|
|
24
|
+
return table[style];
|
|
25
|
+
return table.cover ?? 2 * (table.assemble > 0 ? table.assemble : table.drop);
|
|
26
|
+
}
|
|
27
|
+
export function revealGroupKey(candidate) {
|
|
28
|
+
return `${candidate.phase}:${candidate.levelId ?? ''}`;
|
|
29
|
+
}
|
|
30
|
+
/** A stable pseudo-random key from a node id (FNV-1a): a plan scatters the same way every time. */
|
|
31
|
+
function scatterKey(id) {
|
|
32
|
+
let hash = 0x811c9dc5;
|
|
33
|
+
for (let index = 0; index < id.length; index += 1) {
|
|
34
|
+
hash ^= id.charCodeAt(index);
|
|
35
|
+
hash = Math.imul(hash, 0x01000193);
|
|
36
|
+
}
|
|
37
|
+
return hash >>> 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Start times for `candidates`, from `startMs`. Phase after phase; within a phase every floor
|
|
41
|
+
* plays at once, its nodes in a scattered order (GSAP's stagger from "random") and evenly spaced,
|
|
42
|
+
* floors interleaved within one gap, so no frame starts a crowd. On a floor, what stands on
|
|
43
|
+
* something (a book on a shelf) starts after it.
|
|
44
|
+
*/
|
|
45
|
+
export function planReveal(candidates, startMs, timing = REVEAL_TIMING) {
|
|
46
|
+
const floorsByPhase = new Map();
|
|
47
|
+
for (const candidate of candidates) {
|
|
48
|
+
const floors = floorsByPhase.get(candidate.phase) ?? new Map();
|
|
49
|
+
floorsByPhase.set(candidate.phase, floors);
|
|
50
|
+
const floor = `${candidate.levelIndex}:${candidate.levelId ?? ''}`;
|
|
51
|
+
floors.set(floor, [...(floors.get(floor) ?? []), candidate]);
|
|
52
|
+
}
|
|
53
|
+
const planned = [];
|
|
54
|
+
let phaseStart = 0;
|
|
55
|
+
let end = 0;
|
|
56
|
+
let longest = 0;
|
|
57
|
+
for (const phase of REVEAL_PHASE_ORDER) {
|
|
58
|
+
const floors = floorsByPhase.get(phase);
|
|
59
|
+
if (!floors)
|
|
60
|
+
continue;
|
|
61
|
+
const lanes = [...floors.entries()].sort(([left], [right]) => left.localeCompare(right, undefined, { numeric: true }));
|
|
62
|
+
let phaseEnd = phaseStart;
|
|
63
|
+
lanes.forEach(([, nodes], lane) => {
|
|
64
|
+
const scattered = [...nodes].sort((left, right) => left.depth - right.depth ||
|
|
65
|
+
scatterKey(left.id) - scatterKey(right.id) ||
|
|
66
|
+
left.seq - right.seq);
|
|
67
|
+
scattered.forEach((candidate, index) => {
|
|
68
|
+
const offset = phaseStart + (index + lane / lanes.length) * timing.nodeGapMs;
|
|
69
|
+
planned.push({ candidate, offset });
|
|
70
|
+
phaseEnd = Math.max(phaseEnd, offset);
|
|
71
|
+
longest = Math.max(longest, styleMs(timing.durationMs, candidate.style));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
end = phaseEnd;
|
|
75
|
+
phaseStart = phaseEnd + timing.groupGapMs;
|
|
76
|
+
}
|
|
77
|
+
const room = Math.max(0, timing.capMs - longest);
|
|
78
|
+
const compression = end > room ? room / end : 1;
|
|
79
|
+
const arrival = ({ candidate, offset }) => offset * compression +
|
|
80
|
+
styleMs(timing.durationMs, candidate.style) * REVEAL_ARRIVAL[candidate.style];
|
|
81
|
+
let finale = null;
|
|
82
|
+
for (const entry of planned) {
|
|
83
|
+
// Held still, an assembled node never arrives; its parts do.
|
|
84
|
+
if (entry.candidate.style === 'assemble')
|
|
85
|
+
continue;
|
|
86
|
+
if (!finale || arrival(entry) >= arrival(finale))
|
|
87
|
+
finale = entry;
|
|
88
|
+
}
|
|
89
|
+
return planned
|
|
90
|
+
.sort((left, right) => left.offset - right.offset || left.candidate.seq - right.candidate.seq)
|
|
91
|
+
.map((entry) => ({
|
|
92
|
+
...entry.candidate,
|
|
93
|
+
group: revealGroupKey(entry.candidate),
|
|
94
|
+
startMs: startMs + entry.offset * compression,
|
|
95
|
+
...(entry === finale ? { finale: true } : {}),
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The Framing level's pace: one factor on its whole timeline, the staging and the motions alike. 1 is
|
|
100
|
+
* the pace it was built at; 1.75 makes a build that took 20 s take 35 s, and anything smaller or
|
|
101
|
+
* larger takes proportionally longer. A pace of `p` multiplies every offset, gap, beat, duration and
|
|
102
|
+
* cap of this level (`LAZY_REVEAL_TIMING`) and of the frame each wall plays before it rises (Bones'
|
|
103
|
+
* `WALL_BUILD`), so pieces still snap and the build only plays out slower. The Full level reads
|
|
104
|
+
* `REVEAL_TIMING` and does not move with it.
|
|
105
|
+
*/
|
|
106
|
+
export const FRAMING_PACE = 1.75;
|
|
107
|
+
/** The lazy schedule at pace 1; `LAZY_REVEAL_TIMING` is this at `FRAMING_PACE`. */
|
|
108
|
+
export const LAZY_REVEAL_BASE_TIMING = {
|
|
109
|
+
floorOffsetMs: 1200,
|
|
110
|
+
roomOffsetMs: 420,
|
|
111
|
+
elementOffsetMs: {
|
|
112
|
+
foundation: 200,
|
|
113
|
+
structure: 360,
|
|
114
|
+
circulation: 260,
|
|
115
|
+
openings: 140,
|
|
116
|
+
roof: 180,
|
|
117
|
+
furnishing: 110,
|
|
118
|
+
},
|
|
119
|
+
phaseGapMs: 260,
|
|
120
|
+
jitter: 0.3,
|
|
121
|
+
capMs: 40_000,
|
|
122
|
+
durationMs: { rise: 760, scale: 520, settle: 640, drop: 820, cut: 320, assemble: 0, cover: 4200 },
|
|
123
|
+
coverMsPerM: 450,
|
|
124
|
+
coverMinMs: 1400,
|
|
125
|
+
overshoot: { rise: 0.7, scale: 1.6, settle: 1.6 },
|
|
126
|
+
minHeightM: { drop: 1.8, settle: 0.6 },
|
|
127
|
+
};
|
|
128
|
+
/** `timing` played at `pace` times its length: times scale, shares and heights do not. */
|
|
129
|
+
export function pacedLazyTiming(timing, pace) {
|
|
130
|
+
const scaled = (table) => Object.fromEntries(Object.entries(table).map(([key, ms]) => [key, ms * pace]));
|
|
131
|
+
return {
|
|
132
|
+
...timing,
|
|
133
|
+
floorOffsetMs: timing.floorOffsetMs * pace,
|
|
134
|
+
roomOffsetMs: timing.roomOffsetMs * pace,
|
|
135
|
+
elementOffsetMs: scaled(timing.elementOffsetMs),
|
|
136
|
+
phaseGapMs: timing.phaseGapMs * pace,
|
|
137
|
+
capMs: timing.capMs * pace,
|
|
138
|
+
durationMs: scaled(timing.durationMs),
|
|
139
|
+
coverMsPerM: timing.coverMsPerM * pace,
|
|
140
|
+
coverMinMs: timing.coverMinMs * pace,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** The one place the lazy schedule and the framing level's motion are read from. */
|
|
144
|
+
export const LAZY_REVEAL_TIMING = pacedLazyTiming(LAZY_REVEAL_BASE_TIMING, FRAMING_PACE);
|
|
145
|
+
/**
|
|
146
|
+
* The person's animation speed: how fast the build animations play, as a multiple of today's.
|
|
147
|
+
* Higher is faster, so every time of a schedule is divided by it: ×2 plays a build in half the
|
|
148
|
+
* time, ×0.5 in twice as long. ×1 is every table exactly as shipped (the very same objects).
|
|
149
|
+
*/
|
|
150
|
+
export const ANIMATION_SPEEDS = [0.5, 0.75, 1, 1.5, 2];
|
|
151
|
+
export const DEFAULT_ANIMATION_SPEED = 1;
|
|
152
|
+
/** The step nearest to `value`; anything that is not a finite positive number is ×1. */
|
|
153
|
+
export function normalizeAnimationSpeed(value) {
|
|
154
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
155
|
+
return DEFAULT_ANIMATION_SPEED;
|
|
156
|
+
return ANIMATION_SPEEDS.reduce((best, step) => Math.abs(step - value) < Math.abs(best - value) ? step : best);
|
|
157
|
+
}
|
|
158
|
+
/** `timing` with every time divided by `speed`: durations, gaps and the cap. */
|
|
159
|
+
export function pacedRevealTiming(timing, speed) {
|
|
160
|
+
return {
|
|
161
|
+
nodeGapMs: timing.nodeGapMs / speed,
|
|
162
|
+
groupGapMs: timing.groupGapMs / speed,
|
|
163
|
+
capMs: timing.capMs / speed,
|
|
164
|
+
durationMs: scaleTimes(timing.durationMs, 1 / speed),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** `timing` with every time divided by `speed`; the share `speed` of its pace is not a time. */
|
|
168
|
+
export function pacedRetractTiming(timing, speed) {
|
|
169
|
+
return {
|
|
170
|
+
...timing,
|
|
171
|
+
nodeGapMs: timing.nodeGapMs / speed,
|
|
172
|
+
capMs: timing.capMs / speed,
|
|
173
|
+
durationMs: scaleTimes(timing.durationMs, 1 / speed),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function scaleTimes(table, factor) {
|
|
177
|
+
return Object.fromEntries(Object.entries(table).map(([key, ms]) => [key, ms === undefined ? ms : ms * factor]));
|
|
178
|
+
}
|
|
179
|
+
/** The Full level's schedule at `speed`; at ×1 it is `REVEAL_TIMING` itself. */
|
|
180
|
+
export const revealTimingAt = (speed) => speed === 1 ? REVEAL_TIMING : pacedRevealTiming(REVEAL_TIMING, speed);
|
|
181
|
+
/** The Framing level's schedule at `speed`; at ×1 it is `LAZY_REVEAL_TIMING` itself. */
|
|
182
|
+
export const lazyTimingAt = (speed) => speed === 1 ? LAZY_REVEAL_TIMING : pacedLazyTiming(LAZY_REVEAL_BASE_TIMING, FRAMING_PACE / speed);
|
|
183
|
+
/** The reverse play's schedule at `speed`; at ×1 it is `RETRACT_TIMING` itself. */
|
|
184
|
+
export const retractTimingAt = (speed) => speed === 1 ? RETRACT_TIMING : pacedRetractTiming(RETRACT_TIMING, speed);
|
|
185
|
+
/** A stable share in [0, 1) from an id: the same jitter every time. */
|
|
186
|
+
function jitterKey(id) {
|
|
187
|
+
return (scatterKey(`${id}#jitter`) % 10_007) / 10_007;
|
|
188
|
+
}
|
|
189
|
+
const STAGE_A = ['foundation', 'structure', 'circulation', 'openings'];
|
|
190
|
+
/**
|
|
191
|
+
* Start times for `candidates` on the lazy, nested schedule, from `startMs`:
|
|
192
|
+
* - each floor builds its foundation, structure, stairs and openings, a phase after the other, and
|
|
193
|
+
* the floors go up side by side, `floorOffsetMs` apart;
|
|
194
|
+
* - in a phase, the rooms (`cluster`) start `roomOffsetMs` apart in their sweep order, and the
|
|
195
|
+
* pieces of a room `elementOffsetMs` apart around it; what belongs to the whole floor comes first;
|
|
196
|
+
* - roofs come once every floor is up, then the furniture, floor by floor and room by room again;
|
|
197
|
+
* - a piece with a preroll starts after it (its plugin plays then), and its room's next phase waits.
|
|
198
|
+
* Every start is jittered. The plan grows with the build (its biggest room, its rooms, its floors),
|
|
199
|
+
* and is pulled together only past `capMs`. `startMs` of a planned entry is when the node itself
|
|
200
|
+
* moves; its preroll plays before.
|
|
201
|
+
*/
|
|
202
|
+
export function planLazyReveal(candidates, startMs, timing = LAZY_REVEAL_TIMING) {
|
|
203
|
+
const placed = [];
|
|
204
|
+
const floors = new Map();
|
|
205
|
+
for (const candidate of candidates) {
|
|
206
|
+
const floor = `${candidate.levelIndex}:${candidate.levelId ?? ''}`;
|
|
207
|
+
const list = floors.get(floor);
|
|
208
|
+
if (list)
|
|
209
|
+
list.push(candidate);
|
|
210
|
+
else
|
|
211
|
+
floors.set(floor, [candidate]);
|
|
212
|
+
}
|
|
213
|
+
const lanes = [...floors.entries()].sort(([left], [right]) => left.localeCompare(right, undefined, { numeric: true }));
|
|
214
|
+
const jittered = (id, offset) => (2 * jitterKey(id) - 1) * timing.jitter * offset;
|
|
215
|
+
/** Places one floor's nodes of one phase from `from`; returns when its last piece moves, or null. */
|
|
216
|
+
const placePhase = (nodes, phase, from) => {
|
|
217
|
+
if (nodes.length === 0)
|
|
218
|
+
return null;
|
|
219
|
+
const rooms = new Map();
|
|
220
|
+
for (const node of nodes) {
|
|
221
|
+
const room = node.cluster ?? '';
|
|
222
|
+
const list = rooms.get(room);
|
|
223
|
+
if (list)
|
|
224
|
+
list.push(node);
|
|
225
|
+
else
|
|
226
|
+
rooms.set(room, [node]);
|
|
227
|
+
}
|
|
228
|
+
const sweepOf = (members) => Math.min(...members.map((member) => member.sweep ?? Number.NEGATIVE_INFINITY));
|
|
229
|
+
const ordered = [...rooms.entries()].sort(([leftKey, left], [rightKey, right]) =>
|
|
230
|
+
// What belongs to the whole floor first, then the rooms in their sweep.
|
|
231
|
+
(leftKey === '' ? -1 : 0) - (rightKey === '' ? -1 : 0) ||
|
|
232
|
+
sweepOf(left) - sweepOf(right) ||
|
|
233
|
+
leftKey.localeCompare(rightKey));
|
|
234
|
+
const step = timing.elementOffsetMs[phase];
|
|
235
|
+
let last = from;
|
|
236
|
+
ordered.forEach(([room, members], index) => {
|
|
237
|
+
const roomStart = from +
|
|
238
|
+
index * timing.roomOffsetMs +
|
|
239
|
+
jittered(`${room}:${phase}`, index > 0 ? timing.roomOffsetMs : 0);
|
|
240
|
+
members.sort((left, right) => left.depth - right.depth ||
|
|
241
|
+
(left.order ?? 0) - (right.order ?? 0) ||
|
|
242
|
+
scatterKey(left.id) - scatterKey(right.id) ||
|
|
243
|
+
left.seq - right.seq);
|
|
244
|
+
members.forEach((candidate, rank) => {
|
|
245
|
+
const turn = Math.max(from, roomStart + rank * step + jittered(candidate.id, step));
|
|
246
|
+
const preroll = Math.max(0, candidate.prerollMs ?? 0);
|
|
247
|
+
placed.push({ candidate, motion: turn + preroll, preroll });
|
|
248
|
+
last = Math.max(last, turn + preroll);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
return last;
|
|
252
|
+
};
|
|
253
|
+
const phaseOf = (nodes, phase) => nodes.filter((node) => node.phase === phase);
|
|
254
|
+
/** Every floor builds `phases` from `from`, `floorOffsetMs` apart; returns when the last piece moves. */
|
|
255
|
+
const stage = (phases, from) => {
|
|
256
|
+
let end = null;
|
|
257
|
+
let lane = 0;
|
|
258
|
+
for (const [, nodes] of lanes) {
|
|
259
|
+
const own = phases.flatMap((phase) => phaseOf(nodes, phase));
|
|
260
|
+
if (own.length === 0)
|
|
261
|
+
continue;
|
|
262
|
+
let cursor = from + lane * timing.floorOffsetMs;
|
|
263
|
+
lane += 1;
|
|
264
|
+
for (const phase of phases) {
|
|
265
|
+
const last = placePhase(phaseOf(nodes, phase), phase, cursor);
|
|
266
|
+
if (last === null)
|
|
267
|
+
continue;
|
|
268
|
+
end = Math.max(end ?? last, last);
|
|
269
|
+
cursor = last + timing.phaseGapMs;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return end;
|
|
273
|
+
};
|
|
274
|
+
let cursor = 0;
|
|
275
|
+
for (const phases of [STAGE_A, ['roof'], ['furnishing']]) {
|
|
276
|
+
const end = stage(phases, cursor);
|
|
277
|
+
if (end !== null)
|
|
278
|
+
cursor = end + timing.phaseGapMs;
|
|
279
|
+
}
|
|
280
|
+
const longest = Math.max(0, ...placed.map(({ candidate }) => timing.durationMs[candidate.style]));
|
|
281
|
+
const lastMotion = Math.max(0, ...placed.map(({ motion }) => motion));
|
|
282
|
+
const room = Math.max(0, timing.capMs - longest);
|
|
283
|
+
const compression = lastMotion > room ? room / lastMotion : 1;
|
|
284
|
+
const arrival = ({ candidate, motion }) => motion * compression + timing.durationMs[candidate.style] * REVEAL_ARRIVAL[candidate.style];
|
|
285
|
+
let finale = null;
|
|
286
|
+
for (const entry of placed) {
|
|
287
|
+
if (entry.candidate.style === 'assemble')
|
|
288
|
+
continue;
|
|
289
|
+
if (!finale || arrival(entry) >= arrival(finale))
|
|
290
|
+
finale = entry;
|
|
291
|
+
}
|
|
292
|
+
return placed
|
|
293
|
+
.sort((left, right) => left.motion - right.motion || left.candidate.seq - right.candidate.seq)
|
|
294
|
+
.map((entry) => ({
|
|
295
|
+
...entry.candidate,
|
|
296
|
+
group: revealGroupKey(entry.candidate),
|
|
297
|
+
startMs: startMs + entry.motion * compression,
|
|
298
|
+
...(entry.preroll > 0 ? { prerollMs: entry.preroll * compression } : {}),
|
|
299
|
+
...(entry === finale ? { finale: true } : {}),
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
/** The one place to tune the reverse play's pace. */
|
|
303
|
+
export const RETRACT_TIMING = {
|
|
304
|
+
speed: 0.62,
|
|
305
|
+
nodeGapMs: REVEAL_TIMING.nodeGapMs * 0.62,
|
|
306
|
+
capMs: 2400,
|
|
307
|
+
durationMs: { rise: 420, scale: 300, settle: 380, drop: 400, cut: 260, assemble: 520 },
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* The reverse play at the Framing level: the rewind's durations at the Framing pace, so a deleted
|
|
311
|
+
* floor comes apart as slowly as it went up. `capMs` keeps a whole floor's deconstruction in a
|
|
312
|
+
* bounded time however many pieces it takes back.
|
|
313
|
+
*/
|
|
314
|
+
/** A covering lifts off in this share of the time it took to lay. */
|
|
315
|
+
export const COVER_REVERSE_SHARE = 0.6;
|
|
316
|
+
export const LAZY_RETRACT_TIMING = {
|
|
317
|
+
speed: RETRACT_TIMING.speed,
|
|
318
|
+
nodeGapMs: RETRACT_TIMING.nodeGapMs * FRAMING_PACE,
|
|
319
|
+
capMs: 14_000,
|
|
320
|
+
durationMs: {
|
|
321
|
+
...scaleTimes(RETRACT_TIMING.durationMs, FRAMING_PACE),
|
|
322
|
+
cover: LAZY_REVEAL_TIMING.durationMs.cover * COVER_REVERSE_SHARE,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* How long a roof's covering takes by the size it covers at the Framing level at `speed`: its range
|
|
327
|
+
* in metres of sweep (`coverDurationMs`); `reverse` for the lifting off of a deletion or an undo.
|
|
328
|
+
*/
|
|
329
|
+
export function coverRangeAt(speed, reverse = false) {
|
|
330
|
+
const timing = lazyTimingAt(speed);
|
|
331
|
+
const share = reverse ? COVER_REVERSE_SHARE : 1;
|
|
332
|
+
return {
|
|
333
|
+
perMeterMs: timing.coverMsPerM * share,
|
|
334
|
+
minMs: timing.coverMinMs * share,
|
|
335
|
+
maxMs: timing.durationMs.cover * share,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
/** The Framing level's reverse play at `speed`; at ×1 it is `LAZY_RETRACT_TIMING` itself. */
|
|
339
|
+
export const lazyRetractTimingAt = (speed) => speed === 1 ? LAZY_RETRACT_TIMING : pacedRetractTiming(LAZY_RETRACT_TIMING, speed);
|
|
340
|
+
/**
|
|
341
|
+
* The Framing build played backwards, for a deletion or an undo: the plan of the build (floor by
|
|
342
|
+
* floor, room by room, piece by piece, `planLazyReveal`) read from its end, so the furniture lifts
|
|
343
|
+
* out first, then the roof, the openings, the walls and the slabs last, and the floors come apart
|
|
344
|
+
* from the top down, each room as it went up. Each piece takes its own style's rewind time; a piece
|
|
345
|
+
* that had a preroll keeps it, after its own going, for the plugin that took it apart (a wall's
|
|
346
|
+
* boards come off and its studs fly up then). The whole ends within the cap by pulling the starts
|
|
347
|
+
* closer, never by skipping a piece.
|
|
348
|
+
*/
|
|
349
|
+
export function planLazyRetract(candidates, startMs, timing = LAZY_RETRACT_TIMING, build = LAZY_REVEAL_TIMING) {
|
|
350
|
+
const forward = planLazyReveal(candidates, 0, build);
|
|
351
|
+
const lastTurn = Math.max(0, ...forward.map((entry) => entry.startMs));
|
|
352
|
+
const placed = forward.map((entry) => ({
|
|
353
|
+
entry,
|
|
354
|
+
offset: (lastTurn - entry.startMs) * timing.speed,
|
|
355
|
+
}));
|
|
356
|
+
const longest = Math.max(0, ...placed.map(({ entry }) => styleMs(timing.durationMs, entry.style)));
|
|
357
|
+
const lastStart = Math.max(0, ...placed.map(({ offset }) => offset));
|
|
358
|
+
const room = Math.max(0, timing.capMs - longest);
|
|
359
|
+
const compression = lastStart > room ? room / lastStart : 1;
|
|
360
|
+
return placed
|
|
361
|
+
.map(({ entry, offset }) => {
|
|
362
|
+
const { startMs: _start, finale: _finale, prerollMs, ...candidate } = entry;
|
|
363
|
+
return {
|
|
364
|
+
...candidate,
|
|
365
|
+
...(prerollMs ? { prerollMs: prerollMs * timing.speed * compression } : {}),
|
|
366
|
+
startMs: startMs + offset * compression,
|
|
367
|
+
durationMs: styleMs(timing.durationMs, entry.style),
|
|
368
|
+
};
|
|
369
|
+
})
|
|
370
|
+
.sort((left, right) => left.startMs - right.startMs || right.seq - left.seq);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The build played backwards, for an undo: the last phase goes first and each goes after the one
|
|
374
|
+
* built on it (the furniture, the roof, the openings, the walls, the slab), and inside a phase the last
|
|
375
|
+
* piece to arrive is the first to go, so the plan reads one phase at a time as the build did. It is
|
|
376
|
+
* quicker; each piece takes its own style's rewind duration; and the whole ends within the cap by
|
|
377
|
+
* pulling the starts closer, never by skipping a piece.
|
|
378
|
+
*/
|
|
379
|
+
export function planRetract(candidates, startMs, timing = RETRACT_TIMING, build = REVEAL_TIMING) {
|
|
380
|
+
const forward = planReveal(candidates, 0, build);
|
|
381
|
+
const byPhase = new Map();
|
|
382
|
+
for (const entry of forward)
|
|
383
|
+
byPhase.set(entry.phase, [...(byPhase.get(entry.phase) ?? []), entry]);
|
|
384
|
+
const placed = [];
|
|
385
|
+
let cursor = 0;
|
|
386
|
+
for (const phase of [...REVEAL_PHASE_ORDER].reverse()) {
|
|
387
|
+
const entries = byPhase.get(phase);
|
|
388
|
+
if (!entries)
|
|
389
|
+
continue;
|
|
390
|
+
const first = Math.min(...entries.map((entry) => entry.startMs));
|
|
391
|
+
const last = Math.max(...entries.map((entry) => entry.startMs));
|
|
392
|
+
for (const entry of entries)
|
|
393
|
+
placed.push({ entry, offset: cursor + (last - entry.startMs) * timing.speed });
|
|
394
|
+
cursor += (last - first + build.groupGapMs) * timing.speed;
|
|
395
|
+
}
|
|
396
|
+
const longest = Math.max(0, ...placed.map(({ entry }) => styleMs(timing.durationMs, entry.style)));
|
|
397
|
+
const lastStart = Math.max(0, ...placed.map(({ offset }) => offset));
|
|
398
|
+
const room = Math.max(0, timing.capMs - longest);
|
|
399
|
+
const compression = lastStart > room ? room / lastStart : 1;
|
|
400
|
+
return placed
|
|
401
|
+
.map(({ entry, offset }) => {
|
|
402
|
+
const { startMs: _start, finale: _finale, ...candidate } = entry;
|
|
403
|
+
return {
|
|
404
|
+
...candidate,
|
|
405
|
+
startMs: startMs + offset * compression,
|
|
406
|
+
durationMs: styleMs(timing.durationMs, entry.style),
|
|
407
|
+
};
|
|
408
|
+
})
|
|
409
|
+
.sort((left, right) => left.startMs - right.startMs || right.seq - left.seq);
|
|
410
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Object3D } from 'three';
|
|
2
|
+
/** Keep tiles per metre while a wall rises, without changing shared materials or cached geometry. */
|
|
3
|
+
export declare function setRiseTextureScale(root: Object3D, scaleY: number, pivotY: number): void;
|
|
4
|
+
export declare function clearRiseTextureScale(root: Object3D): void;
|
|
5
|
+
//# sourceMappingURL=reveal-texture-scale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reveal-texture-scale.d.ts","sourceRoot":"","sources":["../../src/lib/reveal-texture-scale.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAsEjF,qGAAqG;AACrG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAmCxF;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAQ1D"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { BufferAttribute, Mesh } from 'three';
|
|
2
|
+
const geometryByRoot = new WeakMap();
|
|
3
|
+
function verticalDerivatives(geometry, uv) {
|
|
4
|
+
const positions = geometry.getAttribute('position');
|
|
5
|
+
const vertical = new Float32Array(uv.count * 2);
|
|
6
|
+
if (!positions)
|
|
7
|
+
return vertical;
|
|
8
|
+
const count = geometry.index?.count ?? positions.count;
|
|
9
|
+
for (let offset = 0; offset + 2 < count; offset += 3) {
|
|
10
|
+
const first = geometry.index?.getX(offset) ?? offset;
|
|
11
|
+
const second = geometry.index?.getX(offset + 1) ?? offset + 1;
|
|
12
|
+
const third = geometry.index?.getX(offset + 2) ?? offset + 2;
|
|
13
|
+
const dy1 = positions.getY(second) - positions.getY(first);
|
|
14
|
+
const dy2 = positions.getY(third) - positions.getY(first);
|
|
15
|
+
const dx1 = positions.getX(second) - positions.getX(first);
|
|
16
|
+
const dx2 = positions.getX(third) - positions.getX(first);
|
|
17
|
+
const dz1 = positions.getZ(second) - positions.getZ(first);
|
|
18
|
+
const dz2 = positions.getZ(third) - positions.getZ(first);
|
|
19
|
+
const useX = Math.abs(dy1 * dx2 - dy2 * dx1) > Math.abs(dy1 * dz2 - dy2 * dz1);
|
|
20
|
+
const horizontal1 = useX ? dx1 : dz1;
|
|
21
|
+
const horizontal2 = useX ? dx2 : dz2;
|
|
22
|
+
const determinant = dy1 * horizontal2 - dy2 * horizontal1;
|
|
23
|
+
if (Math.abs(determinant) < 1e-10)
|
|
24
|
+
continue;
|
|
25
|
+
const du = ((uv.getX(second) - uv.getX(first)) * horizontal2 -
|
|
26
|
+
(uv.getX(third) - uv.getX(first)) * horizontal1) /
|
|
27
|
+
determinant;
|
|
28
|
+
const dv = ((uv.getY(second) - uv.getY(first)) * horizontal2 -
|
|
29
|
+
(uv.getY(third) - uv.getY(first)) * horizontal1) /
|
|
30
|
+
determinant;
|
|
31
|
+
for (const vertex of [first, second, third]) {
|
|
32
|
+
vertical[vertex * 2] = du;
|
|
33
|
+
vertical[vertex * 2 + 1] = dv;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return vertical;
|
|
37
|
+
}
|
|
38
|
+
function cloneDrawnGeometry(mesh) {
|
|
39
|
+
const original = mesh.geometry;
|
|
40
|
+
const channels = [];
|
|
41
|
+
for (const name of ['uv', 'uv1', 'uv2', 'uv3']) {
|
|
42
|
+
const attribute = original.getAttribute(name);
|
|
43
|
+
if (attribute instanceof BufferAttribute) {
|
|
44
|
+
const uv = attribute;
|
|
45
|
+
channels.push({
|
|
46
|
+
original: uv,
|
|
47
|
+
drawn: uv.clone(),
|
|
48
|
+
vertical: verticalDerivatives(original, uv),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (channels.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
const drawn = original.clone();
|
|
55
|
+
let channel = 0;
|
|
56
|
+
for (const name of ['uv', 'uv1', 'uv2', 'uv3']) {
|
|
57
|
+
const attribute = original.getAttribute(name);
|
|
58
|
+
if (attribute instanceof BufferAttribute) {
|
|
59
|
+
const entry = channels[channel++];
|
|
60
|
+
if (entry)
|
|
61
|
+
drawn.setAttribute(name, entry.drawn);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
mesh.geometry = drawn;
|
|
65
|
+
return { original, drawn, channels };
|
|
66
|
+
}
|
|
67
|
+
/** Keep tiles per metre while a wall rises, without changing shared materials or cached geometry. */
|
|
68
|
+
export function setRiseTextureScale(root, scaleY, pivotY) {
|
|
69
|
+
let geometries = geometryByRoot.get(root);
|
|
70
|
+
if (!geometries) {
|
|
71
|
+
geometries = new Map();
|
|
72
|
+
geometryByRoot.set(root, geometries);
|
|
73
|
+
}
|
|
74
|
+
const owned = geometries;
|
|
75
|
+
root.traverse((object) => {
|
|
76
|
+
if (!(object instanceof Mesh))
|
|
77
|
+
return;
|
|
78
|
+
let entry = owned.get(object);
|
|
79
|
+
if (entry && object.geometry !== entry.drawn) {
|
|
80
|
+
entry.drawn.dispose();
|
|
81
|
+
owned.delete(object);
|
|
82
|
+
entry = undefined;
|
|
83
|
+
}
|
|
84
|
+
if (!entry) {
|
|
85
|
+
const cloned = cloneDrawnGeometry(object);
|
|
86
|
+
if (!cloned)
|
|
87
|
+
return;
|
|
88
|
+
entry = cloned;
|
|
89
|
+
owned.set(object, entry);
|
|
90
|
+
}
|
|
91
|
+
const positions = entry.original.getAttribute('position');
|
|
92
|
+
if (!positions)
|
|
93
|
+
return;
|
|
94
|
+
for (const channel of entry.channels) {
|
|
95
|
+
for (let vertex = 0; vertex < channel.original.count; vertex += 1) {
|
|
96
|
+
const shift = (scaleY - 1) * (positions.getY(vertex) - pivotY);
|
|
97
|
+
channel.drawn.setXY(vertex, channel.original.getX(vertex) + shift * (channel.vertical[vertex * 2] ?? 0), channel.original.getY(vertex) + shift * (channel.vertical[vertex * 2 + 1] ?? 0));
|
|
98
|
+
}
|
|
99
|
+
channel.drawn.needsUpdate = true;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function clearRiseTextureScale(root) {
|
|
104
|
+
const geometries = geometryByRoot.get(root);
|
|
105
|
+
if (!geometries)
|
|
106
|
+
return;
|
|
107
|
+
for (const [mesh, entry] of geometries) {
|
|
108
|
+
if (mesh.geometry === entry.drawn)
|
|
109
|
+
mesh.geometry = entry.original;
|
|
110
|
+
entry.drawn.dispose();
|
|
111
|
+
}
|
|
112
|
+
geometryByRoot.delete(root);
|
|
113
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RevealPhase, SceneCommit } from '@pascal-app/core';
|
|
2
|
+
import { type ConstructionRevealLevel } from './reveal-candidates';
|
|
3
|
+
import { type RevealTiming } from './reveal-schedule';
|
|
4
|
+
/** When one (phase, level) group of a reveal plays. */
|
|
5
|
+
export type RevealWindow = {
|
|
6
|
+
phase: RevealPhase;
|
|
7
|
+
levelId: string | null;
|
|
8
|
+
/** When its first node starts, in ms from the reveal's first frame. */
|
|
9
|
+
startMs: number;
|
|
10
|
+
/** When its last node has finished moving in. */
|
|
11
|
+
endMs: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The (phase, level) windows `ConstructionReveal` plays for `commit` at
|
|
15
|
+
* `level`, as it plans them when nothing else is revealing, in ms from the
|
|
16
|
+
* reveal's first frame after the commit. For a presentation that plays beside
|
|
17
|
+
* the reveal (a plugin's own layer) and must land before a group ends.
|
|
18
|
+
*
|
|
19
|
+
* A second commit arriving mid-reveal is planned together with what is still
|
|
20
|
+
* waiting, so its real windows can shift from these.
|
|
21
|
+
*/
|
|
22
|
+
export declare function planRevealWindows(commit: SceneCommit, level?: ConstructionRevealLevel, timing?: RevealTiming): RevealWindow[];
|
|
23
|
+
//# sourceMappingURL=reveal-windows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reveal-windows.d.ts","sourceRoot":"","sources":["../../src/lib/reveal-windows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,KAAK,uBAAuB,EAA0B,MAAM,qBAAqB,CAAA;AAC1F,OAAO,EAGL,KAAK,YAAY,EAGlB,MAAM,mBAAmB,CAAA;AAE1B,uDAAuD;AACvD,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAA;IACf,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,KAAK,GAAE,uBAAgC,EACvC,MAAM,GAAE,YAA4B,GACnC,YAAY,EAAE,CAmBhB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { commitRevealCandidates } from './reveal-candidates';
|
|
2
|
+
import { planReveal, REVEAL_TIMING, revealGroupKey, styleMs, } from './reveal-schedule';
|
|
3
|
+
/**
|
|
4
|
+
* The (phase, level) windows `ConstructionReveal` plays for `commit` at
|
|
5
|
+
* `level`, as it plans them when nothing else is revealing, in ms from the
|
|
6
|
+
* reveal's first frame after the commit. For a presentation that plays beside
|
|
7
|
+
* the reveal (a plugin's own layer) and must land before a group ends.
|
|
8
|
+
*
|
|
9
|
+
* A second commit arriving mid-reveal is planned together with what is still
|
|
10
|
+
* waiting, so its real windows can shift from these.
|
|
11
|
+
*/
|
|
12
|
+
export function planRevealWindows(commit, level = 'full', timing = REVEAL_TIMING) {
|
|
13
|
+
const windows = new Map();
|
|
14
|
+
for (const entry of planReveal(commitRevealCandidates(commit, level), 0, timing)) {
|
|
15
|
+
const endMs = entry.startMs + styleMs(timing.durationMs, entry.style);
|
|
16
|
+
const key = revealGroupKey(entry);
|
|
17
|
+
const window = windows.get(key);
|
|
18
|
+
if (!window) {
|
|
19
|
+
windows.set(key, {
|
|
20
|
+
phase: entry.phase,
|
|
21
|
+
levelId: entry.levelId,
|
|
22
|
+
startMs: entry.startMs,
|
|
23
|
+
endMs,
|
|
24
|
+
});
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
window.startMs = Math.min(window.startMs, entry.startMs);
|
|
28
|
+
window.endMs = Math.max(window.endMs, endMs);
|
|
29
|
+
}
|
|
30
|
+
return [...windows.values()];
|
|
31
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { RevealStyle } from '@pascal-app/core';
|
|
2
|
+
import type { Object3D } from 'three';
|
|
3
|
+
/**
|
|
4
|
+
* The covering of a node laid over what stands under it, in place: a roof at the Framing level
|
|
5
|
+
* keeps its frame and its shell is revealed continuously from its lowest edge to its highest, the
|
|
6
|
+
* way a wall's boards and finish go over its studs. Presentation only, like the pose: the scene is not
|
|
7
|
+
* touched, and the meshes wear masked clones of their materials while it plays (the scene's
|
|
8
|
+
* materials are shared and cached, so a wipe never writes to them).
|
|
9
|
+
*
|
|
10
|
+
* The sweep runs along the height when the node rises enough for that to read as eave to ridge, and
|
|
11
|
+
* along its longer plan side when it is flat.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* What a candidate is played as: a kind's declared style, or `cover`, which an `assemble` node
|
|
15
|
+
* plays at the Framing level (a roof holds still while its covering is laid over its frame). A
|
|
16
|
+
* kind never declares it, so it is the viewer's own and `RevealStyle` does not carry it.
|
|
17
|
+
*/
|
|
18
|
+
export type PlayedStyle = RevealStyle | 'cover';
|
|
19
|
+
export type CoverAxis = 'x' | 'y' | 'z';
|
|
20
|
+
/** Where the sweep starts and ends along its axis, in world metres. */
|
|
21
|
+
export type CoverSweep = {
|
|
22
|
+
axis: CoverAxis;
|
|
23
|
+
from: number;
|
|
24
|
+
to: number;
|
|
25
|
+
};
|
|
26
|
+
/** A node that rises less than this (m) is flat: it sweeps along its longer plan side. */
|
|
27
|
+
export declare const COVER_MIN_RISE_M = 0.6;
|
|
28
|
+
type BoxLike = {
|
|
29
|
+
min: {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
z: number;
|
|
33
|
+
};
|
|
34
|
+
max: {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
z: number;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/** The sweep of a node drawn inside `box` (world space). */
|
|
41
|
+
export declare function coverSweepOf(box: BoxLike): CoverSweep;
|
|
42
|
+
/**
|
|
43
|
+
* How long a covering takes by the size of what it covers, at a level's pace and the person's speed:
|
|
44
|
+
* `perMeterMs` for each metre of the sweep, never under `minMs` (a small porch roof does not crawl,
|
|
45
|
+
* it does not flash either) and never over `maxMs` (a long flat roof does not take half a minute).
|
|
46
|
+
*/
|
|
47
|
+
export type CoverRange = {
|
|
48
|
+
perMeterMs: number;
|
|
49
|
+
minMs: number;
|
|
50
|
+
maxMs: number;
|
|
51
|
+
};
|
|
52
|
+
/** The time a covering of `sweep` takes in `range`. */
|
|
53
|
+
export declare function coverDurationMs(sweep: CoverSweep, range: CoverRange): number;
|
|
54
|
+
/**
|
|
55
|
+
* How much of the sweep the covering has closed `elapsedMs` into `durationMs`, as a share in [0, 1].
|
|
56
|
+
* It moves continuously: an ease in and out (a sine), so it starts and stops without a jolt, and it
|
|
57
|
+
* is exactly 0 at the start and 1 at the end.
|
|
58
|
+
*/
|
|
59
|
+
export declare function coverShareAt(elapsedMs: number, durationMs: number): number;
|
|
60
|
+
/**
|
|
61
|
+
* Shows what is below `at` (world metres along the sweep's axis) of everything drawn under `root`,
|
|
62
|
+
* and hides the rest. Idempotent: a frame later it only moves the level; a mesh whose renderer put
|
|
63
|
+
* its own materials back is dressed again. Cleared by `clearRevealWipe`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function setRevealWipe(root: Object3D, sweep: CoverSweep, at: number): void;
|
|
66
|
+
/** Gives every mesh under `root` its own materials and its shadow back. */
|
|
67
|
+
export declare function clearRevealWipe(root: Object3D): void;
|
|
68
|
+
/** The world coordinate the covering has reached on `root`, or null when it is not under a wipe. */
|
|
69
|
+
export declare function revealWipeLevelOf(root: Object3D): number | null;
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=reveal-wipe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reveal-wipe.d.ts","sourceRoot":"","sources":["../../src/lib/reveal-wipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,KAAK,EAAkB,QAAQ,EAAE,MAAM,OAAO,CAAA;AAIrD;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,OAAO,CAAA;AAE/C,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAEvC,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtE,0FAA0F;AAC1F,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAEnC,KAAK,OAAO,GAAG;IACb,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC,CAAA;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAUrD;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE7E,uDAAuD;AACvD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAG5E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAI1E;AAkCD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CA6BjF;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAWpD;AAED,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAE/D"}
|