feat(assets): EXT_materials_bump für gltf-transform

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5 committed 2026-09-14 10:39:39 +02:00
1 parent 3321a029fa
commit 0ebe8adb8b
4 files changed
+997 -5

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
// EXT_materials_bump für @gltf-transform (docs/superpowers/specs/2026-09-14-hood-yard-design.md).
// @gltf-transform 4.5.0 kennt die Erweiterung nicht und verwirft sie beim Einlesen. Das Wellblech der
// Container aus three-d-stage steckt aber allein darin, die Container selbst sind flache Kästen.
// Aufgebaut wie KHR_materials_transmission in @gltf-transform/extensions: bumpTexture ist eine echte
// Texturreferenz, damit dedup, prune und compressTexture sie sehen. Ihre TextureInfo läuft über
// setTextureInfo/createTextureInfoDef, so kommt KHR_texture_transform (die Wellenteilung, etwa
// scale [3.2, 1]) mit. three.js liest die Erweiterung selbst (GLTFLoader: bumpFactor -> bumpScale,
// bumpTexture -> bumpMap).
import { Extension, ExtensionProperty, PropertyType, TextureChannel, TextureInfo } from "@gltf-transform/core";
export const EXT_MATERIALS_BUMP = "EXT_materials_bump";
export class Bump extends ExtensionProperty {
static EXTENSION_NAME = EXT_MATERIALS_BUMP;
init() {
this.extensionName = EXT_MATERIALS_BUMP;
this.propertyType = "Bump";
this.parentTypes = [PropertyType.MATERIAL];
}
getDefaults() {
return Object.assign(super.getDefaults(), {
bumpFactor: 1,
bumpTexture: null,
bumpTextureInfo: new TextureInfo(this.graph, "bumpTextureInfo"),
});
}
getBumpFactor() {
return this.get("bumpFactor");
}
setBumpFactor(factor) {
return this.set("bumpFactor", factor);
}
getBumpTexture() {
return this.getRef("bumpTexture");
}
getBumpTextureInfo() {
return this.getRef("bumpTexture") ? this.getRef("bumpTextureInfo") : null;
}
setBumpTexture(texture) {
return this.setRef("bumpTexture", texture, { channels: TextureChannel.R });
}
}
export class EXTMaterialsBump extends Extension {
static EXTENSION_NAME = EXT_MATERIALS_BUMP;
extensionName = EXT_MATERIALS_BUMP;
prereadTypes = [PropertyType.MESH];
prewriteTypes = [PropertyType.MESH];
createBump() {
return new Bump(this.document.getGraph());
}
read() {
return this;
}
write() {
return this;
}
preread(context) {
const materialDefs = context.jsonDoc.json.materials ?? [];
const textureDefs = context.jsonDoc.json.textures ?? [];
materialDefs.forEach((materialDef, index) => {
const def = materialDef.extensions?.[EXT_MATERIALS_BUMP];
if (!def) return;
const bump = this.createBump();
context.materials[index].setExtension(EXT_MATERIALS_BUMP, bump);
if (def.extras) bump.setExtras(def.extras);
if (def.bumpFactor !== undefined) bump.setBumpFactor(def.bumpFactor);
if (def.bumpTexture !== undefined) {
bump.setBumpTexture(context.textures[textureDefs[def.bumpTexture.index].source]);
context.setTextureInfo(bump.getBumpTextureInfo(), def.bumpTexture);
}
});
return this;
}
prewrite(context) {
for (const material of this.document.getRoot().listMaterials()) {
const bump = material.getExtension(EXT_MATERIALS_BUMP);
if (!bump) continue;
const materialDef = context.jsonDoc.json.materials[context.materialIndexMap.get(material)];
const def = context.createPropertyDef(bump);
def.bumpFactor = bump.getBumpFactor();
const texture = bump.getBumpTexture();
if (texture) def.bumpTexture = context.createTextureInfoDef(texture, bump.getBumpTextureInfo());
materialDef.extensions = materialDef.extensions ?? {};
materialDef.extensions[EXT_MATERIALS_BUMP] = def;
}
return this;
}
}