reze-engine 0.50.12 → 0.51.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/dist/engine.d.ts +81 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +295 -46
- package/dist/graph/presets/unlit.d.ts +3 -0
- package/dist/graph/presets/unlit.d.ts.map +1 -0
- package/dist/graph/presets/unlit.js +34 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/shaders/lyrics-api.d.ts +11 -2
- package/dist/shaders/lyrics-api.d.ts.map +1 -1
- package/dist/shaders/lyrics-api.js +11 -2
- package/package.json +1 -1
- package/src/engine.ts +315 -11
- package/src/graph/presets/unlit.ts +37 -0
- package/src/index.ts +1 -0
- package/src/shaders/lyrics-api.ts +11 -2
package/dist/engine.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Camera } from "./camera";
|
|
|
2
2
|
import { decodeDds, isDds } from "./dds-loader";
|
|
3
3
|
import { Mat4, Quat, Vec3 } from "./math";
|
|
4
4
|
import { decodePsd, isPsd } from "./psd-loader";
|
|
5
|
-
import { MATERIAL_MORPH_MULTIPLY } from "./model";
|
|
5
|
+
import { Model, MATERIAL_MORPH_MULTIPLY } from "./model";
|
|
6
6
|
import { MORPH_COMPUTE_WGSL } from "./shaders/passes/morph";
|
|
7
7
|
import { CULL_COMPUTE_WGSL } from "./shaders/passes/cull";
|
|
8
8
|
import { buildAnchorTable, anchorAliasWgsl, EMPTY_ANCHOR_TABLE } from "./shaders/anchor-table";
|
|
@@ -41,6 +41,7 @@ import { PICK_SHADER_WGSL } from "./shaders/passes/pick";
|
|
|
41
41
|
import { MIPMAP_BLIT_SHADER_WGSL } from "./shaders/passes/mipmap";
|
|
42
42
|
import { compileGraph } from "./graph/compile";
|
|
43
43
|
import { DEFAULT_GRAPH } from "./graph/presets/default";
|
|
44
|
+
import { UNLIT_GRAPH } from "./graph/presets/unlit";
|
|
44
45
|
import { FACE_GRAPH } from "./graph/presets/face";
|
|
45
46
|
import { HAIR_GRAPH } from "./graph/presets/hair";
|
|
46
47
|
import { BODY_GRAPH } from "./graph/presets/body";
|
|
@@ -983,6 +984,8 @@ export class Engine {
|
|
|
983
984
|
this.framePhysicsMsRaw = 0;
|
|
984
985
|
/** Every shadow caster in one sphere: (x, y, z, radius). radius 0 = nothing
|
|
985
986
|
* casts, -1 = do not use (a rigid caster has no sphere). See updateCasterSphere. */
|
|
987
|
+
/** A card's own texture, by model key — see setPlaneFrame. */
|
|
988
|
+
this.planeTextures = new Map();
|
|
986
989
|
this.casterSphere = new Float32Array(4);
|
|
987
990
|
/** The ground's uniform block, kept so the caster sphere can be refreshed in
|
|
988
991
|
* it every frame rather than rebuilding the buffer (addGround allocates). */
|
|
@@ -2040,41 +2043,6 @@ export class Engine {
|
|
|
2040
2043
|
primitive: { topology: "triangle-list" },
|
|
2041
2044
|
});
|
|
2042
2045
|
}
|
|
2043
|
-
/**
|
|
2044
|
-
* Install the scene's WGSL effect (shadertoy-style), rendered per-pixel in the
|
|
2045
|
-
* composite pass. ONE effect per scene, and the code says where it mounts by
|
|
2046
|
-
* which of these it defines — either, or both in one file:
|
|
2047
|
-
*
|
|
2048
|
-
* fn background(ray: vec3f, uv: vec2f, time: f32) -> vec4f
|
|
2049
|
-
* fn foreground(ray: vec3f, uv: vec2f, time: f32, depth: f32) -> vec4f
|
|
2050
|
-
*
|
|
2051
|
-
* `background` is a LAYER between the base background and the scene,
|
|
2052
|
-
* over-composited onto whichever base is active (solid color, 360 equirect, or
|
|
2053
|
-
* transparency) — its alpha lets the base show through, so a starfield is
|
|
2054
|
-
* stars over the user's background color. `foreground` composites over the
|
|
2055
|
-
* finished frame instead, which is where rain, snow, petals and fog live, and
|
|
2056
|
-
* is handed `depth`: the camera-space distance in metres of whatever the scene
|
|
2057
|
-
* drew at that pixel (the far plane where it drew nothing). Compare a
|
|
2058
|
-
* particle's own distance against it and the model occludes it; fog just reads
|
|
2059
|
-
* it, since fog's alpha IS a function of distance.
|
|
2060
|
-
*
|
|
2061
|
-
* `ray` is the pixel's normalized world-space view direction (LH, +Z forward —
|
|
2062
|
-
* what the skybox samples by), `uv` is 0..1 bottom-left origin, `time` is
|
|
2063
|
-
* seconds since apply, and `bgResolution()` gives the canvas size. Return sRGB
|
|
2064
|
-
* + alpha; alpha is the only "how much does this replace" control there is.
|
|
2065
|
-
* Declared `params` arrive as `params.<name>` (number → f32, Vec3 → vec3f),
|
|
2066
|
-
* shared by both mounts, and are later tweaked without recompiling via
|
|
2067
|
-
* setEffectParam.
|
|
2068
|
-
*
|
|
2069
|
-
* Both mounts are display-space: neither affects lighting, bloom or
|
|
2070
|
-
* tonemapping, and both are captured by offline export. A foreground makes the
|
|
2071
|
-
* scene pass STORE its depth buffer (it otherwise discards it into tile
|
|
2072
|
-
* memory) for as long as one is installed.
|
|
2073
|
-
*
|
|
2074
|
-
* Compiles off the hot path (async pipelines): on failure the previous effect
|
|
2075
|
-
* is KEPT and diagnostics are returned with line numbers relative to the
|
|
2076
|
-
* user's WGSL. Pass null to remove the effect.
|
|
2077
|
-
*/
|
|
2078
2046
|
async compileEffect(wgsl, params,
|
|
2079
2047
|
/** This effect's own declarations, already parsed by the caller — which had
|
|
2080
2048
|
* to read them anyway to build the scene table. */
|
|
@@ -2170,6 +2138,28 @@ export class Engine {
|
|
|
2170
2138
|
// pinning an effect that declares this has to keep installing; saying so is
|
|
2171
2139
|
// all that was ever missing.
|
|
2172
2140
|
const warnings = [];
|
|
2141
|
+
// A DIRECTIVE THAT PARSED AS NOTHING.
|
|
2142
|
+
//
|
|
2143
|
+
// Every pragma is matched with `\s*$` after it, so a line that carries a
|
|
2144
|
+
// note as well — `// @fullres — glyph edges are sub-pixel detail` — matches
|
|
2145
|
+
// none of them and is read as an ordinary comment. Nothing failed, nothing
|
|
2146
|
+
// said anything, and the effect simply ran without the property it asked
|
|
2147
|
+
// for: three shipped effects were silently half-res and a fourth silently
|
|
2148
|
+
// stopped being additive, each one's first line explaining why it needed
|
|
2149
|
+
// the thing it was not getting.
|
|
2150
|
+
//
|
|
2151
|
+
// Every directive this engine knows, so an unrecognised one is named rather
|
|
2152
|
+
// than ignored. Cheap: it runs once per install, over a file a human wrote.
|
|
2153
|
+
for (const m of wgsl.matchAll(/^[ \t]*\/\/[ \t]*(@[a-zA-Z]+)(.*)$/gm)) {
|
|
2154
|
+
const [, tag, rest] = m;
|
|
2155
|
+
if (!Engine.KNOWN.has(tag)) {
|
|
2156
|
+
warnings.push(`${tag} is not a directive this engine knows — it will be ignored.`);
|
|
2157
|
+
}
|
|
2158
|
+
else if (rest.trim() && !/^\s*[\w.\-+]+(\s+[\w.\-+]+)*\s*$/.test(rest)) {
|
|
2159
|
+
warnings.push(`${tag} has a note on the same line, so it does not parse and is being IGNORED. ` +
|
|
2160
|
+
`A directive must be alone on its line — put the note on the next one.`);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2173
2163
|
if (parseParticleBloom(wgsl) && !wantsParticles && !wantsTrails) {
|
|
2174
2164
|
warnings.push("// @bloom does nothing here. A field effect (background/foreground) composites after tone " +
|
|
2175
2165
|
"mapping, past the bloom pyramid — the directive applies to particles and ribbons, which draw " +
|
|
@@ -2406,7 +2396,22 @@ export class Engine {
|
|
|
2406
2396
|
epochScene: this.sceneClock,
|
|
2407
2397
|
// Its OWN resolution, no longer the scene's: an effect that never asked
|
|
2408
2398
|
// for full res is not promoted because a neighbour did.
|
|
2409
|
-
|
|
2399
|
+
// FULL RESOLUTION UNLESS TOLD OTHERWISE.
|
|
2400
|
+
//
|
|
2401
|
+
// It was the other way round, and the default was the bug. An author
|
|
2402
|
+
// who has never heard of the flag writes an effect with an edge in it
|
|
2403
|
+
// and gets a soft one — nothing fails, nothing warns, because nothing
|
|
2404
|
+
// was declared to fail. Three shipped effects DID declare it and were
|
|
2405
|
+
// half-res anyway on a parsing technicality, which is the same bug
|
|
2406
|
+
// wearing a different hat: the safe answer has to be the one you get
|
|
2407
|
+
// for saying nothing.
|
|
2408
|
+
//
|
|
2409
|
+
// The cost is real and is why the half layer stays: `@halfres` is worth
|
|
2410
|
+
// about 3.7x on a full-screen effect (Footprints, measured, 1.2ms
|
|
2411
|
+
// against 4.5ms). It is the right call for a soft additive glow, which
|
|
2412
|
+
// upsamples invisibly — and it is now a claim an author makes about
|
|
2413
|
+
// their own effect rather than a fate that befalls one.
|
|
2414
|
+
fieldLayer: /^\s*\/\/\s*@halfres\s*$/m.test(wgsl) ? 1 : 0,
|
|
2410
2415
|
fieldPipeline,
|
|
2411
2416
|
fieldClock,
|
|
2412
2417
|
// Filled by rebuildFieldBindGroup below, which needs the instance to
|
|
@@ -3345,7 +3350,9 @@ export class Engine {
|
|
|
3345
3350
|
return null;
|
|
3346
3351
|
const view = this.camera.getViewMatrix().values;
|
|
3347
3352
|
for (const inst of this.modelInstances.values()) {
|
|
3348
|
-
|
|
3353
|
+
// Neither a stage nor a plane is a performer, so neither is a subject an
|
|
3354
|
+
// effect can follow.
|
|
3355
|
+
if (!inst.model.visible || inst.isStage || inst.isPlane)
|
|
3349
3356
|
continue;
|
|
3350
3357
|
const model = inst.model;
|
|
3351
3358
|
const matrices = model.getWorldMatrices();
|
|
@@ -5712,7 +5719,7 @@ export class Engine {
|
|
|
5712
5719
|
// is first in insertion order and was seeding this clock with its own
|
|
5713
5720
|
// permanent zero. In a scene with a stage, a camera VMD therefore sampled
|
|
5714
5721
|
// frame 0 forever and the shot never moved.
|
|
5715
|
-
if (inst.isStage)
|
|
5722
|
+
if (inst.isStage || inst.isPlane)
|
|
5716
5723
|
continue;
|
|
5717
5724
|
const p = inst.model.getAnimationProgress();
|
|
5718
5725
|
if (p.playing || p.paused)
|
|
@@ -6054,7 +6061,7 @@ export class Engine {
|
|
|
6054
6061
|
const reader = assetReader ?? createFetchAssetReader();
|
|
6055
6062
|
const basePath = deriveBasePathFromPmxPath(pmxPath);
|
|
6056
6063
|
model.setAssetContext(reader, basePath);
|
|
6057
|
-
await this.setupModelInstance(key, model, basePath, reader, options?.stage ?? false);
|
|
6064
|
+
await this.setupModelInstance(key, model, basePath, reader, options?.stage ?? false, options?.plane ?? false, options?.dynamic ?? false);
|
|
6058
6065
|
return key;
|
|
6059
6066
|
}
|
|
6060
6067
|
/**
|
|
@@ -6082,6 +6089,163 @@ export class Engine {
|
|
|
6082
6089
|
this.setModelTransform(key, options.transform);
|
|
6083
6090
|
return key;
|
|
6084
6091
|
}
|
|
6092
|
+
/**
|
|
6093
|
+
* Put a picture in the scene as a flat card.
|
|
6094
|
+
*
|
|
6095
|
+
* The thing compositors arrange in a post tool's fake 3D space — Nuke calls
|
|
6096
|
+
* it a Card, After Effects a 3D layer, MMD 板ポリ — except the space here is
|
|
6097
|
+
* the real one. A card is occluded by anything in front of it, occludes what
|
|
6098
|
+
* is behind it, takes perspective when turned, and is caught by depth of
|
|
6099
|
+
* field like everything else, because it is ordinary geometry rather than a
|
|
6100
|
+
* layer composited afterwards.
|
|
6101
|
+
*
|
|
6102
|
+
* It is a MODEL, deliberately. Not a new kind of scene object with its own
|
|
6103
|
+
* list, its own persistence and its own selection: a card wants a position,
|
|
6104
|
+
* a rotation and a size, which is exactly what a model already has, and
|
|
6105
|
+
* everything built around models — the transform, the shadow settings, the
|
|
6106
|
+
* material editor, the asset bundle — works on it the day it exists. It is
|
|
6107
|
+
* not a STAGE, though: it skips the same machinery for the same reasons, and
|
|
6108
|
+
* leaves the floor alone. See ModelInstance.isPlane.
|
|
6109
|
+
*
|
|
6110
|
+
* @returns the model key, for setModelTransform and removeModel.
|
|
6111
|
+
*/
|
|
6112
|
+
async addPlane(options) {
|
|
6113
|
+
const { image, name, width, height } = options;
|
|
6114
|
+
const hw = Math.max(width, 1e-4) / 2;
|
|
6115
|
+
const hh = Math.max(height, 1e-4) / 2;
|
|
6116
|
+
// A quad on the XY plane, facing +Z, centred on its own origin — so a
|
|
6117
|
+
// rotation turns it about its middle and a position places its centre,
|
|
6118
|
+
// which is what a handle in the viewport implies.
|
|
6119
|
+
//
|
|
6120
|
+
// V IS FLIPPED, and this is the whole of it: a picture's rows run downward
|
|
6121
|
+
// from its top-left, a UV runs upward from the bottom-left, and a card that
|
|
6122
|
+
// renders its image upside down looks like a bug in everything else.
|
|
6123
|
+
// prettier-ignore
|
|
6124
|
+
const vertexData = new Float32Array([
|
|
6125
|
+
// x y z nx ny nz u v
|
|
6126
|
+
-hw, -hh, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0,
|
|
6127
|
+
hw, -hh, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0,
|
|
6128
|
+
hw, hh, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
|
|
6129
|
+
-hw, hh, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
|
|
6130
|
+
]);
|
|
6131
|
+
// Two triangles, counter-clockwise seen from +Z. A second pair wound the
|
|
6132
|
+
// other way is how "visible from behind" is done here, rather than a
|
|
6133
|
+
// per-material cull flag the rest of the engine has no concept of.
|
|
6134
|
+
const indices = options.doubleSided ? [0, 1, 2, 0, 2, 3, 0, 2, 1, 0, 3, 2] : [0, 1, 2, 0, 2, 3];
|
|
6135
|
+
const indexData = new Uint32Array(indices);
|
|
6136
|
+
// The texture table's one entry. The path is a key, not a location — the
|
|
6137
|
+
// reader below answers it from memory, so nothing is fetched and nothing is
|
|
6138
|
+
// written to disk.
|
|
6139
|
+
//
|
|
6140
|
+
// A PLAIN RELATIVE NAME under a plain directory, because the loader treats
|
|
6141
|
+
// this exactly as it treats a PMX's: it takes the model path's directory
|
|
6142
|
+
// and JOINS the texture entry onto it. A scheme-looking path went through
|
|
6143
|
+
// that as `plane://` + `plane://name` and matched nothing, so every card
|
|
6144
|
+
// came out with the untextured fallback. `plane/<name>` joins to
|
|
6145
|
+
// `plane/<name>` and stays unique per card, which the engine-wide texture
|
|
6146
|
+
// cache needs it to be.
|
|
6147
|
+
const texturePath = `plane/${name}`;
|
|
6148
|
+
const material = {
|
|
6149
|
+
name,
|
|
6150
|
+
diffuse: [1, 1, 1, 1],
|
|
6151
|
+
specular: [0, 0, 0],
|
|
6152
|
+
ambient: [0, 0, 0],
|
|
6153
|
+
shininess: 0,
|
|
6154
|
+
diffuseTextureIndex: 0,
|
|
6155
|
+
normalTextureIndex: -1,
|
|
6156
|
+
sphereTextureIndex: -1,
|
|
6157
|
+
sphereMode: 0,
|
|
6158
|
+
toonTextureIndex: -1,
|
|
6159
|
+
sharedToon: false,
|
|
6160
|
+
// 0 CARRIES TWO DECISIONS, both wanted, and both silent if changed.
|
|
6161
|
+
//
|
|
6162
|
+
// No inverted-hull outline (bit 0x10): a card is not a character, and a
|
|
6163
|
+
// black rim around a light leak is the opposite of what it is for.
|
|
6164
|
+
//
|
|
6165
|
+
// AND NO SHADOW (bit 0x04, which is what castsShadow reads). A card is
|
|
6166
|
+
// usually light or artwork rather than an object, and a rectangle of hard
|
|
6167
|
+
// shadow thrown across the stage by a gradient reads as the renderer
|
|
6168
|
+
// being broken. Set geometry that SHOULD cast one is the rarer case, and
|
|
6169
|
+
// it can say so.
|
|
6170
|
+
edgeFlag: 0,
|
|
6171
|
+
edgeColor: [0, 0, 0, 1],
|
|
6172
|
+
edgeSize: 0,
|
|
6173
|
+
vertexCount: indices.length,
|
|
6174
|
+
};
|
|
6175
|
+
// ONE BONE, because Model requires one — it throws on an empty skeleton,
|
|
6176
|
+
// every vertex has to be skinned to something, and a card has nothing to
|
|
6177
|
+
// articulate. It is never posed; the model transform is what moves a card.
|
|
6178
|
+
const skeleton = {
|
|
6179
|
+
bones: [{ name: "全ての親", parentIndex: -1, bindTranslation: [0, 0, 0], children: [] }],
|
|
6180
|
+
inverseBindMatrices: new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]),
|
|
6181
|
+
};
|
|
6182
|
+
const vertexCount = 4;
|
|
6183
|
+
const joints = new Uint16Array(vertexCount * 4);
|
|
6184
|
+
const weights = new Uint8Array(vertexCount * 4);
|
|
6185
|
+
for (let i = 0; i < vertexCount; i++)
|
|
6186
|
+
weights[i * 4] = 255;
|
|
6187
|
+
const model = new Model(vertexData, indexData, [{ path: name, name }], [material], skeleton, { joints, weights }, { morphs: [] });
|
|
6188
|
+
// ITS OWN PATH, not addStage's. A plane and a stage skip the same machinery
|
|
6189
|
+
// and mean different things, and routing one through the other is how the
|
|
6190
|
+
// ground came to be suppressed by adding a picture.
|
|
6191
|
+
// The picture answers from memory, whatever it is asked for: a card has
|
|
6192
|
+
// exactly ONE texture, so there is nothing to disambiguate and no way for a
|
|
6193
|
+
// path to be wrong. Matching the string instead is what silently produced
|
|
6194
|
+
// untextured cards, because the loader composes that string itself.
|
|
6195
|
+
const reader = { readBinary: async () => image };
|
|
6196
|
+
const key = await this.addModel(model, texturePath, name, reader, { plane: true, dynamic: options.dynamic });
|
|
6197
|
+
// UNLIT, because a card is FOOTAGE and not a surface.
|
|
6198
|
+
//
|
|
6199
|
+
// Its pixels were finished somewhere else — a gradient painted in
|
|
6200
|
+
// Photoshop, a title, a rendered element — so its brightness is the artwork
|
|
6201
|
+
// rather than a response to anything. Shading it means the sun dimming one
|
|
6202
|
+
// side of a thing that has no sides, and the world colour tinting a picture
|
|
6203
|
+
// whose colour was the point. Left ungrouped it would take the neutral
|
|
6204
|
+
// Principled base, which is exactly that mistake.
|
|
6205
|
+
//
|
|
6206
|
+
// A group, not a hard-coded pipeline: a card used as SET geometry — a photo
|
|
6207
|
+
// of a wall, a poster standing in the room — genuinely does want the light,
|
|
6208
|
+
// and this is the same control every other material is changed through, so
|
|
6209
|
+
// that case is a graph swap rather than a feature request.
|
|
6210
|
+
await this.applyStyleGroups(key, [
|
|
6211
|
+
{ id: "plane", label: "Plane", materials: [name], graph: UNLIT_GRAPH, alphaMode: "hashed" },
|
|
6212
|
+
]);
|
|
6213
|
+
if (options.transform)
|
|
6214
|
+
this.setModelTransform(key, options.transform);
|
|
6215
|
+
// Kept so a moving card can push frames into it. The cache is keyed by the
|
|
6216
|
+
// texture's logical path, which is derived rather than stored anywhere the
|
|
6217
|
+
// caller can see — and deriving it twice is how the two would drift.
|
|
6218
|
+
const tex = this.textureCache.get(texturePath);
|
|
6219
|
+
if (tex)
|
|
6220
|
+
this.planeTextures.set(key, tex);
|
|
6221
|
+
return key;
|
|
6222
|
+
}
|
|
6223
|
+
/**
|
|
6224
|
+
* Replace what a card is showing, in place.
|
|
6225
|
+
*
|
|
6226
|
+
* For a moving card: a video element, a decoded frame, a canvas — anything
|
|
6227
|
+
* copyExternalImageToTexture accepts. Nothing is reallocated and no bind group
|
|
6228
|
+
* is rebuilt, so this is a per-frame call rather than a per-clip one; the
|
|
6229
|
+
* texture is written where it stands and the material keeps pointing at it.
|
|
6230
|
+
*
|
|
6231
|
+
* The frame must be the size the card was created at. A card is a fixed
|
|
6232
|
+
* rectangle of texels and resizing one mid-clip would mean rebuilding the
|
|
6233
|
+
* material behind it — so the caller allocates the card at its video's size
|
|
6234
|
+
* and this refuses anything else rather than stretching it silently.
|
|
6235
|
+
*/
|
|
6236
|
+
setPlaneFrame(id, source, width, height) {
|
|
6237
|
+
const tex = this.planeTextures.get(id);
|
|
6238
|
+
if (!tex || !this.device)
|
|
6239
|
+
return false;
|
|
6240
|
+
if (tex.width !== width || tex.height !== height)
|
|
6241
|
+
return false;
|
|
6242
|
+
this.device.queue.copyExternalImageToTexture({ source }, { texture: tex }, [width, height]);
|
|
6243
|
+
// A moving card is allocated with one level precisely so this is never
|
|
6244
|
+
// reached: rebuilding a mip pyramid per frame is a pass per level per card.
|
|
6245
|
+
if (tex.mipLevelCount > 1)
|
|
6246
|
+
this.generateMipmaps(tex, tex.mipLevelCount);
|
|
6247
|
+
return true;
|
|
6248
|
+
}
|
|
6085
6249
|
/** True while a stage is in the scene. Two things turn on it: the built-in
|
|
6086
6250
|
* ground plane must not draw, and the far shadow cascade has nothing to
|
|
6087
6251
|
* cover without one (see the cascade loop). */
|
|
@@ -6100,6 +6264,9 @@ export class Engine {
|
|
|
6100
6264
|
const inst = this.modelInstances.get(name);
|
|
6101
6265
|
if (!inst)
|
|
6102
6266
|
return;
|
|
6267
|
+
// Before the texture cache below frees it: a stale entry here would hand a
|
|
6268
|
+
// destroyed texture to the next setPlaneFrame.
|
|
6269
|
+
this.planeTextures.delete(name);
|
|
6103
6270
|
inst.model.stop();
|
|
6104
6271
|
for (const path of inst.textureCacheKeys) {
|
|
6105
6272
|
const tex = this.textureCache.get(path);
|
|
@@ -6352,10 +6519,10 @@ export class Engine {
|
|
|
6352
6519
|
// A stage never solves IK — nothing drives its chains — and skips the pose
|
|
6353
6520
|
// pass entirely while it is idle. Morph changes still come through, since
|
|
6354
6521
|
// that is the one thing a stage's controls do move.
|
|
6355
|
-
const stageIdle = inst.isStage && inst.model.isIdle();
|
|
6522
|
+
const stageIdle = (inst.isStage || inst.isPlane) && inst.model.isIdle();
|
|
6356
6523
|
let verticesChanged = false;
|
|
6357
6524
|
if (!stageIdle) {
|
|
6358
|
-
verticesChanged = inst.model.update(deltaTime, inst.isStage ? false : this.ikEnabled);
|
|
6525
|
+
verticesChanged = inst.model.update(deltaTime, inst.isStage || inst.isPlane ? false : this.ikEnabled);
|
|
6359
6526
|
inst.skinMatricesDirty = true;
|
|
6360
6527
|
}
|
|
6361
6528
|
animMs += performance.now() - tAnim;
|
|
@@ -7305,7 +7472,7 @@ export class Engine {
|
|
|
7305
7472
|
});
|
|
7306
7473
|
}
|
|
7307
7474
|
}
|
|
7308
|
-
async setupModelInstance(name, model, basePath, assetReader, isStage = false) {
|
|
7475
|
+
async setupModelInstance(name, model, basePath, assetReader, isStage = false, isPlane = false, dynamicTexture = false) {
|
|
7309
7476
|
const vertices = model.getVertices();
|
|
7310
7477
|
const skinning = model.getSkinning();
|
|
7311
7478
|
const skeleton = model.getSkeleton();
|
|
@@ -7348,7 +7515,7 @@ export class Engine {
|
|
|
7348
7515
|
// A stage never simulates, so its bodies are never built — constructing the
|
|
7349
7516
|
// solver for the heaviest mesh in the scene and dropping it afterwards was
|
|
7350
7517
|
// both wasted work and an invariant maintained in the wrong place.
|
|
7351
|
-
const physics = !isStage && rbs.length > 0 ? new RezePhysics(rbs, model.getJoints()) : null;
|
|
7518
|
+
const physics = !isStage && !isPlane && rbs.length > 0 ? new RezePhysics(rbs, model.getJoints()) : null;
|
|
7352
7519
|
// Which bones the simulation will overwrite, handed to the pose pipeline so
|
|
7353
7520
|
// the append (付与) pass can consume the simulated result instead of the
|
|
7354
7521
|
// animated one. Precomputed here, once, because the answer is topology —
|
|
@@ -7420,6 +7587,8 @@ export class Engine {
|
|
|
7420
7587
|
pickPerInstanceBindGroup,
|
|
7421
7588
|
pickDrawCalls: [],
|
|
7422
7589
|
isStage,
|
|
7590
|
+
isPlane,
|
|
7591
|
+
dynamicTexture,
|
|
7423
7592
|
// Seeded true: the bind pose has to reach the GPU once before any frame.
|
|
7424
7593
|
skinMatricesDirty: true,
|
|
7425
7594
|
hiddenMaterials: new Set(),
|
|
@@ -7841,7 +8010,21 @@ export class Engine {
|
|
|
7841
8010
|
bounds[3] += grow;
|
|
7842
8011
|
bounds[4] += grow;
|
|
7843
8012
|
bounds[5] += grow;
|
|
7844
|
-
|
|
8013
|
+
// A CARD IS ALWAYS OPAQUE-PHASE, whatever its alpha says.
|
|
8014
|
+
//
|
|
8015
|
+
// The scene pass runs opaque -> ground -> transparent, and the ground
|
|
8016
|
+
// writes depth at every opacity (effects locate the floor by it). Every
|
|
8017
|
+
// card qualifies as transparent — a cutout has translucent texels, and a
|
|
8018
|
+
// video card starts from a blank sheet that is nothing but — so cards
|
|
8019
|
+
// drew after the ground and an INVISIBLE floor rejected them. Turning the
|
|
8020
|
+
// ground down for the shadow catcher made pictures disappear into it.
|
|
8021
|
+
//
|
|
8022
|
+
// Not a workaround: alphaMode "hashed" is alpha-to-coverage, which is the
|
|
8023
|
+
// transparency technique built for this phase, and addPlane already sets
|
|
8024
|
+
// it. The cost is dithering on a large soft gradient, where MSAA has four
|
|
8025
|
+
// coverage levels to spend — a cutout edge, which is what a card usually
|
|
8026
|
+
// has, resolves exactly.
|
|
8027
|
+
const type = inst.isPlane ? "opaque" : isTransparent ? "transparent" : "opaque";
|
|
7845
8028
|
inst.drawCalls.push({
|
|
7846
8029
|
type,
|
|
7847
8030
|
count: indexCount,
|
|
@@ -8134,7 +8317,13 @@ export class Engine {
|
|
|
8134
8317
|
`so sheer fabric will not blend. The canvas 2D readback is what failed.`);
|
|
8135
8318
|
}
|
|
8136
8319
|
this.textureAlphaCache.set(cacheKey, alphaPlane);
|
|
8137
|
-
|
|
8320
|
+
// NO MIPS FOR A MOVING CARD. The chain would have to be rebuilt on every
|
|
8321
|
+
// frame written into it — a full pyramid of render passes per video plane
|
|
8322
|
+
// per frame, which is most of what a moving card was costing. Level 0 is
|
|
8323
|
+
// the only level a card in frame reads anyway; the price is aliasing on one
|
|
8324
|
+
// shrunk far into the distance, which is the case a video card is least
|
|
8325
|
+
// often in.
|
|
8326
|
+
const mipLevelCount = inst.dynamicTexture ? 1 : Math.floor(Math.log2(Math.max(width, height))) + 1;
|
|
8138
8327
|
const texture = this.device.createTexture({
|
|
8139
8328
|
label: `texture: ${cacheKey}`,
|
|
8140
8329
|
size: [width, height],
|
|
@@ -10337,6 +10526,66 @@ Engine.FILMIC_LUT_WIDTH = 256;
|
|
|
10337
10526
|
// (vsync-to-vsync), recomputed at STATS_REFRESH_MS so the readout doesn't flicker.
|
|
10338
10527
|
Engine.STATS_WINDOW = 120;
|
|
10339
10528
|
Engine.STATS_REFRESH_MS = 500;
|
|
10529
|
+
/**
|
|
10530
|
+
* Install the scene's WGSL effect (shadertoy-style), rendered per-pixel in the
|
|
10531
|
+
* composite pass. ONE effect per scene, and the code says where it mounts by
|
|
10532
|
+
* which of these it defines — either, or both in one file:
|
|
10533
|
+
*
|
|
10534
|
+
* fn background(ray: vec3f, uv: vec2f, time: f32) -> vec4f
|
|
10535
|
+
* fn foreground(ray: vec3f, uv: vec2f, time: f32, depth: f32) -> vec4f
|
|
10536
|
+
*
|
|
10537
|
+
* `background` is a LAYER between the base background and the scene,
|
|
10538
|
+
* over-composited onto whichever base is active (solid color, 360 equirect, or
|
|
10539
|
+
* transparency) — its alpha lets the base show through, so a starfield is
|
|
10540
|
+
* stars over the user's background color. `foreground` composites over the
|
|
10541
|
+
* finished frame instead, which is where rain, snow, petals and fog live, and
|
|
10542
|
+
* is handed `depth`: the camera-space distance in metres of whatever the scene
|
|
10543
|
+
* drew at that pixel (the far plane where it drew nothing). Compare a
|
|
10544
|
+
* particle's own distance against it and the model occludes it; fog just reads
|
|
10545
|
+
* it, since fog's alpha IS a function of distance.
|
|
10546
|
+
*
|
|
10547
|
+
* `ray` is the pixel's normalized world-space view direction (LH, +Z forward —
|
|
10548
|
+
* what the skybox samples by), `uv` is 0..1 bottom-left origin, `time` is
|
|
10549
|
+
* seconds since apply, and `bgResolution()` gives the canvas size. Return sRGB
|
|
10550
|
+
* + alpha; alpha is the only "how much does this replace" control there is.
|
|
10551
|
+
* Declared `params` arrive as `params.<name>` (number → f32, Vec3 → vec3f),
|
|
10552
|
+
* shared by both mounts, and are later tweaked without recompiling via
|
|
10553
|
+
* setEffectParam.
|
|
10554
|
+
*
|
|
10555
|
+
* Both mounts are display-space: neither affects lighting, bloom or
|
|
10556
|
+
* tonemapping, and both are captured by offline export. A foreground makes the
|
|
10557
|
+
* scene pass STORE its depth buffer (it otherwise discards it into tile
|
|
10558
|
+
* memory) for as long as one is installed.
|
|
10559
|
+
*
|
|
10560
|
+
* Compiles off the hot path (async pipelines): on failure the previous effect
|
|
10561
|
+
* is KEPT and diagnostics are returned with line numbers relative to the
|
|
10562
|
+
* user's WGSL. Pass null to remove the effect.
|
|
10563
|
+
*/
|
|
10564
|
+
/**
|
|
10565
|
+
* Every directive an effect may declare — used ONLY to tell an author that
|
|
10566
|
+
* the line they wrote is not doing what they think. See compileEffect.
|
|
10567
|
+
*
|
|
10568
|
+
* It has to be complete, including the ones this engine does not read itself:
|
|
10569
|
+
* `@dissolve` is parsed by the host, and warning about it would be worse than
|
|
10570
|
+
* the silence this replaced — a false alarm on a working line teaches authors
|
|
10571
|
+
* to ignore the channel.
|
|
10572
|
+
*/
|
|
10573
|
+
Engine.KNOWN = new Set([
|
|
10574
|
+
"@anchor",
|
|
10575
|
+
"@layer",
|
|
10576
|
+
"@blend",
|
|
10577
|
+
"@bloom",
|
|
10578
|
+
"@lights",
|
|
10579
|
+
"@grid",
|
|
10580
|
+
"@particles",
|
|
10581
|
+
"@halfres",
|
|
10582
|
+
// Accepted and inert: full resolution is the default now, and an effect
|
|
10583
|
+
// that still says so is right about what it wants. Warning about it would
|
|
10584
|
+
// be telling authors off for the thing that used to be necessary.
|
|
10585
|
+
"@fullres",
|
|
10586
|
+
// The HOST's, not this engine's — see the note above.
|
|
10587
|
+
"@dissolve",
|
|
10588
|
+
]);
|
|
10340
10589
|
// ── GPU frustum cull ────────────────────────────────────────────────────────
|
|
10341
10590
|
//
|
|
10342
10591
|
// Sizes, once, so the arithmetic below is readable: a DrawMeta is 32 bytes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unlit.d.ts","sourceRoot":"","sources":["../../../src/graph/presets/unlit.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,eAAO,MAAM,WAAW,EAAE,WAmBzB,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Unlit — the texture, at its own brightness, and nothing else.
|
|
2
|
+
//
|
|
3
|
+
// What FOOTAGE needs. A media plane carries pixels somebody already finished
|
|
4
|
+
// somewhere else: a gradient painted in Photoshop, a title card, a rendered
|
|
5
|
+
// element. Shading it is not a stylistic choice but a mistake — the scene's sun
|
|
6
|
+
// would dim one side of a card that has no side, and the world colour would
|
|
7
|
+
// tint artwork whose colour is the point. Blender's answer is the same one:
|
|
8
|
+
// wire the image straight into an Emission shader and let it out at the value
|
|
9
|
+
// it was authored at.
|
|
10
|
+
//
|
|
11
|
+
// Emission rather than Principled with roughness 1: emission is radiance, so it
|
|
12
|
+
// leaves the light loop entirely instead of being an unusually flat surface
|
|
13
|
+
// inside it. That is also what lets a card sit in front of a lamp without
|
|
14
|
+
// picking up its highlight.
|
|
15
|
+
export const UNLIT_GRAPH = {
|
|
16
|
+
version: 1,
|
|
17
|
+
name: "Unlit",
|
|
18
|
+
tags: ["unlit", "plane"],
|
|
19
|
+
nodes: [
|
|
20
|
+
{ id: "tex", type: "texture" },
|
|
21
|
+
// The PMX material colour still multiplies in, so a plane can be tinted or
|
|
22
|
+
// faded through the same dial every other material uses rather than needing
|
|
23
|
+
// one of its own.
|
|
24
|
+
{ id: "mat", type: "material_diffuse" },
|
|
25
|
+
{ id: "base", type: "mix/multiply", inputs: { fac: 1.0 } },
|
|
26
|
+
{ id: "emit", type: "emission", inputs: { strength: 1.0 } },
|
|
27
|
+
],
|
|
28
|
+
links: [
|
|
29
|
+
{ from: { node: "tex", socket: "color" }, to: { node: "base", socket: "a" } },
|
|
30
|
+
{ from: { node: "mat", socket: "color" }, to: { node: "base", socket: "b" } },
|
|
31
|
+
{ from: { node: "base", socket: "color" }, to: { node: "emit", socket: "color" } },
|
|
32
|
+
],
|
|
33
|
+
output: { node: "emit", socket: "color" },
|
|
34
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { BODY_GRAPH } from "./graph/presets/body";
|
|
|
18
18
|
export { STOCKINGS_GRAPH } from "./graph/presets/stockings";
|
|
19
19
|
export { EYE_GRAPH } from "./graph/presets/eye";
|
|
20
20
|
export { FACE_GRAPH } from "./graph/presets/face";
|
|
21
|
+
export { UNLIT_GRAPH } from "./graph/presets/unlit";
|
|
21
22
|
export { Model, MATERIAL_MORPH_MULTIPLY, MATERIAL_MORPH_ADD, type ClipEventInfo, type RootMotionProfile, type Morph, type Morphing, type BoneMorphOffset, type MaterialMorphOffset, type UvMorphOffset, } from "./model";
|
|
22
23
|
export { Vec3, Quat, Mat4, easeInOut, type EulerOrder } from "./math";
|
|
23
24
|
export type { AnimationClip, AnimationPlayOptions, AnimationProgress, BlendEntry, BoneKeyframe, IkKeyframe, MorphKeyframe, BoneInterpolation, ControlPoint, } from "./animation";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,8BAA8B,EAC9B,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,QAAQ,GACd,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACvG,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAGzC,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE/C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAIrH,OAAO,EAAE,gBAAgB,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAChF,OAAO,EACL,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,WAAW,EACX,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC3E,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7G,YAAY,EACV,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EACL,KAAK,EACL,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,KAAK,EACV,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,aAAa,GACnB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAA;AACrE,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,qBAAqB,EAAE,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAC5H,OAAO,EACL,GAAG,EACH,iBAAiB,EACjB,wBAAwB,EACxB,mCAAmC,GACpC,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,SAAS,EAAE,4BAA4B,EAAE,KAAK,cAAc,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AACzG,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export { BODY_GRAPH } from "./graph/presets/body";
|
|
|
22
22
|
export { STOCKINGS_GRAPH } from "./graph/presets/stockings";
|
|
23
23
|
export { EYE_GRAPH } from "./graph/presets/eye";
|
|
24
24
|
export { FACE_GRAPH } from "./graph/presets/face";
|
|
25
|
+
export { UNLIT_GRAPH } from "./graph/presets/unlit";
|
|
25
26
|
export { Model, MATERIAL_MORPH_MULTIPLY, MATERIAL_MORPH_ADD, } from "./model";
|
|
26
27
|
export { Vec3, Quat, Mat4, easeInOut } from "./math";
|
|
27
28
|
export { LocomotionController, } from "./locomotion";
|
|
@@ -5,8 +5,17 @@ export declare const LYRICS_FLOATS: number;
|
|
|
5
5
|
/** Bounds on the line atlas the host packs rasterised lines into. It is sized
|
|
6
6
|
* to the track that arrives rather than allocated at the maximum: a scene with
|
|
7
7
|
* no lyrics carries a 1×1 placeholder, and a song's atlas is as tall as its
|
|
8
|
-
* own lines need. 8192 is the smallest texture dimension WebGPU guarantees.
|
|
9
|
-
|
|
8
|
+
* own lines need. 8192 is the smallest texture dimension WebGPU guarantees.
|
|
9
|
+
*
|
|
10
|
+
* WIDTH IS WHAT DECIDES SHARPNESS on a long line. A lyric spans most of the
|
|
11
|
+
* frame — the shipped effect leaves 6% a side — so at 4K it is drawn across
|
|
12
|
+
* some 3400 pixels, and 2048 could only ever be stretched to cover them. That
|
|
13
|
+
* is the stair-stepped text on a 4K render, and no row height fixes it: the
|
|
14
|
+
* line is already as wide as the sheet. 1080p never showed it, because 1690
|
|
15
|
+
* pixels of line fit inside 2048. The atlas is r8unorm, one byte a texel, and
|
|
16
|
+
* only ever as wide as its own longest line — so this ceiling costs nothing
|
|
17
|
+
* until a song actually needs it. */
|
|
18
|
+
export declare const LYRIC_ATLAS_MAX_W = 4096;
|
|
10
19
|
export declare const LYRIC_ATLAS_MAX_H = 8192;
|
|
11
20
|
export type LyricLine = {
|
|
12
21
|
/** Seconds on the scene clock. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lyrics-api.d.ts","sourceRoot":"","sources":["../../src/shaders/lyrics-api.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,aAAa,QAAgD,CAAA;AAE1E
|
|
1
|
+
{"version":3,"file":"lyrics-api.d.ts","sourceRoot":"","sources":["../../src/shaders/lyrics-api.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,aAAa,QAAgD,CAAA;AAE1E;;;;;;;;;;;;sCAYsC;AACtC,eAAO,MAAM,iBAAiB,OAAO,CAAA;AACrC,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAErC,MAAM,MAAM,SAAS,GAAG;IACtB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAExD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CA+BpD;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,YAAY,CAAC,WAAW,CAAC,CAkB7F;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAsDhE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAgC5F"}
|
|
@@ -26,8 +26,17 @@ export const LYRICS_FLOATS = LYRIC_HEADER + LYRIC_LINES_MAX * LYRIC_STRIDE;
|
|
|
26
26
|
/** Bounds on the line atlas the host packs rasterised lines into. It is sized
|
|
27
27
|
* to the track that arrives rather than allocated at the maximum: a scene with
|
|
28
28
|
* no lyrics carries a 1×1 placeholder, and a song's atlas is as tall as its
|
|
29
|
-
* own lines need. 8192 is the smallest texture dimension WebGPU guarantees.
|
|
30
|
-
|
|
29
|
+
* own lines need. 8192 is the smallest texture dimension WebGPU guarantees.
|
|
30
|
+
*
|
|
31
|
+
* WIDTH IS WHAT DECIDES SHARPNESS on a long line. A lyric spans most of the
|
|
32
|
+
* frame — the shipped effect leaves 6% a side — so at 4K it is drawn across
|
|
33
|
+
* some 3400 pixels, and 2048 could only ever be stretched to cover them. That
|
|
34
|
+
* is the stair-stepped text on a 4K render, and no row height fixes it: the
|
|
35
|
+
* line is already as wide as the sheet. 1080p never showed it, because 1690
|
|
36
|
+
* pixels of line fit inside 2048. The atlas is r8unorm, one byte a texel, and
|
|
37
|
+
* only ever as wide as its own longest line — so this ceiling costs nothing
|
|
38
|
+
* until a song actually needs it. */
|
|
39
|
+
export const LYRIC_ATLAS_MAX_W = 4096;
|
|
31
40
|
export const LYRIC_ATLAS_MAX_H = 8192;
|
|
32
41
|
/**
|
|
33
42
|
* Parse an .lrc file: `[mm:ss.xx]` tags (several per line share the text),
|