From 836de1bb283eb7813f41073df8700ac346004750 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:24:20 +0900 Subject: [PATCH] wip --- packages/frontend/src/pages/room.vue | 104 +++++++++++++++++- .../frontend/src/world/room/controller.ts | 16 +++ packages/frontend/src/world/room/engine.ts | 49 ++++----- packages/frontend/src/world/utility.ts | 77 ++++++++++--- 4 files changed, 202 insertions(+), 44 deletions(-) diff --git a/packages/frontend/src/pages/room.vue b/packages/frontend/src/pages/room.vue index 7be6562c44..d60724381e 100644 --- a/packages/frontend/src/pages/room.vue +++ b/packages/frontend/src/pages/room.vue @@ -220,6 +220,10 @@ const resolution = computed(() => resolutionRaw.value ?? resolutionAutoV const useVirtualJoystick = isTouchUsing && (deviceKind === 'smartphone' || deviceKind === 'tablet'); +const wasdVec = { x: 0, y: 0 }; +const pointerVec = { x: 0, y: 0 }; +let isDashing = false; + const joyStickRadiusPx = 100; const joyStickLeftEl = useTemplateRef('joyStickLeftEl'); const joyStickRightEl = useTemplateRef('joyStickRightEl'); @@ -270,7 +274,7 @@ const roomControllerOptions = computed(() => ({ fps: fps.value, resolution: resolution.value, useVirtualJoystick, - workerMode: false, + workerMode: true, })); const controller = new RoomController(data, roomControllerOptions.value); @@ -343,6 +347,104 @@ onMounted(async () => { }); } + canvas.value!.addEventListener('keydown', (ev) => { + if (ev.repeat) return; + + switch (ev.code) { + case 'KeyW': + wasdVec.y = -1; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + case 'KeyS': + wasdVec.y = 1; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + case 'KeyA': + wasdVec.x = -1; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + case 'KeyD': + wasdVec.x = 1; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + case 'ShiftLeft': + case 'ShiftRight': + isDashing = true; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + } + }); + + canvas.value!.addEventListener('keyup', (ev) => { + switch (ev.code) { + case 'KeyW': + wasdVec.y = 0; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + case 'KeyS': + wasdVec.y = 0; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + case 'KeyA': + wasdVec.x = 0; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + case 'KeyD': + wasdVec.x = 0; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + case 'ShiftLeft': + case 'ShiftRight': + isDashing = false; + controller.setCameraMoveVector(wasdVec, isDashing); + break; + } + }); + + canvas.value!.addEventListener('pointerdown', (ev) => { + pointerVec.x = ev.clientX; + pointerVec.y = ev.clientY; + + let timeoutId: number | null = null; + + const onMove = (ev: PointerEvent) => { + if (timeoutId != null) { + window.clearTimeout(timeoutId); + timeoutId = null; + } + + const before = pointerVec; + const after = { x: ev.clientX, y: ev.clientY }; + + controller.setCameraRotateVector({ + x: after.x - before.x, + y: after.y - before.y, + }); + + pointerVec.x = after.x; + pointerVec.y = after.y; + + timeoutId = window.setTimeout(() => { + timeoutId = null; + pointerVec.x = 0; + pointerVec.y = 0; + + controller.setCameraRotateVector(pointerVec); + }, 10); + }; + + canvas.value!.addEventListener('pointermove', onMove); + + canvas.value!.addEventListener('pointerup', (ev) => { + canvas.value!.removeEventListener('pointermove', onMove); + + pointerVec.x = 0; + pointerVec.y = 0; + + controller.setCameraRotateVector(pointerVec); + }); + }); + watch([graphicsQuality, fps, resolution], () => { refresh(); }); diff --git a/packages/frontend/src/world/room/controller.ts b/packages/frontend/src/world/room/controller.ts index 6b73f5a24c..31ea8fdbc8 100644 --- a/packages/frontend/src/world/room/controller.ts +++ b/packages/frontend/src/world/room/controller.ts @@ -245,6 +245,22 @@ export class RoomController { } } + public setCameraMoveVector(vec: { x: number; y: number }, dash: boolean) { + if (this.worker != null) { + this.worker.postMessage({ type: 'call', fn: 'cameraMove', args: [vec, dash] }); + } else if (this.engine != null) { + this.engine.cameraMove(vec, dash); + } + } + + public setCameraRotateVector(vec: { x: number; y: number }) { + if (this.worker != null) { + this.worker.postMessage({ type: 'call', fn: 'cameraRotate', args: [vec] }); + } else if (this.engine != null) { + this.engine.cameraRotate(vec); + } + } + public setCameraJoystickMoveVector(vec: { x: number; y: number }) { if (this.worker != null) { this.worker.postMessage({ type: 'call', fn: 'cameraJoystickMove', args: [vec] }); diff --git a/packages/frontend/src/world/room/engine.ts b/packages/frontend/src/world/room/engine.ts index 13efd6ebc9..c470e1aab7 100644 --- a/packages/frontend/src/world/room/engine.ts +++ b/packages/frontend/src/world/room/engine.ts @@ -14,7 +14,7 @@ import * as BABYLON from '@babylonjs/core'; import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic'; import { EventEmitter } from 'eventemitter3'; -import { TIME_MAP, scaleMorph, camelToKebab, cm, WORLD_SCALE, getMeshesBoundingBox, Timer, getYRotationDirection, FreeCameraTouchVirtualJoystickInput } from '../utility.js'; +import { TIME_MAP, scaleMorph, camelToKebab, cm, WORLD_SCALE, getMeshesBoundingBox, Timer, getYRotationDirection, FreeCameraVirtualJoystickInput, FreeCameraManualInput } from '../utility.js'; import { getObjectDef } from './object-defs.js'; import { findMaterial, ModelManager, SYSTEM_HEYA_MESH_NAMES, SYSTEM_MESH_NAMES } from './utility.js'; import { SimpleHeyaManager } from './heya.js'; @@ -26,7 +26,7 @@ import { deepClone } from '@/utility/clone.js'; const BAKE_TRANSFORM = false; // 実験的 const SNAPSHOT_RENDERING = true; // 実験的 -const IGNORE_OBJECTS: string[] = []; // for debug +const IGNORE_OBJECTS: string[] = ['aquarium']; // for debug const RENDER_OUTDOOR_ENV = false; const IN_WEB_WORKER = typeof window === 'undefined'; @@ -290,7 +290,7 @@ export class RoomEngine extends EventEmitter { this.scene.collisionsEnabled = true; - this.camera = options.useVirtualJoystick ? new BABYLON.FreeCamera('camera', new BABYLON.Vector3(0, cm(130), cm(0)), this.scene) : new BABYLON.UniversalCamera('camera', new BABYLON.Vector3(0, cm(130), cm(0)), this.scene); + this.camera = new BABYLON.FreeCamera('camera', new BABYLON.Vector3(0, cm(130), cm(0)), this.scene); this.camera.minZ = cm(1); this.camera.maxZ = RENDER_OUTDOOR_ENV ? cm(10000) : cm(1000); this.camera.fov = 1; @@ -298,36 +298,19 @@ export class RoomEngine extends EventEmitter { this.camera.checkCollisions = true; this.camera.applyGravity = true; this.camera.needMoveForGravity = true; + this.camera.inputs.clear(); if (options.useVirtualJoystick) { - this.camera.inputs.clear(); - this.camera.inputs.add(new FreeCameraTouchVirtualJoystickInput({ + this.camera.inputs.add(new FreeCameraVirtualJoystickInput({ moveSensitivity: 0.015 * WORLD_SCALE, - rotationSensitivity: 0.1, + rotationSensitivity: 0.01, })); this.camera.inertia = 0.75; } else { - const normalSpeed = 0.02 * WORLD_SCALE; - this.camera.speed = normalSpeed; - - this.camera.keysUp.push(87); // W - this.camera.keysDown.push(83); // S - this.camera.keysLeft.push(65); // A - this.camera.keysRight.push(68); // D - this.scene.onKeyboardObservable.add((kbInfo) => { - switch (kbInfo.type) { - case BABYLON.KeyboardEventTypes.KEYDOWN: - if (kbInfo.event.key === 'Shift') { - this.camera.speed = normalSpeed * 4; - } - break; - case BABYLON.KeyboardEventTypes.KEYUP: - if (kbInfo.event.key === 'Shift') { - this.camera.speed = normalSpeed; - } - break; - } - }); + this.camera.inputs.add(new FreeCameraManualInput({ + moveSensitivity: 0.001 * WORLD_SCALE, + rotationSensitivity: 0.01, + })); } this.camera.attachControl(this.canvas); @@ -622,12 +605,20 @@ export class RoomEngine extends EventEmitter { this.startRenderLoop(); } + public cameraMove(vector: { x: number; y: number; }, dash: boolean) { + (this.camera.inputs.attached.manual as FreeCameraManualInput).setMoveVector(dash ? { x: vector.x * 4, y: vector.y * 4 } : vector); + } + + public cameraRotate(vector: { x: number; y: number; }) { + (this.camera.inputs.attached.manual as FreeCameraManualInput).setRotationVector(vector); + } + public cameraJoystickMove(vector: { x: number; y: number; }) { - (this.camera.inputs.attached.joystick as FreeCameraTouchVirtualJoystickInput).setJoystickMoveVector(vector); + (this.camera.inputs.attached.joystick as FreeCameraVirtualJoystickInput).setJoystickMoveVector(vector); } public cameraJoystickRotate(vector: { x: number; y: number; }) { - (this.camera.inputs.attached.joystick as FreeCameraTouchVirtualJoystickInput).setJoystickRotationVector(vector); + (this.camera.inputs.attached.joystick as FreeCameraVirtualJoystickInput).setJoystickRotationVector(vector); } public selectObject(objectId: string | null) { diff --git a/packages/frontend/src/world/utility.ts b/packages/frontend/src/world/utility.ts index cd8369ca50..b705e664e3 100644 --- a/packages/frontend/src/world/utility.ts +++ b/packages/frontend/src/world/utility.ts @@ -618,20 +618,20 @@ export function getRgb(hex: string | number): [number, number, number] | null { return m.map(x => parseInt(x, 16) / 255) as [number, number, number]; } -export class FreeCameraTouchVirtualJoystickInput implements BABYLON.ICameraInput { +export class FreeCameraVirtualJoystickInput implements BABYLON.ICameraInput { public camera: BABYLON.FreeCamera; - private joystickMoveSensitivity: number; - private joystickRotationSensitivity: number; - private joystickMoveVector = BABYLON.Vector3.Zero(); - private joystickRotationVecX = 0; - private joystickRotationVecY = 0; + private moveSensitivity: number; + private rotationSensitivity: number; + private moveVector = BABYLON.Vector3.Zero(); + private rotationVecX = 0; + private rotationVecY = 0; constructor(options: { moveSensitivity?: number; rotationSensitivity?: number; }) { - this.joystickMoveSensitivity = options.moveSensitivity ?? 0.01; - this.joystickRotationSensitivity = options.rotationSensitivity ?? 0.01; + this.moveSensitivity = options.moveSensitivity ?? 0.01; + this.rotationSensitivity = options.rotationSensitivity ?? 0.01; } getClassName = () => this.constructor.name; @@ -645,7 +645,7 @@ export class FreeCameraTouchVirtualJoystickInput implements BABYLON.ICameraInput } public setJoystickMoveVector(vec: { x: number; y: number }) { - this.joystickMoveVector = new BABYLON.Vector3(vec.x, 0, -vec.y).scale(this.joystickMoveSensitivity); + this.moveVector = new BABYLON.Vector3(vec.x, 0, -vec.y).scale(this.moveSensitivity); } public setJoystickRotationVector(vec: { x: number; y: number }) { @@ -653,16 +653,65 @@ export class FreeCameraTouchVirtualJoystickInput implements BABYLON.ICameraInput if (this.camera.getScene().useRightHandedSystem) directionAdjust *= -1; if (this.camera.parent && this.camera.parent._getWorldMatrixDeterminant() < 0) directionAdjust *= -1; - this.joystickRotationVecX = vec.y * this.joystickRotationSensitivity * this.joystickRotationSensitivity; - this.joystickRotationVecY = vec.x * this.joystickRotationSensitivity * directionAdjust * this.joystickRotationSensitivity; + this.rotationVecX = vec.y * this.rotationSensitivity; + this.rotationVecY = vec.x * this.rotationSensitivity * directionAdjust; } checkInputs() { - this.camera.cameraRotation.y += this.joystickRotationVecY; - this.camera.cameraRotation.x += this.joystickRotationVecX; + this.camera.cameraRotation.y += this.rotationVecY; + this.camera.cameraRotation.x += this.rotationVecX; this.camera.cameraDirection.addInPlace( - BABYLON.Vector3.TransformCoordinates(this.joystickMoveVector, BABYLON.Matrix.RotationY(this.camera.rotation.y)), + BABYLON.Vector3.TransformCoordinates(this.moveVector, BABYLON.Matrix.RotationY(this.camera.rotation.y)), + ); + } +} + +export class FreeCameraManualInput implements BABYLON.ICameraInput { + public camera: BABYLON.FreeCamera; + private moveSensitivity: number; + private rotationSensitivity: number; + private moveVector = BABYLON.Vector3.Zero(); + private rotationVecX = 0; + private rotationVecY = 0; + + constructor(options: { + moveSensitivity?: number; + rotationSensitivity?: number; + }) { + this.moveSensitivity = options.moveSensitivity ?? 0.01; + this.rotationSensitivity = options.rotationSensitivity ?? 0.01; + } + + getClassName = () => this.constructor.name; + + getSimpleName = () => 'manual'; + + attachControl(noPreventDefault) { + } + + detachControl() { + } + + public setMoveVector(vec: { x: number; y: number }) { + this.moveVector = new BABYLON.Vector3(vec.x, 0, -vec.y).scale(this.moveSensitivity); + } + + public setRotationVector(vec: { x: number; y: number }) { + let directionAdjust = 1; + if (this.camera.getScene().useRightHandedSystem) directionAdjust *= -1; + if (this.camera.parent && this.camera.parent._getWorldMatrixDeterminant() < 0) directionAdjust *= -1; + + this.rotationVecX = vec.y * this.rotationSensitivity; + this.rotationVecY = vec.x * this.rotationSensitivity * directionAdjust; + } + + checkInputs() { + this.camera.cameraRotation.y += this.rotationVecY; + this.camera.cameraRotation.x += this.rotationVecX; + + this.camera.cameraDirection.addInPlace( + BABYLON.Vector3.TransformCoordinates(this.moveVector, BABYLON.Matrix.RotationY(this.camera.rotation.y)), ); } }