mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 01:38:44 +00:00
PageScene: useFreeCam hook + delete free_cam.ts
The 446-line FreeCam class is gone. Its responsibilities split: - Math (move/rotate vectors, pan/zoom from inputs, lookAt) lives in cameraMath.ts as pure functions, plus a freeCamFrameTick() that integrates the per-frame state. - Listener attachment + drag/key state ownership moves into hooks/useFreeCam.ts. Listeners are scoped to the canvas (no window/document handlers); held-key state is reset on CAMERA_VIEW exit so a stale W keypress can't drift the camera. - The shared FreeCamControlState struct flows through Editor via editor.setFreeCamState(state); the render loop reads it and calls freeCamFrameTick() each frame. Side effects: keybinds_controls.ts now imports isPointerLockSupported from browserChecks; helper.ts and PageEditor's camera-select handler stop poking .cameraViewControls and reset velocity via freeCamState instead. PreviewEngineCamera's camera-view toggle now just flips the store state — useFreeCam picks it up on its own (no more enableFreeCamControls/disableFreeCamControls call site).
This commit is contained in:
1 parent
718b8261fd
commit
efa9a54bee
9 files changed
+309
-496
No files matched your search
@@ -5,7 +5,6 @@ import { Controls3D } from "./comps/Controls3D";
|
||||
import { ControlsTopButtons } from "./comps/ControlsTopButtons";
|
||||
import { ControlPanelSceneObject } from "./comps/ControlPanelSceneObject";
|
||||
import { PreviewEngineCamera } from "./comps/PreviewEngineCamera";
|
||||
import { PreviewFrameImage } from "./comps/PreviewFrameImage";
|
||||
import { authentication, pageHeight, pageWidth } from "~/signals";
|
||||
import { PoseModeSelector } from "./comps/PoseModeSelector";
|
||||
import ImageToVideo from "../PageVideo/ImageToVideo";
|
||||
@@ -31,6 +30,8 @@ import { DomLevels } from "~/pages/PageScene/PageSceneStore";
|
||||
import { setCameraAspect } from "~/pages/PageScene/actions";
|
||||
import { EditorCanvas } from "./comps/EngineCanvases";
|
||||
import { SceneContainer } from "./comps/SceneContainer";
|
||||
import { useEditorCanvas } from "./hooks/useEditorCanvas";
|
||||
import { useFreeCam } from "./hooks/useFreeCam";
|
||||
import { Outliner } from "./comps/Outliner";
|
||||
import { CameraAspectRatio } from "./enums";
|
||||
import { PromptBox3D } from "@storyteller/ui-promptbox";
|
||||
@@ -156,6 +157,8 @@ export const PageEditor = () => {
|
||||
|
||||
// These are callbacks required by promptbox
|
||||
const editorEngine = useContext(EngineContext);
|
||||
const editorCanvas = useEditorCanvas();
|
||||
useFreeCam(editorCanvas, editorEngine);
|
||||
const handleCameraSelect = (selectedItem: PopoverItem) => {
|
||||
const selectedCamera = cameras.value.find(
|
||||
(cam) => cam.label === selectedItem.label,
|
||||
@@ -196,10 +199,10 @@ export const PageEditor = () => {
|
||||
);
|
||||
editorEngine.camera.updateProjectionMatrix();
|
||||
|
||||
// Reset and update camera controls
|
||||
if (editorEngine.cameraViewControls) {
|
||||
editorEngine.cameraViewControls.reset();
|
||||
editorEngine.cameraViewControls.update(0);
|
||||
// Reset free-cam motion so a switch doesn't carry over a
|
||||
// half-applied drag from the previous camera.
|
||||
if (editorEngine.freeCamState) {
|
||||
editorEngine.freeCamState.velocity.set(0, 0, 0);
|
||||
}
|
||||
|
||||
// Force a render to update the view
|
||||
@@ -259,10 +262,10 @@ export const PageEditor = () => {
|
||||
editorEngine.camera.fov = editorEngine.focalLengthToFov(24);
|
||||
editorEngine.camera.updateProjectionMatrix();
|
||||
|
||||
// Reset and update camera controls
|
||||
if (editorEngine.cameraViewControls) {
|
||||
editorEngine.cameraViewControls.reset();
|
||||
editorEngine.cameraViewControls.update(0);
|
||||
// Reset free-cam motion so the camera doesn't drift after
|
||||
// teleport.
|
||||
if (editorEngine.freeCamState) {
|
||||
editorEngine.freeCamState.velocity.set(0, 0, 0);
|
||||
}
|
||||
|
||||
// Force a render to update the view
|
||||
@@ -464,7 +467,6 @@ export const PageEditor = () => {
|
||||
<div className="relative w-full overflow-hidden bg-transparent">
|
||||
<SceneContainer>
|
||||
<EditorCanvas />
|
||||
<PreviewFrameImage />
|
||||
</SceneContainer>
|
||||
|
||||
{/* Focal Length Display */}
|
||||
|
||||
+5
-8
@@ -17,7 +17,6 @@ import { CameraViewCanvas } from "~/pages/PageScene/comps/EngineCanvases";
|
||||
import { ButtonIcon } from "@storyteller/ui-button-icon";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { getActiveEditor } from "~/pages/PageScene/contexts/EngineContext/EngineContext";
|
||||
|
||||
export const PreviewEngineCamera = () => {
|
||||
useSignals();
|
||||
@@ -33,15 +32,13 @@ export const PreviewEngineCamera = () => {
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
const { editorState, setEditorState } = usePageSceneStore.getState();
|
||||
const next =
|
||||
setEditorState(
|
||||
editorState === EditorStates.CAMERA_VIEW
|
||||
? EditorStates.EDIT
|
||||
: EditorStates.CAMERA_VIEW;
|
||||
setEditorState(next);
|
||||
const editor = getActiveEditor();
|
||||
if (!editor) return;
|
||||
if (next === EditorStates.CAMERA_VIEW) editor.enableFreeCamControls();
|
||||
else editor.disableFreeCamControls();
|
||||
: EditorStates.CAMERA_VIEW,
|
||||
);
|
||||
// useFreeCam picks up the editorState change from the store and
|
||||
// (de)activates its listeners + clears held keys automatically.
|
||||
};
|
||||
|
||||
const getLargeScreenHeightClass = () => {
|
||||
|
||||
@@ -107,3 +107,86 @@ export const lerpVelocity = (
|
||||
velocity.z = MathUtils.lerp(velocity.z, target.z, smoothing);
|
||||
return velocity;
|
||||
};
|
||||
|
||||
// Per-frame state owned by useFreeCam. The editor's render loop
|
||||
// reads this struct on every tick and applies the resulting motion.
|
||||
// Mutated in place by event handlers (keyboard) so the React
|
||||
// reference stays stable.
|
||||
export interface FreeCamControlState {
|
||||
enabled: boolean;
|
||||
moveKeys: HeldMoveKeys;
|
||||
rotateKeys: HeldRotateKeys;
|
||||
velocity: Vector3;
|
||||
movementSpeed: number;
|
||||
rollSpeed: number;
|
||||
smoothing: number;
|
||||
}
|
||||
|
||||
export const createFreeCamControlState = (): FreeCamControlState => ({
|
||||
enabled: false,
|
||||
moveKeys: emptyMoveKeys(),
|
||||
rotateKeys: emptyRotateKeys(),
|
||||
velocity: new Vector3(),
|
||||
movementSpeed: 1.15,
|
||||
rollSpeed: Math.PI / 180,
|
||||
smoothing: 0.2,
|
||||
});
|
||||
|
||||
// Translate a code from a KeyboardEvent into the move-state slot it
|
||||
// drives, if any. Returns the property name on HeldMoveKeys, or null.
|
||||
export const moveSlotForKeyCode = (
|
||||
code: string,
|
||||
): keyof HeldMoveKeys | null => {
|
||||
switch (code) {
|
||||
case "KeyW": return "forward";
|
||||
case "KeyS": return "back";
|
||||
case "KeyA": return "left";
|
||||
case "KeyD": return "right";
|
||||
case "KeyQ": return "down";
|
||||
case "KeyE": return "up";
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const rotateSlotForKeyCode = (
|
||||
code: string,
|
||||
): keyof HeldRotateKeys | null => {
|
||||
switch (code) {
|
||||
case "ArrowUp": return "pitchUp";
|
||||
case "ArrowDown": return "pitchDown";
|
||||
case "ArrowLeft": return "yawLeft";
|
||||
case "ArrowRight": return "yawRight";
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Per-frame integration step. Reads `state` (held keys + velocity),
|
||||
// applies the resulting movement / rotation to `camera`, and returns
|
||||
// `true` if anything changed. The caller is responsible for any
|
||||
// downstream sync (e.g. lookAt updates) when this returns true.
|
||||
export const freeCamFrameTick = (
|
||||
camera: PerspectiveCamera,
|
||||
state: FreeCamControlState,
|
||||
delta: number,
|
||||
): boolean => {
|
||||
if (!state.enabled) return false;
|
||||
|
||||
const moveVec = moveVectorFromKeys(state.moveKeys);
|
||||
const rotVec = rotationVectorFromKeys(state.rotateKeys, state.rollSpeed);
|
||||
|
||||
const stationary = moveVec.lengthSq() === 0;
|
||||
const noRotation = rotVec.lengthSq() === 0;
|
||||
const velocityIdle = state.velocity.lengthSq() < 0.0001;
|
||||
if (stationary && noRotation && velocityIdle) return false;
|
||||
|
||||
const target = moveVec.multiplyScalar(delta * state.movementSpeed);
|
||||
lerpVelocity(state.velocity, target, state.smoothing);
|
||||
|
||||
camera.translateX(state.velocity.x);
|
||||
camera.translateY(state.velocity.y);
|
||||
camera.translateZ(state.velocity.z);
|
||||
camera.rotateX(rotVec.x);
|
||||
camera.rotateY(rotVec.y);
|
||||
camera.rotateZ(rotVec.z);
|
||||
return true;
|
||||
};
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as THREE from "three";
|
||||
import { signal } from "@preact/signals-react";
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { FreeCam } from "./free_cam";
|
||||
import {
|
||||
freeCamFrameTick,
|
||||
lookAtFromCamera,
|
||||
type FreeCamControlState,
|
||||
} from "./cameraMath";
|
||||
import { TransformControls } from "./TransformControls.js";
|
||||
import Scene from "./scene.js";
|
||||
import { APIManager, ArtStyle } from "./api_manager.js";
|
||||
@@ -100,7 +104,10 @@ class Editor {
|
||||
transform_interaction = false;
|
||||
rendering: boolean;
|
||||
api_manager: APIManager;
|
||||
cameraViewControls: FreeCam | undefined;
|
||||
freeCamState: FreeCamControlState | null = null;
|
||||
setFreeCamState(state: FreeCamControlState | null) {
|
||||
this.freeCamState = state;
|
||||
}
|
||||
orbitControls: OrbitControls | undefined;
|
||||
locked: boolean;
|
||||
|
||||
@@ -509,16 +516,8 @@ class Editor {
|
||||
this.camera,
|
||||
this.renderer.domElement,
|
||||
);
|
||||
this.cameraViewControls = new FreeCam(
|
||||
this.camera,
|
||||
this.renderer.domElement,
|
||||
);
|
||||
this.cameraViewControls.movementSpeed = 1.15;
|
||||
this.cameraViewControls.domElement = this.renderer.domElement;
|
||||
this.cameraViewControls.rollSpeed = Math.PI / 180;
|
||||
this.cameraViewControls.autoForward = false;
|
||||
this.cameraViewControls.dragToLook = true;
|
||||
this.cameraViewControls.enabled = true;
|
||||
// FreeCam math + listeners now live in hooks/useFreeCam.ts; the
|
||||
// editor reads `freeCamState` (set by that hook) on every render.
|
||||
|
||||
this.control = new TransformControls(this.camera, this.renderer.domElement);
|
||||
this.control.space = "world"; // Default to world space for translate mode
|
||||
@@ -561,7 +560,7 @@ class Editor {
|
||||
this.mouse_controls = new MouseControls(
|
||||
this.camera,
|
||||
this.get_camera_person_mode.bind(this),
|
||||
this.cameraViewControls,
|
||||
this.freeCamState,
|
||||
this.lockControls,
|
||||
this.camera_last_pos,
|
||||
this.selectedCanvas,
|
||||
@@ -656,22 +655,16 @@ class Editor {
|
||||
document.body.appendChild(this.stats.dom);
|
||||
}
|
||||
|
||||
// Track whether the cursor is currently over the viewport canvas.
|
||||
// useFreeCam owns the actual input listeners, but a few sites
|
||||
// (e.g. keybinds_controls.ts) still consult `selectedCanvas`.
|
||||
private mouseOverEventHandler(event: MouseEvent) {
|
||||
if (this.cameraViewControls) {
|
||||
if (
|
||||
event.target instanceof HTMLCanvasElement ||
|
||||
(event.target as HTMLElement).id == "letterbox"
|
||||
) {
|
||||
this.cameraViewControls.enabled = true;
|
||||
this.selectedCanvas = true;
|
||||
this.focused = true;
|
||||
} else {
|
||||
this.cameraViewControls.reset();
|
||||
this.focused = false;
|
||||
this.cameraViewControls.enabled = false;
|
||||
this.selectedCanvas = false;
|
||||
}
|
||||
}
|
||||
const target = event.target;
|
||||
const onCanvas =
|
||||
target instanceof HTMLCanvasElement ||
|
||||
(target instanceof HTMLElement && target.id === "letterbox");
|
||||
this.selectedCanvas = onCanvas;
|
||||
this.focused = onCanvas;
|
||||
}
|
||||
|
||||
public enableFreeCamControls() {
|
||||
@@ -1172,12 +1165,32 @@ class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
this.cameraViewControls?.update(5 * delta_time);
|
||||
if (this.freeCamState && this.camera) {
|
||||
const moved = freeCamFrameTick(this.camera, this.freeCamState, 5 * delta_time);
|
||||
// Mirror the active camera's transform back into the cameras
|
||||
// signal so PromptBox3D and the camera-list UI stay in sync.
|
||||
// (Bridge signal: retire when PromptBox3D takes store callbacks.)
|
||||
if (moved && selectedCameraId.value) {
|
||||
const lookAt = lookAtFromCamera(this.camera);
|
||||
const pos = this.camera.position;
|
||||
const rot = this.camera.rotation;
|
||||
cameras.value = cameras.value.map((cam) =>
|
||||
cam.id === selectedCameraId.value
|
||||
? {
|
||||
...cam,
|
||||
position: { x: pos.x, y: pos.y, z: pos.z },
|
||||
rotation: { x: rot.x, y: rot.y, z: rot.z },
|
||||
lookAt: { x: lookAt.x, y: lookAt.y, z: lookAt.z },
|
||||
}
|
||||
: cam,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.activeScene.shader_objects.forEach((shader) => {
|
||||
shader.material.uniforms["time"].value += 0.5 * delta_time;
|
||||
});
|
||||
|
||||
if (this.cameraViewControls && this.camera_person_mode) {
|
||||
if (this.camera_person_mode) {
|
||||
if (this.cam_obj && this.camera) {
|
||||
// Without a timeline scrubber, edits in camera-person mode write
|
||||
// back into cam_obj rather than copying out of it.
|
||||
@@ -1268,7 +1281,6 @@ class Editor {
|
||||
this.startRenderLoop();
|
||||
this.sceneManager?.attachEventListeners();
|
||||
this.enableFreeCamControls();
|
||||
this.cameraViewControls?.attachEventListeners();
|
||||
console.log("3D Editor Engine remounted");
|
||||
}
|
||||
|
||||
@@ -1277,7 +1289,6 @@ class Editor {
|
||||
this.stopRenderLoop();
|
||||
this.sceneManager?.detachEventListeners();
|
||||
this.disableFreeCamControls();
|
||||
this.cameraViewControls?.detachEventListeners();
|
||||
|
||||
// Fix: dispose 3D contexts
|
||||
this.renderer?.dispose();
|
||||
|
||||
@@ -1,432 +0,0 @@
|
||||
import {
|
||||
EventDispatcher,
|
||||
Quaternion,
|
||||
Vector3,
|
||||
Vector2,
|
||||
MathUtils,
|
||||
} from "three";
|
||||
import * as THREE from "three";
|
||||
import { cameras, selectedCameraId } from "~/pages/PageScene/signals/camera";
|
||||
import { isPromptBoxFocused } from "~/pages/PageScene/signals/promptBox";
|
||||
|
||||
// Re-exported here for back-compat with keybinds_controls.ts; the
|
||||
// definitions live in browserChecks.ts and will be imported directly
|
||||
// once Phase 6 deletes this file.
|
||||
export {
|
||||
isSafariOrWebKit,
|
||||
hasApplePay,
|
||||
isSafari,
|
||||
isPointerLockSupported,
|
||||
} from "./browserChecks";
|
||||
|
||||
class FreeCam extends EventDispatcher {
|
||||
object: THREE.PerspectiveCamera;
|
||||
domElement: HTMLElement | Document;
|
||||
enabled = true;
|
||||
movementSpeed = 1.0;
|
||||
rollSpeed = 0.005;
|
||||
dragToLook = false;
|
||||
autoForward = false;
|
||||
dragging = false;
|
||||
xOffset = 0;
|
||||
yOffset = 0;
|
||||
|
||||
// Smoothing properties
|
||||
smoothingFactor = 0.2;
|
||||
currentVelocity = new Vector3();
|
||||
lastPosition = new Vector3();
|
||||
|
||||
lastMousePosition = new Vector2();
|
||||
mouseVelocity = new Vector2();
|
||||
tmpQuaternion = new Quaternion();
|
||||
|
||||
status = 0;
|
||||
|
||||
moveState = {
|
||||
up: 0,
|
||||
down: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
forward: 0,
|
||||
back: 0,
|
||||
pitchUp: 0,
|
||||
pitchDown: 0,
|
||||
yawLeft: 0,
|
||||
yawRight: 0,
|
||||
rollLeft: 0,
|
||||
rollRight: 0,
|
||||
};
|
||||
moveVector = new Vector3(0, 0, 0);
|
||||
rotationVector = new Vector3(0, 0, 0);
|
||||
|
||||
constructor(
|
||||
object: THREE.PerspectiveCamera,
|
||||
domElement: HTMLElement | Document,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.object = object;
|
||||
this.domElement = domElement;
|
||||
this.lastPosition.copy(object.position);
|
||||
|
||||
this.keydown = this.keydown.bind(this);
|
||||
this.keyup = this.keyup.bind(this);
|
||||
this.mousedown = this.mousedown.bind(this);
|
||||
this.mouseup = this.mouseup.bind(this);
|
||||
this.mousemove = this.mousemove.bind(this);
|
||||
this.mousewheel = this.mousewheel.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
this.updateMovementVector = this.updateMovementVector.bind(this);
|
||||
this.updateRotationVector = this.updateRotationVector.bind(this);
|
||||
this.getContainerDimensions = this.getContainerDimensions.bind(this);
|
||||
this.dispose = this.dispose.bind(this);
|
||||
|
||||
this.domElement.addEventListener("contextmenu", function (event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
this.updateMovementVector();
|
||||
this.updateRotationVector();
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
window.addEventListener("keydown", this.keydown);
|
||||
window.addEventListener("keyup", this.keyup);
|
||||
window.addEventListener("mousedown", this.mousedown);
|
||||
window.addEventListener("mouseup", this.mouseup);
|
||||
window.addEventListener("mousemove", this.mousemove);
|
||||
window.addEventListener("wheel", this.mousewheel);
|
||||
}
|
||||
|
||||
detachEventListeners() {
|
||||
window.removeEventListener("keydown", this.keydown);
|
||||
window.removeEventListener("keyup", this.keyup);
|
||||
window.removeEventListener("mousedown", this.mousedown);
|
||||
window.removeEventListener("mouseup", this.mouseup);
|
||||
window.removeEventListener("mousemove", this.mousemove);
|
||||
window.removeEventListener("wheel", this.mousewheel);
|
||||
}
|
||||
|
||||
keydown(event: KeyboardEvent) {
|
||||
if (!this.enabled || isPromptBoxFocused.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.code) {
|
||||
case "KeyW":
|
||||
this.moveState.forward = 1;
|
||||
break;
|
||||
case "KeyS":
|
||||
this.moveState.back = 1;
|
||||
break;
|
||||
|
||||
case "KeyA":
|
||||
this.moveState.left = 1;
|
||||
break;
|
||||
case "KeyD":
|
||||
this.moveState.right = 1;
|
||||
break;
|
||||
|
||||
case "KeyQ":
|
||||
this.moveState.down = 1;
|
||||
break;
|
||||
case "KeyE":
|
||||
this.moveState.up = 1;
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
this.moveState.pitchUp = 1;
|
||||
break;
|
||||
case "ArrowDown":
|
||||
this.moveState.pitchDown = 1;
|
||||
break;
|
||||
|
||||
case "ArrowLeft":
|
||||
this.moveState.yawLeft = 1;
|
||||
break;
|
||||
case "ArrowRight":
|
||||
this.moveState.yawRight = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
this.updateMovementVector();
|
||||
this.updateRotationVector();
|
||||
}
|
||||
|
||||
keyup(event: KeyboardEvent) {
|
||||
if (!this.enabled || isPromptBoxFocused.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.code) {
|
||||
case "KeyW":
|
||||
this.moveState.forward = 0;
|
||||
break;
|
||||
case "KeyS":
|
||||
this.moveState.back = 0;
|
||||
break;
|
||||
|
||||
case "KeyA":
|
||||
this.moveState.left = 0;
|
||||
break;
|
||||
case "KeyD":
|
||||
this.moveState.right = 0;
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
this.moveState.pitchUp = 0;
|
||||
break;
|
||||
case "ArrowDown":
|
||||
this.moveState.pitchDown = 0;
|
||||
break;
|
||||
|
||||
case "ArrowLeft":
|
||||
this.moveState.yawLeft = 0;
|
||||
break;
|
||||
case "ArrowRight":
|
||||
this.moveState.yawRight = 0;
|
||||
break;
|
||||
|
||||
case "KeyQ":
|
||||
this.moveState.down = 0;
|
||||
break;
|
||||
case "KeyE":
|
||||
this.moveState.up = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
this.updateMovementVector();
|
||||
this.updateRotationVector();
|
||||
}
|
||||
|
||||
mousedown(event: MouseEvent) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
if (event.button === 2) {
|
||||
this.dragging = true;
|
||||
this.xOffset = event.clientX;
|
||||
this.yOffset = event.clientY;
|
||||
this.resetVelocity();
|
||||
}
|
||||
}
|
||||
|
||||
mouseup(event: MouseEvent) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
if (event.button === 2) {
|
||||
this.xOffset = event.clientX;
|
||||
this.yOffset = event.clientY;
|
||||
this.resetVelocity();
|
||||
}
|
||||
this.dragging = false;
|
||||
}
|
||||
|
||||
mousemove(event: MouseEvent) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
if (this.dragging) {
|
||||
const mouseX = event.clientX - this.xOffset;
|
||||
const mouseY = event.clientY - this.yOffset;
|
||||
this.xOffset = event.clientX;
|
||||
this.yOffset = event.clientY;
|
||||
|
||||
if (Math.abs(mouseX + mouseY) > 0) {
|
||||
// Store last position before movement
|
||||
this.lastPosition.copy(this.object.position);
|
||||
|
||||
// Apply movement with smoothing
|
||||
const translationX = -mouseX * (this.movementSpeed * 0.01);
|
||||
const translationY = mouseY * (this.movementSpeed * 0.01);
|
||||
|
||||
this.currentVelocity.x = MathUtils.lerp(
|
||||
this.currentVelocity.x,
|
||||
translationX,
|
||||
this.smoothingFactor,
|
||||
);
|
||||
this.currentVelocity.y = MathUtils.lerp(
|
||||
this.currentVelocity.y,
|
||||
translationY,
|
||||
this.smoothingFactor,
|
||||
);
|
||||
|
||||
this.object.translateX(this.currentVelocity.x);
|
||||
this.object.translateY(this.currentVelocity.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mousewheel(event: WheelEvent) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store last position before zoom
|
||||
this.lastPosition.copy(this.object.position);
|
||||
|
||||
const zoomDelta = event.deltaY / 120;
|
||||
this.currentVelocity.z = MathUtils.lerp(
|
||||
this.currentVelocity.z,
|
||||
zoomDelta,
|
||||
this.smoothingFactor,
|
||||
);
|
||||
this.object.translateZ(this.currentVelocity.z);
|
||||
}
|
||||
|
||||
resetVelocity() {
|
||||
this.currentVelocity.set(0, 0, 0);
|
||||
this.lastPosition.copy(this.object.position);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.moveState.forward = 0;
|
||||
this.moveState.back = 0;
|
||||
this.moveState.left = 0;
|
||||
this.moveState.right = 0;
|
||||
this.moveState.pitchUp = 0;
|
||||
this.moveState.pitchDown = 0;
|
||||
this.moveState.yawLeft = 0;
|
||||
this.moveState.yawRight = 0;
|
||||
this.moveState.down = 0;
|
||||
this.moveState.up = 0;
|
||||
|
||||
this.resetVelocity();
|
||||
|
||||
this.updateMovementVector();
|
||||
this.updateRotationVector();
|
||||
}
|
||||
|
||||
isStationary() {
|
||||
return (
|
||||
this.moveVector.distanceTo(new Vector3(0, 0, 0)) === 0 &&
|
||||
this.currentVelocity.distanceTo(new Vector3(0, 0, 0)) < 0.0001
|
||||
);
|
||||
}
|
||||
|
||||
isRotating() {
|
||||
return (
|
||||
this.moveState.pitchUp ||
|
||||
this.moveState.pitchDown ||
|
||||
this.moveState.yawLeft ||
|
||||
this.moveState.yawRight
|
||||
);
|
||||
}
|
||||
|
||||
update(delta: number) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip update if there's no movement, velocity, or rotation
|
||||
if (this.isStationary() && !this.isRotating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const moveMulti = delta * this.movementSpeed;
|
||||
|
||||
// Apply WASD movement with smoothing
|
||||
const targetMovement = new Vector3(
|
||||
this.moveVector.x * moveMulti,
|
||||
this.moveVector.y * moveMulti,
|
||||
this.moveVector.z * moveMulti,
|
||||
);
|
||||
|
||||
// Smooth out the movement
|
||||
this.currentVelocity.lerp(targetMovement, this.smoothingFactor);
|
||||
|
||||
// Apply the smoothed movement
|
||||
this.object.translateX(this.currentVelocity.x);
|
||||
this.object.translateY(this.currentVelocity.y);
|
||||
this.object.translateZ(this.currentVelocity.z);
|
||||
|
||||
// Store last position
|
||||
this.lastPosition.copy(this.object.position);
|
||||
|
||||
|
||||
// Done with positions
|
||||
// Now handle rotations
|
||||
this.object.rotateX(this.rotationVector.x);
|
||||
this.object.rotateY(this.rotationVector.y);
|
||||
this.object.rotateZ(this.rotationVector.z);
|
||||
|
||||
// Update camera signals
|
||||
if (selectedCameraId.value) {
|
||||
const pos = this.object.position;
|
||||
const rot = this.object.rotation;
|
||||
|
||||
// Calculate lookAt point based on camera's forward direction
|
||||
const lookAtPoint = new Vector3(0, 0, -1);
|
||||
lookAtPoint.applyQuaternion(this.object.quaternion);
|
||||
lookAtPoint.add(pos);
|
||||
|
||||
cameras.value = cameras.value.map((cam) =>
|
||||
cam.id === selectedCameraId.value
|
||||
? {
|
||||
...cam,
|
||||
position: { x: pos.x, y: pos.y, z: pos.z },
|
||||
rotation: { x: rot.x, y: rot.y, z: rot.z },
|
||||
lookAt: { x: lookAtPoint.x, y: lookAtPoint.y, z: lookAtPoint.z },
|
||||
}
|
||||
: cam,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
updateMovementVector() {
|
||||
const forward =
|
||||
this.moveState.forward || (this.autoForward && !this.moveState.back)
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
this.moveVector.x = -this.moveState.left + this.moveState.right;
|
||||
this.moveVector.y = -this.moveState.down + this.moveState.up;
|
||||
this.moveVector.z = -forward + this.moveState.back;
|
||||
}
|
||||
|
||||
updateRotationVector() {
|
||||
this.rotationVector.x = -this.moveState.pitchDown + this.moveState.pitchUp;
|
||||
this.rotationVector.y = -this.moveState.yawRight + this.moveState.yawLeft;
|
||||
this.rotationVector.z = -this.moveState.rollRight + this.moveState.rollLeft;
|
||||
this.rotationVector.multiplyScalar(this.rollSpeed);
|
||||
}
|
||||
|
||||
getContainerDimensions() {
|
||||
if (this.domElement !== document) {
|
||||
return {
|
||||
size: [
|
||||
(this.domElement as HTMLElement).offsetWidth,
|
||||
(this.domElement as HTMLElement).offsetHeight,
|
||||
],
|
||||
offset: [
|
||||
(this.domElement as HTMLElement).offsetLeft,
|
||||
(this.domElement as HTMLElement).offsetTop,
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
size: [window.innerWidth, window.innerHeight],
|
||||
offset: [0, 0],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
// window.addEventListener('contextmenu', function(event) {
|
||||
// event.preventDefault();
|
||||
// });
|
||||
|
||||
// this.domElement.removeEventListener("contextmenu", this.contextmenu);
|
||||
// this.domElement.removeEventListener("pointerdown", this.pointerdown);
|
||||
// this.domElement.removeEventListener("pointermove", this.pointermove);
|
||||
// this.domElement.removeEventListener("pointerup", this.pointerup);
|
||||
// this.domElement.removeEventListener("pointercancel", this.pointercancel);
|
||||
//
|
||||
window.removeEventListener("keydown", this.keydown);
|
||||
window.removeEventListener("keyup", this.keyup);
|
||||
}
|
||||
}
|
||||
|
||||
export { FreeCam };
|
||||
@@ -117,7 +117,9 @@ export class SceneUtils {
|
||||
|
||||
switchCameraView() {
|
||||
this.editor.camera_person_mode = !this.editor.camera_person_mode;
|
||||
this.editor.cameraViewControls?.reset();
|
||||
if (this.editor.freeCamState) {
|
||||
this.editor.freeCamState.velocity.set(0, 0, 0);
|
||||
}
|
||||
if (this.editor.cam_obj) {
|
||||
if (this.editor.camera_person_mode && this.editor.camera) {
|
||||
this.editor.last_cam_pos.copy(this.editor.camera.position);
|
||||
@@ -131,9 +133,8 @@ export class SceneUtils {
|
||||
this.editor.lockControls.getObject(),
|
||||
);
|
||||
}
|
||||
if (this.editor.cameraViewControls) {
|
||||
this.editor.cameraViewControls.enabled = true;
|
||||
}
|
||||
// useFreeCam reads editorState from the store and enables
|
||||
// itself when CAMERA_VIEW; nothing to do here.
|
||||
this.editor.cam_obj.scale.set(0, 0, 0);
|
||||
|
||||
this.removeTransformControls();
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
} from "three/examples/jsm/Addons.js";
|
||||
import { TransformControls } from "./TransformControls";
|
||||
import { SceneManager, SceneObject } from "./scene_manager_api";
|
||||
import { FreeCam, isPointerLockSupported } from "./free_cam";
|
||||
import { isPointerLockSupported } from "./browserChecks";
|
||||
import type { FreeCamControlState } from "./cameraMath";
|
||||
import { FKHelper } from "./KinHelpers/FKHelper";
|
||||
import { Euler } from "three";
|
||||
|
||||
@@ -81,7 +82,7 @@ export class MouseControls {
|
||||
setSelected: Function;
|
||||
sceneManager: SceneManager | undefined;
|
||||
private isProcessing: boolean = false;
|
||||
private cameraViewControls: FreeCam;
|
||||
private cameraViewControls: FreeCamControlState | null;
|
||||
private isMouseClicked: boolean = false;
|
||||
private isMovable: Function;
|
||||
enable_stats: Function;
|
||||
@@ -95,7 +96,7 @@ export class MouseControls {
|
||||
constructor(
|
||||
camera: THREE.PerspectiveCamera,
|
||||
camera_person_mode: boolean,
|
||||
cameraViewControls: FreeCam,
|
||||
cameraViewControls: FreeCamControlState | null,
|
||||
lockControls: PointerLockControls | undefined,
|
||||
camera_last_pos: THREE.Vector3,
|
||||
selectedCanvas: boolean,
|
||||
@@ -437,12 +438,14 @@ export class MouseControls {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (event.shiftKey) {
|
||||
this.cameraViewControls.movementSpeed = 3;
|
||||
} else if (event.altKey) {
|
||||
this.cameraViewControls.movementSpeed = 0.1;
|
||||
} else {
|
||||
this.cameraViewControls.movementSpeed = 0.75;
|
||||
if (this.cameraViewControls) {
|
||||
if (event.shiftKey) {
|
||||
this.cameraViewControls.movementSpeed = 3;
|
||||
} else if (event.altKey) {
|
||||
this.cameraViewControls.movementSpeed = 0.1;
|
||||
} else {
|
||||
this.cameraViewControls.movementSpeed = 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key === "Escape") {
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { MathUtils } from "three";
|
||||
import {
|
||||
createFreeCamControlState,
|
||||
emptyMoveKeys,
|
||||
emptyRotateKeys,
|
||||
moveSlotForKeyCode,
|
||||
panFromDrag,
|
||||
rotateSlotForKeyCode,
|
||||
zoomFromWheel,
|
||||
type FreeCamControlState,
|
||||
} from "~/pages/PageScene/engine/cameraMath";
|
||||
import { isPromptBoxFocused } from "~/pages/PageScene/signals/promptBox";
|
||||
import { usePageSceneStore } from "~/pages/PageScene/PageSceneStore";
|
||||
import { EditorStates } from "~/pages/PageScene/enums";
|
||||
import type Editor from "~/pages/PageScene/engine/editor";
|
||||
|
||||
// Drives the 3D viewport's camera while the editor is in CAMERA_VIEW.
|
||||
// Owns:
|
||||
// - Listener attachment to the canvas (no window/document handlers).
|
||||
// - Drag state + held-key state in refs.
|
||||
// - The shared FreeCamControlState handed to the editor's render
|
||||
// loop via setFreeCamState.
|
||||
//
|
||||
// The editor reads this state on every frame (see editor.ts render
|
||||
// loop) and integrates it via freeCamFrameTick. There is no FreeCam
|
||||
// class instance — the math lives in cameraMath.ts.
|
||||
|
||||
export const useFreeCam = (
|
||||
canvas: HTMLCanvasElement | null,
|
||||
editor: Editor | null,
|
||||
) => {
|
||||
const stateRef = useRef<FreeCamControlState>(createFreeCamControlState());
|
||||
const dragRef = useRef<{ x: number; y: number; pointerId: number } | null>(
|
||||
null,
|
||||
);
|
||||
const enabled = usePageSceneStore(
|
||||
(s) => s.editorState === EditorStates.CAMERA_VIEW,
|
||||
);
|
||||
|
||||
// Hand the state to the editor so its render loop can integrate it.
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
editor.setFreeCamState(stateRef.current);
|
||||
return () => editor.setFreeCamState(null);
|
||||
}, [editor]);
|
||||
|
||||
// Toggle enabled and clear motion when leaving CAMERA_VIEW so the
|
||||
// camera doesn't drift on a stale held key.
|
||||
useEffect(() => {
|
||||
const state = stateRef.current;
|
||||
state.enabled = enabled;
|
||||
if (!enabled) {
|
||||
state.moveKeys = emptyMoveKeys();
|
||||
state.rotateKeys = emptyRotateKeys();
|
||||
state.velocity.set(0, 0, 0);
|
||||
dragRef.current = null;
|
||||
}
|
||||
}, [enabled]);
|
||||
|
||||
// Attach listeners to the canvas. Canvas-scoped means no listener
|
||||
// leaks beyond the viewport, and no need for mouseover gating.
|
||||
useEffect(() => {
|
||||
if (!canvas || !enabled || !editor) return;
|
||||
const state = stateRef.current;
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (isPromptBoxFocused.value) return;
|
||||
const moveSlot = moveSlotForKeyCode(e.code);
|
||||
if (moveSlot) state.moveKeys[moveSlot] = 1;
|
||||
const rotateSlot = rotateSlotForKeyCode(e.code);
|
||||
if (rotateSlot) state.rotateKeys[rotateSlot] = 1;
|
||||
};
|
||||
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
if (isPromptBoxFocused.value) return;
|
||||
const moveSlot = moveSlotForKeyCode(e.code);
|
||||
if (moveSlot) state.moveKeys[moveSlot] = 0;
|
||||
const rotateSlot = rotateSlotForKeyCode(e.code);
|
||||
if (rotateSlot) state.rotateKeys[rotateSlot] = 0;
|
||||
};
|
||||
|
||||
const onPointerDown = (e: PointerEvent) => {
|
||||
if (e.button !== 2) return;
|
||||
dragRef.current = { x: e.clientX, y: e.clientY, pointerId: e.pointerId };
|
||||
state.velocity.set(0, 0, 0);
|
||||
try {
|
||||
canvas.setPointerCapture(e.pointerId);
|
||||
} catch {
|
||||
// pointerCapture can throw if pointer is already captured elsewhere
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (e: PointerEvent) => {
|
||||
if (!dragRef.current || dragRef.current.pointerId !== e.pointerId) return;
|
||||
try {
|
||||
canvas.releasePointerCapture(e.pointerId);
|
||||
} catch {
|
||||
// ignore: capture may already be released
|
||||
}
|
||||
dragRef.current = null;
|
||||
state.velocity.set(0, 0, 0);
|
||||
};
|
||||
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
const drag = dragRef.current;
|
||||
if (!drag || !editor.camera) return;
|
||||
const dx = e.clientX - drag.x;
|
||||
const dy = e.clientY - drag.y;
|
||||
drag.x = e.clientX;
|
||||
drag.y = e.clientY;
|
||||
if (Math.abs(dx) + Math.abs(dy) === 0) return;
|
||||
|
||||
const pan = panFromDrag(dx, dy, state.movementSpeed);
|
||||
state.velocity.x = MathUtils.lerp(state.velocity.x, pan.x, state.smoothing);
|
||||
state.velocity.y = MathUtils.lerp(state.velocity.y, pan.y, state.smoothing);
|
||||
editor.camera.translateX(state.velocity.x);
|
||||
editor.camera.translateY(state.velocity.y);
|
||||
};
|
||||
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
if (!editor.camera) return;
|
||||
const z = zoomFromWheel(e.deltaY);
|
||||
state.velocity.z = MathUtils.lerp(state.velocity.z, z, state.smoothing);
|
||||
editor.camera.translateZ(state.velocity.z);
|
||||
};
|
||||
|
||||
const onContextMenu = (e: Event) => e.preventDefault();
|
||||
|
||||
canvas.addEventListener("keydown", onKeyDown);
|
||||
canvas.addEventListener("keyup", onKeyUp);
|
||||
canvas.addEventListener("pointerdown", onPointerDown);
|
||||
canvas.addEventListener("pointerup", onPointerUp);
|
||||
canvas.addEventListener("pointermove", onPointerMove);
|
||||
canvas.addEventListener("wheel", onWheel, { passive: true });
|
||||
canvas.addEventListener("contextmenu", onContextMenu);
|
||||
|
||||
return () => {
|
||||
canvas.removeEventListener("keydown", onKeyDown);
|
||||
canvas.removeEventListener("keyup", onKeyUp);
|
||||
canvas.removeEventListener("pointerdown", onPointerDown);
|
||||
canvas.removeEventListener("pointerup", onPointerUp);
|
||||
canvas.removeEventListener("pointermove", onPointerMove);
|
||||
canvas.removeEventListener("wheel", onWheel);
|
||||
canvas.removeEventListener("contextmenu", onContextMenu);
|
||||
};
|
||||
}, [canvas, editor, enabled]);
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
|
||||
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
|
||||
Reference in new issue
Block a user