@driftengine/assets 3.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +9 -0
- package/dist/assetPath.d.ts +51 -0
- package/dist/assetPath.js +59 -0
- package/dist/coarseLevel.d.ts +94 -0
- package/dist/coarseLevel.js +606 -0
- package/dist/dds.d.ts +41 -0
- package/dist/dds.js +425 -0
- package/dist/drawKey.d.ts +64 -0
- package/dist/drawKey.js +42 -0
- package/dist/drftLoader.d.ts +420 -0
- package/dist/drftLoader.js +996 -0
- package/dist/drftTextures.d.ts +87 -0
- package/dist/drftTextures.js +138 -0
- package/dist/fbx.d.ts +86 -0
- package/dist/fbx.js +792 -0
- package/dist/fbxHarness.d.ts +68 -0
- package/dist/fbxHarness.js +185 -0
- package/dist/fbxInflate.d.ts +60 -0
- package/dist/fbxInflate.js +204 -0
- package/dist/fbxMaterial.d.ts +87 -0
- package/dist/fbxMaterial.js +345 -0
- package/dist/fbxRig.d.ts +130 -0
- package/dist/fbxRig.js +601 -0
- package/dist/gltf.d.ts +242 -0
- package/dist/gltf.js +904 -0
- package/dist/gltfAccessor.d.ts +2 -0
- package/dist/gltfAccessor.js +106 -0
- package/dist/gltfSkin.d.ts +32 -0
- package/dist/gltfSkin.js +144 -0
- package/dist/iesPhotometry.d.ts +44 -0
- package/dist/iesPhotometry.js +94 -0
- package/dist/imageInfo.d.ts +31 -0
- package/dist/imageInfo.js +119 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +132 -0
- package/dist/kn5.d.ts +158 -0
- package/dist/kn5.js +557 -0
- package/dist/loadProgress.d.ts +118 -0
- package/dist/loadProgress.js +1 -0
- package/dist/localise.d.ts +35 -0
- package/dist/localise.js +253 -0
- package/dist/obj.d.ts +54 -0
- package/dist/obj.js +335 -0
- package/dist/orient.d.ts +104 -0
- package/dist/orient.js +241 -0
- package/dist/radianceHdr.d.ts +36 -0
- package/dist/radianceHdr.js +172 -0
- package/dist/readModel.d.ts +153 -0
- package/dist/readModel.js +386 -0
- package/dist/recognise.d.ts +38 -0
- package/dist/recognise.js +97 -0
- package/dist/stl.d.ts +22 -0
- package/dist/stl.js +131 -0
- package/dist/tangentOrder.d.ts +24 -0
- package/dist/tangentOrder.js +31 -0
- package/dist/threemf.d.ts +46 -0
- package/dist/threemf.js +254 -0
- package/dist/uploadBudget.d.ts +82 -0
- package/dist/uploadBudget.js +86 -0
- package/dist/usd.d.ts +49 -0
- package/dist/usd.js +542 -0
- package/dist/weld.d.ts +44 -0
- package/dist/weld.js +237 -0
- package/dist/zip.d.ts +35 -0
- package/dist/zip.js +110 -0
- package/package.json +61 -0
- package/src/assetPath.ts +79 -0
- package/src/coarseLevel.ts +720 -0
- package/src/dds.ts +507 -0
- package/src/drawKey.ts +91 -0
- package/src/drftLoader.ts +1197 -0
- package/src/drftTextures.ts +153 -0
- package/src/fbx.ts +934 -0
- package/src/fbxHarness.ts +215 -0
- package/src/fbxInflate.ts +258 -0
- package/src/fbxMaterial.ts +410 -0
- package/src/fbxRig.ts +652 -0
- package/src/gltf.ts +1161 -0
- package/src/gltfAccessor.ts +127 -0
- package/src/gltfSkin.ts +210 -0
- package/src/iesPhotometry.ts +136 -0
- package/src/imageInfo.ts +130 -0
- package/src/index.ts +162 -0
- package/src/kn5.ts +705 -0
- package/src/loadProgress.ts +131 -0
- package/src/localise.ts +275 -0
- package/src/obj.ts +386 -0
- package/src/orient.ts +262 -0
- package/src/radianceHdr.ts +195 -0
- package/src/readModel.ts +515 -0
- package/src/recognise.ts +124 -0
- package/src/stl.ts +147 -0
- package/src/tangentOrder.ts +32 -0
- package/src/threemf.ts +303 -0
- package/src/uploadBudget.ts +90 -0
- package/src/usd.ts +642 -0
- package/src/weld.ts +264 -0
- package/src/zip.ts +130 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/** Loading a `.drft` into drawable parts progressively, without stalling the frame. */
|
|
2
|
+
import type { MeshData } from '@driftengine/drft';
|
|
3
|
+
import type { RendererApi } from '@driftengine/core';
|
|
4
|
+
import type { SurfaceTextureHandle } from '@driftengine/core';
|
|
5
|
+
import type { DrftMaterial } from '@driftengine/drft';
|
|
6
|
+
import { TextureSet } from './drftTextures.ts';
|
|
7
|
+
import type { AnimationClip, DrftMorph, DrftNode, DrftSkin } from '@driftengine/drft';
|
|
8
|
+
import type { DrftLoadProgress, DrftPart } from './loadProgress.ts';
|
|
9
|
+
/**
|
|
10
|
+
* How a loaded model is placed: fitted to a size, or left exactly where its file puts it.
|
|
11
|
+
*
|
|
12
|
+
* **`'none'` exists because the way to ask for nothing was to ask for the answer.** A game whose
|
|
13
|
+
* importer has already put a model in its own frame — metres, y = 0 at the road — wanted no fit
|
|
14
|
+
* at all, and the only way to say so was to hand `load` the footprint and height it would have
|
|
15
|
+
* computed, which means measuring the model to tell the loader not to measure it. The obvious
|
|
16
|
+
* shortcut is a trap and a consumer fell in it: `footprint: Number.MAX_SAFE_INTEGER` does not mean
|
|
17
|
+
* "do not scale", it means a scale of 2.3e15, and a car drawn nine quadrillion metres wide looks
|
|
18
|
+
* from inside exactly like a model that failed to load.
|
|
19
|
+
*/
|
|
20
|
+
export type DrftFit = {
|
|
21
|
+
/** The size the larger of the model's footprint and height is scaled to. */
|
|
22
|
+
readonly footprint: number;
|
|
23
|
+
readonly height: number;
|
|
24
|
+
/** Where its lowest point sits, for a scene whose floor is not at zero. Zero unless stated. */
|
|
25
|
+
readonly baseY?: number;
|
|
26
|
+
} | {
|
|
27
|
+
/** Scale 1 and no offset: the model arrives in the coordinates the file states. */
|
|
28
|
+
readonly fit: 'none';
|
|
29
|
+
};
|
|
30
|
+
export interface DrftLoaderOptions {
|
|
31
|
+
/**
|
|
32
|
+
* The most parts one `update` may take, however cheap they turn out to be.
|
|
33
|
+
*
|
|
34
|
+
* **A ceiling, not the budget.** The budget is `uploadMsPerFrame`, because what a part costs
|
|
35
|
+
* cannot be known by counting parts — see `mayBeginMore`. This stays as the guard against the
|
|
36
|
+
* opposite shape of asset, a model of hundreds of tiny parts where the clock would happily take
|
|
37
|
+
* a hundred of them in one frame and spend the whole reveal in two.
|
|
38
|
+
*/
|
|
39
|
+
readonly uploadsPerFrame?: number;
|
|
40
|
+
/**
|
|
41
|
+
* How long one `update` may spend starting new stream work, in milliseconds.
|
|
42
|
+
*
|
|
43
|
+
* `DEFAULT_UPLOAD_MS_PER_FRAME` unless stated. Raise it for a tool that would rather have the
|
|
44
|
+
* model in front of it sooner than hold a rate; lower it for a scene where the frame matters
|
|
45
|
+
* more than the wait. Zero is the slowest honest setting rather than a stall: one piece a frame
|
|
46
|
+
* still begins, because a load has to finish.
|
|
47
|
+
*/
|
|
48
|
+
readonly uploadMsPerFrame?: number;
|
|
49
|
+
/** Seconds for a part to fade from nothing to itself. Zero makes it appear. */
|
|
50
|
+
readonly revealSec?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Anisotropic filtering asked of every image, where the extension exists.
|
|
53
|
+
*
|
|
54
|
+
* A model's maps are the case for it: tread on a tyre, a panel gap, a plate, all bands of fine
|
|
55
|
+
* detail seen at a grazing angle, which is exactly where a trilinear sample gives up.
|
|
56
|
+
*/
|
|
57
|
+
readonly anisotropy?: number;
|
|
58
|
+
/**
|
|
59
|
+
* How every image this asset carries behaves past its own edge. `repeat` unless stated.
|
|
60
|
+
*
|
|
61
|
+
* **Reported from outside, and it is a real trap on a bought model.** A surface texture repeats
|
|
62
|
+
* by default, which is right for a tiling map and wrong at the border of a UV island: a sample
|
|
63
|
+
* whose footprint crosses the edge wraps to the far side of the image, and a mip level high
|
|
64
|
+
* enough averages across islands that have nothing to do with each other. On a model whose
|
|
65
|
+
* untextured surfaces sit at an exporter's default `0.8` grey, the result is single bright pixels
|
|
66
|
+
* tracing every seam of every textured part, which is invisible against a light background and
|
|
67
|
+
* obvious against black paint.
|
|
68
|
+
*
|
|
69
|
+
* `clamp` is the repair where an asset's own UV padding is thin, and it is stated rather than
|
|
70
|
+
* assumed because a model that genuinely tiles a map needs `repeat` and would band without it.
|
|
71
|
+
*/
|
|
72
|
+
readonly textureWrap?: 'repeat' | 'clamp';
|
|
73
|
+
/**
|
|
74
|
+
* A last chance to change a mesh's vertex data before it is uploaded.
|
|
75
|
+
*
|
|
76
|
+
* For a scene that dresses an import rather than taking it as it comes: a bought asset that
|
|
77
|
+
* stores every surface as the same default grey while naming them `body`, `glass` and `chrome`
|
|
78
|
+
* can have its paint decided here, per material, without the loader knowing what a car is.
|
|
79
|
+
*/
|
|
80
|
+
readonly transform?: (mesh: MeshData, material: DrftMaterial | undefined) => MeshData;
|
|
81
|
+
/** Per-material opacity and reflectivity overrides, for the same reason as `transform`. */
|
|
82
|
+
readonly surface?: (material: DrftMaterial | undefined) => {
|
|
83
|
+
readonly opacity?: number;
|
|
84
|
+
readonly reflectivity?: number;
|
|
85
|
+
} | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Draw a coarse whole model while the parts arrive, where the file carries one.
|
|
88
|
+
*
|
|
89
|
+
* **On when the file carries one, and `false` refuses it.** This was off by default for two
|
|
90
|
+
* releases, on the argument that a `LODM` chunk is a *decimated* model and how good it looks
|
|
91
|
+
* is a property of the asset rather than of this code. That argument was about the old hull,
|
|
92
|
+
* which was built by clustering vertices and joined clusters lying on different surfaces of
|
|
93
|
+
* the model, so a car grew spikes through its own bodywork and somebody waiting for it saw
|
|
94
|
+
* white cliffs. A bad preview reads as a broken import where an empty stage reads as a load,
|
|
95
|
+
* so refusing to guess was right.
|
|
96
|
+
*
|
|
97
|
+
* The hull is the surface of an occupancy grid now (`buildCoarseLevel`), which cannot do that:
|
|
98
|
+
* it emits the boundary of a solid, so the worst case is a coarse version of the shape rather
|
|
99
|
+
* than a shape that was never there. And nothing carries a `LODM` by accident. A chunk exists
|
|
100
|
+
* only because somebody baked one, so ignoring it by default meant the file said one thing and
|
|
101
|
+
* the reader did another.
|
|
102
|
+
*
|
|
103
|
+
* `outline: false` still refuses, for a caller swapping a model already on screen for a better
|
|
104
|
+
* one, where a coarse version of the new one is a step backwards.
|
|
105
|
+
*/
|
|
106
|
+
readonly outline?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Keep the arrival state after the load, so `replay` can put any point of it back on screen.
|
|
109
|
+
*
|
|
110
|
+
* **Off by default, because it costs a second copy of the model on the GPU.** The load
|
|
111
|
+
* normally ends by disposing the outline and the per-part meshes, since the merged groups
|
|
112
|
+
* draw the same geometry in seven draws rather than 188. Holding them is what makes the
|
|
113
|
+
* reveal *inspectable* rather than a thing that happens once and cannot be looked at again:
|
|
114
|
+
* a stage that is only ever seen while the bytes are arriving can only be studied by
|
|
115
|
+
* reloading, at whatever speed the network happens to give.
|
|
116
|
+
*
|
|
117
|
+
* The cost is stated rather than hidden: on a 187-part car the per-part meshes are about the
|
|
118
|
+
* same vertex count as the merged ones, so this roughly doubles the geometry the asset holds.
|
|
119
|
+
* For a tool, a documentation page or a demo that is worth it. For a game shipping a level
|
|
120
|
+
* it is not, which is why it is not the default.
|
|
121
|
+
*/
|
|
122
|
+
readonly keepReveal?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Put a small version of each image on its surfaces before the full one, at this many pixels
|
|
125
|
+
* on the longest side. Absent means every image arrives once, at full size.
|
|
126
|
+
*
|
|
127
|
+
* **This is the blur-to-sharp arrival of docs/FORMAT.md §4.6, done from the bytes already in
|
|
128
|
+
* the file.** The section asks for mip levels stored smallest first as separate chunks, which
|
|
129
|
+
* is the better answer for a metered connection because it can stop early and keep the small
|
|
130
|
+
* one. It needs the *baker* to resize an image, and the baker runs in Node where there is no
|
|
131
|
+
* decoder for a JPEG, so that half is a dependency decision rather than an implementation.
|
|
132
|
+
*
|
|
133
|
+
* What is available without one: `createImageBitmap` resizes while it decodes. A 256 pixel
|
|
134
|
+
* version of a 2048 square costs a fraction of the full decode, so a surface stops being
|
|
135
|
+
* flat about a second earlier on a real model, and the full image then replaces it in the
|
|
136
|
+
* same GPU object. It costs a second decode per image, off the main thread, and not one byte
|
|
137
|
+
* of the file — which is why it is an option a caller weighs rather than a default.
|
|
138
|
+
*/
|
|
139
|
+
readonly texturePreview?: number;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* A `.drft` loaded progressively: the model builds up on screen instead of appearing.
|
|
143
|
+
*
|
|
144
|
+
* **What this exists to prevent is the two obvious ways of doing it, both of which are worse.**
|
|
145
|
+
* Reading the whole file and uploading it at the end switches from nothing to everything after
|
|
146
|
+
* several seconds, which demonstrates nothing about a format built to arrive in priority order.
|
|
147
|
+
* Uploading each part the moment its bytes land puts an unbounded number of GPU allocations in
|
|
148
|
+
* one animation frame and hitches the page. This does neither: bytes stream, arrivals queue, and
|
|
149
|
+
* a bounded number of parts are uploaded per frame with a fade.
|
|
150
|
+
*
|
|
151
|
+
* **It starts on an outline where the file carries one.** A `LODM` chunk is a coarse whole model
|
|
152
|
+
* laid out ahead of everything else, so it is uploaded first and drawn until the real geometry is
|
|
153
|
+
* complete — which turns "nothing, then parts appearing" into "an object, sharpening". The parts
|
|
154
|
+
* fade in over it and the swap to the finished model is atomic, because a frame showing both is
|
|
155
|
+
* showing the same car twice. A caller that would rather hold the last picture says
|
|
156
|
+
* `outline: false`. See `DrftLoaderOptions.outline`.
|
|
157
|
+
*
|
|
158
|
+
* **It ends in one mesh per material group.** The reveal wants one upload per part and the steady
|
|
159
|
+
* state wants as few draws as possible, so the load does the first and finishes by doing the
|
|
160
|
+
* second: after the last byte the parts are merged by the image and blend they wear, uploaded
|
|
161
|
+
* once, and the per-part meshes are disposed. On a 187-part car that is 187 draws while loading
|
|
162
|
+
* and seven afterwards.
|
|
163
|
+
*
|
|
164
|
+
* Nothing here touches WebGL. Every GPU object comes from the `Renderer` it is handed, which is
|
|
165
|
+
* what keeps the engine's rule about where raw GL may live intact.
|
|
166
|
+
*/
|
|
167
|
+
export declare class DrftLoader {
|
|
168
|
+
private readonly renderer;
|
|
169
|
+
private readonly options;
|
|
170
|
+
private readonly revealed;
|
|
171
|
+
/** Arrived, not yet uploaded. Drained by `update` at the budget. */
|
|
172
|
+
private readonly queue;
|
|
173
|
+
/**
|
|
174
|
+
* Coarse levels that have arrived and not yet been uploaded, in arrival order.
|
|
175
|
+
*
|
|
176
|
+
* Its own queue rather than the part queue, because it must not wait behind a budget meant
|
|
177
|
+
* for parts: the entire value of an outline is that it is on screen before them.
|
|
178
|
+
*/
|
|
179
|
+
private readonly lodQueue;
|
|
180
|
+
/** The outline currently on screen, and its level. -1 means none has landed. */
|
|
181
|
+
private outlinePart;
|
|
182
|
+
private outlineMesh;
|
|
183
|
+
private outlineLevel;
|
|
184
|
+
/**
|
|
185
|
+
* The merged groups, accumulated *as parts arrive* rather than all at once at the end.
|
|
186
|
+
*
|
|
187
|
+
* This is the second of the two stalls this class exists to avoid, and it was the worse one.
|
|
188
|
+
* Building seven merged meshes out of 187 parts after the last byte is a million vertices of
|
|
189
|
+
* copying in a single frame, and it landed exactly where a viewer had just started looking at a
|
|
190
|
+
* finished car. Adding each part to its group as it is uploaded spreads that cost over the whole
|
|
191
|
+
* load, where there is already a budget governing it, and leaves only one `build` and one upload
|
|
192
|
+
* per group to do at the end — which `update` then spends one group per frame.
|
|
193
|
+
*/
|
|
194
|
+
private readonly groups;
|
|
195
|
+
/** Group keys still to be built and uploaded, drained one per frame after the stream ends. */
|
|
196
|
+
private readonly mergeQueue;
|
|
197
|
+
private readonly mergedParts;
|
|
198
|
+
/**
|
|
199
|
+
* Every part in the order it arrived, and the finished model, held only for `keepReveal`.
|
|
200
|
+
*
|
|
201
|
+
* The load itself needs neither: it draws from `revealed` and throws the singles away at the
|
|
202
|
+
* merge. These exist so `replay` can put an arrival state back, which needs the *order* as
|
|
203
|
+
* much as the meshes — a reveal replayed in the order the groups merged would be a different
|
|
204
|
+
* sequence from the one the file delivered.
|
|
205
|
+
*/
|
|
206
|
+
private readonly arrivals;
|
|
207
|
+
private readonly finished;
|
|
208
|
+
/** Decoded images waiting for the GPU, uploaded one per frame for the same reason. */
|
|
209
|
+
private readonly imageQueue;
|
|
210
|
+
/** Which images have arrived at full size, so a late preview cannot blur one of them again. */
|
|
211
|
+
private readonly sharp;
|
|
212
|
+
/**
|
|
213
|
+
* Which images are on the GPU, and how many, which is what `imagesDone` reports.
|
|
214
|
+
*
|
|
215
|
+
* Counted at the upload rather than at the arrival of the bytes, and the difference is a
|
|
216
|
+
* second and three quarters on a real asset. See `DrftLoadProgress`.
|
|
217
|
+
*/
|
|
218
|
+
private readonly bound;
|
|
219
|
+
private boundCount;
|
|
220
|
+
private readonly singles;
|
|
221
|
+
private readonly images;
|
|
222
|
+
private readonly chunks;
|
|
223
|
+
private materials;
|
|
224
|
+
/**
|
|
225
|
+
* The rig, as its chunks land.
|
|
226
|
+
*
|
|
227
|
+
* **Collected here rather than dropped**, which is not a hypothetical worry about this class:
|
|
228
|
+
* the normal-map texture index was written, carried and readable for a whole release while this
|
|
229
|
+
* was the single layer that discarded it. A field the container carries and the loader does not
|
|
230
|
+
* hand on is invisible to every test of either side.
|
|
231
|
+
*/
|
|
232
|
+
private loadedNodes;
|
|
233
|
+
private readonly loadedSkins;
|
|
234
|
+
private readonly loadedClips;
|
|
235
|
+
/**
|
|
236
|
+
* Deltas as their chunks land, paired to their meshes when a part is built.
|
|
237
|
+
*
|
|
238
|
+
* Collected rather than applied on arrival because a `MORP` chunk can precede the `MESH` it
|
|
239
|
+
* names, and the mesh ordinal in its payload is what makes the pairing independent of order.
|
|
240
|
+
*/
|
|
241
|
+
private readonly loadedMorphs;
|
|
242
|
+
private textureSet;
|
|
243
|
+
private fit;
|
|
244
|
+
private bounds;
|
|
245
|
+
private meshShare;
|
|
246
|
+
private imageShare;
|
|
247
|
+
private state;
|
|
248
|
+
/** Set once the last byte has landed, so `update` knows when to merge. */
|
|
249
|
+
private streamed;
|
|
250
|
+
private merged;
|
|
251
|
+
private disposed;
|
|
252
|
+
private arrived;
|
|
253
|
+
constructor(renderer: RendererApi, options?: DrftLoaderOptions);
|
|
254
|
+
get progress(): DrftLoadProgress;
|
|
255
|
+
/**
|
|
256
|
+
* The parts to draw this frame. Grows while loading, then becomes the merged groups.
|
|
257
|
+
*
|
|
258
|
+
* While a coarse level is on screen it is the **first** entry, so a caller drawing them in
|
|
259
|
+
* order draws the outline behind the parts that are replacing it. It leaves the list in the
|
|
260
|
+
* same frame the merged model enters it.
|
|
261
|
+
*/
|
|
262
|
+
get parts(): readonly DrftPart[];
|
|
263
|
+
/**
|
|
264
|
+
* Whether a coarse level is on screen, or held for a replay.
|
|
265
|
+
*
|
|
266
|
+
* False for most loads: the file has to carry one *and* the consumer has to have asked. It is
|
|
267
|
+
* here because a caller wording a readout needs it — with an outline, state 1 of a replay is
|
|
268
|
+
* the outline alone and state 2 is the first part, and without one the states shift down by
|
|
269
|
+
* one. A caller counting that out for itself would be guessing at what the file contained.
|
|
270
|
+
*/
|
|
271
|
+
/**
|
|
272
|
+
* The asset's hierarchy, or empty for a file carrying no `NODE`.
|
|
273
|
+
*
|
|
274
|
+
* Handed on rather than consumed here, because what to do with a hierarchy is the caller's:
|
|
275
|
+
* this class draws parts, and placing them under a graph is a scene decision.
|
|
276
|
+
*/
|
|
277
|
+
get nodes(): readonly DrftNode[];
|
|
278
|
+
/** Skins the file carried, in the order their chunks appeared. Empty for a file with none. */
|
|
279
|
+
get skins(): readonly DrftSkin[];
|
|
280
|
+
/** Clips the file carried, in the order their chunks appeared. Empty for a file with none. */
|
|
281
|
+
get clips(): readonly AnimationClip[];
|
|
282
|
+
/** Morph deltas the file carried, each naming the mesh ordinal it deforms. */
|
|
283
|
+
get morphs(): readonly DrftMorph[];
|
|
284
|
+
get hasOutline(): boolean;
|
|
285
|
+
/**
|
|
286
|
+
* How many states the finished reveal can be put back into, or 0 when none were kept.
|
|
287
|
+
*
|
|
288
|
+
* A **count of states rather than a fraction**, because the states are what a viewer is
|
|
289
|
+
* actually choosing between and they are not evenly spaced in anything: an outline is one
|
|
290
|
+
* thing, a part is one thing, and the merge is one thing. A caller drives it with an integer
|
|
291
|
+
* so every state is reachable — a fraction would make the outline a band 1/188th of a slider
|
|
292
|
+
* wide, which is a state nobody can stop on.
|
|
293
|
+
*
|
|
294
|
+
* State 0 is an empty stage. State 1 is the outline where the file carried one, and the first
|
|
295
|
+
* part where it did not. The last state is the finished model, drawn exactly as `ready` draws
|
|
296
|
+
* it. Everything between adds one more part, in the order the file delivered them.
|
|
297
|
+
*/
|
|
298
|
+
get revealSteps(): number;
|
|
299
|
+
/**
|
|
300
|
+
* Put one state of the reveal back on screen. Needs `keepReveal`, and does nothing without it.
|
|
301
|
+
*
|
|
302
|
+
* **This is a presentation control, not a second loader.** Nothing is re-read, re-uploaded or
|
|
303
|
+
* re-merged: the meshes the load made are all still there and this decides which of them
|
|
304
|
+
* `parts` names. So it is free to call every frame, and a caller may scrub it as fast as it
|
|
305
|
+
* likes without touching the GPU.
|
|
306
|
+
*/
|
|
307
|
+
replay(step: number): void;
|
|
308
|
+
/** The asset's images by the name its source gave them, or null before any have arrived. */
|
|
309
|
+
get textures(): TextureSet<SurfaceTextureHandle> | null;
|
|
310
|
+
/** The model's own bounds, from `HEAD`, which arrives before any geometry. */
|
|
311
|
+
get modelBounds(): readonly number[];
|
|
312
|
+
/**
|
|
313
|
+
* How the model was fitted: a uniform scale and an offset that centres it on the origin with
|
|
314
|
+
* its base at zero. Null until `HEAD` lands.
|
|
315
|
+
*/
|
|
316
|
+
get placement(): {
|
|
317
|
+
readonly scale: number;
|
|
318
|
+
readonly x: number;
|
|
319
|
+
readonly y: number;
|
|
320
|
+
readonly z: number;
|
|
321
|
+
} | null;
|
|
322
|
+
/**
|
|
323
|
+
* Start loading, and resolve when the last byte has been read.
|
|
324
|
+
*
|
|
325
|
+
* Deliberately not the thing that produces the parts: the parts appear through `update`, so a
|
|
326
|
+
* caller draws its frames as usual and never awaits anything. A failed load leaves the phase at
|
|
327
|
+
* `failed` with a message rather than throwing, because a scene is usually still valid without
|
|
328
|
+
* its model and a blank screen would report the wrong problem.
|
|
329
|
+
*
|
|
330
|
+
* `fitTo` is the size the model's larger of footprint and height is scaled to fit, which is the
|
|
331
|
+
* one thing a loader cannot infer: a bought asset arrives in whatever units its author used.
|
|
332
|
+
* `baseY` is where its lowest point should sit, for a scene whose floor is not at zero — a
|
|
333
|
+
* plinth, a turntable, a deck. It defaults to zero, and getting it wrong is very visible: the
|
|
334
|
+
* first version of this had no such parameter and put a car's wheels through the turntable it
|
|
335
|
+
* was standing on.
|
|
336
|
+
*
|
|
337
|
+
* **`{ fit: 'none' }` declines all of it** — scale 1, no centring, no base — for a caller whose
|
|
338
|
+
* importer has already placed the model. See `DrftFit` for why that is a stated option rather
|
|
339
|
+
* than a very large footprint.
|
|
340
|
+
*/
|
|
341
|
+
load(url: string, fitTo: DrftFit): Promise<void>;
|
|
342
|
+
/**
|
|
343
|
+
* The same load, from a container this class did not fetch.
|
|
344
|
+
*
|
|
345
|
+
* Split out because a `.drft` is not the only way a model arrives. A source format read at
|
|
346
|
+
* runtime is parsed and written to a container in a worker, and what comes back is bytes
|
|
347
|
+
* that have never been near a URL. Everything after the first byte is identical, so it would
|
|
348
|
+
* be a second copy of the stream, the upload budget, the fade and the merge, kept in step by
|
|
349
|
+
* hand. A `Response` is the seam because it is what `streamDrft` already takes, and one can
|
|
350
|
+
* be built over an `ArrayBuffer` with no copy and no server.
|
|
351
|
+
*/
|
|
352
|
+
consume(response: Response, fitTo: DrftFit): Promise<void>;
|
|
353
|
+
/**
|
|
354
|
+
* Spend this frame's upload budget and advance the fades. Call once per frame.
|
|
355
|
+
*
|
|
356
|
+
* Returns whether anything changed, for a caller that wants to know it should redraw a
|
|
357
|
+
* progress readout rather than rebuild one every frame.
|
|
358
|
+
*/
|
|
359
|
+
update(dtSec: number): boolean;
|
|
360
|
+
dispose(): void;
|
|
361
|
+
/**
|
|
362
|
+
* The coarse whole model, on screen until the real one is complete.
|
|
363
|
+
*
|
|
364
|
+
* **Drawn as a part rather than through a second path**, so a scene that already iterates
|
|
365
|
+
* `parts` needs no change and cannot forget it. It wears no image and no reflection, because
|
|
366
|
+
* a level of detail carries the four mandatory attributes only — see `buildCoarseLevel`, and
|
|
367
|
+
* §4.6 for why a silhouette does not pay for the roughness of a surface nobody looks at.
|
|
368
|
+
*
|
|
369
|
+
* **The replacement is atomic**, which §4.6 asks for by name: a better level takes the worse
|
|
370
|
+
* one's place in the draw list in the same call it is uploaded in, so no frame draws both and
|
|
371
|
+
* none draws neither. It keeps the fade it had, or a finer level arriving would restart it and
|
|
372
|
+
* the model would appear to flash.
|
|
373
|
+
*/
|
|
374
|
+
private uploadOutline;
|
|
375
|
+
/** One part, transformed, dressed and uploaded. Returns whether it landed. */
|
|
376
|
+
private uploadOne;
|
|
377
|
+
/**
|
|
378
|
+
* Replace the per-part meshes with one per material group.
|
|
379
|
+
*
|
|
380
|
+
* Grouped by opacity and reflectivity as well as by image, because two surfaces can share a map
|
|
381
|
+
* and differ in blend: merging on the image alone means one cannot be made translucent without
|
|
382
|
+
* taking the other with it.
|
|
383
|
+
*/
|
|
384
|
+
private buildGroup;
|
|
385
|
+
/**
|
|
386
|
+
* Put the merged groups on screen and throw the per-part meshes away.
|
|
387
|
+
*
|
|
388
|
+
* **In one step, once every group exists.** A frame drawn with both sets present would draw the
|
|
389
|
+
* model twice and blend its translucent surfaces against themselves, and a frame drawn with the
|
|
390
|
+
* singles already gone would show a model with holes in it. So the groups are built over as many
|
|
391
|
+
* frames as it takes and swapped in together.
|
|
392
|
+
*/
|
|
393
|
+
private swapInMerged;
|
|
394
|
+
/**
|
|
395
|
+
* Rebuild the name-addressed texture set as images decode.
|
|
396
|
+
*
|
|
397
|
+
* Rebuilt rather than mutated because `TextureSet` is immutable once made, and this happens
|
|
398
|
+
* once per image rather than once per frame. Addressing by name is what stops a regroup
|
|
399
|
+
* silently reassigning an image to a different surface.
|
|
400
|
+
*/
|
|
401
|
+
private rebuildTextures;
|
|
402
|
+
/** One place that writes the progress, so every field stays consistent with the phase. */
|
|
403
|
+
private set;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* The MIME type a codec's bytes carry, or `null` for one the browser has no decoder for.
|
|
407
|
+
*
|
|
408
|
+
* **Exported because the fall-through was the bug.** This was written inline as two ternaries
|
|
409
|
+
* ending in `'image/jpeg'`, so every codec the chain did not name became a JPEG — and `CODEC_RAW`,
|
|
410
|
+
* which the baker writes for every image it could not find, was handed four bytes of pixel and a
|
|
411
|
+
* decoder that had no chance with them. Naming each codec and returning `null` for the one that is
|
|
412
|
+
* not an encoded image makes the omission a value rather than a default, and lets a test say so.
|
|
413
|
+
*/
|
|
414
|
+
export declare function imageTypeFor(codec: number): string;
|
|
415
|
+
/**
|
|
416
|
+
* Whether a codec is decoded from its own bytes rather than through an image decoder.
|
|
417
|
+
*
|
|
418
|
+
* Raw is the only one, and it is not an encoded image at all: the bytes *are* the pixels.
|
|
419
|
+
*/
|
|
420
|
+
export declare function isRawCodec(codec: number): boolean;
|