use-controls.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import { computed, ref, watch, watchEffect } from "vue";
  2. import {
  3. installThreeGlobalVar,
  4. useCamera,
  5. useContainer,
  6. useRender,
  7. useStageProps,
  8. } from "./use-stage";
  9. import { OrbitControls } from "three/examples/jsm/Addons.js";
  10. import { listener } from "@/utils/event";
  11. import { Intersection, Matrix3, Vector3 } from "three";
  12. import { mergeFuns } from "@/utils/shared";
  13. import { useMouseEventRegister } from "./use-event";
  14. import { useCameraAnimation } from "./use-animation";
  15. import { getMoveDirectrionByKeys, useFigureMoveCollision } from "./use-move";
  16. import { useGetIntersectObject } from "./use-getter";
  17. import { subgroupName } from "../container";
  18. const useModelControls = () => {
  19. const container = useContainer();
  20. const camera = useCamera();
  21. const _render = useRender();
  22. const render = () => {
  23. camera.bus.emit("change");
  24. _render();
  25. };
  26. const controls = new OrbitControls(camera);
  27. controls.target.set(0, 5, 0);
  28. controls.enabled = false;
  29. const unListener = listener(controls as any, "change", render);
  30. let prevOrigin: Vector3 | null = null;
  31. let prevDire: Vector3 | null = null;
  32. watch(
  33. container,
  34. (container, _, onCleanup) => {
  35. if (container) {
  36. controls.domElement = container;
  37. controls.connect();
  38. onCleanup(() => {
  39. controls.disconnect();
  40. });
  41. }
  42. },
  43. { immediate: true }
  44. );
  45. return {
  46. controls,
  47. onDestory() {
  48. controls.domElement && controls.dispose();
  49. unListener();
  50. },
  51. syncCamera() {
  52. controls.update();
  53. },
  54. disable() {
  55. prevOrigin = camera.position.clone();
  56. prevDire = camera.getWorldDirection(new Vector3());
  57. controls.enabled = false;
  58. },
  59. enable() {
  60. controls.enabled = true;
  61. },
  62. get current() {
  63. return {
  64. prevOrigin,
  65. prevDire,
  66. };
  67. },
  68. };
  69. };
  70. const roamingEysHeight = 120;
  71. const useRoamingControls = () => {
  72. const container = useContainer();
  73. const camera = useCamera();
  74. const _render = useRender();
  75. const render = () => {
  76. camera.bus.emit("change");
  77. _render();
  78. };
  79. const enabled = ref(false);
  80. const syncCamera = (direction = camera.getWorldDirection(new Vector3())) => {
  81. controls.target.copy(direction.add(camera.position));
  82. controls.update();
  83. };
  84. const { direction, onDestory: onDownDestory } = getMoveDirectrionByKeys();
  85. const move = useFigureMoveCollision(camera, direction, syncCamera);
  86. move.pause();
  87. const controls = new OrbitControls(camera);
  88. controls.rotateSpeed = -0.3;
  89. controls.enableZoom = false;
  90. let prevOrigin: Vector3 | null = null;
  91. let prevDire: Vector3 | null = null;
  92. return {
  93. controls,
  94. onDestory: mergeFuns(
  95. watchEffect(() => (controls.enabled = enabled.value)),
  96. watch(
  97. container,
  98. (container, _, onCleanup) => {
  99. if (container) {
  100. controls.domElement = container;
  101. controls.connect();
  102. onCleanup(() => {
  103. controls.disconnect();
  104. });
  105. }
  106. },
  107. { immediate: true }
  108. ),
  109. move.destory,
  110. () => {
  111. controls.domElement && controls.dispose();
  112. },
  113. listener(controls as any, "change", render),
  114. onDownDestory
  115. ),
  116. syncCamera,
  117. disable() {
  118. prevOrigin = camera.position.clone();
  119. prevDire = camera.getWorldDirection(new Vector3());
  120. controls.enabled = false;
  121. move.pause();
  122. },
  123. enable() {
  124. controls.enabled = true;
  125. move.continue();
  126. render();
  127. },
  128. get current() {
  129. return {
  130. prevOrigin,
  131. prevDire,
  132. };
  133. },
  134. };
  135. };
  136. const controlsFactory = {
  137. model: useModelControls,
  138. roaming: useRoamingControls,
  139. };
  140. export type ControlsType = keyof typeof controlsFactory;
  141. export type Controls = ReturnType<(typeof controlsFactory)[ControlsType]>;
  142. export const useControls = installThreeGlobalVar(() => {
  143. const container = useContainer();
  144. const type = ref<ControlsType>();
  145. const controls = ref<Controls>();
  146. const controlsMap = {} as Record<ControlsType, Controls>;
  147. for (const [type, factory] of Object.entries(controlsFactory)) {
  148. controlsMap[type as ControlsType] = factory();
  149. }
  150. const stopWatch = watch(
  151. [container, type],
  152. ([container, type], _, onCleanup) => {
  153. if (!(type && container)) return;
  154. const ct = controlsMap[type];
  155. ct.enable();
  156. controls.value = ct;
  157. onCleanup(() => {
  158. ct.disable();
  159. controls.value = undefined;
  160. });
  161. },
  162. { immediate: true, flush: "sync" }
  163. );
  164. return {
  165. var: { type, value: controls },
  166. onDestroy: () => {
  167. stopWatch();
  168. for (const controls of Object.values(controlsMap)) {
  169. controls.onDestory();
  170. }
  171. },
  172. };
  173. });
  174. export const useFlyRoaming = installThreeGlobalVar(() => {
  175. const camera = useCamera();
  176. const { type, value: controls } = useControls();
  177. const cameraAnimation = useCameraAnimation();
  178. const getIntersectObject = useGetIntersectObject();
  179. const normalMatrix = new Matrix3();
  180. const bottom = new Vector3(0, -1, 0);
  181. return async (point: Intersection | Vector3) => {
  182. let intersect: Intersection;
  183. if ("point" in point) {
  184. intersect = point;
  185. } else {
  186. point = point.clone();
  187. const objects = getIntersectObject(point, bottom);
  188. if (!objects.length) {
  189. throw "当前位置无法漫游";
  190. }
  191. intersect = objects[0];
  192. }
  193. const normal = intersect.face?.normal.clone();
  194. if (!normal) {
  195. throw "当前位置无法漫游";
  196. }
  197. normalMatrix.getNormalMatrix(intersect.object.matrixWorld);
  198. normal.applyMatrix3(normalMatrix).normalize();
  199. if (normal.y < 0.8) {
  200. throw "当前位置无法漫游";
  201. }
  202. if (type.value !== 'roaming') {
  203. type.value = undefined
  204. } else {
  205. controls.value?.disable()
  206. }
  207. const position = intersect.point
  208. .clone()
  209. .add({ x: 0, y: roamingEysHeight, z: 0 });
  210. const direction = camera.getWorldDirection(new Vector3());
  211. const target = position.clone().add(direction);
  212. await cameraAnimation(position, target);
  213. type.value = "roaming";
  214. controls.value?.enable()
  215. controls.value?.syncCamera();
  216. };
  217. });
  218. export const useFlyModel = installThreeGlobalVar(() => {
  219. const camera = useCamera();
  220. const { type, value: controls } = useControls();
  221. const cameraAnimation = useCameraAnimation();
  222. return async (set: { point?: Vector3; direction?: Vector3 } = {}) => {
  223. type.value = "model";
  224. const prev = controls.value?.current;
  225. if (!set.point) {
  226. set.point =
  227. prev?.prevOrigin ||
  228. camera.position.clone() ||
  229. new Vector3(400, 400, 400);
  230. }
  231. if (!set.direction) {
  232. set.direction =
  233. prev?.prevDire ||
  234. camera.getWorldDirection(new Vector3()) ||
  235. camera.getWorldDirection(new Vector3());
  236. }
  237. const direction = set.direction;
  238. const target = set.point.clone().add(direction);
  239. await cameraAnimation(set.point, target);
  240. controls.value?.syncCamera();
  241. };
  242. });
  243. export const installAutoSwitchControls = installThreeGlobalVar(() => {
  244. const { type } = useControls();
  245. const mouseRegister = useMouseEventRegister();
  246. const flyRoaming = useFlyRoaming();
  247. const flyModel = useFlyModel();
  248. const sProps = useStageProps();
  249. const canFlyTypes = ["line", "icon"] as const;
  250. const canFlyNames = computed(() => {
  251. const names = canFlyTypes.flatMap((type) =>
  252. sProps.value.draw.store.getTypeItems(type).map((item) => item.id)
  253. )
  254. names.push(subgroupName)
  255. return names
  256. });
  257. const onDestroy = mergeFuns(
  258. listener(document.documentElement, "keydown", (ev) => {
  259. if (ev.key === "Escape" && type.value !== "model") {
  260. flyModel();
  261. }
  262. }),
  263. watchEffect((onCleanup) => {
  264. const cleanups = canFlyNames.value.map((name) =>
  265. mouseRegister(name, "dblclick", (object) => {
  266. flyRoaming(object);
  267. })
  268. );
  269. onCleanup(mergeFuns(cleanups))
  270. })
  271. );
  272. type.value = "model";
  273. return { onDestroy };
  274. });