mirror of
https://github.com/isledecomp/isle.pizza.git
synced 2026-03-01 06:17:38 +00:00
Add drag-to-orbit controls to vehicle and actor editors
Use Three.js OrbitControls in BaseRenderer for rotation-only orbiting with damping. Vehicle editor auto-rotates and resets on part switch. Actor editor uses orbit without auto-rotate (has skeletal animations). Drag vs click detection uses pointermove threshold to avoid false positives from autoRotate damping.
This commit is contained in:
parent
f796ea4299
commit
fe9117dd5e
@ -94,6 +94,9 @@ export class ActorRenderer extends BaseRenderer {
|
|||||||
this.camera.position.set(2, 0.8, 3.5);
|
this.camera.position.set(2, 0.8, 3.5);
|
||||||
this.camera.lookAt(0, 0.2, 0);
|
this.camera.lookAt(0, 0.2, 0);
|
||||||
|
|
||||||
|
this.setupControls(new THREE.Vector3(0, 0.2, 0));
|
||||||
|
this.controls.autoRotate = false;
|
||||||
|
|
||||||
this.raycaster = new THREE.Raycaster();
|
this.raycaster = new THREE.Raycaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,17 +890,13 @@ export class ActorRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
if (this.mixer) {
|
if (this.mixer) {
|
||||||
this.mixer.update(delta);
|
this.mixer.update(delta);
|
||||||
} else if (this.modelGroup) {
|
|
||||||
// Fallback: rotate if no animation loaded
|
|
||||||
this.modelGroup.rotation.y += 0.01;
|
|
||||||
}
|
}
|
||||||
|
this.controls?.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.animating = false;
|
|
||||||
this.stopAnimation();
|
this.stopAnimation();
|
||||||
this.clearModel();
|
super.dispose();
|
||||||
this.renderer?.dispose();
|
|
||||||
this.animationCache.clear();
|
this.animationCache.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base renderer providing shared Three.js setup, lighting, texture creation,
|
* Base renderer providing shared Three.js setup, lighting, texture creation,
|
||||||
@ -24,6 +25,9 @@ export class BaseRenderer {
|
|||||||
this.renderer.setClearColor(0x000000, 0);
|
this.renderer.setClearColor(0x000000, 0);
|
||||||
|
|
||||||
this.setupLighting();
|
this.setupLighting();
|
||||||
|
|
||||||
|
this.controls = null;
|
||||||
|
this._didDrag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupLighting() {
|
setupLighting() {
|
||||||
@ -35,6 +39,39 @@ export class BaseRenderer {
|
|||||||
this.scene.add(sunLight);
|
this.scene.add(sunLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupControls(target) {
|
||||||
|
this.controls = new OrbitControls(this.camera, this.canvas);
|
||||||
|
this.controls.enableZoom = false;
|
||||||
|
this.controls.enablePan = false;
|
||||||
|
this.controls.enableDamping = true;
|
||||||
|
this.controls.dampingFactor = 0.1;
|
||||||
|
this.controls.autoRotate = true;
|
||||||
|
this.controls.autoRotateSpeed = 4.0;
|
||||||
|
this.controls.target.copy(target);
|
||||||
|
|
||||||
|
this.controls.addEventListener('start', () => {
|
||||||
|
this.controls.autoRotate = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._onPointerDown = (e) => {
|
||||||
|
this._didDrag = false;
|
||||||
|
this._pointerStart = { x: e.clientX, y: e.clientY };
|
||||||
|
};
|
||||||
|
this._onPointerMove = (e) => {
|
||||||
|
if (!this._pointerStart) return;
|
||||||
|
const dx = e.clientX - this._pointerStart.x;
|
||||||
|
const dy = e.clientY - this._pointerStart.y;
|
||||||
|
if (dx * dx + dy * dy > 9) this._didDrag = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.canvas.addEventListener('pointerdown', this._onPointerDown);
|
||||||
|
this.canvas.addEventListener('pointermove', this._onPointerMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
wasDragged() {
|
||||||
|
return this._didDrag;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Three.js texture from parsed texture data
|
* Create a Three.js texture from parsed texture data
|
||||||
*/
|
*/
|
||||||
@ -190,9 +227,7 @@ export class BaseRenderer {
|
|||||||
* Called each frame before rendering.
|
* Called each frame before rendering.
|
||||||
*/
|
*/
|
||||||
updateAnimation() {
|
updateAnimation() {
|
||||||
if (this.modelGroup) {
|
this.controls?.update();
|
||||||
this.modelGroup.rotation.y += 0.01;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resize(width, height) {
|
resize(width, height) {
|
||||||
@ -203,6 +238,11 @@ export class BaseRenderer {
|
|||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.animating = false;
|
this.animating = false;
|
||||||
|
if (this.controls) {
|
||||||
|
this.controls.dispose();
|
||||||
|
this.canvas.removeEventListener('pointerdown', this._onPointerDown);
|
||||||
|
this.canvas.removeEventListener('pointermove', this._onPointerMove);
|
||||||
|
}
|
||||||
this.clearModel();
|
this.clearModel();
|
||||||
this.renderer?.dispose();
|
this.renderer?.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ export class VehiclePartRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
this.camera.position.set(0, 0, 3);
|
this.camera.position.set(0, 0, 3);
|
||||||
this.camera.lookAt(0, 0, 0);
|
this.camera.lookAt(0, 0, 0);
|
||||||
|
|
||||||
|
this.setupControls(new THREE.Vector3(0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,6 +58,7 @@ export class VehiclePartRenderer extends BaseRenderer {
|
|||||||
this.centerAndScaleModel(1.5);
|
this.centerAndScaleModel(1.5);
|
||||||
|
|
||||||
this.scene.add(this.modelGroup);
|
this.scene.add(this.modelGroup);
|
||||||
|
this.controls.autoRotate = true;
|
||||||
this.renderer.render(this.scene, this.camera);
|
this.renderer.render(this.scene, this.camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -203,6 +203,7 @@
|
|||||||
|
|
||||||
function handleCanvasClick(event) {
|
function handleCanvasClick(event) {
|
||||||
if (!renderer || !slot?.characters || !charState) return;
|
if (!renderer || !slot?.characters || !charState) return;
|
||||||
|
if (renderer.wasDragged()) return;
|
||||||
|
|
||||||
const playerId = slot.header?.actorId;
|
const playerId = slot.header?.actorId;
|
||||||
let acted = false;
|
let acted = false;
|
||||||
@ -393,10 +394,14 @@
|
|||||||
canvas {
|
canvas {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: grab;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
canvas:focus {
|
canvas:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -251,6 +251,7 @@
|
|||||||
|
|
||||||
function cycleColor() {
|
function cycleColor() {
|
||||||
if (!currentPart || partError) return;
|
if (!currentPart || partError) return;
|
||||||
|
if (renderer?.wasDragged()) return;
|
||||||
|
|
||||||
// Find current color index and cycle to next
|
// Find current color index and cycle to next
|
||||||
const currentIdx = LegoColorNames.indexOf(currentColorValue);
|
const currentIdx = LegoColorNames.indexOf(currentColorValue);
|
||||||
@ -398,10 +399,14 @@
|
|||||||
canvas {
|
canvas {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: grab;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
canvas:focus {
|
canvas:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user