@driftengine/assets 4.1.1 → 4.1.3

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.
@@ -950,20 +950,37 @@ export function isRawCodec(codec) {
950
950
  * the view would keep the whole file alive for as long as the image does.
951
951
  */
952
952
  /**
953
- * How every image a model carries is decoded: **straight alpha, and no colour conversion.**
953
+ * How every image a model carries is decoded: **premultiplied, and no colour conversion.**
954
954
  *
955
- * `createImageBitmap` premultiplies unless told not to, and the upload into a straight-alpha
956
- * texture then divides back out. A texel with no alpha comes back with no colour, so a cutout or an
957
- * emblem loses the colour its author padded past its edge, and filtering pulls black into the edge;
958
- * a partly transparent one comes back rounded to a coarser step. Colour management rewrites a normal
959
- * or ORM map, whose values are not colours at all, and glTF — which these assets are baked from —
960
- * says an image's own colour metadata is ignored. `render/imageTexels.ts` asks for the same two.
955
+ * **Straight alpha was tried in 4.0.0 and it is what this reverts.** The argument for it was that
956
+ * `createImageBitmap` premultiplies by default, so a texel with no alpha comes back with no colour
957
+ * and a cutout or an emblem loses the colour its author padded past its edge. That reasoning is
958
+ * sound about the pixels and wrong about the content: an imported material's albedo routinely
959
+ * carries arbitrary bytes under its fully transparent texels, and premultiplying was what kept
960
+ * them out of the frame.
961
961
  *
962
- * What it gives up: an albedo authored in a wide-gamut space with a profile saying so is read as
963
- * sRGB. The baker is where that conversion belongs, and it has no colour management either.
962
+ * **What it cost, reported and then reproduced.** A car came through with its interior, grille,
963
+ * mirrors and lamps as hard black and white shards. Held frames of one scene in the showroom, on
964
+ * one machine, at one frame: a model with 41 images drew clean and two with 64 and 65 drew shards,
965
+ * on **both** backends, with the old container and the new one alike — the count only decides how
966
+ * likely a model is to carry a masked texture at all. Restoring this one option drew the car
967
+ * correctly again.
968
+ *
969
+ * **Why an opaque material sees it.** Nothing discards on an opaque draw, so the shader takes
970
+ * `texel.rgb` whatever the alpha beside it says. Premultiplied, the bytes under a mask arrive as
971
+ * black and disappear into a dark surface; straight, they arrive as whatever the exporter left
972
+ * there. A material that genuinely means to cut out says so with its own alpha reference, which is
973
+ * what `kn5.ts`'s `alphaReference` now reads correctly.
974
+ *
975
+ * **What would make this wrong:** content authored *for* straight alpha, whose colour under a mask
976
+ * is meaningful and wanted — a decal atlas relying on the padding. Nothing measured here is that,
977
+ * and the day one arrives it wants the option per material rather than per engine.
978
+ *
979
+ * `colorSpaceConversion` stays off: a normal or ORM map holds values that are not colours, and
980
+ * glTF says an image's own colour metadata is ignored.
964
981
  */
965
982
  const AS_AUTHORED = {
966
- premultiplyAlpha: 'none',
983
+ premultiplyAlpha: 'premultiply',
967
984
  colorSpaceConversion: 'none',
968
985
  };
969
986
  async function decodeImage(texture, longestSide) {
package/dist/kn5.js CHANGED
@@ -311,6 +311,25 @@ export function readKn5Nodes(header) {
311
311
  * this stays only for the warning, which is the thing a name genuinely can answer: a shader
312
312
  * nobody here has seen is still geometry somebody wants.
313
313
  */
314
+ /**
315
+ * What a tested material's alpha reference actually is, where it states zero or states none.
316
+ *
317
+ * **Zero means the shader's own threshold, not "discard nothing".** A material that says it is
318
+ * alpha tested has already said that something is meant to be discarded, so a reference of zero
319
+ * cannot be read literally without contradicting the flag beside it. Assetto Corsa's `*AT*` shaders
320
+ * carry a built-in reference and a material overrides it by stating one; stating zero is how a
321
+ * material says it is not overriding anything.
322
+ *
323
+ * **A half, because that is the convention those shaders were authored against**, and because the
324
+ * masks in question are two-valued: a grille's hole and a seat's stitching are cut out of a texture
325
+ * whose alpha is 0 or 255, so any threshold strictly inside the range separates them identically.
326
+ * What would make this wrong is a material whose mask is a soft gradient, which wants blending
327
+ * rather than a test and says so with `alphaBlendMode`.
328
+ */
329
+ const DEFAULT_ALPHA_REFERENCE = 0.5;
330
+ function alphaReference(stated) {
331
+ return stated > 0 ? stated : DEFAULT_ALPHA_REFERENCE;
332
+ }
314
333
  const KNOWN_SHADERS = new Set([
315
334
  'ksPerPixelAlpha',
316
335
  'ksWindscreen',
@@ -441,7 +460,7 @@ export function kn5ToMeshes(buffer) {
441
460
  * rather than on evidence — stated plainly for the same reason the bone block is read past
442
461
  * rather than interpreted. What the change fixes is not this line but the one above it.
443
462
  */
444
- cutout: material.alphaTested ? property('ksAlphaRef', 0) : 0,
463
+ cutout: material.alphaTested ? alphaReference(property('ksAlphaRef', 0)) : 0,
445
464
  albedo: bind(material, 'txDiffuse'),
446
465
  normalMap: bind(material, 'txNormal'),
447
466
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/assets",
3
- "version": "4.1.1",
3
+ "version": "4.1.3",
4
4
  "description": "Model readers and the streaming loader for the .drft container",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -27,11 +27,11 @@
27
27
  ],
28
28
  "sideEffects": false,
29
29
  "dependencies": {
30
- "@driftengine/drft": "4.1.1",
31
- "@driftengine/texture": "4.1.1"
30
+ "@driftengine/drft": "4.1.3",
31
+ "@driftengine/texture": "4.1.3"
32
32
  },
33
33
  "peerDependencies": {
34
- "@driftengine/core": "4.1.1"
34
+ "@driftengine/core": "4.1.3"
35
35
  },
36
36
  "author": "Drift Technologies",
37
37
  "repository": {
package/src/drftLoader.ts CHANGED
@@ -1162,20 +1162,37 @@ export function isRawCodec(codec: number): boolean {
1162
1162
  * the view would keep the whole file alive for as long as the image does.
1163
1163
  */
1164
1164
  /**
1165
- * How every image a model carries is decoded: **straight alpha, and no colour conversion.**
1165
+ * How every image a model carries is decoded: **premultiplied, and no colour conversion.**
1166
1166
  *
1167
- * `createImageBitmap` premultiplies unless told not to, and the upload into a straight-alpha
1168
- * texture then divides back out. A texel with no alpha comes back with no colour, so a cutout or an
1169
- * emblem loses the colour its author padded past its edge, and filtering pulls black into the edge;
1170
- * a partly transparent one comes back rounded to a coarser step. Colour management rewrites a normal
1171
- * or ORM map, whose values are not colours at all, and glTF — which these assets are baked from —
1172
- * says an image's own colour metadata is ignored. `render/imageTexels.ts` asks for the same two.
1167
+ * **Straight alpha was tried in 4.0.0 and it is what this reverts.** The argument for it was that
1168
+ * `createImageBitmap` premultiplies by default, so a texel with no alpha comes back with no colour
1169
+ * and a cutout or an emblem loses the colour its author padded past its edge. That reasoning is
1170
+ * sound about the pixels and wrong about the content: an imported material's albedo routinely
1171
+ * carries arbitrary bytes under its fully transparent texels, and premultiplying was what kept
1172
+ * them out of the frame.
1173
1173
  *
1174
- * What it gives up: an albedo authored in a wide-gamut space with a profile saying so is read as
1175
- * sRGB. The baker is where that conversion belongs, and it has no colour management either.
1174
+ * **What it cost, reported and then reproduced.** A car came through with its interior, grille,
1175
+ * mirrors and lamps as hard black and white shards. Held frames of one scene in the showroom, on
1176
+ * one machine, at one frame: a model with 41 images drew clean and two with 64 and 65 drew shards,
1177
+ * on **both** backends, with the old container and the new one alike — the count only decides how
1178
+ * likely a model is to carry a masked texture at all. Restoring this one option drew the car
1179
+ * correctly again.
1180
+ *
1181
+ * **Why an opaque material sees it.** Nothing discards on an opaque draw, so the shader takes
1182
+ * `texel.rgb` whatever the alpha beside it says. Premultiplied, the bytes under a mask arrive as
1183
+ * black and disappear into a dark surface; straight, they arrive as whatever the exporter left
1184
+ * there. A material that genuinely means to cut out says so with its own alpha reference, which is
1185
+ * what `kn5.ts`'s `alphaReference` now reads correctly.
1186
+ *
1187
+ * **What would make this wrong:** content authored *for* straight alpha, whose colour under a mask
1188
+ * is meaningful and wanted — a decal atlas relying on the padding. Nothing measured here is that,
1189
+ * and the day one arrives it wants the option per material rather than per engine.
1190
+ *
1191
+ * `colorSpaceConversion` stays off: a normal or ORM map holds values that are not colours, and
1192
+ * glTF says an image's own colour metadata is ignored.
1176
1193
  */
1177
1194
  const AS_AUTHORED = {
1178
- premultiplyAlpha: 'none',
1195
+ premultiplyAlpha: 'premultiply',
1179
1196
  colorSpaceConversion: 'none',
1180
1197
  } as const satisfies ImageBitmapOptions;
1181
1198
 
package/src/kn5.ts CHANGED
@@ -430,6 +430,27 @@ export function readKn5Nodes(header: Kn5Header): { nodes: Kn5Node[]; meshes: Kn5
430
430
  * this stays only for the warning, which is the thing a name genuinely can answer: a shader
431
431
  * nobody here has seen is still geometry somebody wants.
432
432
  */
433
+ /**
434
+ * What a tested material's alpha reference actually is, where it states zero or states none.
435
+ *
436
+ * **Zero means the shader's own threshold, not "discard nothing".** A material that says it is
437
+ * alpha tested has already said that something is meant to be discarded, so a reference of zero
438
+ * cannot be read literally without contradicting the flag beside it. Assetto Corsa's `*AT*` shaders
439
+ * carry a built-in reference and a material overrides it by stating one; stating zero is how a
440
+ * material says it is not overriding anything.
441
+ *
442
+ * **A half, because that is the convention those shaders were authored against**, and because the
443
+ * masks in question are two-valued: a grille's hole and a seat's stitching are cut out of a texture
444
+ * whose alpha is 0 or 255, so any threshold strictly inside the range separates them identically.
445
+ * What would make this wrong is a material whose mask is a soft gradient, which wants blending
446
+ * rather than a test and says so with `alphaBlendMode`.
447
+ */
448
+ const DEFAULT_ALPHA_REFERENCE = 0.5;
449
+
450
+ function alphaReference(stated: number): number {
451
+ return stated > 0 ? stated : DEFAULT_ALPHA_REFERENCE;
452
+ }
453
+
433
454
  const KNOWN_SHADERS = new Set([
434
455
  'ksPerPixelAlpha',
435
456
  'ksWindscreen',
@@ -591,7 +612,7 @@ export function kn5ToMeshes(buffer: ArrayBuffer): Kn5Import {
591
612
  * rather than on evidence — stated plainly for the same reason the bone block is read past
592
613
  * rather than interpreted. What the change fixes is not this line but the one above it.
593
614
  */
594
- cutout: material.alphaTested ? property('ksAlphaRef', 0) : 0,
615
+ cutout: material.alphaTested ? alphaReference(property('ksAlphaRef', 0)) : 0,
595
616
  albedo: bind(material, 'txDiffuse'),
596
617
  normalMap: bind(material, 'txNormal'),
597
618
  /*