dispose of transform controls on deselect
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { runInAction } from "mobx";
|
import { runInAction } from "mobx";
|
||||||
import type { ObjectInstance, R3, Runtime } from "../types";
|
import type { ObjectInstance, R3, Runtime } from "../types";
|
||||||
import { useMemo, useRef, type RefObject } from "react";
|
import { useMemo, useRef, useEffect, useCallback, type RefObject } from "react";
|
||||||
import type { Group } from "three";
|
import type { Group } from "three";
|
||||||
import { Euler, Quaternion, Vector3 } from "three";
|
import { Euler, Quaternion, Vector3, BoxHelper, type LineBasicMaterial } from "three";
|
||||||
import { TransformControls, useHelper } from "@react-three/drei";
|
import { TransformControls } from "@react-three/drei";
|
||||||
import { BoxHelper } from "three";
|
import { useThree, useFrame } from "@react-three/fiber";
|
||||||
import { state } from "../state";
|
import { state } from "../state";
|
||||||
import { nextObjectEditMode } from "../state/worldEditorState";
|
import { nextObjectEditMode } from "../state/worldEditorState";
|
||||||
import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal";
|
import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal";
|
||||||
@@ -39,13 +39,44 @@ const SelectionOverlay = observer(function ({ objectId, ref, onTransformChange,
|
|||||||
[ref],
|
[ref],
|
||||||
);
|
);
|
||||||
|
|
||||||
useHelper(isSelected ? groupRef : { current: null }, BoxHelper, 'white');
|
// BoxHelper — manually disposed because BoxHelper has no dispose() of its own
|
||||||
|
const scene = useThree(s => s.scene);
|
||||||
|
const helperRef = useRef<BoxHelper | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const target = groupRef.current;
|
||||||
|
if (!isSelected || !target)
|
||||||
|
return;
|
||||||
|
const helper = new BoxHelper(target, 'white');
|
||||||
|
scene.add(helper);
|
||||||
|
helperRef.current = helper;
|
||||||
|
return () => {
|
||||||
|
scene.remove(helper);
|
||||||
|
helper.geometry.dispose();
|
||||||
|
(helper.material as LineBasicMaterial).dispose();
|
||||||
|
helperRef.current = null;
|
||||||
|
};
|
||||||
|
}, [isSelected, groupRef, scene]);
|
||||||
|
useFrame(() => helperRef.current?.update());
|
||||||
|
|
||||||
|
// TransformControls — drei never calls dispose() on unmount, leaking all gizmo
|
||||||
|
// geometries. Callback ref ignores null so tcRef.current survives React's
|
||||||
|
// ref-clear-on-unmount; effect cleanup then calls dispose() with the live instance.
|
||||||
|
const tcRef = useRef<{ dispose(): void } | null>(null);
|
||||||
|
const setTcRef = useCallback((tc: { dispose(): void } | null) => {
|
||||||
|
if (tc !== null)
|
||||||
|
tcRef.current = tc;
|
||||||
|
}, []);
|
||||||
|
useEffect(() => () => {
|
||||||
|
tcRef.current?.dispose();
|
||||||
|
tcRef.current = null;
|
||||||
|
}, [isSelected]);
|
||||||
|
|
||||||
if (!isSelected || selectionMode === undefined || !groupRef.current)
|
if (!isSelected || selectionMode === undefined || !groupRef.current)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TransformControls
|
<TransformControls
|
||||||
|
ref={setTcRef as never}
|
||||||
object={groupRef as RefObject<Group>}
|
object={groupRef as RefObject<Group>}
|
||||||
mode={selectionMode}
|
mode={selectionMode}
|
||||||
onChange={onTransformChange}
|
onChange={onTransformChange}
|
||||||
|
|||||||
Reference in New Issue
Block a user