Feat/map-environment #6
@@ -11,6 +11,7 @@ import {
|
||||
Redo2,
|
||||
RotateCw,
|
||||
Save,
|
||||
ScanSearch,
|
||||
Undo2,
|
||||
Unlock,
|
||||
X,
|
||||
@@ -37,6 +38,7 @@ interface EditorControlsProps {
|
||||
redoCount: number;
|
||||
onUndo: () => void;
|
||||
onRedo: () => void;
|
||||
onResetCamera: () => void;
|
||||
onExportJson: () => void;
|
||||
onSaveToServer?: (() => void | Promise<void>) | undefined;
|
||||
onPlayerMode?: (() => void) | undefined;
|
||||
@@ -101,6 +103,7 @@ export function EditorControls({
|
||||
redoCount,
|
||||
onUndo,
|
||||
onRedo,
|
||||
onResetCamera,
|
||||
onExportJson,
|
||||
onSaveToServer,
|
||||
onPlayerMode,
|
||||
@@ -268,6 +271,11 @@ export function EditorControls({
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button className="editor-action-button" onClick={onResetCamera}>
|
||||
<ScanSearch size={16} aria-hidden="true" />
|
||||
Reset camera
|
||||
</button>
|
||||
|
||||
<label className="editor-checkbox-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -3,11 +3,15 @@ import { OrbitControls } from "@react-three/drei";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import gsap from "gsap";
|
||||
import * as THREE from "three";
|
||||
import type { OrbitControls as OrbitControlsImpl } from "three-stdlib";
|
||||
import { EditorMap } from "@/components/editor/scene/EditorMap";
|
||||
import { FlyController } from "@/controls/editor/FlyController";
|
||||
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
|
||||
import type { MapNode, TransformMode, SceneData } from "@/types/editor/editor";
|
||||
|
||||
const EDITOR_CAMERA_HOME_POSITION = new THREE.Vector3(0, 50, 100);
|
||||
const EDITOR_CAMERA_HOME_TARGET = new THREE.Vector3(0, 0, 0);
|
||||
|
||||
export interface EditorCinematicPreviewRequest {
|
||||
id: string;
|
||||
cinematic: CinematicDefinition;
|
||||
@@ -28,6 +32,7 @@ interface EditorSceneProps {
|
||||
onNodeTransform: (nodeIndex: number, transform: MapNode) => void;
|
||||
onUndo: () => void;
|
||||
onRedo: () => void;
|
||||
resetCameraRequest: number;
|
||||
isPlayerMode?: boolean;
|
||||
cinematicPreviewRequest?: EditorCinematicPreviewRequest | null;
|
||||
onCinematicPreviewComplete?: (() => void) | undefined;
|
||||
@@ -48,11 +53,55 @@ export function EditorScene({
|
||||
onNodeTransform,
|
||||
onUndo,
|
||||
onRedo,
|
||||
resetCameraRequest,
|
||||
isPlayerMode = false,
|
||||
cinematicPreviewRequest = null,
|
||||
onCinematicPreviewComplete,
|
||||
}: EditorSceneProps): React.JSX.Element {
|
||||
const isCinematicPreviewing = cinematicPreviewRequest !== null;
|
||||
const camera = useThree((state) => state.camera);
|
||||
const orbitControlsRef = useRef<OrbitControlsImpl | null>(null);
|
||||
const previousSelectedNodeIndexRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedNodeIndex === previousSelectedNodeIndexRef.current) return;
|
||||
previousSelectedNodeIndexRef.current = selectedNodeIndex;
|
||||
|
||||
if (selectedNodeIndex === null || isPlayerMode || isCinematicPreviewing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedNode = sceneData.mapNodes[selectedNodeIndex];
|
||||
if (!selectedNode) return;
|
||||
|
||||
const controls = orbitControlsRef.current;
|
||||
const target = new THREE.Vector3(...selectedNode.position);
|
||||
const currentTarget = controls?.target ?? EDITOR_CAMERA_HOME_TARGET;
|
||||
const cameraOffset = camera.position.clone().sub(currentTarget);
|
||||
|
||||
camera.position.copy(target).add(cameraOffset);
|
||||
camera.lookAt(target);
|
||||
controls?.target.copy(target);
|
||||
controls?.update();
|
||||
}, [
|
||||
camera,
|
||||
isCinematicPreviewing,
|
||||
isPlayerMode,
|
||||
sceneData,
|
||||
selectedNodeIndex,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetCameraRequest === 0 || isPlayerMode || isCinematicPreviewing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controls = orbitControlsRef.current;
|
||||
camera.position.copy(EDITOR_CAMERA_HOME_POSITION);
|
||||
camera.lookAt(EDITOR_CAMERA_HOME_TARGET);
|
||||
controls?.target.copy(EDITOR_CAMERA_HOME_TARGET);
|
||||
controls?.update();
|
||||
}, [camera, isCinematicPreviewing, isPlayerMode, resetCameraRequest]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -109,6 +158,7 @@ export function EditorScene({
|
||||
<FlyController disabled={isCinematicPreviewing} />
|
||||
) : (
|
||||
<OrbitControls
|
||||
ref={orbitControlsRef}
|
||||
enabled={!isCinematicPreviewing}
|
||||
enableDamping
|
||||
dampingFactor={0.05}
|
||||
|
||||
+2
-1
@@ -1371,7 +1371,8 @@ canvas {
|
||||
transform 160ms ease;
|
||||
}
|
||||
|
||||
.editor-action-button + .editor-action-button {
|
||||
.editor-action-button + .editor-action-button,
|
||||
.editor-player-button + .editor-action-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ export function EditorPage(): React.JSX.Element {
|
||||
const [isPlayerMode, setIsPlayerMode] = useState(false);
|
||||
const [isSelectionLocked, setIsSelectionLocked] = useState(false);
|
||||
const [lockTerrainSelection, setLockTerrainSelection] = useState(true);
|
||||
const [resetCameraRequest, setResetCameraRequest] = useState(0);
|
||||
const [sceneLoadingState, setSceneLoadingState] = useState<SceneLoadingState>(
|
||||
{
|
||||
...INITIAL_SCENE_LOADING_STATE,
|
||||
@@ -257,6 +258,10 @@ export function EditorPage(): React.JSX.Element {
|
||||
setIsPlayerMode((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const handleResetCamera = useCallback(() => {
|
||||
setResetCameraRequest((request) => request + 1);
|
||||
}, []);
|
||||
|
||||
const handlePreviewCinematic = useCallback(
|
||||
(cinematic: CinematicDefinition) => {
|
||||
setCinematicPreviewRequest({
|
||||
@@ -375,6 +380,7 @@ export function EditorPage(): React.JSX.Element {
|
||||
onNodeTransform={handleNodeTransform}
|
||||
onUndo={handleUndo}
|
||||
onRedo={handleRedo}
|
||||
resetCameraRequest={resetCameraRequest}
|
||||
isPlayerMode={isPlayerMode}
|
||||
cinematicPreviewRequest={cinematicPreviewRequest}
|
||||
onCinematicPreviewComplete={handleCinematicPreviewComplete}
|
||||
@@ -405,6 +411,7 @@ export function EditorPage(): React.JSX.Element {
|
||||
redoCount={redoCount}
|
||||
onUndo={handleUndo}
|
||||
onRedo={handleRedo}
|
||||
onResetCamera={handleResetCamera}
|
||||
onExportJson={handleExportJson}
|
||||
onSaveToServer={import.meta.env.DEV ? handleSaveToServer : undefined}
|
||||
onPlayerMode={handlePlayerMode}
|
||||
|
||||
Reference in New Issue
Block a user