dispose of transform controls on deselect

This commit is contained in:
azykov@mail.ru
2026-06-17 11:21:21 +03:00
parent 31c77b0a72
commit 9d0f97e9ea
+36 -5
View File
@@ -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}