fix: restore 3d pbr textures in canvas viewer
This commit is contained in:
parent
86b1ee9eeb
commit
89ec01cfb8
@ -72,6 +72,7 @@ import {
|
||||
import {
|
||||
getGenerationCreditCostFromImageGenConfig,
|
||||
getImageCreditCostFromConfig,
|
||||
getImageModelCreditDiscountLabel,
|
||||
} from "@/lib/ai/image-model-pricing";
|
||||
import { getVideoToolOptionsForMode } from "@/lib/ai/video-tool-capability";
|
||||
import type { Attachment, ChatMessage } from "@/lib/types";
|
||||
@ -9321,7 +9322,12 @@ export function CanvasChatPanel({
|
||||
)}
|
||||
>
|
||||
<div className="max-h-[312px] space-y-0.5 overflow-y-auto pr-1">
|
||||
{selectableModels.map((model) => (
|
||||
{selectableModels.map((model) => {
|
||||
const discountLabel = getImageModelCreditDiscountLabel(
|
||||
model.imageGenConfig
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectItem
|
||||
key={model.id}
|
||||
value={model.id}
|
||||
@ -9368,19 +9374,34 @@ export function CanvasChatPanel({
|
||||
</span>
|
||||
<span className="truncate text-[12px] font-medium leading-none">{model.displayName}</span>
|
||||
</span>
|
||||
{modelSelectValue === model.id ? (
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-semibold",
|
||||
isVideoWorkspace ? "text-foreground" : "text-[#1f2937]"
|
||||
)}
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
) : null}
|
||||
<span className="flex shrink-0 items-center gap-1.5">
|
||||
{discountLabel ? (
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-md border px-1.5 py-0.5 text-[10px] font-semibold leading-none",
|
||||
isVideoWorkspace
|
||||
? "border-border/70 bg-muted/80 text-foreground/80"
|
||||
: "border-[#d0d4dc] bg-[#eef1f5] text-[#5c6574] dark:border-white/15 dark:bg-white/10 dark:text-zinc-200"
|
||||
)}
|
||||
>
|
||||
{discountLabel}
|
||||
</span>
|
||||
) : null}
|
||||
{modelSelectValue === model.id ? (
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-semibold",
|
||||
isVideoWorkspace ? "text-foreground" : "text-[#1f2937]"
|
||||
)}
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
{selectableModels.length === 0 ? (
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@ -13,6 +13,7 @@ import { AlertCircle, Box, ExternalLink, Loader2 } from "lucide-react";
|
||||
import React, { Suspense, useEffect, useMemo, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
import type { Mesh, Object3D } from "three";
|
||||
import { KTX2Loader } from "three-stdlib";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { buildThreeAssetUrl } from "@/lib/canvas/three-asset-url";
|
||||
|
||||
@ -72,6 +73,8 @@ const CAMERA_PRESET_POSITIONS: Record<
|
||||
top: [0, 3.4, 0.001],
|
||||
};
|
||||
|
||||
const KTX2_TRANSCODER_PATH = "/three/basis/";
|
||||
|
||||
function CameraPresetController({
|
||||
controlsRef,
|
||||
preset,
|
||||
@ -201,7 +204,28 @@ function ModelScene({
|
||||
onLayersChange?: (layers: Model3DLayer[]) => void;
|
||||
onStatsChange?: (stats: Model3DStats) => void;
|
||||
}) {
|
||||
const model = useGLTF(url);
|
||||
const { gl } = useThree();
|
||||
const ktx2Loader = useMemo(() => {
|
||||
const nextLoader = new KTX2Loader();
|
||||
nextLoader.setTranscoderPath(KTX2_TRANSCODER_PATH);
|
||||
nextLoader.detectSupport(gl);
|
||||
return nextLoader;
|
||||
}, [gl]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
ktx2Loader.dispose();
|
||||
};
|
||||
}, [ktx2Loader]);
|
||||
|
||||
const model = useGLTF(
|
||||
url,
|
||||
true,
|
||||
true,
|
||||
(loader) => {
|
||||
loader.setKTX2Loader(ktx2Loader);
|
||||
},
|
||||
);
|
||||
const scene = useMemo(() => model.scene.clone(), [model.scene]);
|
||||
const meta = useMemo(() => collectSceneMeta(scene), [scene]);
|
||||
|
||||
@ -219,15 +243,25 @@ function ModelScene({
|
||||
|
||||
useEffect(() => {
|
||||
for (const { mesh } of meta.meshRefs) {
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
const materials = Array.isArray(mesh.material)
|
||||
? mesh.material
|
||||
: [mesh.material];
|
||||
for (const material of materials) {
|
||||
if (!material) continue;
|
||||
const textureLikeKeys = ["map", "emissiveMap"] as const;
|
||||
for (const key of textureLikeKeys) {
|
||||
const texture = (material as any)?.[key];
|
||||
if (texture && "colorSpace" in texture) {
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
texture.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
if ("wireframe" in material) {
|
||||
(material as any).wireframe = settings.wireframe;
|
||||
material.needsUpdate = true;
|
||||
}
|
||||
material.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
}, [meta.meshRefs, settings.wireframe]);
|
||||
@ -381,6 +415,11 @@ export function Model3DViewer({
|
||||
}}
|
||||
className="bg-gradient-to-b from-zinc-900 to-zinc-950"
|
||||
gl={{ preserveDrawingBuffer: true }}
|
||||
onCreated={({ gl }) => {
|
||||
gl.outputColorSpace = THREE.SRGBColorSpace;
|
||||
gl.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
gl.toneMappingExposure = 1;
|
||||
}}
|
||||
>
|
||||
<ViewerCaptureBridge onCaptureReady={onCaptureReady} />
|
||||
<CameraPresetController
|
||||
|
||||
19
public/three/basis/basis_transcoder.js
Normal file
19
public/three/basis/basis_transcoder.js
Normal file
File diff suppressed because one or more lines are too long
BIN
public/three/basis/basis_transcoder.wasm
Normal file
BIN
public/three/basis/basis_transcoder.wasm
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user