transform-controls.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. "use client";
  2. import { useRef, useEffect, useCallback } from "react";
  3. import { useThree } from "@react-three/fiber";
  4. import { TransformControls, OrbitControls } from "@react-three/drei";
  5. import * as THREE from "three";
  6. import { useEditorStore } from "@/lib/store";
  7. export function EditorControls() {
  8. const isPlaying = useEditorStore((s) => s.isPlaying);
  9. if (isPlaying) return null;
  10. return (
  11. <>
  12. <EditorOrbitControls />
  13. <TransformGizmo />
  14. </>
  15. );
  16. }
  17. function EditorOrbitControls() {
  18. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  19. const orbitRef = useRef<any>(null);
  20. useEffect(() => {
  21. const enable = () => {
  22. if (orbitRef.current) orbitRef.current.enabled = false;
  23. };
  24. const disable = () => {
  25. if (orbitRef.current) orbitRef.current.enabled = true;
  26. };
  27. window.addEventListener("transform-start", enable);
  28. window.addEventListener("transform-end", disable);
  29. return () => {
  30. window.removeEventListener("transform-start", enable);
  31. window.removeEventListener("transform-end", disable);
  32. };
  33. }, []);
  34. return (
  35. <OrbitControls
  36. ref={orbitRef}
  37. makeDefault
  38. enableDamping
  39. dampingFactor={0.1}
  40. minDistance={1}
  41. maxDistance={200}
  42. />
  43. );
  44. }
  45. function TransformGizmo() {
  46. const {
  47. selectedObjectId,
  48. transformMode,
  49. isPlaying,
  50. updateObjectTransform,
  51. saveToHistory,
  52. } = useEditorStore();
  53. const { scene } = useThree();
  54. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  55. const controlsRef = useRef<any>(null);
  56. const findObjectById = useCallback(
  57. (id: string): THREE.Object3D | null => {
  58. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  59. let found: any = null;
  60. scene.traverse((child) => {
  61. if (child.userData?.id === id || child.userData?.objectId === id) {
  62. found = child;
  63. }
  64. });
  65. return found;
  66. },
  67. [scene],
  68. );
  69. useEffect(() => {
  70. if (
  71. !controlsRef.current ||
  72. !selectedObjectId ||
  73. isPlaying ||
  74. transformMode === "select"
  75. ) {
  76. if (controlsRef.current) {
  77. try {
  78. controlsRef.current.detach();
  79. } catch {
  80. /* may not be attached */
  81. }
  82. }
  83. return;
  84. }
  85. const timer = setTimeout(() => {
  86. const obj = findObjectById(selectedObjectId);
  87. if (obj && controlsRef.current) {
  88. try {
  89. controlsRef.current.attach(obj);
  90. } catch {
  91. /* object may not be ready */
  92. }
  93. }
  94. }, 50);
  95. return () => clearTimeout(timer);
  96. }, [selectedObjectId, transformMode, isPlaying, findObjectById]);
  97. useEffect(() => {
  98. const controls = controlsRef.current;
  99. if (!controls) return;
  100. const onDragStart = () =>
  101. window.dispatchEvent(new CustomEvent("transform-start"));
  102. const onDragEnd = () =>
  103. window.dispatchEvent(new CustomEvent("transform-end"));
  104. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  105. controls.addEventListener("dragging-changed", (e: any) => {
  106. if (e.value) onDragStart();
  107. else onDragEnd();
  108. });
  109. return () => {
  110. controls.removeEventListener("dragging-changed", onDragStart);
  111. };
  112. }, []);
  113. const handleChange = useCallback(() => {
  114. if (!controlsRef.current || !selectedObjectId) return;
  115. const obj = controlsRef.current.object;
  116. if (!obj) return;
  117. updateObjectTransform(selectedObjectId, {
  118. position: [obj.position.x, obj.position.y, obj.position.z],
  119. rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
  120. scale: [obj.scale.x, obj.scale.y, obj.scale.z],
  121. });
  122. }, [selectedObjectId, updateObjectTransform]);
  123. const handleMouseUp = useCallback(() => {
  124. saveToHistory();
  125. }, [saveToHistory]);
  126. if (!selectedObjectId || isPlaying || transformMode === "select") return null;
  127. const mode =
  128. transformMode === "translate"
  129. ? "translate"
  130. : transformMode === "rotate"
  131. ? "rotate"
  132. : "scale";
  133. return (
  134. <TransformControls
  135. ref={controlsRef}
  136. mode={mode}
  137. onObjectChange={handleChange}
  138. onMouseUp={handleMouseUp}
  139. />
  140. );
  141. }