@google/model-viewer
Version:
Easily display interactive 3D models on the web and in AR!
212 lines (193 loc) • 6.87 kB
text/typescript
/* @license
* Copyright 2020 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ColorSpace, LinearSRGBColorSpace, MeshPhysicalMaterial, SRGBColorSpace, Texture as ThreeTexture, Vector2, VideoTexture} from 'three';
import {TextureInfo as TextureInfoInterface} from './api.js';
import {$threeTexture} from './image.js';
import {Texture} from './texture.js';
const $texture = Symbol('texture');
const $transform = Symbol('transform');
export const $materials = Symbol('materials');
export const $usage = Symbol('usage');
const $onUpdate = Symbol('onUpdate');
const $activeVideo = Symbol('activeVideo');
// Defines what a texture will be used for.
export enum TextureUsage {
Base,
MetallicRoughness,
Normal,
Occlusion,
Emissive,
Clearcoat,
ClearcoatRoughness,
ClearcoatNormal,
SheenColor,
SheenRoughness,
Transmission,
Thickness,
Specular,
SpecularColor,
Iridescence,
IridescenceThickness,
Anisotropy,
}
interface TextureTransform {
rotation: number;
scale: Vector2;
offset: Vector2;
}
/**
* TextureInfo facade implementation for Three.js materials
*/
export class TextureInfo implements TextureInfoInterface {
private[$texture]: Texture|null = null;
private[$transform]: TextureTransform = {
rotation: 0,
scale: new Vector2(1, 1),
offset: new Vector2(0, 0)
};
// Holds a reference to the Three data that backs the material object.
private[$materials]: Set<MeshPhysicalMaterial>|null;
// Texture usage defines the how the texture is used (ie Normal, Emissive...
// etc)
private[$usage]: TextureUsage;
private[$onUpdate]: () => void;
private[$activeVideo] = false;
constructor(
onUpdate: () => void, usage: TextureUsage,
threeTexture: ThreeTexture|null, material: Set<MeshPhysicalMaterial>) {
// Creates image, sampler, and texture if valid texture info is provided.
if (threeTexture) {
this[$transform].rotation = threeTexture.rotation;
this[$transform].scale.copy(threeTexture.repeat);
this[$transform].offset.copy(threeTexture.offset);
this[$texture] = new Texture(onUpdate, threeTexture);
}
this[$onUpdate] = onUpdate;
this[$materials] = material;
this[$usage] = usage;
}
get texture(): Texture|null {
return this[$texture];
}
setTexture(texture: Texture|null): void {
const threeTexture: ThreeTexture|null =
texture != null ? texture.source[$threeTexture] : null;
const oldTexture = this[$texture]?.source[$threeTexture] as VideoTexture;
if (oldTexture != null && oldTexture.isVideoTexture) {
this[$activeVideo] = false;
} else if (this[$texture]?.source.animation) {
this[$texture].source.animation.removeEventListener(
'enterFrame', this[$onUpdate]);
}
this[$texture] = texture;
if (threeTexture != null && (threeTexture as VideoTexture).isVideoTexture) {
const element = threeTexture.image;
this[$activeVideo] = true;
if (element.requestVideoFrameCallback != null) {
const update = () => {
if (!this[$activeVideo]) {
return;
}
this[$onUpdate]();
element.requestVideoFrameCallback(update);
};
element.requestVideoFrameCallback(update);
} else {
const update = () => {
if (!this[$activeVideo]) {
return;
}
this[$onUpdate]();
requestAnimationFrame(update);
};
requestAnimationFrame(update);
}
} else if (texture?.source.animation != null) {
texture.source.animation.addEventListener('enterFrame', this[$onUpdate]);
}
let colorSpace: ColorSpace = SRGBColorSpace;
if (this[$materials]) {
for (const material of this[$materials]!) {
switch (this[$usage]) {
case TextureUsage.Base:
material.map = threeTexture;
break;
case TextureUsage.MetallicRoughness:
colorSpace = LinearSRGBColorSpace;
material.metalnessMap = threeTexture;
material.roughnessMap = threeTexture;
break;
case TextureUsage.Normal:
colorSpace = LinearSRGBColorSpace;
material.normalMap = threeTexture;
break;
case TextureUsage.Occlusion:
colorSpace = LinearSRGBColorSpace;
material.aoMap = threeTexture;
break;
case TextureUsage.Emissive:
material.emissiveMap = threeTexture;
break;
case TextureUsage.Clearcoat:
material.clearcoatMap = threeTexture;
break;
case TextureUsage.ClearcoatRoughness:
material.clearcoatRoughnessMap = threeTexture;
break;
case TextureUsage.ClearcoatNormal:
material.clearcoatNormalMap = threeTexture;
break;
case TextureUsage.SheenColor:
material.sheenColorMap = threeTexture;
break;
case TextureUsage.SheenRoughness:
material.sheenRoughnessMap = threeTexture;
break;
case TextureUsage.Transmission:
material.transmissionMap = threeTexture;
break;
case TextureUsage.Thickness:
material.thicknessMap = threeTexture;
break;
case TextureUsage.Specular:
material.specularIntensityMap = threeTexture;
break;
case TextureUsage.SpecularColor:
material.specularColorMap = threeTexture;
break;
case TextureUsage.Iridescence:
material.iridescenceMap = threeTexture;
break;
case TextureUsage.IridescenceThickness:
material.iridescenceThicknessMap = threeTexture;
break;
case TextureUsage.Anisotropy:
(material as any).anisotropyMap = threeTexture;
break;
default:
}
material.needsUpdate = true;
}
}
if (threeTexture) {
// Updates the colorSpace for the texture, affects all references.
threeTexture.colorSpace = colorSpace;
threeTexture.rotation = this[$transform].rotation;
threeTexture.repeat = this[$transform].scale;
threeTexture.offset = this[$transform].offset;
}
this[$onUpdate]();
}
}