Skip to content

Commit 9f978e7

Browse files
authored
Fix variants with multiple internal materials (#4882)
* refactor to handle multiple internal materials for each gltf material * simplify
1 parent 2edbce1 commit 9f978e7

File tree

6 files changed

+66
-53
lines changed

6 files changed

+66
-53
lines changed

packages/model-viewer/src/features/scene-graph/material.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export const $gltfIndex = Symbol('gltfIndex');
4040
export const $setActive = Symbol('setActive');
4141
export const $variantIndices = Symbol('variantIndices');
4242
const $isActive = Symbol('isActive');
43-
export const $variantSet = Symbol('variantSet');
4443
const $modelVariants = Symbol('modelVariants');
4544
const $name = Symbol('name');
4645
const $pbrTextures = Symbol('pbrTextures');
@@ -56,7 +55,7 @@ export class Material extends ThreeDOMElement implements MaterialInterface {
5655
private[$lazyLoadGLTFInfo]?: LazyLoader;
5756
private[$gltfIndex]: number;
5857
private[$isActive]: boolean;
59-
private[$variantSet] = new Set<number>();
58+
public[$variantIndices] = new Set<number>();
6059
private[$name]?: string;
6160
readonly[$modelVariants]: Map<string, VariantData>;
6261
private[$pbrTextures] = new Map<TextureUsage, TextureInfo>();
@@ -147,12 +146,9 @@ export class Material extends ThreeDOMElement implements MaterialInterface {
147146
createTextureInfo(TextureUsage.Anisotropy);
148147
}
149148

150-
async[$getLoadedMaterial](): Promise<MeshPhysicalMaterial> {
149+
async[$getLoadedMaterial](): Promise<MeshPhysicalMaterial|null> {
151150
if (this[$lazyLoadGLTFInfo] != null) {
152-
const {set, material} = await this[$lazyLoadGLTFInfo]!.doLazyLoad();
153-
154-
// Fills in the missing data.
155-
this[$correlatedObjects] = set as Set<MeshPhysicalMaterial>;
151+
const material = await this[$lazyLoadGLTFInfo]!.doLazyLoad();
156152

157153
this[$initialize]();
158154
// Releases lazy load info.
@@ -161,7 +157,7 @@ export class Material extends ThreeDOMElement implements MaterialInterface {
161157
this.ensureLoaded = async () => {};
162158
return material as MeshPhysicalMaterial;
163159
}
164-
return this[$correlatedObjects]!.values().next().value;
160+
return null;
165161
}
166162

167163
private colorFromRgb(rgb: RGB|string): Color {
@@ -240,13 +236,9 @@ export class Material extends ThreeDOMElement implements MaterialInterface {
240236
return this[$gltfIndex];
241237
}
242238

243-
[$variantIndices]() {
244-
return this[$variantSet];
245-
}
246-
247239
hasVariant(name: string): boolean {
248240
const variantData = this[$modelVariants].get(name);
249-
return variantData != null && this[$variantSet].has(variantData.index);
241+
return variantData != null && this[$variantIndices].has(variantData.index);
250242
}
251243

252244
setEmissiveFactor(rgb: RGB|string) {

packages/model-viewer/src/features/scene-graph/model.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515

1616
import {Intersection, Material as ThreeMaterial, Mesh, MeshPhysicalMaterial, Object3D} from 'three';
1717

18-
import {CorrelatedSceneGraph, GLTFElementToThreeObjectMap, ThreeObjectSet} from '../../three-components/gltf-instance/correlated-scene-graph.js';
18+
import {CorrelatedSceneGraph, GLTFElementToThreeObjectMap} from '../../three-components/gltf-instance/correlated-scene-graph.js';
1919
import {GLTF, GLTFElement} from '../../three-components/gltf-instance/gltf-2.0.js';
2020

2121
import {Model as ModelInterface} from './api.js';
22-
import {$setActive, $variantSet, Material} from './material.js';
22+
import {$setActive, $variantIndices, Material} from './material.js';
2323
import {Node, PrimitiveNode} from './nodes/primitive-node.js';
2424
import {$correlatedObjects} from './three-dom-element.js';
2525

@@ -46,12 +46,10 @@ export class LazyLoader {
4646
gltf: GLTF;
4747
gltfElementMap: GLTFElementToThreeObjectMap;
4848
mapKey: GLTFElement;
49-
doLazyLoad: () => Promise<{set: ThreeObjectSet, material: ThreeMaterial}>;
49+
doLazyLoad: () => Promise<ThreeMaterial>;
5050
constructor(
5151
gltf: GLTF, gltfElementMap: GLTFElementToThreeObjectMap,
52-
mapKey: GLTFElement,
53-
doLazyLoad:
54-
() => Promise<{set: ThreeObjectSet, material: ThreeMaterial}>) {
52+
mapKey: GLTFElement, doLazyLoad: () => Promise<ThreeMaterial>) {
5553
this.gltf = gltf;
5654
this.gltfElementMap = gltfElementMap;
5755
this.mapKey = mapKey;
@@ -88,29 +86,28 @@ export class Model implements ModelInterface {
8886

8987
for (const [i, material] of gltf.materials!.entries()) {
9088
const correlatedMaterial =
91-
gltfElementMap.get(material) as Set<MeshPhysicalMaterial>;
89+
gltfElementMap.get(material) as Set<MeshPhysicalMaterial>| null;
9290

9391
if (correlatedMaterial != null) {
9492
this[$materials].push(new Material(
95-
onUpdate, i, true, this[$variantData], correlatedMaterial, material.name));
93+
onUpdate,
94+
i,
95+
true,
96+
this[$variantData],
97+
correlatedMaterial,
98+
material.name));
9699
} else {
97100
const elementArray = gltf['materials'] || [];
98101
const gltfMaterialDef = elementArray[i];
99102

100-
// Loads the three.js material.
101-
const capturedMatIndex = i;
103+
const threeMaterialSet = new Set<MeshPhysicalMaterial>();
104+
gltfElementMap.set(gltfMaterialDef, threeMaterialSet);
102105
const materialLoadCallback = async () => {
103-
const threeMaterial =
104-
await threeGLTF.parser.getDependency(
105-
'material', capturedMatIndex) as MeshPhysicalMaterial;
106-
107-
// Adds correlation, maps the variant gltf-def to the
108-
// three material set containing the variant material.
109-
const threeMaterialSet = new Set<MeshPhysicalMaterial>();
110-
gltfElementMap.set(gltfMaterialDef, threeMaterialSet);
106+
const threeMaterial = await threeGLTF.parser.getDependency(
107+
'material', i) as MeshPhysicalMaterial;
111108
threeMaterialSet.add(threeMaterial);
112109

113-
return {set: threeMaterialSet, material: threeMaterial};
110+
return threeMaterial;
114111
};
115112

116113
// Configures the material for lazy loading.
@@ -119,7 +116,7 @@ export class Model implements ModelInterface {
119116
i,
120117
false,
121118
this[$variantData],
122-
correlatedMaterial,
119+
threeMaterialSet,
123120
material.name,
124121
new LazyLoader(
125122
gltf, gltfElementMap, gltfMaterialDef, materialLoadCallback)));
@@ -387,7 +384,7 @@ export class Model implements ModelInterface {
387384

388385
for (const material of this.materials) {
389386
if (material.hasVariant(variantName)) {
390-
material[$variantSet].delete(variant.index);
387+
material[$variantIndices].delete(variant.index);
391388
}
392389
}
393390

packages/model-viewer/src/features/scene-graph/nodes/primitive-node.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export class PrimitiveNode extends Node {
117117
const {name} = variantNames[variant];
118118
this.variantToMaterialMap.set(variant, mvMaterial);
119119
// Provides variant info for material self lookup.
120-
mvMaterial[$variantIndices]().add(variant);
120+
mvMaterial[$variantIndices].add(variant);
121121
// Updates the models variant data.
122122
if (!modelVariants.has(name)) {
123123
modelVariants.set(name, {name, index: variant} as VariantData);
@@ -128,18 +128,20 @@ export class PrimitiveNode extends Node {
128128
}
129129

130130
async setActiveMaterial(material: number): Promise<ThreeMaterial|null> {
131-
const mvMaterial = this.materials.get(material);
132-
if (mvMaterial != null && material !== this.activeMaterialIdx) {
133-
this.mesh.material = await mvMaterial[$getLoadedMaterial]();
134-
const {normalScale} = this.mesh.material as MeshPhysicalMaterial;
135-
// TODO: remove this hack in favor of properly storing the different
136-
// three.js materials that are potentially created for different meshes
137-
// that share a glTF material.
138-
if (normalScale != null &&
139-
(normalScale.y * normalScale.x < 0) !=
140-
(this.mesh.geometry.attributes.tangent == null)) {
141-
this.parser.assignFinalMaterial(this.mesh);
131+
const mvMaterial = this.materials.get(material)!;
132+
if (material !== this.activeMaterialIdx) {
133+
const backingMaterials =
134+
mvMaterial[$correlatedObjects] as Set<MeshPhysicalMaterial>;
135+
136+
const baseMaterial = await mvMaterial[$getLoadedMaterial]();
137+
if (baseMaterial != null) {
138+
this.mesh.material = baseMaterial;
139+
} else {
140+
this.mesh.material = backingMaterials.values().next().value;
142141
}
142+
143+
this.parser.assignFinalMaterial(this.mesh);
144+
backingMaterials.add(this.mesh.material as MeshPhysicalMaterial);
143145
this.activeMaterialIdx = material;
144146
}
145147
return this.mesh.material as ThreeMaterial;
@@ -210,7 +212,7 @@ export class PrimitiveNode extends Node {
210212
const variantIndex = modelVariantData.index;
211213

212214
// Updates materials mapped to the variant.
213-
materialVariant[$variantIndices]().add(variantIndex);
215+
materialVariant[$variantIndices].add(variantIndex);
214216

215217
// Updates internal mappings.
216218
this.variantToMaterialMap.set(variantIndex, materialVariant);
@@ -236,7 +238,7 @@ export class PrimitiveNode extends Node {
236238
private updateVariantUserData(
237239
variantIndex: number, materialVariant: Material) {
238240
// Adds variants name to material variants set.
239-
materialVariant[$variantIndices]().add(variantIndex);
241+
materialVariant[$variantIndices].add(variantIndex);
240242

241243
this.mesh.userData.variantData = this.modelVariants;
242244
// Updates import data (see VariantMaterialLoaderPlugin.ts).

packages/model-viewer/src/features/scene-graph/three-dom-element.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ type CorrelatedObjects = Set<Object3D>|Set<Material>|Set<Texture>;
2929
export class ThreeDOMElement {
3030
readonly[$onUpdate]: () => void;
3131
// The Three.js scene graph construct for this element.
32-
[$correlatedObjects]: CorrelatedObjects|null;
32+
[$correlatedObjects]: CorrelatedObjects;
3333

34-
constructor(
35-
onUpdate: () => void, correlatedObjects: CorrelatedObjects|null = null) {
34+
constructor(onUpdate: () => void, correlatedObjects: CorrelatedObjects) {
3635
this[$onUpdate] = onUpdate;
3736
this[$correlatedObjects] = correlatedObjects;
3837
}

packages/model-viewer/src/test/features/scene-graph-spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,29 @@ suite('SceneGraph', () => {
147147
expect(gltfRoot.children[1].userData.variantMaterials.size).to.be.eq(3);
148148
});
149149

150+
test('allows the scene graph to be manipulated', async () => {
151+
element.variantName = 'Yellow Red';
152+
await waitForEvent(element, 'variant-applied');
153+
154+
const material =
155+
(element[$scene].model!.children[1] as Mesh).material as
156+
MeshStandardMaterial;
157+
158+
const mat = element.model!.getMaterialByName('red')!;
159+
160+
expect(mat.isActive).to.be.true;
161+
162+
mat.pbrMetallicRoughness.setBaseColorFactor([0.5, 0.5, 0.5, 1]);
163+
164+
const color = mat.pbrMetallicRoughness.baseColorFactor;
165+
166+
expect(color).to.be.eql([0.5, 0.5, 0.5, 1]);
167+
168+
console.log(material.name, ': actual material ', material.uuid);
169+
170+
expect(material.color).to.include({r: 0.5, g: 0.5, b: 0.5});
171+
});
172+
150173
test(
151174
`Setting variantName to null results in primitive
152175
reverting to default/initial material`,

packages/model-viewer/src/test/features/scene-graph/model-spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ suite('scene-graph/model', () => {
8383
test('Switch variant and lazy load', async () => {
8484
const threeGLTF = await loadThreeGLTF(CUBES_GLTF_PATH);
8585
const model = new Model(CorrelatedSceneGraph.from(threeGLTF));
86-
expect(model[$materials][2][$correlatedObjects]).to.be.null;
86+
expect(model[$materials][2][$correlatedObjects]).to.be.empty;
8787
expect(model[$materials][2][$lazyLoadGLTFInfo]).to.be.ok;
8888

8989
await model[$switchVariant]('Yellow Red');
9090

91-
expect(model[$materials][2][$correlatedObjects]).to.not.be.null;
91+
expect(model[$materials][2][$correlatedObjects]).to.not.be.empty;
9292
expect(model[$materials][2][$lazyLoadGLTFInfo]).to.not.be.ok;
9393
});
9494

0 commit comments

Comments
 (0)