fix: restore 3d pbr textures in canvas viewer
This commit is contained in:
parent
86b1ee9eeb
commit
89ec01cfb8
@ -72,6 +72,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
getGenerationCreditCostFromImageGenConfig,
|
getGenerationCreditCostFromImageGenConfig,
|
||||||
getImageCreditCostFromConfig,
|
getImageCreditCostFromConfig,
|
||||||
|
getImageModelCreditDiscountLabel,
|
||||||
} from "@/lib/ai/image-model-pricing";
|
} from "@/lib/ai/image-model-pricing";
|
||||||
import { getVideoToolOptionsForMode } from "@/lib/ai/video-tool-capability";
|
import { getVideoToolOptionsForMode } from "@/lib/ai/video-tool-capability";
|
||||||
import type { Attachment, ChatMessage } from "@/lib/types";
|
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">
|
<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
|
<SelectItem
|
||||||
key={model.id}
|
key={model.id}
|
||||||
value={model.id}
|
value={model.id}
|
||||||
@ -9368,19 +9374,34 @@ export function CanvasChatPanel({
|
|||||||
</span>
|
</span>
|
||||||
<span className="truncate text-[12px] font-medium leading-none">{model.displayName}</span>
|
<span className="truncate text-[12px] font-medium leading-none">{model.displayName}</span>
|
||||||
</span>
|
</span>
|
||||||
{modelSelectValue === model.id ? (
|
<span className="flex shrink-0 items-center gap-1.5">
|
||||||
<span
|
{discountLabel ? (
|
||||||
className={cn(
|
<span
|
||||||
"text-sm font-semibold",
|
className={cn(
|
||||||
isVideoWorkspace ? "text-foreground" : "text-[#1f2937]"
|
"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"
|
||||||
</span>
|
)}
|
||||||
) : null}
|
>
|
||||||
|
{discountLabel}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{modelSelectValue === model.id ? (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm font-semibold",
|
||||||
|
isVideoWorkspace ? "text-foreground" : "text-[#1f2937]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
{selectableModels.length === 0 ? (
|
{selectableModels.length === 0 ? (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { AlertCircle, Box, ExternalLink, Loader2 } from "lucide-react";
|
|||||||
import React, { Suspense, useEffect, useMemo, useRef } from "react";
|
import React, { Suspense, useEffect, useMemo, useRef } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import type { Mesh, Object3D } from "three";
|
import type { Mesh, Object3D } from "three";
|
||||||
|
import { KTX2Loader } from "three-stdlib";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { buildThreeAssetUrl } from "@/lib/canvas/three-asset-url";
|
import { buildThreeAssetUrl } from "@/lib/canvas/three-asset-url";
|
||||||
|
|
||||||
@ -72,6 +73,8 @@ const CAMERA_PRESET_POSITIONS: Record<
|
|||||||
top: [0, 3.4, 0.001],
|
top: [0, 3.4, 0.001],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const KTX2_TRANSCODER_PATH = "/three/basis/";
|
||||||
|
|
||||||
function CameraPresetController({
|
function CameraPresetController({
|
||||||
controlsRef,
|
controlsRef,
|
||||||
preset,
|
preset,
|
||||||
@ -201,7 +204,28 @@ function ModelScene({
|
|||||||
onLayersChange?: (layers: Model3DLayer[]) => void;
|
onLayersChange?: (layers: Model3DLayer[]) => void;
|
||||||
onStatsChange?: (stats: Model3DStats) => 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 scene = useMemo(() => model.scene.clone(), [model.scene]);
|
||||||
const meta = useMemo(() => collectSceneMeta(scene), [scene]);
|
const meta = useMemo(() => collectSceneMeta(scene), [scene]);
|
||||||
|
|
||||||
@ -219,15 +243,25 @@ function ModelScene({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const { mesh } of meta.meshRefs) {
|
for (const { mesh } of meta.meshRefs) {
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
const materials = Array.isArray(mesh.material)
|
const materials = Array.isArray(mesh.material)
|
||||||
? mesh.material
|
? mesh.material
|
||||||
: [mesh.material];
|
: [mesh.material];
|
||||||
for (const material of materials) {
|
for (const material of materials) {
|
||||||
if (!material) continue;
|
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) {
|
if ("wireframe" in material) {
|
||||||
(material as any).wireframe = settings.wireframe;
|
(material as any).wireframe = settings.wireframe;
|
||||||
material.needsUpdate = true;
|
|
||||||
}
|
}
|
||||||
|
material.needsUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [meta.meshRefs, settings.wireframe]);
|
}, [meta.meshRefs, settings.wireframe]);
|
||||||
@ -381,6 +415,11 @@ export function Model3DViewer({
|
|||||||
}}
|
}}
|
||||||
className="bg-gradient-to-b from-zinc-900 to-zinc-950"
|
className="bg-gradient-to-b from-zinc-900 to-zinc-950"
|
||||||
gl={{ preserveDrawingBuffer: true }}
|
gl={{ preserveDrawingBuffer: true }}
|
||||||
|
onCreated={({ gl }) => {
|
||||||
|
gl.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
|
gl.toneMapping = THREE.ACESFilmicToneMapping;
|
||||||
|
gl.toneMappingExposure = 1;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ViewerCaptureBridge onCaptureReady={onCaptureReady} />
|
<ViewerCaptureBridge onCaptureReady={onCaptureReady} />
|
||||||
<CameraPresetController
|
<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