mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-19 13:35:30 +02:00
joystick
This commit is contained in:
61
packages/frontend/src/world/joystick.ts
Normal file
61
packages/frontend/src/world/joystick.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
|
||||
export class Joystick extends EventEmitter<{
|
||||
'start': (vec: { x: number; y: number }) => void;
|
||||
'end': () => void;
|
||||
'updateVector': (vec: { x: number; y: number }) => void; // -1.0 ~ 1.0
|
||||
}> {
|
||||
private el: HTMLDivElement;
|
||||
private startPos: { x: number; y: number } | null = null;
|
||||
private radiusPx: number;
|
||||
|
||||
constructor(el: HTMLDivElement, options: { radiusPx: number }) {
|
||||
super();
|
||||
this.el = el;
|
||||
this.radiusPx = options.radiusPx;
|
||||
this.el.addEventListener('pointerdown', this.onPointerDown);
|
||||
this.el.addEventListener('pointermove', this.onPointerMove);
|
||||
this.el.addEventListener('pointerup', this.onPointerUp);
|
||||
this.el.addEventListener('touchstart', ev => ev.preventDefault(), { passive: false });
|
||||
this.el.addEventListener('touchmove', ev => ev.preventDefault(), { passive: false });
|
||||
}
|
||||
|
||||
private onPointerDown = (ev: PointerEvent) => {
|
||||
ev.preventDefault();
|
||||
this.el.setPointerCapture(ev.pointerId);
|
||||
this.startPos = { x: ev.offsetX, y: ev.offsetY };
|
||||
this.emit('start', this.startPos);
|
||||
};
|
||||
|
||||
private onPointerMove = (ev: PointerEvent) => {
|
||||
ev.preventDefault();
|
||||
if (this.startPos == null) return;
|
||||
const vec = {
|
||||
x: ev.offsetX - this.startPos.x,
|
||||
y: ev.offsetY - this.startPos.y,
|
||||
};
|
||||
const len = Math.sqrt(vec.x * vec.x + vec.y * vec.y);
|
||||
if (len > this.radiusPx) {
|
||||
vec.x = (vec.x / len) * this.radiusPx;
|
||||
vec.y = (vec.y / len) * this.radiusPx;
|
||||
}
|
||||
const normVec = {
|
||||
x: vec.x / this.radiusPx,
|
||||
y: vec.y / this.radiusPx,
|
||||
};
|
||||
this.emit('updateVector', normVec);
|
||||
};
|
||||
|
||||
private onPointerUp = (ev: PointerEvent) => {
|
||||
ev.preventDefault();
|
||||
this.el.releasePointerCapture(ev.pointerId);
|
||||
this.startPos = null;
|
||||
this.emit('end');
|
||||
this.emit('updateVector', { x: 0, y: 0 });
|
||||
};
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import * as sound from '@/utility/sound.js';
|
||||
type Options = {
|
||||
workerMode?: boolean;
|
||||
graphicsQuality: number;
|
||||
useVirtualJoystick?: boolean;
|
||||
};
|
||||
|
||||
// 抽象化レイヤー
|
||||
@@ -59,7 +60,7 @@ export class RoomController {
|
||||
if (this.options.workerMode) {
|
||||
const offscreen = canvas.transferControlToOffscreen();
|
||||
this.worker = new RoomWorker();
|
||||
this.worker.postMessage({ type: 'init', canvas: offscreen, roomState: this.roomState.value, graphicsQuality: this.options.graphicsQuality }, [offscreen]);
|
||||
this.worker.postMessage({ type: 'init', canvas: offscreen, roomState: this.roomState.value, graphicsQuality: this.options.graphicsQuality, useVirtualJoystick: this.options.useVirtualJoystick }, [offscreen]);
|
||||
this.isReady.value = true;
|
||||
} else {
|
||||
const babylonEngine = new BABYLON.WebGPUEngine(canvas, { doNotHandleContextLost: true });
|
||||
@@ -67,7 +68,7 @@ export class RoomController {
|
||||
babylonEngine.enableOfflineSupport = false;
|
||||
await babylonEngine.initAsync();
|
||||
|
||||
this.engine = new RoomEngine(this.roomState.value, { canvas, engine: babylonEngine, graphicsQuality: this.options.graphicsQuality });
|
||||
this.engine = new RoomEngine(this.roomState.value, { canvas, engine: babylonEngine, graphicsQuality: this.options.graphicsQuality, useVirtualJoystick: this.options.useVirtualJoystick });
|
||||
this.engine.on('loadingProgress', ({ progress }) => {
|
||||
this.initializeProgress.value = progress;
|
||||
});
|
||||
@@ -201,6 +202,22 @@ export class RoomController {
|
||||
}
|
||||
}
|
||||
|
||||
public setCameraJoystickMoveVector(vec: { x: number; y: number }) {
|
||||
if (this.worker != null) {
|
||||
this.worker.postMessage({ type: 'setCameraJoystickMoveVector', vec });
|
||||
} else if (this.engine != null) {
|
||||
this.engine.cameraJoystickMove(vec);
|
||||
}
|
||||
}
|
||||
|
||||
public setCameraJoystickRotateVector(vec: { x: number; y: number }) {
|
||||
if (this.worker != null) {
|
||||
this.worker.postMessage({ type: 'setCameraJoystickRotateVector', vec });
|
||||
} else if (this.engine != null) {
|
||||
this.engine.cameraJoystickRotate(vec);
|
||||
}
|
||||
}
|
||||
|
||||
public enterEditMode() {
|
||||
if (this.worker != null) {
|
||||
this.worker.postMessage({ type: 'enterEditMode' });
|
||||
|
||||
@@ -15,7 +15,7 @@ import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic';
|
||||
import { BoundingBoxRenderer } from '@babylonjs/core/Rendering/boundingBoxRenderer';
|
||||
import { GridMaterial } from '@babylonjs/materials';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { TIME_MAP, scaleMorph, camelToKebab, cm, WORLD_SCALE, getMeshesBoundingBox, Timer, getYRotationDirection } from '../utility.js';
|
||||
import { TIME_MAP, scaleMorph, camelToKebab, cm, WORLD_SCALE, getMeshesBoundingBox, Timer, getYRotationDirection, FreeCameraTouchVirtualJoystickInput } 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';
|
||||
@@ -23,8 +23,6 @@ import type { HeyaManager, JapaneseHeyaOptions, SimpleHeyaOptions } from './heya
|
||||
import type { ObjectDef, RoomObjectInstance, RoomStateObject } from './object.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import { isTouchUsing } from '@/utility/touch.js';
|
||||
import { deviceKind } from '@/utility/device-kind.js';
|
||||
|
||||
const BAKE_TRANSFORM = false; // 実験的
|
||||
const SNAPSHOT_RENDERING = true; // 実験的
|
||||
@@ -226,6 +224,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
canvas: HTMLCanvasElement;
|
||||
engine: BABYLON.WebGPUEngine;
|
||||
graphicsQuality: number;
|
||||
useVirtualJoystick?: boolean;
|
||||
}) {
|
||||
super();
|
||||
|
||||
@@ -289,14 +288,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
|
||||
this.scene.collisionsEnabled = true;
|
||||
|
||||
const useVirtualJoystick = isTouchUsing && (deviceKind === 'smartphone' || deviceKind === 'tablet');
|
||||
|
||||
this.camera = useVirtualJoystick ? new BABYLON.VirtualJoysticksCamera('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.attachControl(this.canvas);
|
||||
if (useVirtualJoystick) {
|
||||
(this.camera.inputs.attached.virtualJoystick as BABYLON.FreeCameraVirtualJoystickInput).getLeftJoystick().setJoystickSensibility(0.3);
|
||||
(this.camera.inputs.attached.virtualJoystick as BABYLON.FreeCameraVirtualJoystickInput).getRightJoystick().setJoystickSensibility(0.025);
|
||||
}
|
||||
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.minZ = cm(1);
|
||||
this.camera.maxZ = RENDER_OUTDOOR_ENV ? cm(10000) : cm(1000);
|
||||
this.camera.fov = 1;
|
||||
@@ -304,26 +296,39 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
this.camera.checkCollisions = true;
|
||||
this.camera.applyGravity = true;
|
||||
this.camera.needMoveForGravity = true;
|
||||
this.camera.keysUp.push(87); // W
|
||||
this.camera.keysDown.push(83); // S
|
||||
this.camera.keysLeft.push(65); // A
|
||||
this.camera.keysRight.push(68); // D
|
||||
const normalSpeed = 0.02 * WORLD_SCALE;
|
||||
this.camera.speed = normalSpeed;
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
if (options.useVirtualJoystick) {
|
||||
this.camera.inputs.clear();
|
||||
this.camera.inputs.add(new FreeCameraTouchVirtualJoystickInput({
|
||||
moveSensitivity: 0.015 * WORLD_SCALE,
|
||||
rotationSensitivity: 0.1,
|
||||
}));
|
||||
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.attachControl(this.canvas);
|
||||
|
||||
//this.scene.activeCamera = this.camera;
|
||||
|
||||
@@ -448,6 +453,14 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
public cameraJoystickMove(vector: { x: number; y: number; }) {
|
||||
this.camera.inputs.attached.joystick.setJoystickMoveVector(vector);
|
||||
}
|
||||
|
||||
public cameraJoystickRotate(vector: { x: number; y: number; }) {
|
||||
this.camera.inputs.attached.joystick.setJoystickRotationVector(vector);
|
||||
}
|
||||
|
||||
public async init() {
|
||||
await this.loadHeya();
|
||||
if (RENDER_OUTDOOR_ENV) await this.loadEnvModel();
|
||||
|
||||
@@ -607,3 +607,52 @@ export function getRgb(hex: string | number): [number, number, number] | null {
|
||||
if (m == null) return [0, 0, 0];
|
||||
return m.map(x => parseInt(x, 16) / 255) as [number, number, number];
|
||||
}
|
||||
|
||||
export class FreeCameraTouchVirtualJoystickInput implements BABYLON.ICameraInput<BABYLON.FreeCamera> {
|
||||
public camera: BABYLON.FreeCamera;
|
||||
private joystickMoveSensitivity: number;
|
||||
private joystickRotationSensitivity: number;
|
||||
private joystickMoveVector = BABYLON.Vector3.Zero();
|
||||
private joystickRotationVecX = 0;
|
||||
private joystickRotationVecY = 0;
|
||||
|
||||
constructor(options: {
|
||||
moveSensitivity?: number;
|
||||
rotationSensitivity?: number;
|
||||
}) {
|
||||
this.joystickMoveSensitivity = options.moveSensitivity ?? 0.01;
|
||||
this.joystickRotationSensitivity = options.rotationSensitivity ?? 0.01;
|
||||
}
|
||||
|
||||
getClassName = () => this.constructor.name;
|
||||
|
||||
getSimpleName = () => 'joystick';
|
||||
|
||||
attachControl(noPreventDefault) {
|
||||
}
|
||||
|
||||
detachControl() {
|
||||
}
|
||||
|
||||
public setJoystickMoveVector(vec: { x: number; y: number }) {
|
||||
this.joystickMoveVector = new BABYLON.Vector3(vec.x, 0, -vec.y).scale(this.joystickMoveSensitivity);
|
||||
}
|
||||
|
||||
public setJoystickRotationVector(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.joystickRotationVecX = vec.y * this.joystickRotationSensitivity * this.joystickRotationSensitivity;
|
||||
this.joystickRotationVecY = vec.x * this.joystickRotationSensitivity * directionAdjust * this.joystickRotationSensitivity;
|
||||
}
|
||||
|
||||
checkInputs() {
|
||||
this.camera.cameraRotation.y += this.joystickRotationVecY;
|
||||
this.camera.cameraRotation.x += this.joystickRotationVecX;
|
||||
|
||||
this.camera.cameraDirection.addInPlace(
|
||||
BABYLON.Vector3.TransformCoordinates(this.joystickMoveVector, BABYLON.Matrix.RotationY(this.camera.rotation.y)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user