import { Suspense, useLayoutEffect } from 'react';
import { useLoader } from '@react-three/fiber';
import { useTexture } from '@react-three/drei';
import type { TTSObject } from '@tts/shared';
import * as THREE from 'three';
import type { Object3D } from 'three';
import { assetUrl } from '@tts/http';
import { FlexibleModelLoader } from './flexibleModelLoader';
import { objectTint, tintedColor } from './sharedResources';
/**
* The mesh content for a custom model, exported so the full-setup view can
* compose it into a shared scene. Renders the model from `CustomMesh.MeshURL`
* (or a neutral box placeholder when absent).
*/
export function CustomModelMesh({ object }: { object: TTSObject }) {
const meshUrl = object.CustomMesh?.MeshURL;
const tint = objectTint(object);
if (!meshUrl) {
return (
);
}
return (
);
}
function Model({
meshUrl,
diffuseUrl,
tint,
}: {
meshUrl: string;
diffuseUrl?: string;
tint: THREE.Color;
}) {
const root = useLoader(FlexibleModelLoader, assetUrl(meshUrl));
return (
<>
{diffuseUrl && }
>
);
}
// Apply the object's tint to every material on the loaded model, multiplying
// the existing color. Rendered after the model so it runs once the materials
// exist.
function Tint({ root, tint }: { root: Object3D; tint: THREE.Color }) {
useLayoutEffect(() => {
root.traverse((child) => {
const mesh = child as THREE.Mesh;
if (mesh.isMesh) {
const material = Array.isArray(mesh.material)
? mesh.material[0]
: mesh.material;
if (material && 'color' in material) {
(material as THREE.MeshStandardMaterial).color.multiply(tint);
material.needsUpdate = true;
}
}
});
}, [root, tint]);
return null;
}
// Rendered inside the Canvas so `useTexture` can access the R3F store. Only
// mounted when a diffuse URL exists, so the hook count stays consistent.
function DiffuseTexture({ root, url }: { root: Object3D; url: string }) {
const texture = useTexture(assetUrl(url));
// Diffuse art is sRGB-encoded; `TextureLoader` leaves `colorSpace` as
// `NoColorSpace`, which double-decodes it in the shader and washes out
// contrast. Mark it sRGB so the GPU decodes it once, correctly.
texture.colorSpace = THREE.SRGBColorSpace;
// Apply the diffuse texture to every mesh material on the loaded model.
useLayoutEffect(() => {
root.traverse((child) => {
const mesh = child as THREE.Mesh;
if (mesh.isMesh) {
const material = Array.isArray(mesh.material)
? mesh.material[0]
: mesh.material;
if (material && 'map' in material) {
material.map = texture;
material.needsUpdate = true;
}
}
});
}, [root, texture]);
return null;
}