auto-git:
[change] src/app/App.tsx [change] src/document/scene-document.ts [change] src/geometry/box-face-uvs.ts [change] src/materials/starter-material-textures.ts [change] src/runtime-three/runtime-host.ts [change] src/viewport-three/viewport-host.ts
This commit is contained in:
@@ -210,7 +210,10 @@ import {
|
||||
snapPositiveSizeToGrid,
|
||||
snapVec3ToGrid
|
||||
} from "../geometry/grid-snapping";
|
||||
import { createFitToFaceBoxBrushFaceUvState } from "../geometry/box-face-uvs";
|
||||
import {
|
||||
createFitToFaceBoxBrushFaceUvState,
|
||||
createFitToMaterialTileBoxBrushFaceUvState
|
||||
} from "../geometry/box-face-uvs";
|
||||
import {
|
||||
DEFAULT_ENTITY_POSITION,
|
||||
DEFAULT_INTERACTABLE_PROMPT,
|
||||
@@ -333,6 +336,8 @@ import {
|
||||
} from "../controls/control-surface";
|
||||
import {
|
||||
STARTER_MATERIAL_LIBRARY,
|
||||
getStarterMaterialPreviewUrl,
|
||||
getStarterMaterialTileSizeMeters,
|
||||
type MaterialDef
|
||||
} from "../materials/starter-material-library";
|
||||
import { RunnerCanvas } from "../runner-web/RunnerCanvas";
|
||||
@@ -1534,32 +1539,13 @@ function sortDocumentMaterials(
|
||||
}
|
||||
|
||||
function getMaterialPreviewStyle(material: MaterialDef): CSSProperties {
|
||||
switch (material.pattern) {
|
||||
case "grid":
|
||||
return {
|
||||
backgroundColor: material.baseColorHex,
|
||||
backgroundImage: `linear-gradient(${material.accentColorHex} 2px, transparent 2px), linear-gradient(90deg, ${material.accentColorHex} 2px, transparent 2px)`,
|
||||
backgroundSize: "18px 18px"
|
||||
};
|
||||
case "checker":
|
||||
return {
|
||||
backgroundColor: material.baseColorHex,
|
||||
backgroundImage: `linear-gradient(45deg, ${material.accentColorHex} 25%, transparent 25%, transparent 75%, ${material.accentColorHex} 75%, ${material.accentColorHex}), linear-gradient(45deg, ${material.accentColorHex} 25%, transparent 25%, transparent 75%, ${material.accentColorHex} 75%, ${material.accentColorHex})`,
|
||||
backgroundPosition: "0 0, 9px 9px",
|
||||
backgroundSize: "18px 18px"
|
||||
};
|
||||
case "stripes":
|
||||
return {
|
||||
backgroundColor: material.baseColorHex,
|
||||
backgroundImage: `repeating-linear-gradient(135deg, ${material.accentColorHex} 0 9px, transparent 9px 18px)`
|
||||
};
|
||||
case "diamond":
|
||||
return {
|
||||
backgroundColor: material.baseColorHex,
|
||||
backgroundImage: `linear-gradient(45deg, ${material.accentColorHex} 12%, transparent 12%, transparent 88%, ${material.accentColorHex} 88%), linear-gradient(-45deg, ${material.accentColorHex} 12%, transparent 12%, transparent 88%, ${material.accentColorHex} 88%)`,
|
||||
backgroundSize: "22px 22px"
|
||||
};
|
||||
}
|
||||
return {
|
||||
backgroundColor: material.swatchColorHex,
|
||||
backgroundImage: `url("${getStarterMaterialPreviewUrl(material)}")`,
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "cover"
|
||||
};
|
||||
}
|
||||
|
||||
function rotateQuarterTurns(
|
||||
@@ -10033,7 +10019,13 @@ export function App({ store, initialStatusMessage }: AppProps) {
|
||||
}
|
||||
|
||||
applyFaceUvState(
|
||||
createFitToFaceBoxBrushFaceUvState(selectedBrush, selectedFaceId),
|
||||
selectedFaceMaterial === undefined || selectedFaceMaterial === null
|
||||
? createFitToFaceBoxBrushFaceUvState(selectedBrush, selectedFaceId)
|
||||
: createFitToMaterialTileBoxBrushFaceUvState(
|
||||
selectedBrush,
|
||||
selectedFaceId,
|
||||
getStarterMaterialTileSizeMeters(selectedFaceMaterial)
|
||||
),
|
||||
"Fit face UV to face",
|
||||
"Fit the selected face UVs to the face bounds."
|
||||
);
|
||||
|
||||
@@ -32,7 +32,9 @@ import {
|
||||
type ProjectSequenceLibrary
|
||||
} from "../sequencer/project-sequences";
|
||||
|
||||
export const SCENE_DOCUMENT_VERSION = 58 as const;
|
||||
export const SCENE_DOCUMENT_VERSION = 59 as const;
|
||||
export const STARTER_PBR_MATERIAL_LIBRARY_SCENE_DOCUMENT_VERSION =
|
||||
59 as const;
|
||||
export const SCENE_TRANSITION_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION =
|
||||
58 as const;
|
||||
export const PROJECT_SEQUENCE_UNIFIED_VISIBILITY_SCENE_DOCUMENT_VERSION =
|
||||
|
||||
@@ -43,6 +43,22 @@ export function createFitToFaceBoxBrushFaceUvState(brush: BoxBrush, faceId: BoxF
|
||||
};
|
||||
}
|
||||
|
||||
export function createFitToMaterialTileBoxBrushFaceUvState(
|
||||
brush: BoxBrush,
|
||||
faceId: BoxFaceId,
|
||||
tileSize: Vec2
|
||||
): FaceUvState {
|
||||
const faceSize = getBoxBrushFaceSize(brush, faceId);
|
||||
|
||||
return {
|
||||
...createDefaultFaceUvState(),
|
||||
scale: {
|
||||
x: tileSize.x / faceSize.x,
|
||||
y: tileSize.y / faceSize.y
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function projectBoxFaceVertexToUv(vertexPosition: Vec3, brush: BoxBrushUvProjectionSource, faceId: BoxFaceId): Vec2 {
|
||||
const halfSize = {
|
||||
x: brush.size.x * 0.5,
|
||||
|
||||
@@ -1,89 +1,161 @@
|
||||
import { CanvasTexture, RepeatWrapping, SRGBColorSpace } from "three";
|
||||
import {
|
||||
RepeatWrapping,
|
||||
SRGBColorSpace,
|
||||
Texture,
|
||||
TextureLoader
|
||||
} from "three";
|
||||
|
||||
import type { MaterialDef } from "./starter-material-library";
|
||||
import {
|
||||
getStarterMaterialBaseColorUrl,
|
||||
getStarterMaterialMetallicUrl,
|
||||
getStarterMaterialNormalUrl,
|
||||
getStarterMaterialRoughnessUrl,
|
||||
getStarterMaterialSpecularUrl,
|
||||
getStarterMaterialTextureRepeat,
|
||||
type MaterialDef
|
||||
} from "./starter-material-library";
|
||||
|
||||
export interface StarterMaterialTextureSet {
|
||||
baseColor: Texture;
|
||||
normal: Texture;
|
||||
roughness: Texture;
|
||||
metallic: Texture | null;
|
||||
specular: Texture | null;
|
||||
}
|
||||
|
||||
export function createStarterMaterialSignature(material: MaterialDef): string {
|
||||
return `${material.baseColorHex}|${material.accentColorHex}|${material.pattern}`;
|
||||
return [
|
||||
material.assetFolder,
|
||||
material.workflow,
|
||||
material.previewImageName,
|
||||
material.sizeCm.width,
|
||||
material.sizeCm.height,
|
||||
material.swatchColorHex
|
||||
].join("|");
|
||||
}
|
||||
|
||||
function fillMaterialPattern(context: CanvasRenderingContext2D, material: MaterialDef, size: number) {
|
||||
context.fillStyle = material.baseColorHex;
|
||||
context.fillRect(0, 0, size, size);
|
||||
context.strokeStyle = material.accentColorHex;
|
||||
context.fillStyle = material.accentColorHex;
|
||||
function isPowerOfTwo(value: number): boolean {
|
||||
return value > 0 && (value & (value - 1)) === 0;
|
||||
}
|
||||
|
||||
switch (material.pattern) {
|
||||
case "grid":
|
||||
context.lineWidth = Math.max(2, size / 32);
|
||||
function floorPowerOfTwo(value: number): number {
|
||||
return 2 ** Math.max(0, Math.floor(Math.log2(Math.max(1, value))));
|
||||
}
|
||||
|
||||
for (let offset = 0; offset <= size; offset += size / 4) {
|
||||
context.beginPath();
|
||||
context.moveTo(offset, 0);
|
||||
context.lineTo(offset, size);
|
||||
context.stroke();
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(0, offset);
|
||||
context.lineTo(size, offset);
|
||||
context.stroke();
|
||||
}
|
||||
break;
|
||||
case "checker": {
|
||||
const checkerSize = size / 4;
|
||||
|
||||
for (let row = 0; row < 4; row += 1) {
|
||||
for (let column = 0; column < 4; column += 1) {
|
||||
if ((row + column) % 2 === 0) {
|
||||
context.fillRect(column * checkerSize, row * checkerSize, checkerSize, checkerSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "stripes":
|
||||
context.lineWidth = size / 6;
|
||||
|
||||
for (let offset = -size; offset <= size * 2; offset += size / 3) {
|
||||
context.beginPath();
|
||||
context.moveTo(offset, size);
|
||||
context.lineTo(offset + size, 0);
|
||||
context.stroke();
|
||||
}
|
||||
break;
|
||||
case "diamond":
|
||||
context.lineWidth = Math.max(2, size / 28);
|
||||
|
||||
for (let offset = -size; offset <= size; offset += size / 3) {
|
||||
context.beginPath();
|
||||
context.moveTo(size * 0.5, offset);
|
||||
context.lineTo(size - offset, size * 0.5);
|
||||
context.lineTo(size * 0.5, size - offset);
|
||||
context.lineTo(-offset, size * 0.5);
|
||||
context.closePath();
|
||||
context.stroke();
|
||||
}
|
||||
break;
|
||||
function createRepeatableTextureImage(
|
||||
image: { width: number; height: number }
|
||||
): { width: number; height: number } | HTMLCanvasElement {
|
||||
if (isPowerOfTwo(image.width) && isPowerOfTwo(image.height)) {
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
export function createStarterMaterialTexture(material: MaterialDef, size = 128): CanvasTexture {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
canvas.width = floorPowerOfTwo(image.width);
|
||||
canvas.height = floorPowerOfTwo(image.height);
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
|
||||
if (context === null) {
|
||||
throw new Error("2D canvas context is unavailable for starter material texture generation.");
|
||||
throw new Error("2D canvas context is unavailable for starter material texture conversion.");
|
||||
}
|
||||
|
||||
fillMaterialPattern(context, material, size);
|
||||
context.drawImage(
|
||||
image as CanvasImageSource,
|
||||
0,
|
||||
0,
|
||||
canvas.width,
|
||||
canvas.height
|
||||
);
|
||||
|
||||
const texture = new CanvasTexture(canvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function configureTexture(
|
||||
texture: Texture,
|
||||
material: MaterialDef,
|
||||
options: { colorSpace?: typeof SRGBColorSpace | null }
|
||||
) {
|
||||
const repeat = getStarterMaterialTextureRepeat(material);
|
||||
texture.wrapS = RepeatWrapping;
|
||||
texture.wrapT = RepeatWrapping;
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
texture.needsUpdate = true;
|
||||
texture.repeat.set(repeat.x, repeat.y);
|
||||
texture.colorSpace = options.colorSpace ?? texture.colorSpace;
|
||||
}
|
||||
|
||||
function loadMaterialTexture(
|
||||
loader: TextureLoader,
|
||||
url: string,
|
||||
material: MaterialDef,
|
||||
options: { colorSpace?: typeof SRGBColorSpace | null }
|
||||
): Texture {
|
||||
const texture = loader.load(url, (loadedTexture) => {
|
||||
loadedTexture.image = createRepeatableTextureImage(
|
||||
loadedTexture.image as { width: number; height: number }
|
||||
);
|
||||
configureTexture(loadedTexture, material, options);
|
||||
loadedTexture.needsUpdate = true;
|
||||
});
|
||||
|
||||
configureTexture(texture, material, options);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
export function createStarterMaterialTextureSet(
|
||||
material: MaterialDef,
|
||||
loader: TextureLoader = new TextureLoader()
|
||||
): StarterMaterialTextureSet {
|
||||
const metallicUrl = getStarterMaterialMetallicUrl(material);
|
||||
const specularUrl = getStarterMaterialSpecularUrl(material);
|
||||
|
||||
return {
|
||||
baseColor: loadMaterialTexture(loader, getStarterMaterialBaseColorUrl(material), material, {
|
||||
colorSpace: SRGBColorSpace
|
||||
}),
|
||||
normal: loadMaterialTexture(loader, getStarterMaterialNormalUrl(material), material, {
|
||||
colorSpace: null
|
||||
}),
|
||||
roughness: loadMaterialTexture(
|
||||
loader,
|
||||
getStarterMaterialRoughnessUrl(material),
|
||||
material,
|
||||
{
|
||||
colorSpace: null
|
||||
}
|
||||
),
|
||||
metallic:
|
||||
metallicUrl === null
|
||||
? null
|
||||
: loadMaterialTexture(loader, metallicUrl, material, {
|
||||
colorSpace: null
|
||||
}),
|
||||
specular:
|
||||
specularUrl === null
|
||||
? null
|
||||
: loadMaterialTexture(loader, specularUrl, material, {
|
||||
colorSpace: null
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export function disposeStarterMaterialTextureSet(
|
||||
textureSet: StarterMaterialTextureSet
|
||||
): void {
|
||||
const textures = new Set<Texture>([
|
||||
textureSet.baseColor,
|
||||
textureSet.normal,
|
||||
textureSet.roughness
|
||||
]);
|
||||
|
||||
if (textureSet.metallic !== null) {
|
||||
textures.add(textureSet.metallic);
|
||||
}
|
||||
|
||||
if (textureSet.specular !== null) {
|
||||
textures.add(textureSet.specular);
|
||||
}
|
||||
|
||||
for (const texture of textures) {
|
||||
texture.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
BufferGeometry,
|
||||
BoxGeometry,
|
||||
CapsuleGeometry,
|
||||
Color,
|
||||
ConeGeometry,
|
||||
DirectionalLight,
|
||||
Euler,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
Material,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
MeshPhysicalMaterial,
|
||||
MeshStandardMaterial,
|
||||
PerspectiveCamera,
|
||||
PointLight,
|
||||
@@ -24,6 +26,7 @@ import {
|
||||
ShaderMaterial,
|
||||
Vector3,
|
||||
SpotLight,
|
||||
TextureLoader,
|
||||
WebGLRenderTarget,
|
||||
WebGLRenderer
|
||||
} from "three";
|
||||
@@ -55,8 +58,11 @@ import {
|
||||
import { buildBoxBrushDerivedMeshData } from "../geometry/box-brush-mesh";
|
||||
import {
|
||||
createStarterMaterialSignature,
|
||||
createStarterMaterialTexture
|
||||
createStarterMaterialTextureSet,
|
||||
disposeStarterMaterialTextureSet,
|
||||
type StarterMaterialTextureSet
|
||||
} from "../materials/starter-material-textures";
|
||||
import type { MaterialDef } from "../materials/starter-material-library";
|
||||
import {
|
||||
applyAdvancedRenderingLightShadowFlags,
|
||||
applyAdvancedRenderingRenderableShadowFlags,
|
||||
@@ -146,7 +152,7 @@ import { resolvePlayerStartPauseInput } from "./player-input-bindings";
|
||||
|
||||
interface CachedMaterialTexture {
|
||||
signature: string;
|
||||
texture: ReturnType<typeof createStarterMaterialTexture>;
|
||||
textureSet: StarterMaterialTextureSet;
|
||||
}
|
||||
|
||||
function isEditableEventTarget(target: EventTarget | null): boolean {
|
||||
@@ -266,6 +272,7 @@ export class RuntimeHost {
|
||||
string,
|
||||
CachedMaterialTexture
|
||||
>();
|
||||
private readonly materialTextureLoader = new TextureLoader();
|
||||
private readonly animationMixers = new Map<string, AnimationMixer>();
|
||||
private readonly instanceAnimationClips = new Map<string, AnimationClip[]>();
|
||||
private readonly controllerContext: RuntimeControllerContext;
|
||||
@@ -744,7 +751,7 @@ export class RuntimeHost {
|
||||
}
|
||||
|
||||
for (const cachedTexture of this.materialTextureCache.values()) {
|
||||
cachedTexture.texture.dispose();
|
||||
disposeStarterMaterialTextureSet(cachedTexture.textureSet);
|
||||
}
|
||||
|
||||
this.materialTextureCache.clear();
|
||||
@@ -2275,11 +2282,18 @@ export class RuntimeHost {
|
||||
return faceMaterial;
|
||||
}
|
||||
|
||||
const faceMaterial = new MeshStandardMaterial({
|
||||
const textureSet = this.getOrCreateTextureSet(material);
|
||||
const faceMaterial = new MeshPhysicalMaterial({
|
||||
color: 0xffffff,
|
||||
map: this.getOrCreateTexture(material),
|
||||
roughness: 0.92,
|
||||
metalness: 0.02
|
||||
map: textureSet.baseColor,
|
||||
normalMap: textureSet.normal,
|
||||
roughnessMap: textureSet.roughness,
|
||||
roughness: 1,
|
||||
metalnessMap: textureSet.metallic,
|
||||
metalness: textureSet.metallic === null ? 0.03 : 1,
|
||||
specularColorMap: textureSet.specular,
|
||||
specularColor: new Color(0xffffff),
|
||||
specularIntensity: textureSet.specular === null ? 0.2 : 1
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -2566,25 +2580,30 @@ export class RuntimeHost {
|
||||
}
|
||||
}
|
||||
|
||||
private getOrCreateTexture(
|
||||
private getOrCreateTextureSet(
|
||||
material: NonNullable<RuntimeBoxBrushInstance["faces"]["posX"]["material"]>
|
||||
) {
|
||||
const signature = createStarterMaterialSignature(material);
|
||||
const cachedTexture = this.materialTextureCache.get(material.id);
|
||||
|
||||
if (cachedTexture !== undefined && cachedTexture.signature === signature) {
|
||||
return cachedTexture.texture;
|
||||
return cachedTexture.textureSet;
|
||||
}
|
||||
|
||||
cachedTexture?.texture.dispose();
|
||||
if (cachedTexture !== undefined) {
|
||||
disposeStarterMaterialTextureSet(cachedTexture.textureSet);
|
||||
}
|
||||
|
||||
const texture = createStarterMaterialTexture(material);
|
||||
const textureSet = createStarterMaterialTextureSet(
|
||||
material,
|
||||
this.materialTextureLoader
|
||||
);
|
||||
this.materialTextureCache.set(material.id, {
|
||||
signature,
|
||||
texture
|
||||
textureSet
|
||||
});
|
||||
|
||||
return texture;
|
||||
return textureSet;
|
||||
}
|
||||
|
||||
private clearLocalLights() {
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
AxesHelper,
|
||||
BufferGeometry,
|
||||
BoxGeometry,
|
||||
CanvasTexture,
|
||||
CapsuleGeometry,
|
||||
Color,
|
||||
ConeGeometry,
|
||||
CylinderGeometry,
|
||||
DirectionalLight,
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Matrix4,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
MeshPhysicalMaterial,
|
||||
MeshStandardMaterial,
|
||||
Object3D,
|
||||
OrthographicCamera,
|
||||
@@ -33,6 +34,7 @@ import {
|
||||
Spherical,
|
||||
TorusGeometry,
|
||||
SpotLight,
|
||||
TextureLoader,
|
||||
Vector2,
|
||||
Vector3,
|
||||
WebGLRenderTarget,
|
||||
@@ -160,7 +162,9 @@ import { buildGeneratedModelCollider } from "../geometry/model-instance-collider
|
||||
import { DEFAULT_GRID_SIZE, snapValueToGrid } from "../geometry/grid-snapping";
|
||||
import {
|
||||
createStarterMaterialSignature,
|
||||
createStarterMaterialTexture
|
||||
createStarterMaterialTextureSet,
|
||||
disposeStarterMaterialTextureSet,
|
||||
type StarterMaterialTextureSet
|
||||
} from "../materials/starter-material-textures";
|
||||
import type { MaterialDef } from "../materials/starter-material-library";
|
||||
import {
|
||||
@@ -320,7 +324,7 @@ const VIEWPORT_GRID_VISUAL_DIVISIONS = 400;
|
||||
|
||||
interface CachedMaterialTexture {
|
||||
signature: string;
|
||||
texture: CanvasTexture;
|
||||
textureSet: StarterMaterialTextureSet;
|
||||
}
|
||||
|
||||
interface EntityRenderObjects {
|
||||
@@ -405,6 +409,7 @@ export class ViewportHost {
|
||||
string,
|
||||
CachedMaterialTexture
|
||||
>();
|
||||
private readonly materialTextureLoader = new TextureLoader();
|
||||
private currentDocument: SceneDocument | null = null;
|
||||
private currentWorld: WorldSettings | null = null;
|
||||
private currentSimulationScene: RuntimeSceneDefinition | null = null;
|
||||
@@ -1020,7 +1025,7 @@ export class ViewportHost {
|
||||
this.renderer.autoClear = true;
|
||||
|
||||
for (const cachedTexture of this.materialTextureCache.values()) {
|
||||
cachedTexture.texture.dispose();
|
||||
disposeStarterMaterialTextureSet(cachedTexture.textureSet);
|
||||
}
|
||||
|
||||
this.materialTextureCache.clear();
|
||||
@@ -4671,6 +4676,21 @@ export class ViewportHost {
|
||||
return "none";
|
||||
}
|
||||
|
||||
private getMaterialSwatchColorHex(
|
||||
material: MaterialDef,
|
||||
highlightState: "none" | "hovered" | "selected"
|
||||
): number {
|
||||
const swatchColor = new Color(material.swatchColorHex);
|
||||
|
||||
if (highlightState === "selected") {
|
||||
swatchColor.lerp(new Color(SELECTED_FACE_FALLBACK_COLOR), 0.42);
|
||||
} else if (highlightState === "hovered") {
|
||||
swatchColor.lerp(new Color(HOVERED_FACE_FALLBACK_COLOR), 0.28);
|
||||
}
|
||||
|
||||
return swatchColor.getHex();
|
||||
}
|
||||
|
||||
private createFaceMaterial(
|
||||
brush: BoxBrush,
|
||||
faceId: BoxFaceId,
|
||||
@@ -4822,9 +4842,7 @@ export class ViewportHost {
|
||||
: hoveredFace
|
||||
? HOVERED_FACE_FALLBACK_COLOR
|
||||
: FALLBACK_FACE_COLOR
|
||||
: emphasizedFace
|
||||
? material.accentColorHex
|
||||
: material.baseColorHex;
|
||||
: this.getMaterialSwatchColorHex(material, highlightState);
|
||||
|
||||
return new MeshBasicMaterial({
|
||||
color: colorHex,
|
||||
@@ -4842,9 +4860,7 @@ export class ViewportHost {
|
||||
: hoveredFace
|
||||
? HOVERED_FACE_FALLBACK_COLOR
|
||||
: FALLBACK_FACE_COLOR
|
||||
: emphasizedFace
|
||||
? material.accentColorHex
|
||||
: material.baseColorHex;
|
||||
: this.getMaterialSwatchColorHex(material, highlightState);
|
||||
|
||||
return new MeshBasicMaterial({
|
||||
color: colorHex,
|
||||
@@ -4885,17 +4901,24 @@ export class ViewportHost {
|
||||
return faceMaterial;
|
||||
}
|
||||
|
||||
const faceMaterial = new MeshStandardMaterial({
|
||||
const textureSet = this.getOrCreateTextureSet(material);
|
||||
const faceMaterial = new MeshPhysicalMaterial({
|
||||
color: 0xffffff,
|
||||
map: this.getOrCreateTexture(material),
|
||||
map: textureSet.baseColor,
|
||||
normalMap: textureSet.normal,
|
||||
roughnessMap: textureSet.roughness,
|
||||
emissive: selectedFace
|
||||
? SELECTED_FACE_EMISSIVE
|
||||
: hoveredFace
|
||||
? HOVERED_FACE_EMISSIVE
|
||||
: 0x000000,
|
||||
emissiveIntensity: selectedFace ? 0.32 : hoveredFace ? 0.18 : 0,
|
||||
roughness: 0.92,
|
||||
metalness: 0.02
|
||||
roughness: 1,
|
||||
metalnessMap: textureSet.metallic,
|
||||
metalness: textureSet.metallic === null ? 0.03 : 1,
|
||||
specularColorMap: textureSet.specular,
|
||||
specularColor: new Color(0xffffff),
|
||||
specularIntensity: textureSet.specular === null ? 0.2 : 1
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -5150,24 +5173,29 @@ export class ViewportHost {
|
||||
}
|
||||
}
|
||||
|
||||
private getOrCreateTexture(material: MaterialDef): CanvasTexture {
|
||||
private getOrCreateTextureSet(material: MaterialDef) {
|
||||
const signature = createStarterMaterialSignature(material);
|
||||
const cachedTexture = this.materialTextureCache.get(material.id);
|
||||
|
||||
if (cachedTexture !== undefined && cachedTexture.signature === signature) {
|
||||
return cachedTexture.texture;
|
||||
return cachedTexture.textureSet;
|
||||
}
|
||||
|
||||
cachedTexture?.texture.dispose();
|
||||
if (cachedTexture !== undefined) {
|
||||
disposeStarterMaterialTextureSet(cachedTexture.textureSet);
|
||||
}
|
||||
|
||||
const texture = createStarterMaterialTexture(material);
|
||||
const textureSet = createStarterMaterialTextureSet(
|
||||
material,
|
||||
this.materialTextureLoader
|
||||
);
|
||||
|
||||
this.materialTextureCache.set(material.id, {
|
||||
signature,
|
||||
texture
|
||||
textureSet
|
||||
});
|
||||
|
||||
return texture;
|
||||
return textureSet;
|
||||
}
|
||||
|
||||
private collectViewportWaterContactPatches(
|
||||
|
||||
Reference in New Issue
Block a user