diff --git a/packages/model-viewer/src/features/scene-graph/model.ts b/packages/model-viewer/src/features/scene-graph/model.ts index 609117adeb..3337b62cb8 100644 --- a/packages/model-viewer/src/features/scene-graph/model.ts +++ b/packages/model-viewer/src/features/scene-graph/model.ts @@ -91,7 +91,7 @@ export class Model implements ModelInterface { const {gltf, threeGLTF, gltfElementMap} = correlatedSceneGraph; this[$threeScene] = threeGLTF.scene; - for (const [i, material] of gltf.materials!.entries()) { + for (const [i, material] of gltf.materials?.entries() ?? []) { const correlatedMaterial = gltfElementMap.get(material) as Set; diff --git a/packages/model-viewer/src/features/scene-graph/nodes/primitive-node.ts b/packages/model-viewer/src/features/scene-graph/nodes/primitive-node.ts index b50c351625..7e1eda2eee 100644 --- a/packages/model-viewer/src/features/scene-graph/nodes/primitive-node.ts +++ b/packages/model-viewer/src/features/scene-graph/nodes/primitive-node.ts @@ -68,11 +68,11 @@ export class PrimitiveNode extends Node { // Captures the primitive's initial material. const materialMappings = threeObjectMap.get(mesh.material as ThreeMaterial)!; - if (materialMappings.materials != null) { + if (materialMappings?.materials != null) { this[$initialMaterialIdx] = this[$activeMaterialIdx] = materialMappings.materials; } else { - console.error( + console.warn( `Primitive (${mesh.name}) missing initial material reference.`); } diff --git a/packages/model-viewer/src/test/features/scene-graph/model-spec.ts b/packages/model-viewer/src/test/features/scene-graph/model-spec.ts index 47ec98a140..c8f63ff7d0 100644 --- a/packages/model-viewer/src/test/features/scene-graph/model-spec.ts +++ b/packages/model-viewer/src/test/features/scene-graph/model-spec.ts @@ -33,6 +33,7 @@ const ASTRONAUT_GLB_PATH = assetPath('models/Astronaut.glb'); const KHRONOS_TRIANGLE_GLB_PATH = assetPath('models/glTF-Sample-Models/2.0/Triangle/glTF/Triangle.gltf'); const CUBES_GLTF_PATH = assetPath('models/cubes.gltf'); +const VASE_NO_MATERIALS_GLB_PATH = assetPath('models/VaseNoMaterials.glb'); suite('scene-graph/model', () => { suite('Model', () => { @@ -82,6 +83,13 @@ suite('scene-graph/model', () => { expect(collectedMaterials.size).to.be.equal(materials.size); }); + test('should have no materials given a model without any materials defined', async () => { + const threeGLTF = await loadThreeGLTF(VASE_NO_MATERIALS_GLB_PATH); + const model = new Model(CorrelatedSceneGraph.from(threeGLTF)); + + expect(model.materials.length).to.be.eq(0); + }); + suite('Model Variants', () => { test('Switch variant and lazy load', async () => { const threeGLTF = await loadThreeGLTF(CUBES_GLTF_PATH); diff --git a/packages/model-viewer/src/test/features/scene-graph/nodes/primitive-node-spec.ts b/packages/model-viewer/src/test/features/scene-graph/nodes/primitive-node-spec.ts index 34b2ae245e..431126ebcc 100644 --- a/packages/model-viewer/src/test/features/scene-graph/nodes/primitive-node-spec.ts +++ b/packages/model-viewer/src/test/features/scene-graph/nodes/primitive-node-spec.ts @@ -33,6 +33,7 @@ const CUBE_GLTF_PATH = assetPath('models/cube.gltf'); const MESH_PRIMITIVES_GLB_PATH = assetPath('models/MeshPrimitivesVariants.glb'); const KHRONOS_TRIANGLE_GLB_PATH = assetPath('models/glTF-Sample-Models/2.0/Triangle/glTF/Triangle.gltf'); +const VASE_NO_MATERIALS_GLB_PATH = assetPath('models/VaseNoMaterials.glb'); const findPrimitivesWithVariant = (model: Model, variantName: string) => { const result = new Array(); @@ -69,6 +70,29 @@ suite('scene-graph/model/mesh-primitives', () => { }); }); + suite('Primitive with no material', () => { + let element: ModelViewerElement; + setup(async () => { + element = new ModelViewerElement(); + element.src = VASE_NO_MATERIALS_GLB_PATH; + document.body.insertBefore(element, document.body.firstChild); + await waitForEvent(element, 'load'); + }); + + teardown(() => { + document.body.removeChild(element); + }); + + test('has a no material', async () => { + const model = element.model!; + + expect(model[$primitivesList].length).to.equal(1); + expect(model.materials.length).to.equal(0); + expect(model[$primitivesList][0][$initialMaterialIdx]).to.be.undefined; + expect(model.materials[0]).to.be.undefined; + }); + }); + suite('Static Primitive Without Variant', () => { let model: Model; setup(async () => { diff --git a/packages/shared-assets/models/VaseNoMaterials.glb b/packages/shared-assets/models/VaseNoMaterials.glb new file mode 100644 index 0000000000..93dbf27cd9 Binary files /dev/null and b/packages/shared-assets/models/VaseNoMaterials.glb differ diff --git a/packages/space-opera/src/components/materials_panel/materials_panel.ts b/packages/space-opera/src/components/materials_panel/materials_panel.ts index 4e23d9dd36..56c920c55f 100644 --- a/packages/space-opera/src/components/materials_panel/materials_panel.ts +++ b/packages/space-opera/src/components/materials_panel/materials_panel.ts @@ -77,6 +77,7 @@ export class MaterialPanel extends ConnectedLitElement { @internalProperty() isInterpolating: boolean = false; @query('#material-container') panel!: HTMLDivElement; + @query('#material-placeholder') placeholder!: HTMLDivElement; @query('me-color-picker#base-color-picker') baseColorPicker!: ColorPicker; @query('me-slider-with-input#roughness-factor') roughnessFactorSlider!: SliderWithInputElement; @@ -525,10 +526,18 @@ export class MaterialPanel extends ConnectedLitElement { } get selectedMaterialIndex(): number { - return this.selectableMaterials[this.materialSelector.selectedIndex].index; + if (!this.materialSelector) { + return 0; + } + + return this.selectableMaterials[this.materialSelector.selectedIndex]?.index ?? 0; } set selectedMaterialIndex(index: number) { + if (!this.materialSelector) { + return; + } + this.materialSelector.selectedIndex = index; this.onSelectMaterial(); } @@ -1099,6 +1108,10 @@ export class MaterialPanel extends ConnectedLitElement { `; } + get hasSelectableMaterials(): boolean { + return this.selectableMaterials?.length > 0; + } + updateSelectableMaterials() { this.selectableMaterials = []; for (const material of getModelViewer()!.model!.materials) { @@ -1125,7 +1138,11 @@ export class MaterialPanel extends ConnectedLitElement { render() { return html` -