import { describe, expect, it, vi } from "vitest"; import { ClampToEdgeWrapping, DoubleSide, LinearFilter, LinearMipmapLinearFilter, LinearSRGBColorSpace, MirroredRepeatWrapping, NearestFilter, RepeatWrapping, SRGBColorSpace, } from "three"; import { createModelMaterial, createModelTexture, createValidModelDocument, createValidModelDocumentV2, } from "../../model-import/canonical/model-document-test-support"; import { createToolcraftThreeModelAppearance } from "./three-model-appearance"; function bitmap(): ImageBitmap { return { close: vi.fn(), height: 2, width: 2 } as unknown as ImageBitmap; } describe("Toolcraft Three model appearance", () => { it("uses the exact neutral linear fallback for version-1 geometry", () => { const appearance = createToolcraftThreeModelAppearance( createValidModelDocument(), new Map(), ); const material = appearance.materialForPrimitive( createValidModelDocument().primitives[0]!, ); expect(material.color.toArray()).toEqual([0.8, 0.8, 0.8]); expect(material.emissive.toArray()).toEqual([0, 0, 0]); expect(material.metalness).toBe(0); expect(material.roughness).toBe(0.5); expect(material.opacity).toBe(1); expect(material.transparent).toBe(false); expect(material.color.getHex(LinearSRGBColorSpace)).toBe(0xcccccc); }); it("maps authored factors, texture channels, samplers, and vertex colors", () => { const images = new Map([ ["texture-1", bitmap()], ["texture-linear", bitmap()], ]); const document = createValidModelDocumentV2({ materials: [createModelMaterial({ alphaCutoff: 0.35, alphaMode: "blend", baseColor: [0.25, 0.5, 0.75], doubleSided: true, emissive: [0.1, 0.2, 0.3], emissiveTexture: { texCoord: 1, textureId: "texture-1" }, metallic: 0.65, metallicRoughnessTexture: { texCoord: 1, textureId: "texture-linear" }, normalTexture: { scale: 0.4, texCoord: 0, textureId: "texture-linear" }, occlusionTexture: { strength: 0.6, texCoord: 1, textureId: "texture-linear" }, opacity: 0.7, roughness: 0.2, })], textures: [ createModelTexture({ sampler: { magFilter: "linear", minFilter: "linear-mipmap-linear", wrapS: "repeat", wrapT: "mirrored-repeat", }, }), createModelTexture({ colorSpace: "linear", id: "texture-linear", sampler: { magFilter: "nearest", minFilter: "linear", wrapS: "clamp-to-edge", wrapT: "repeat", }, }), ], }); const appearance = createToolcraftThreeModelAppearance(document, images); const material = appearance.materialForPrimitive(document.primitives[0]!); expect(material.color.toArray()).toEqual([0.25, 0.5, 0.75]); expect(material.emissive.toArray()).toEqual([0.1, 0.2, 0.3]); expect(material.metalness).toBe(0.65); expect(material.roughness).toBe(0.2); expect(material.opacity).toBe(0.7); expect(material.transparent).toBe(true); expect(material.depthWrite).toBe(false); expect(material.side).toBe(DoubleSide); expect(material.vertexColors).toBe(true); expect(material.map?.colorSpace).toBe(SRGBColorSpace); expect(material.map?.channel).toBe(0); expect(material.map?.wrapS).toBe(RepeatWrapping); expect(material.map?.wrapT).toBe(MirroredRepeatWrapping); expect(material.map?.magFilter).toBe(LinearFilter); expect(material.map?.minFilter).toBe(LinearMipmapLinearFilter); expect(material.emissiveMap?.channel).toBe(1); expect(material.normalMap?.colorSpace).toBe(LinearSRGBColorSpace); expect(material.normalMap?.channel).toBe(0); expect(material.normalMap?.wrapS).toBe(ClampToEdgeWrapping); expect(material.normalMap?.wrapT).toBe(RepeatWrapping); expect(material.normalMap?.magFilter).toBe(NearestFilter); expect(material.normalScale.toArray()).toEqual([0.4, 0.4]); expect(material.aoMap?.channel).toBe(1); expect(material.aoMapIntensity).toBe(0.6); }); it("falls back per missing texture channel without discarding factors", () => { const document = createValidModelDocumentV2(); const appearance = createToolcraftThreeModelAppearance(document, new Map()); const material = appearance.materialForPrimitive(document.primitives[0]!); expect(material.map).toBeNull(); expect(material.color.toArray()).toEqual([0.8, 0.7, 0.6]); expect(material.roughness).toBe(0.5); expect(appearance.textures).toEqual([]); }); });