@driftengine/assets 4.12.0 → 4.13.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/README.md +1 -1
- package/dist/drftLoader.d.ts +38 -1
- package/dist/drftLoader.js +60 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/partDraws.d.ts +43 -0
- package/dist/partDraws.js +100 -0
- package/package.json +4 -4
- package/src/drftLoader.ts +71 -2
- package/src/index.ts +5 -0
- package/src/partDraws.ts +138 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Model readers, the loader that streams a `.drft` file onto the screen, and the textures a phone
|
|
4
4
|
samples.
|
|
5
5
|
|
|
6
|
-
**Cost:
|
|
6
|
+
**Cost: 29.2 KB gzipped on top of core.** Measured by `scripts/size-gate.test.mjs`, which fails if it
|
|
7
7
|
drifts more than 3% — the number is a fact about the build rather than a claim in a document.
|
|
8
8
|
|
|
9
9
|
- **Readers.** glTF and GLB, OBJ, STL, USD and 3MF, and experimentally FBX and Blender's `.blend`,
|
package/dist/drftLoader.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Loading a `.drft` into drawable parts progressively, without stalling the frame. */
|
|
2
2
|
import type { MeshData } from '@driftengine/drft';
|
|
3
|
-
import type { RendererApi } from '@driftengine/core';
|
|
3
|
+
import type { RendererApi, ShadowCasters, Vec3 } from '@driftengine/core';
|
|
4
4
|
import type { SurfaceTextureHandle } from '@driftengine/core';
|
|
5
5
|
import type { DrftMaterial } from '@driftengine/drft';
|
|
6
6
|
import type { DrftFieldPlacement } from './fieldPlacement.ts';
|
|
@@ -328,6 +328,8 @@ export declare class DrftLoader {
|
|
|
328
328
|
private arrived;
|
|
329
329
|
/** A streamed world's regions, kept apart from the parts and never merged. See `regionStore.ts`. */
|
|
330
330
|
private readonly regionStore;
|
|
331
|
+
/** What `draw`, `casters` and `prepare` do with the parts. See `partDraws.ts`. */
|
|
332
|
+
private readonly partDraws;
|
|
331
333
|
constructor(renderer: RendererApi, options?: DrftLoaderOptions);
|
|
332
334
|
get progress(): DrftLoadProgress;
|
|
333
335
|
/**
|
|
@@ -444,6 +446,41 @@ export declare class DrftLoader {
|
|
|
444
446
|
readonly y: number;
|
|
445
447
|
readonly z: number;
|
|
446
448
|
} | null;
|
|
449
|
+
/**
|
|
450
|
+
* Draw this frame's parts into the open mesh pass, each by every rule a baked container needs:
|
|
451
|
+
* its whole material, its copies through the instanced path, a blended part translucent and
|
|
452
|
+
* writing no depth, glass as glass, its own reflectivity. Returns the draws issued.
|
|
453
|
+
*
|
|
454
|
+
* **The one call a model wants**, because each rule missed is a wrong picture rather than an
|
|
455
|
+
* error: a repeated arch drawn once, a decal as a black patch, foliage without its alpha test.
|
|
456
|
+
* What it gives up, and what drawing `parts` by hand is for, is a per-part override; see
|
|
457
|
+
* `PartDraws`.
|
|
458
|
+
*/
|
|
459
|
+
draw(): number;
|
|
460
|
+
/**
|
|
461
|
+
* Every part that stands in the light, for `drawShadowCasters` or a mirror's replay: all but a
|
|
462
|
+
* decal, which would shadow its own surface, each with its material so a cutout casts the shape
|
|
463
|
+
* in its texture, glass as glass and copies as copies. Always this frame's parts.
|
|
464
|
+
*/
|
|
465
|
+
readonly casters: ShadowCasters;
|
|
466
|
+
/**
|
|
467
|
+
* Compile what every part's draw will take, off the frame, and wait for it. Call it once the load
|
|
468
|
+
* has finished, behind whatever is showing then; one frame drawn there as well compiles the
|
|
469
|
+
* shadow passes' own pipelines, which this cannot reach, and the first frame a player sees then
|
|
470
|
+
* waits on nothing.
|
|
471
|
+
*/
|
|
472
|
+
prepare(): Promise<void>;
|
|
473
|
+
/**
|
|
474
|
+
* A directional light's matrix covering the model whole, as fitted, into `out`, through
|
|
475
|
+
* `computeLightMatrixForBounds`; returns the depth span for `shadowDepthSpan`, or null until
|
|
476
|
+
* `HEAD` has said how large the model is.
|
|
477
|
+
*
|
|
478
|
+
* **What it gives up** is the bounds' own: they are the file's, fitted, so a `transform` that
|
|
479
|
+
* moves a mesh moves it past them, and a model far larger than what the viewer sees spreads the
|
|
480
|
+
* map over all of it — a city wants the map around the viewer instead, through
|
|
481
|
+
* `computeLightMatrix`.
|
|
482
|
+
*/
|
|
483
|
+
shadowFit(lightDir: Vec3, shadowMapSize: number, out: Float32Array): number | null;
|
|
447
484
|
/**
|
|
448
485
|
* Start loading, and resolve when the last byte has been read.
|
|
449
486
|
*
|
package/dist/drftLoader.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Loading a `.drft` into drawable parts progressively, without stalling the frame. */
|
|
2
|
-
import { MeshBuilder, concatMeshes, placeMesh } from '@driftengine/core';
|
|
2
|
+
import { MeshBuilder, computeLightMatrixForBounds, concatMeshes, placeMesh, } from '@driftengine/core';
|
|
3
|
+
import { PartDraws } from './partDraws.js';
|
|
3
4
|
import { placeFields } from './fieldPlacement.js';
|
|
4
5
|
import { TextureSet, textureColorSpaces } from './drftTextures.js';
|
|
5
6
|
import { bcPlan, createBcDecoder } from './bcLoad.js';
|
|
@@ -11,6 +12,9 @@ import { drawKeyOf, resolveDrawGrouping } from './drawKey.js';
|
|
|
11
12
|
import { streamDrft, CODEC_JPEG, CODEC_PNG, CODEC_RAW, CODEC_WEBP, DrftError, expandAssembly, } from '@driftengine/drft';
|
|
12
13
|
import { RegionStore, fitCopies } from './regionStore.js';
|
|
13
14
|
import { DEFAULT_REVEAL_SEC, DEFAULT_UPLOAD_MS_PER_FRAME, DEFAULT_UPLOADS_PER_FRAME, isDocumentResponse, mayBeginMore, } from './uploadBudget.js';
|
|
15
|
+
/** `shadowFit`'s fitted bounds, rewritten in place on each call. */
|
|
16
|
+
const FIT_MIN = [0, 0, 0];
|
|
17
|
+
const FIT_MAX = [0, 0, 0];
|
|
14
18
|
/**
|
|
15
19
|
* A `.drft` loaded progressively: the model builds up on screen instead of appearing.
|
|
16
20
|
*
|
|
@@ -162,10 +166,13 @@ export class DrftLoader {
|
|
|
162
166
|
arrived = 0;
|
|
163
167
|
/** A streamed world's regions, kept apart from the parts and never merged. See `regionStore.ts`. */
|
|
164
168
|
regionStore;
|
|
169
|
+
/** What `draw`, `casters` and `prepare` do with the parts. See `partDraws.ts`. */
|
|
170
|
+
partDraws;
|
|
165
171
|
constructor(renderer, options = {}) {
|
|
166
172
|
this.renderer = renderer;
|
|
167
173
|
this.options = options;
|
|
168
174
|
this.regionStore = new RegionStore(renderer, () => this.fit);
|
|
175
|
+
this.partDraws = new PartDraws(renderer);
|
|
169
176
|
}
|
|
170
177
|
get progress() {
|
|
171
178
|
return this.state;
|
|
@@ -388,6 +395,58 @@ export class DrftLoader {
|
|
|
388
395
|
get placement() {
|
|
389
396
|
return this.fit;
|
|
390
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Draw this frame's parts into the open mesh pass, each by every rule a baked container needs:
|
|
400
|
+
* its whole material, its copies through the instanced path, a blended part translucent and
|
|
401
|
+
* writing no depth, glass as glass, its own reflectivity. Returns the draws issued.
|
|
402
|
+
*
|
|
403
|
+
* **The one call a model wants**, because each rule missed is a wrong picture rather than an
|
|
404
|
+
* error: a repeated arch drawn once, a decal as a black patch, foliage without its alpha test.
|
|
405
|
+
* What it gives up, and what drawing `parts` by hand is for, is a per-part override; see
|
|
406
|
+
* `PartDraws`.
|
|
407
|
+
*/
|
|
408
|
+
draw() {
|
|
409
|
+
return this.partDraws.draw(this.revealed, this.textureSet);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Every part that stands in the light, for `drawShadowCasters` or a mirror's replay: all but a
|
|
413
|
+
* decal, which would shadow its own surface, each with its material so a cutout casts the shape
|
|
414
|
+
* in its texture, glass as glass and copies as copies. Always this frame's parts.
|
|
415
|
+
*/
|
|
416
|
+
casters = (sink) => this.partDraws.cast(sink, this.revealed, this.textureSet);
|
|
417
|
+
/**
|
|
418
|
+
* Compile what every part's draw will take, off the frame, and wait for it. Call it once the load
|
|
419
|
+
* has finished, behind whatever is showing then; one frame drawn there as well compiles the
|
|
420
|
+
* shadow passes' own pipelines, which this cannot reach, and the first frame a player sees then
|
|
421
|
+
* waits on nothing.
|
|
422
|
+
*/
|
|
423
|
+
prepare() {
|
|
424
|
+
return this.partDraws.prepare(this.revealed, this.textureSet);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* A directional light's matrix covering the model whole, as fitted, into `out`, through
|
|
428
|
+
* `computeLightMatrixForBounds`; returns the depth span for `shadowDepthSpan`, or null until
|
|
429
|
+
* `HEAD` has said how large the model is.
|
|
430
|
+
*
|
|
431
|
+
* **What it gives up** is the bounds' own: they are the file's, fitted, so a `transform` that
|
|
432
|
+
* moves a mesh moves it past them, and a model far larger than what the viewer sees spreads the
|
|
433
|
+
* map over all of it — a city wants the map around the viewer instead, through
|
|
434
|
+
* `computeLightMatrix`.
|
|
435
|
+
*/
|
|
436
|
+
shadowFit(lightDir, shadowMapSize, out) {
|
|
437
|
+
const fit = this.fit;
|
|
438
|
+
const b = this.bounds;
|
|
439
|
+
if (fit === null || b.length < 6)
|
|
440
|
+
return null;
|
|
441
|
+
const { scale, x, y, z } = fit;
|
|
442
|
+
FIT_MIN[0] = b[0] * scale + x;
|
|
443
|
+
FIT_MIN[1] = b[1] * scale + y;
|
|
444
|
+
FIT_MIN[2] = b[2] * scale + z;
|
|
445
|
+
FIT_MAX[0] = b[3] * scale + x;
|
|
446
|
+
FIT_MAX[1] = b[4] * scale + y;
|
|
447
|
+
FIT_MAX[2] = b[5] * scale + z;
|
|
448
|
+
return computeLightMatrixForBounds(lightDir, FIT_MIN, FIT_MAX, shadowMapSize, out);
|
|
449
|
+
}
|
|
391
450
|
/**
|
|
392
451
|
* Start loading, and resolve when the last byte has been read.
|
|
393
452
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { DrftLoader } from './drftLoader.ts';
|
|
10
10
|
export { writePartMaterial } from './partMaterial.ts';
|
|
11
|
+
export { PartDraws } from './partDraws.ts';
|
|
11
12
|
export { dtexTileBytes, tileGridFromDtex } from './dtexTiles.ts';
|
|
12
13
|
export type { DrftFit, DrftLoaderOptions } from './drftLoader.ts';
|
|
13
14
|
export { drawKeyOf, resolveDrawGrouping } from './drawKey.ts';
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,11 @@ export { DrftLoader } from './drftLoader.js';
|
|
|
17
17
|
* it: a copy that left out the occlusion strength drew a bought model black.
|
|
18
18
|
*/
|
|
19
19
|
export { writePartMaterial } from './partMaterial.js';
|
|
20
|
+
/*
|
|
21
|
+
* Parts drawn, cast and prepared by every rule a container needs, which `DrftLoader.draw` is: here
|
|
22
|
+
* for a caller drawing parts of its own choosing, several loaders' or a list it filtered.
|
|
23
|
+
*/
|
|
24
|
+
export { PartDraws } from './partDraws.js';
|
|
20
25
|
/*
|
|
21
26
|
* **A `DTEX` chunk's tile table as the grid residency asks about.** The other constructor of a
|
|
22
27
|
* `MaterialTileGrid` — `latentTileGrid` cuts one from a latent a baker holds, and this reads one a
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A loaded model's parts drawn, cast and prepared by the rules a baked container needs, so a caller
|
|
3
|
+
* gets them right by calling one method rather than by knowing each of them.
|
|
4
|
+
*
|
|
5
|
+
* **Every rule here is one that fails as a picture rather than an error**, which is why they are in
|
|
6
|
+
* one place: a part's whole material (`writePartMaterial`), or foliage loses its alpha test and a
|
|
7
|
+
* one-sided card vanishes from behind; a part's copies through the instanced path, or a repeated arch
|
|
8
|
+
* is drawn once; a blended part translucent whatever its opacity and writing no depth, since its
|
|
9
|
+
* alpha is in its texture, or a decal draws as a black patch on the stone; glass as glass, its options
|
|
10
|
+
* kept per pane so a frame allocates none; each part's own reflectivity; and in the shadow, every
|
|
11
|
+
* part but a decal, which would shadow its own surface, with its material so a cutout casts the
|
|
12
|
+
* shape in its texture. A part fades in on its `reveal`, translucent while it does.
|
|
13
|
+
*
|
|
14
|
+
* **What it gives up**: one material and one reflectivity a part, as the container says, with no
|
|
15
|
+
* per-part override; a caller who wants one (a map switched off, a part left out) draws the parts
|
|
16
|
+
* itself, which `writePartMaterial` and the fields of `DrftPart` are for. It sets no camera and opens
|
|
17
|
+
* no pass: it draws into whichever mesh pass is open, as `drawMesh` does.
|
|
18
|
+
*/
|
|
19
|
+
import type { RendererApi, ShadowCasterSink, SurfaceTextureHandle } from '@driftengine/core';
|
|
20
|
+
import type { TextureSet } from './drftTextures.ts';
|
|
21
|
+
import type { DrftPart } from './loadProgress.ts';
|
|
22
|
+
/** Parts drawn, cast and prepared by every rule a container needs; `DrftLoader.draw` is one. */
|
|
23
|
+
export declare class PartDraws {
|
|
24
|
+
private readonly renderer;
|
|
25
|
+
private readonly material;
|
|
26
|
+
private readonly casterMaterial;
|
|
27
|
+
/** Each pane's draw options, made once: a frame drawing glass allocates nothing. */
|
|
28
|
+
private readonly glassDraws;
|
|
29
|
+
constructor(renderer: RendererApi);
|
|
30
|
+
/** Draw every part into the open mesh pass. Returns the draws issued. */
|
|
31
|
+
draw(parts: readonly DrftPart[], textures: TextureSet<SurfaceTextureHandle> | null): number;
|
|
32
|
+
/** Offer every part that stands in the light to a shadow pass, or to a mirror's replay. */
|
|
33
|
+
cast(sink: ShadowCasterSink, parts: readonly DrftPart[], textures: TextureSet<SurfaceTextureHandle> | null): void;
|
|
34
|
+
/**
|
|
35
|
+
* Compile, off the frame, what every part's draw will take, then wait for it: each in its own
|
|
36
|
+
* material, translucent as well where it will be drawn so, through its batch where it has copies.
|
|
37
|
+
* What it cannot reach is the shadow passes' and a probe's own pipelines, which one frame drawn
|
|
38
|
+
* behind a loading screen compiles; see `RendererApi.prepareMesh`.
|
|
39
|
+
*/
|
|
40
|
+
prepare(parts: readonly DrftPart[], textures: TextureSet<SurfaceTextureHandle> | null): Promise<void>;
|
|
41
|
+
/** A part's translucent draw options: glass as glass, a blended part writing no depth. */
|
|
42
|
+
private optionsFor;
|
|
43
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { writePartMaterial } from './partMaterial.js';
|
|
2
|
+
const IDENTITY = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
3
|
+
/** A blended part's draw: its alpha is in its texture, and a depth it wrote would hide what is behind. */
|
|
4
|
+
const BLENDED = Object.freeze({ depthWrite: false });
|
|
5
|
+
const OPAQUE_PREPARED = Object.freeze({ translucent: false });
|
|
6
|
+
const TRANSLUCENT_PREPARED = Object.freeze({ translucent: true });
|
|
7
|
+
/** Parts drawn, cast and prepared by every rule a container needs; `DrftLoader.draw` is one. */
|
|
8
|
+
export class PartDraws {
|
|
9
|
+
renderer;
|
|
10
|
+
material = {};
|
|
11
|
+
casterMaterial = {};
|
|
12
|
+
/** Each pane's draw options, made once: a frame drawing glass allocates nothing. */
|
|
13
|
+
glassDraws = new Map();
|
|
14
|
+
constructor(renderer) {
|
|
15
|
+
this.renderer = renderer;
|
|
16
|
+
}
|
|
17
|
+
/** Draw every part into the open mesh pass. Returns the draws issued. */
|
|
18
|
+
draw(parts, textures) {
|
|
19
|
+
const renderer = this.renderer;
|
|
20
|
+
let draws = 0;
|
|
21
|
+
for (const part of parts) {
|
|
22
|
+
renderer.setMaterial(writePartMaterial(part, textures, this.material));
|
|
23
|
+
renderer.setSurfaceReflectivity(part.reflectivity);
|
|
24
|
+
const opacity = part.opacity * part.reveal;
|
|
25
|
+
const options = this.optionsFor(part);
|
|
26
|
+
const instances = part.instances;
|
|
27
|
+
if (instances !== null) {
|
|
28
|
+
if (translucent(part, opacity)) {
|
|
29
|
+
renderer.drawTranslucentInstanced(instances.batch, instances.data, opacity, options);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
renderer.drawInstanced(instances.batch, instances.data);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (translucent(part, opacity)) {
|
|
36
|
+
renderer.drawTranslucentMesh(part.mesh, IDENTITY, opacity, options);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
renderer.drawMesh(part.mesh, IDENTITY);
|
|
40
|
+
}
|
|
41
|
+
draws++;
|
|
42
|
+
}
|
|
43
|
+
/* Nothing of the last part is left for the caller's own draws: no material, no mirror. */
|
|
44
|
+
renderer.setMaterial(null);
|
|
45
|
+
renderer.setSurfaceReflectivity(0);
|
|
46
|
+
return draws;
|
|
47
|
+
}
|
|
48
|
+
/** Offer every part that stands in the light to a shadow pass, or to a mirror's replay. */
|
|
49
|
+
cast(sink, parts, textures) {
|
|
50
|
+
const material = this.casterMaterial;
|
|
51
|
+
for (const part of parts) {
|
|
52
|
+
/* A blended part that is not glass is a decal, which would shadow the surface it lies on. */
|
|
53
|
+
if (part.blend && part.glass === null)
|
|
54
|
+
continue;
|
|
55
|
+
writePartMaterial(part, textures, material);
|
|
56
|
+
material.glass = part.glass ?? undefined;
|
|
57
|
+
if (part.instances !== null) {
|
|
58
|
+
sink.instanced?.(part.instances.batch, part.instances.data, material);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
sink.mesh(part.mesh, IDENTITY, material);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Compile, off the frame, what every part's draw will take, then wait for it: each in its own
|
|
67
|
+
* material, translucent as well where it will be drawn so, through its batch where it has copies.
|
|
68
|
+
* What it cannot reach is the shadow passes' and a probe's own pipelines, which one frame drawn
|
|
69
|
+
* behind a loading screen compiles; see `RendererApi.prepareMesh`.
|
|
70
|
+
*/
|
|
71
|
+
async prepare(parts, textures) {
|
|
72
|
+
const renderer = this.renderer;
|
|
73
|
+
for (const part of parts) {
|
|
74
|
+
const material = writePartMaterial(part, textures, this.material);
|
|
75
|
+
const options = translucent(part, part.opacity) ? TRANSLUCENT_PREPARED : OPAQUE_PREPARED;
|
|
76
|
+
if (part.instances !== null)
|
|
77
|
+
renderer.prepareInstanced(part.instances.batch, material, options);
|
|
78
|
+
else
|
|
79
|
+
renderer.prepareMesh(part.mesh, material, options);
|
|
80
|
+
}
|
|
81
|
+
await renderer.ready();
|
|
82
|
+
}
|
|
83
|
+
/** A part's translucent draw options: glass as glass, a blended part writing no depth. */
|
|
84
|
+
optionsFor(part) {
|
|
85
|
+
const glass = part.glass;
|
|
86
|
+
if (glass !== null) {
|
|
87
|
+
let options = this.glassDraws.get(glass);
|
|
88
|
+
if (options === undefined) {
|
|
89
|
+
options = Object.freeze({ ...BLENDED, glass });
|
|
90
|
+
this.glassDraws.set(glass, options);
|
|
91
|
+
}
|
|
92
|
+
return options;
|
|
93
|
+
}
|
|
94
|
+
return part.blend ? BLENDED : undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** A blended part is drawn translucent whatever its opacity: its alpha is in its texture. */
|
|
98
|
+
function translucent(part, opacity) {
|
|
99
|
+
return opacity < 1 || part.blend;
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftengine/assets",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0",
|
|
4
4
|
"description": "Model readers and the streaming loader for the .drft container",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
],
|
|
33
33
|
"sideEffects": false,
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@driftengine/drft": "4.
|
|
36
|
-
"@driftengine/texture": "4.
|
|
35
|
+
"@driftengine/drft": "4.13.0",
|
|
36
|
+
"@driftengine/texture": "4.13.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@driftengine/core": "4.
|
|
39
|
+
"@driftengine/core": "4.13.0"
|
|
40
40
|
},
|
|
41
41
|
"author": "Drift Technologies",
|
|
42
42
|
"repository": {
|
package/src/drftLoader.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
/** Loading a `.drft` into drawable parts progressively, without stalling the frame. */
|
|
2
2
|
|
|
3
3
|
import type { MeshData } from '@driftengine/drft';
|
|
4
|
-
import type { MeshHandle, RendererApi } from '@driftengine/core';
|
|
4
|
+
import type { MeshHandle, RendererApi, ShadowCasters, Vec3 } from '@driftengine/core';
|
|
5
5
|
import type {
|
|
6
6
|
CompressedTextureSource,
|
|
7
7
|
GlassOptions,
|
|
8
8
|
SurfaceTextureHandle,
|
|
9
9
|
} from '@driftengine/core';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
MeshBuilder,
|
|
12
|
+
computeLightMatrixForBounds,
|
|
13
|
+
concatMeshes,
|
|
14
|
+
placeMesh,
|
|
15
|
+
} from '@driftengine/core';
|
|
16
|
+
import { PartDraws } from './partDraws.ts';
|
|
11
17
|
import type { DrftMaterial } from '@driftengine/drft';
|
|
12
18
|
import type { DrftSdfvEntry } from '@driftengine/drft';
|
|
13
19
|
import { placeFields } from './fieldPlacement.ts';
|
|
@@ -226,6 +232,10 @@ export interface DrftLoaderOptions {
|
|
|
226
232
|
readonly onMesh?: (mesh: MeshData, ordinal: number) => boolean;
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
/** `shadowFit`'s fitted bounds, rewritten in place on each call. */
|
|
236
|
+
const FIT_MIN: Vec3 = [0, 0, 0];
|
|
237
|
+
const FIT_MAX: Vec3 = [0, 0, 0];
|
|
238
|
+
|
|
229
239
|
/**
|
|
230
240
|
* A `.drft` loaded progressively: the model builds up on screen instead of appearing.
|
|
231
241
|
*
|
|
@@ -411,11 +421,14 @@ export class DrftLoader {
|
|
|
411
421
|
private arrived = 0;
|
|
412
422
|
/** A streamed world's regions, kept apart from the parts and never merged. See `regionStore.ts`. */
|
|
413
423
|
private readonly regionStore: RegionStore;
|
|
424
|
+
/** What `draw`, `casters` and `prepare` do with the parts. See `partDraws.ts`. */
|
|
425
|
+
private readonly partDraws: PartDraws;
|
|
414
426
|
|
|
415
427
|
constructor(renderer: RendererApi, options: DrftLoaderOptions = {}) {
|
|
416
428
|
this.renderer = renderer;
|
|
417
429
|
this.options = options;
|
|
418
430
|
this.regionStore = new RegionStore(renderer, () => this.fit);
|
|
431
|
+
this.partDraws = new PartDraws(renderer);
|
|
419
432
|
}
|
|
420
433
|
|
|
421
434
|
get progress(): DrftLoadProgress {
|
|
@@ -657,6 +670,62 @@ export class DrftLoader {
|
|
|
657
670
|
return this.fit;
|
|
658
671
|
}
|
|
659
672
|
|
|
673
|
+
/**
|
|
674
|
+
* Draw this frame's parts into the open mesh pass, each by every rule a baked container needs:
|
|
675
|
+
* its whole material, its copies through the instanced path, a blended part translucent and
|
|
676
|
+
* writing no depth, glass as glass, its own reflectivity. Returns the draws issued.
|
|
677
|
+
*
|
|
678
|
+
* **The one call a model wants**, because each rule missed is a wrong picture rather than an
|
|
679
|
+
* error: a repeated arch drawn once, a decal as a black patch, foliage without its alpha test.
|
|
680
|
+
* What it gives up, and what drawing `parts` by hand is for, is a per-part override; see
|
|
681
|
+
* `PartDraws`.
|
|
682
|
+
*/
|
|
683
|
+
draw(): number {
|
|
684
|
+
return this.partDraws.draw(this.revealed, this.textureSet);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Every part that stands in the light, for `drawShadowCasters` or a mirror's replay: all but a
|
|
689
|
+
* decal, which would shadow its own surface, each with its material so a cutout casts the shape
|
|
690
|
+
* in its texture, glass as glass and copies as copies. Always this frame's parts.
|
|
691
|
+
*/
|
|
692
|
+
readonly casters: ShadowCasters = (sink) =>
|
|
693
|
+
this.partDraws.cast(sink, this.revealed, this.textureSet);
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Compile what every part's draw will take, off the frame, and wait for it. Call it once the load
|
|
697
|
+
* has finished, behind whatever is showing then; one frame drawn there as well compiles the
|
|
698
|
+
* shadow passes' own pipelines, which this cannot reach, and the first frame a player sees then
|
|
699
|
+
* waits on nothing.
|
|
700
|
+
*/
|
|
701
|
+
prepare(): Promise<void> {
|
|
702
|
+
return this.partDraws.prepare(this.revealed, this.textureSet);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* A directional light's matrix covering the model whole, as fitted, into `out`, through
|
|
707
|
+
* `computeLightMatrixForBounds`; returns the depth span for `shadowDepthSpan`, or null until
|
|
708
|
+
* `HEAD` has said how large the model is.
|
|
709
|
+
*
|
|
710
|
+
* **What it gives up** is the bounds' own: they are the file's, fitted, so a `transform` that
|
|
711
|
+
* moves a mesh moves it past them, and a model far larger than what the viewer sees spreads the
|
|
712
|
+
* map over all of it — a city wants the map around the viewer instead, through
|
|
713
|
+
* `computeLightMatrix`.
|
|
714
|
+
*/
|
|
715
|
+
shadowFit(lightDir: Vec3, shadowMapSize: number, out: Float32Array): number | null {
|
|
716
|
+
const fit = this.fit;
|
|
717
|
+
const b = this.bounds;
|
|
718
|
+
if (fit === null || b.length < 6) return null;
|
|
719
|
+
const { scale, x, y, z } = fit;
|
|
720
|
+
FIT_MIN[0] = (b[0] as number) * scale + x;
|
|
721
|
+
FIT_MIN[1] = (b[1] as number) * scale + y;
|
|
722
|
+
FIT_MIN[2] = (b[2] as number) * scale + z;
|
|
723
|
+
FIT_MAX[0] = (b[3] as number) * scale + x;
|
|
724
|
+
FIT_MAX[1] = (b[4] as number) * scale + y;
|
|
725
|
+
FIT_MAX[2] = (b[5] as number) * scale + z;
|
|
726
|
+
return computeLightMatrixForBounds(lightDir, FIT_MIN, FIT_MAX, shadowMapSize, out);
|
|
727
|
+
}
|
|
728
|
+
|
|
660
729
|
/**
|
|
661
730
|
* Start loading, and resolve when the last byte has been read.
|
|
662
731
|
*
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,11 @@ export { DrftLoader } from './drftLoader.ts';
|
|
|
18
18
|
* it: a copy that left out the occlusion strength drew a bought model black.
|
|
19
19
|
*/
|
|
20
20
|
export { writePartMaterial } from './partMaterial.ts';
|
|
21
|
+
/*
|
|
22
|
+
* Parts drawn, cast and prepared by every rule a container needs, which `DrftLoader.draw` is: here
|
|
23
|
+
* for a caller drawing parts of its own choosing, several loaders' or a list it filtered.
|
|
24
|
+
*/
|
|
25
|
+
export { PartDraws } from './partDraws.ts';
|
|
21
26
|
/*
|
|
22
27
|
* **A `DTEX` chunk's tile table as the grid residency asks about.** The other constructor of a
|
|
23
28
|
* `MaterialTileGrid` — `latentTileGrid` cuts one from a latent a baker holds, and this reads one a
|
package/src/partDraws.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A loaded model's parts drawn, cast and prepared by the rules a baked container needs, so a caller
|
|
3
|
+
* gets them right by calling one method rather than by knowing each of them.
|
|
4
|
+
*
|
|
5
|
+
* **Every rule here is one that fails as a picture rather than an error**, which is why they are in
|
|
6
|
+
* one place: a part's whole material (`writePartMaterial`), or foliage loses its alpha test and a
|
|
7
|
+
* one-sided card vanishes from behind; a part's copies through the instanced path, or a repeated arch
|
|
8
|
+
* is drawn once; a blended part translucent whatever its opacity and writing no depth, since its
|
|
9
|
+
* alpha is in its texture, or a decal draws as a black patch on the stone; glass as glass, its options
|
|
10
|
+
* kept per pane so a frame allocates none; each part's own reflectivity; and in the shadow, every
|
|
11
|
+
* part but a decal, which would shadow its own surface, with its material so a cutout casts the
|
|
12
|
+
* shape in its texture. A part fades in on its `reveal`, translucent while it does.
|
|
13
|
+
*
|
|
14
|
+
* **What it gives up**: one material and one reflectivity a part, as the container says, with no
|
|
15
|
+
* per-part override; a caller who wants one (a map switched off, a part left out) draws the parts
|
|
16
|
+
* itself, which `writePartMaterial` and the fields of `DrftPart` are for. It sets no camera and opens
|
|
17
|
+
* no pass: it draws into whichever mesh pass is open, as `drawMesh` does.
|
|
18
|
+
*/
|
|
19
|
+
import type {
|
|
20
|
+
GlassOptions,
|
|
21
|
+
RendererApi,
|
|
22
|
+
ShadowCasterSink,
|
|
23
|
+
SurfaceMaterial,
|
|
24
|
+
SurfaceTextureHandle,
|
|
25
|
+
TranslucentMeshOptions,
|
|
26
|
+
} from '@driftengine/core';
|
|
27
|
+
import type { TextureSet } from './drftTextures.ts';
|
|
28
|
+
import type { DrftPart } from './loadProgress.ts';
|
|
29
|
+
import { writePartMaterial } from './partMaterial.ts';
|
|
30
|
+
|
|
31
|
+
const IDENTITY = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
32
|
+
|
|
33
|
+
/** A blended part's draw: its alpha is in its texture, and a depth it wrote would hide what is behind. */
|
|
34
|
+
const BLENDED: TranslucentMeshOptions = Object.freeze({ depthWrite: false });
|
|
35
|
+
const OPAQUE_PREPARED = Object.freeze({ translucent: false });
|
|
36
|
+
const TRANSLUCENT_PREPARED = Object.freeze({ translucent: true });
|
|
37
|
+
|
|
38
|
+
/** A caster's material: a part's, and its glass, rewritten for each part. */
|
|
39
|
+
type CasterMaterial = SurfaceMaterial<SurfaceTextureHandle> & { glass?: GlassOptions };
|
|
40
|
+
|
|
41
|
+
/** Parts drawn, cast and prepared by every rule a container needs; `DrftLoader.draw` is one. */
|
|
42
|
+
export class PartDraws {
|
|
43
|
+
private readonly material: SurfaceMaterial<SurfaceTextureHandle> = {};
|
|
44
|
+
private readonly casterMaterial: CasterMaterial = {};
|
|
45
|
+
/** Each pane's draw options, made once: a frame drawing glass allocates nothing. */
|
|
46
|
+
private readonly glassDraws = new Map<GlassOptions, TranslucentMeshOptions>();
|
|
47
|
+
|
|
48
|
+
constructor(private readonly renderer: RendererApi) {}
|
|
49
|
+
|
|
50
|
+
/** Draw every part into the open mesh pass. Returns the draws issued. */
|
|
51
|
+
draw(parts: readonly DrftPart[], textures: TextureSet<SurfaceTextureHandle> | null): number {
|
|
52
|
+
const renderer = this.renderer;
|
|
53
|
+
let draws = 0;
|
|
54
|
+
for (const part of parts) {
|
|
55
|
+
renderer.setMaterial(writePartMaterial(part, textures, this.material));
|
|
56
|
+
renderer.setSurfaceReflectivity(part.reflectivity);
|
|
57
|
+
const opacity = part.opacity * part.reveal;
|
|
58
|
+
const options = this.optionsFor(part);
|
|
59
|
+
const instances = part.instances;
|
|
60
|
+
if (instances !== null) {
|
|
61
|
+
if (translucent(part, opacity)) {
|
|
62
|
+
renderer.drawTranslucentInstanced(instances.batch, instances.data, opacity, options);
|
|
63
|
+
} else {
|
|
64
|
+
renderer.drawInstanced(instances.batch, instances.data);
|
|
65
|
+
}
|
|
66
|
+
} else if (translucent(part, opacity)) {
|
|
67
|
+
renderer.drawTranslucentMesh(part.mesh, IDENTITY, opacity, options);
|
|
68
|
+
} else {
|
|
69
|
+
renderer.drawMesh(part.mesh, IDENTITY);
|
|
70
|
+
}
|
|
71
|
+
draws++;
|
|
72
|
+
}
|
|
73
|
+
/* Nothing of the last part is left for the caller's own draws: no material, no mirror. */
|
|
74
|
+
renderer.setMaterial(null);
|
|
75
|
+
renderer.setSurfaceReflectivity(0);
|
|
76
|
+
return draws;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Offer every part that stands in the light to a shadow pass, or to a mirror's replay. */
|
|
80
|
+
cast(
|
|
81
|
+
sink: ShadowCasterSink,
|
|
82
|
+
parts: readonly DrftPart[],
|
|
83
|
+
textures: TextureSet<SurfaceTextureHandle> | null,
|
|
84
|
+
): void {
|
|
85
|
+
const material = this.casterMaterial;
|
|
86
|
+
for (const part of parts) {
|
|
87
|
+
/* A blended part that is not glass is a decal, which would shadow the surface it lies on. */
|
|
88
|
+
if (part.blend && part.glass === null) continue;
|
|
89
|
+
writePartMaterial(part, textures, material);
|
|
90
|
+
material.glass = part.glass ?? undefined;
|
|
91
|
+
if (part.instances !== null) {
|
|
92
|
+
sink.instanced?.(part.instances.batch, part.instances.data, material);
|
|
93
|
+
} else {
|
|
94
|
+
sink.mesh(part.mesh, IDENTITY, material);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Compile, off the frame, what every part's draw will take, then wait for it: each in its own
|
|
101
|
+
* material, translucent as well where it will be drawn so, through its batch where it has copies.
|
|
102
|
+
* What it cannot reach is the shadow passes' and a probe's own pipelines, which one frame drawn
|
|
103
|
+
* behind a loading screen compiles; see `RendererApi.prepareMesh`.
|
|
104
|
+
*/
|
|
105
|
+
async prepare(
|
|
106
|
+
parts: readonly DrftPart[],
|
|
107
|
+
textures: TextureSet<SurfaceTextureHandle> | null,
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
const renderer = this.renderer;
|
|
110
|
+
for (const part of parts) {
|
|
111
|
+
const material = writePartMaterial(part, textures, this.material);
|
|
112
|
+
const options = translucent(part, part.opacity) ? TRANSLUCENT_PREPARED : OPAQUE_PREPARED;
|
|
113
|
+
if (part.instances !== null)
|
|
114
|
+
renderer.prepareInstanced(part.instances.batch, material, options);
|
|
115
|
+
else renderer.prepareMesh(part.mesh, material, options);
|
|
116
|
+
}
|
|
117
|
+
await renderer.ready();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** A part's translucent draw options: glass as glass, a blended part writing no depth. */
|
|
121
|
+
private optionsFor(part: DrftPart): TranslucentMeshOptions | undefined {
|
|
122
|
+
const glass = part.glass;
|
|
123
|
+
if (glass !== null) {
|
|
124
|
+
let options = this.glassDraws.get(glass);
|
|
125
|
+
if (options === undefined) {
|
|
126
|
+
options = Object.freeze({ ...BLENDED, glass });
|
|
127
|
+
this.glassDraws.set(glass, options);
|
|
128
|
+
}
|
|
129
|
+
return options;
|
|
130
|
+
}
|
|
131
|
+
return part.blend ? BLENDED : undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A blended part is drawn translucent whatever its opacity: its alpha is in its texture. */
|
|
136
|
+
function translucent(part: DrftPart, opacity: number): boolean {
|
|
137
|
+
return opacity < 1 || part.blend;
|
|
138
|
+
}
|