mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Improved quest editor camera controls.
This commit is contained in:
parent
04a7798f96
commit
bbfc4403ff
@ -35,6 +35,7 @@ export class QuestEditorRendererView extends QuestRendererView {
|
||||
|
||||
this.element.addEventListener("focus", () => quest_editor_store.undo.make_current(), true);
|
||||
|
||||
// Must be initialized before camera controls.
|
||||
this.entity_controls = this.disposable(
|
||||
new QuestEntityControls(quest_editor_store, this.renderer),
|
||||
);
|
||||
@ -50,6 +51,7 @@ export class QuestEditorRendererView extends QuestRendererView {
|
||||
),
|
||||
);
|
||||
|
||||
// Must be initialized after QuestEntityControls.
|
||||
this.renderer.init_camera_controls();
|
||||
|
||||
this.finalize_construction(QuestEditorRendererView);
|
||||
|
@ -1,9 +1,18 @@
|
||||
import { QuestEntityModel } from "../model/QuestEntityModel";
|
||||
import { Euler, Intersection, Mesh, Plane, Quaternion, Raycaster, Vector2, Vector3 } from "three";
|
||||
import {
|
||||
Euler,
|
||||
Intersection,
|
||||
Mesh,
|
||||
Object3D,
|
||||
Plane,
|
||||
Quaternion,
|
||||
Raycaster,
|
||||
Vector2,
|
||||
Vector3,
|
||||
} from "three";
|
||||
import { QuestRenderer } from "./QuestRenderer";
|
||||
import { EntityUserData } from "./conversion/entities";
|
||||
import { QuestNpcModel } from "../model/QuestNpcModel";
|
||||
import { AreaUserData } from "./conversion/areas";
|
||||
import { SectionModel } from "../model/SectionModel";
|
||||
import { Disposable } from "../../core/observable/Disposable";
|
||||
import { Disposer } from "../../core/observable/Disposer";
|
||||
@ -21,10 +30,10 @@ import { CreateEntityAction } from "../actions/CreateEntityAction";
|
||||
import { RotateEntityAction } from "../actions/RotateEntityAction";
|
||||
import { RemoveEntityAction } from "../actions/RemoveEntityAction";
|
||||
import { TranslateEntityAction } from "../actions/TranslateEntityAction";
|
||||
import { Object3D } from "three/src/core/Object3D";
|
||||
import { create_quest_npc } from "../../core/data_formats/parsing/quest/QuestNpc";
|
||||
import { create_quest_object } from "../../core/data_formats/parsing/quest/QuestObject";
|
||||
import { Episode } from "../../core/data_formats/parsing/quest/Episode";
|
||||
import { pick_ground } from "./pick_ground";
|
||||
|
||||
const ZERO_VECTOR = Object.freeze(new Vector3(0, 0, 0));
|
||||
const UP_VECTOR = Object.freeze(new Vector3(0, 1, 0));
|
||||
@ -917,38 +926,6 @@ const rotate_entity = (() => {
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* @param renderer
|
||||
* @param pointer_pos - pointer coordinates in normalized device space
|
||||
* @param drag_adjust - vector from origin of entity to grabbing point
|
||||
*/
|
||||
function pick_ground(
|
||||
renderer: QuestRenderer,
|
||||
pointer_pos: Vector2,
|
||||
drag_adjust: Vector3,
|
||||
): {
|
||||
intersection?: Intersection;
|
||||
section?: SectionModel;
|
||||
} {
|
||||
raycaster.setFromCamera(pointer_pos, renderer.camera);
|
||||
raycaster.ray.origin.add(drag_adjust);
|
||||
const intersections = raycaster.intersectObjects(renderer.collision_geometry.children, true);
|
||||
|
||||
// Don't allow entities to be placed on very steep terrain.
|
||||
// E.g. walls.
|
||||
// TODO: make use of the flags field in the collision data.
|
||||
for (const intersection of intersections) {
|
||||
if (intersection.face!.normal.y > 0.75) {
|
||||
return {
|
||||
intersection,
|
||||
section: (intersection.object.userData as AreaUserData).section,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
const pick_nearest_visible_object = (() => {
|
||||
const intersections: Intersection[] = [];
|
||||
|
||||
|
@ -1,10 +1,22 @@
|
||||
import { DisposableThreeRenderer, Renderer } from "../../core/rendering/Renderer";
|
||||
import { Group, Mesh, MeshLambertMaterial, Object3D, PerspectiveCamera } from "three";
|
||||
import {
|
||||
Group,
|
||||
Mesh,
|
||||
MeshLambertMaterial,
|
||||
Object3D,
|
||||
PerspectiveCamera,
|
||||
Vector2,
|
||||
Vector3,
|
||||
} from "three";
|
||||
import { QuestEntityModel } from "../model/QuestEntityModel";
|
||||
import { Quest3DModelManager } from "./Quest3DModelManager";
|
||||
import { Disposer } from "../../core/observable/Disposer";
|
||||
import { ColorType, EntityUserData, NPC_COLORS, OBJECT_COLORS } from "./conversion/entities";
|
||||
import { QuestNpcModel } from "../model/QuestNpcModel";
|
||||
import { pick_ground } from "./pick_ground";
|
||||
|
||||
const ZERO_VECTOR_2 = Object.freeze(new Vector2(0, 0));
|
||||
const ZERO_VECTOR_3 = Object.freeze(new Vector3(0, 0, 0));
|
||||
|
||||
export class QuestRenderer extends Renderer {
|
||||
private _collision_geometry = new Object3D();
|
||||
@ -14,6 +26,8 @@ export class QuestRenderer extends Renderer {
|
||||
private readonly entity_to_mesh = new Map<QuestEntityModel, Mesh>();
|
||||
private hovered_mesh?: Mesh;
|
||||
private selected_mesh?: Mesh;
|
||||
private camera_target_timeout?: number;
|
||||
private old_camera_target = new Vector3();
|
||||
|
||||
get debug(): boolean {
|
||||
return super.debug;
|
||||
@ -27,7 +41,7 @@ export class QuestRenderer extends Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
readonly camera = new PerspectiveCamera(60, 1, 10, 10000);
|
||||
readonly camera = new PerspectiveCamera(60, 1, 10, 5_000);
|
||||
|
||||
get collision_geometry(): Object3D {
|
||||
return this._collision_geometry;
|
||||
@ -68,6 +82,11 @@ export class QuestRenderer extends Renderer {
|
||||
*/
|
||||
init_camera_controls(): void {
|
||||
super.init_camera_controls();
|
||||
|
||||
this.controls.verticalDragToForward = true;
|
||||
this.controls.truckSpeed = 2.5;
|
||||
|
||||
this.controls.addEventListener("update", this.camera_controls_updated);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@ -159,6 +178,40 @@ export class QuestRenderer extends Renderer {
|
||||
|
||||
this.selected_mesh = undefined;
|
||||
}
|
||||
|
||||
protected render(): void {
|
||||
const distance = this.controls.distance;
|
||||
this.camera.near = distance / 100;
|
||||
this.camera.far = Math.max(1_000, distance * 5);
|
||||
this.camera.updateProjectionMatrix();
|
||||
super.render();
|
||||
}
|
||||
|
||||
private camera_controls_updated = (): void => {
|
||||
window.clearTimeout(this.camera_target_timeout);
|
||||
// If we call update_camera_target directly here, the camera will
|
||||
// randomly rotate when panning and releasing the mouse button quickly.
|
||||
// No idea why, but wrapping this call in a timeout makes it work.
|
||||
this.camera_target_timeout = window.setTimeout(this.update_camera_target, 100);
|
||||
};
|
||||
|
||||
private update_camera_target = (): void => {
|
||||
// If the user moved the camera, try setting the camera
|
||||
// target to a better point.
|
||||
this.controls.updateCameraUp();
|
||||
const { intersection } = pick_ground(this, ZERO_VECTOR_2, ZERO_VECTOR_3);
|
||||
|
||||
if (intersection) {
|
||||
this.controls.getTarget(this.old_camera_target);
|
||||
const new_target = intersection.point;
|
||||
|
||||
if (new_target.distanceTo(this.old_camera_target) > 10) {
|
||||
this.controls.setTarget(new_target.x, new_target.y, new_target.z);
|
||||
}
|
||||
}
|
||||
|
||||
this.camera_target_timeout = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
function set_color(mesh: Mesh, type: ColorType): void {
|
||||
|
38
src/quest_editor/rendering/pick_ground.ts
Normal file
38
src/quest_editor/rendering/pick_ground.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { QuestRenderer } from "./QuestRenderer";
|
||||
import { Intersection, Raycaster, Vector2, Vector3 } from "three";
|
||||
import { SectionModel } from "../model/SectionModel";
|
||||
import { AreaUserData } from "./conversion/areas";
|
||||
|
||||
const raycaster = new Raycaster();
|
||||
|
||||
/**
|
||||
* @param renderer
|
||||
* @param pointer_pos - pointer coordinates in normalized device space
|
||||
* @param drag_adjust - vector from origin of entity to grabbing point
|
||||
*/
|
||||
export function pick_ground(
|
||||
renderer: QuestRenderer,
|
||||
pointer_pos: Vector2,
|
||||
drag_adjust: Vector3,
|
||||
): {
|
||||
intersection?: Intersection;
|
||||
section?: SectionModel;
|
||||
} {
|
||||
raycaster.setFromCamera(pointer_pos, renderer.camera);
|
||||
raycaster.ray.origin.add(drag_adjust);
|
||||
const intersections = raycaster.intersectObjects(renderer.collision_geometry.children, true);
|
||||
|
||||
// Don't allow entities to be placed on very steep terrain.
|
||||
// E.g. walls.
|
||||
// TODO: make use of the flags field in the collision data.
|
||||
for (const intersection of intersections) {
|
||||
if (intersection.face!.normal.y > 0.75) {
|
||||
return {
|
||||
intersection,
|
||||
section: (intersection.object.userData as AreaUserData).section,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
Loading…
Reference in New Issue
Block a user