From bfe8c49323081e43043b1d3e353d48d3366a50e6 Mon Sep 17 00:00:00 2001 From: math-pixel <59537610+math-pixel@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:25:56 +0200 Subject: [PATCH] fix :editor --- src/components/editor/EditorPage.tsx | 29 +++++- src/components/editor/MapViewer.tsx | 149 ++++++++++----------------- src/components/game/GameMap.tsx | 87 +++++----------- 3 files changed, 109 insertions(+), 156 deletions(-) diff --git a/src/components/editor/EditorPage.tsx b/src/components/editor/EditorPage.tsx index 0d952d3..4254ae1 100644 --- a/src/components/editor/EditorPage.tsx +++ b/src/components/editor/EditorPage.tsx @@ -282,8 +282,35 @@ export function EditorPage(): React.JSX.Element { try { const modelUrl = `/models/${modelName}/model.gltf`; const modelResponse = await fetch(modelUrl); + if (modelResponse.ok) { - models.set(modelName, modelUrl); + const contentType = + modelResponse.headers.get("content-type") || ""; + + if ( + contentType.includes("gltf") || + contentType.includes("json") || + contentType.includes("model") + ) { + const text = await modelResponse.text(); + if ( + text.includes('"glTF"') || + text.includes('"scene"') || + text.includes('"nodes"') + ) { + models.set(modelName, modelUrl); + } else { + console.warn( + `Invalid GLTF content for ${modelName}:`, + text.substring(0, 100), + ); + } + } else { + console.warn( + `Invalid Content-Type for ${modelName}:`, + contentType, + ); + } } } catch { /* empty */ diff --git a/src/components/editor/MapViewer.tsx b/src/components/editor/MapViewer.tsx index 2651208..1df0739 100644 --- a/src/components/editor/MapViewer.tsx +++ b/src/components/editor/MapViewer.tsx @@ -1,12 +1,4 @@ -import { - useMemo, - useRef, - useEffect, - useState, - Suspense, - Component, - type ReactNode, -} from "react"; +import { useMemo, useRef, useEffect, useState } from "react"; import { useGLTF } from "@react-three/drei"; import { Grid, TransformControls } from "@react-three/drei"; import * as THREE from "three"; @@ -25,33 +17,6 @@ interface MapViewerProps { onNodeTransform: (nodeIndex: number, transform: MapNode) => void; } -const clonedScenesCache = new Map(); - -class ErrorBoundary extends Component< - { children: ReactNode; fallback?: ReactNode }, - { hasError: boolean } -> { - constructor(props: { children: ReactNode; fallback?: ReactNode }) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError(): { hasError: boolean } { - return { hasError: true }; - } - - componentDidCatch(error: Error, _errorInfo: React.ErrorInfo): void { - console.warn("Model loading error:", error.message); - } - - render() { - if (this.state.hasError) { - return this.props.fallback || null; - } - return this.props.children; - } -} - export default function MapViewer({ sceneData, selectedNodeIndex, @@ -63,17 +28,13 @@ export default function MapViewer({ onTransformEnd, onNodeTransform, }: MapViewerProps): React.JSX.Element { - const isTransforming = useRef(false); const objectsMapRef = useRef>(new Map()); const handleTransformMouseDown = () => { - isTransforming.current = true; onTransformStart?.(); }; const handleTransformMouseUp = () => { - isTransforming.current = false; - if (selectedNodeIndex !== null) { const obj = objectsMapRef.current.get(selectedNodeIndex); if (!obj) return; @@ -88,7 +49,6 @@ export default function MapViewer({ onNodeTransform?.(selectedNodeIndex, updatedNode); } } - onTransformEnd?.(); }; @@ -133,20 +93,17 @@ export default function MapViewer({ if (modelUrl) { return ( - - - - - + ); } else { return ( @@ -199,14 +156,7 @@ function ModelNodeWithRef({ const groupRef = useRef(null); const { scene } = useGLTF(modelUrl); - const clonedScene = useMemo(() => { - if (!clonedScenesCache.has(modelUrl)) { - const clone = scene.clone(true); - clonedScenesCache.set(modelUrl, clone); - return clone; - } - return clonedScenesCache.get(modelUrl)!; - }, [modelUrl, scene]); + const sceneInstance = useMemo(() => scene.clone(true), [scene]); useEffect(() => { if (groupRef.current) { @@ -221,46 +171,45 @@ function ModelNodeWithRef({ return () => { currentMap.delete(currentIndex); }; - }, [index, node, objectsMapRef]); + }, [index]); - const instance = useMemo(() => { - const inst = clonedScene.clone(true); - - if (isSelected) { - inst.traverse((child) => { - if ((child as THREE.Mesh).isMesh && (child as THREE.Mesh).material) { - const mesh = child as THREE.Mesh; - const mat = mesh.material as unknown as THREE.MeshStandardMaterial; - mesh.material = mat.clone(); - (mesh.material as unknown as THREE.MeshStandardMaterial).color.set( - "#ff6600", - ); - } - }); - } else if (isHovered) { - inst.traverse((child) => { - if ((child as THREE.Mesh).isMesh && (child as THREE.Mesh).material) { - const mesh = child as THREE.Mesh; - const mat = mesh.material as unknown as THREE.MeshStandardMaterial; - mesh.material = mat.clone(); - (mesh.material as unknown as THREE.MeshStandardMaterial).color.set( - "#ff9900", - ); - } - }); + useEffect(() => { + if (groupRef.current) { + groupRef.current.position.set(...node.position); + groupRef.current.rotation.set(...node.rotation); + groupRef.current.scale.set(...node.scale); } + }, [node.position, node.rotation, node.scale]); - inst.position.set(...node.position); - inst.rotation.set(...node.rotation); - inst.scale.set(...node.scale); + useEffect(() => { + if (!groupRef.current) return; - return inst; - }, [clonedScene, node, isSelected, isHovered]); + groupRef.current.traverse((child) => { + if ((child as THREE.Mesh).isMesh) { + const mesh = child as THREE.Mesh; + if ( + mesh.material && + mesh.material instanceof THREE.MeshStandardMaterial + ) { + if (isSelected) { + mesh.material = mesh.material.clone(); + (mesh.material as THREE.MeshStandardMaterial).color.set("#ff6600"); + } else if (isHovered) { + mesh.material = mesh.material.clone(); + (mesh.material as THREE.MeshStandardMaterial).color.set("#ff9900"); + } + } + } + }); + }, [isSelected, isHovered]); return ( { (e as { stopPropagation?: () => void }).stopPropagation?.(); onSelectNode(index); @@ -309,7 +258,15 @@ function FallbackNodeWithRef({ return () => { currentMap.delete(currentIndex); }; - }, [index, node, objectsMapRef]); + }, [index]); + + useEffect(() => { + if (meshRef.current) { + meshRef.current.position.set(...node.position); + meshRef.current.rotation.set(...node.rotation); + meshRef.current.scale.set(...node.scale); + } + }, [node.position, node.rotation, node.scale]); const color = isSelected ? "#ff6600" : isHovered ? "#ff9900" : "#cccccc"; diff --git a/src/components/game/GameMap.tsx b/src/components/game/GameMap.tsx index 1afa43c..d2ec661 100644 --- a/src/components/game/GameMap.tsx +++ b/src/components/game/GameMap.tsx @@ -1,12 +1,4 @@ -import { - useEffect, - useState, - useMemo, - useRef, - Suspense, - Component, - type ReactNode, -} from "react"; +import { useEffect, useState, useMemo, useRef } from "react"; import { useGLTF } from "@react-three/drei"; import * as THREE from "three"; import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode"; @@ -22,33 +14,6 @@ interface MapNode { const MAP_JSON_PATH = "/map.json"; -const clonedScenesCache = new Map(); - -class GameMapErrorBoundary extends Component< - { children: ReactNode; fallback?: ReactNode }, - { hasError: boolean } -> { - constructor(props: { children: ReactNode; fallback?: ReactNode }) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError(): { hasError: boolean } { - return { hasError: true }; - } - - componentDidCatch(error: Error, _errorInfo: React.ErrorInfo): void { - console.warn("GameMap model loading error:", error.message); - } - - render() { - if (this.state.hasError) { - return this.props.fallback || null; - } - return this.props.children; - } -} - interface GameMapProps { onOctreeReady: OctreeReadyHandler; } @@ -113,11 +78,7 @@ export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { return ( {nodesToRender.map((node, index) => ( - - - - - + ))} ); @@ -125,26 +86,34 @@ export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { function ModelInstance({ node }: { node: MapNode }): React.JSX.Element { const modelPath = `/models/${node.name}/model.gltf`; + const groupRef = useRef(null); const { scene } = useGLTF(modelPath); - const groupRef = useRef(null); - - const clonedScene = useMemo(() => { - if (!clonedScenesCache.has(modelPath)) { - const clone = scene.clone(true); - clonedScenesCache.set(modelPath, clone); - return clone; + useEffect(() => { + if (groupRef.current) { + groupRef.current.position.set(...node.position); + groupRef.current.rotation.set(...node.rotation); + groupRef.current.scale.set(...node.scale); } - return clonedScenesCache.get(modelPath)!; - }, [modelPath, scene]); + }, [ + node.position[0], + node.position[1], + node.position[2], + node.rotation[0], + node.rotation[1], + node.rotation[2], + node.scale[0], + node.scale[1], + node.scale[2], + ]); - const instance = useMemo(() => { - const inst = clonedScene.clone(true); - inst.position.set(...node.position); - inst.rotation.set(...node.rotation); - inst.scale.set(...node.scale); - return inst; - }, [clonedScene, node.position, node.rotation, node.scale]); - - return ; + return ( + + ); }