// 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; } }