Entity creation is now undoable. Fixed a bug that occurred when you started to translate an entity and then moved the cursor outside of the 3D-view.

This commit is contained in:
Daan Vanden Bosch 2019-09-21 14:39:04 +02:00
parent a97b56cecc
commit 79b85fc859
8 changed files with 161 additions and 96 deletions

View File

@ -1,7 +1,6 @@
import CameraControls from "camera-controls"; import CameraControls from "camera-controls";
import * as THREE from "three"; import * as THREE from "three";
import { import {
Camera,
Clock, Clock,
Color, Color,
Group, Group,
@ -34,8 +33,8 @@ export abstract class Renderer implements Disposable {
this._debug = debug; this._debug = debug;
} }
readonly camera: Camera; abstract readonly camera: PerspectiveCamera | OrthographicCamera;
readonly controls: CameraControls; readonly controls!: CameraControls;
readonly scene = new Scene(); readonly scene = new Scene();
readonly light_holder = new Group(); readonly light_holder = new Group();
@ -44,23 +43,19 @@ export abstract class Renderer implements Disposable {
private animation_frame_handle?: number = undefined; private animation_frame_handle?: number = undefined;
private light = new HemisphereLight(0xffffff, 0x505050, 1.2); private light = new HemisphereLight(0xffffff, 0x505050, 1.2);
private controls_clock = new Clock(); private controls_clock = new Clock();
private size = new Vector2();
protected constructor(camera: PerspectiveCamera | OrthographicCamera) { protected constructor() {
this.camera = camera;
this.dom_element.tabIndex = 0; this.dom_element.tabIndex = 0;
this.dom_element.addEventListener("mousedown", this.on_mouse_down); this.dom_element.addEventListener("mousedown", this.on_mouse_down);
this.dom_element.style.outline = "none"; this.dom_element.style.outline = "none";
this.controls = new CameraControls(camera, this.renderer.domElement);
this.controls.dampingFactor = 1;
this.controls.draggingDampingFactor = 1;
this.scene.background = new Color(0x181818); this.scene.background = new Color(0x181818);
this.light_holder.add(this.light); this.light_holder.add(this.light);
this.scene.add(this.light_holder); this.scene.add(this.light_holder);
this.renderer.setPixelRatio(window.devicePixelRatio); this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.getSize(this.size);
} }
get dom_element(): HTMLElement { get dom_element(): HTMLElement {
@ -68,15 +63,13 @@ export abstract class Renderer implements Disposable {
} }
set_size(width: number, height: number): void { set_size(width: number, height: number): void {
this.size.set(width, height);
this.renderer.setSize(width, height); this.renderer.setSize(width, height);
this.schedule_render(); this.schedule_render();
} }
pointer_pos_to_device_coords(e: MouseEvent): Vector2 { pointer_pos_to_device_coords(pos: Vector2): void {
const coords = this.renderer.getSize(new Vector2()); pos.set((pos.x / this.size.width) * 2 - 1, (pos.y / this.size.height) * -2 + 1);
coords.width = (e.offsetX / coords.width) * 2 - 1;
coords.height = (e.offsetY / coords.height) * -2 + 1;
return coords;
} }
start_rendering(): void { start_rendering(): void {
@ -108,6 +101,16 @@ export abstract class Renderer implements Disposable {
dispose(): void { dispose(): void {
this.renderer.dispose(); this.renderer.dispose();
this.controls.dispose();
}
protected init_camera_controls(): void {
(this.controls as CameraControls) = new CameraControls(
this.camera,
this.renderer.domElement,
);
this.controls.dampingFactor = 1;
this.controls.draggingDampingFactor = 1;
} }
protected render(): void { protected render(): void {

View File

@ -0,0 +1,30 @@
import { Action } from "../../core/undo/Action";
import { QuestEntityModel } from "../model/QuestEntityModel";
import { entity_data } from "../../core/data_formats/parsing/quest/entities";
import { quest_editor_store } from "../stores/QuestEditorStore";
export class CreateEntityAction implements Action {
readonly description: string;
constructor(private entity: QuestEntityModel) {
this.description = `Create ${entity_data(entity.type).name}`;
}
undo(): void {
const quest = quest_editor_store.current_quest.val;
if (quest) {
quest.remove_entity(this.entity);
}
}
redo(): void {
const quest = quest_editor_store.current_quest.val;
if (quest) {
quest.add_entity(this.entity);
quest_editor_store.set_selected_entity(this.entity);
}
}
}

View File

@ -12,6 +12,7 @@ import { area_store } from "../stores/AreaStore";
import { ListProperty } from "../../core/observable/property/list/ListProperty"; import { ListProperty } from "../../core/observable/property/list/ListProperty";
import { WritableListProperty } from "../../core/observable/property/list/WritableListProperty"; import { WritableListProperty } from "../../core/observable/property/list/WritableListProperty";
import { QuestEntityModel } from "./QuestEntityModel"; import { QuestEntityModel } from "./QuestEntityModel";
import { entity_type_to_string } from "../../core/data_formats/parsing/quest/entities";
const logger = Logger.get("quest_editor/model/QuestModel"); const logger = Logger.get("quest_editor/model/QuestModel");
@ -111,6 +112,7 @@ export class QuestModel {
private readonly _long_description: WritableProperty<string> = property(""); private readonly _long_description: WritableProperty<string> = property("");
private readonly _map_designations: WritableProperty<Map<number, number>>; private readonly _map_designations: WritableProperty<Map<number, number>>;
private readonly _area_variants: WritableListProperty<AreaVariantModel> = list_property(); private readonly _area_variants: WritableListProperty<AreaVariantModel> = list_property();
private readonly _objects: WritableListProperty<QuestObjectModel>;
private readonly _npcs: WritableListProperty<QuestNpcModel>; private readonly _npcs: WritableListProperty<QuestNpcModel>;
constructor( constructor(
@ -150,7 +152,8 @@ export class QuestModel {
this.episode = episode; this.episode = episode;
this._map_designations = property(map_designations); this._map_designations = property(map_designations);
this.map_designations = this._map_designations; this.map_designations = this._map_designations;
this.objects = list_property(undefined, ...objects); this._objects = list_property(undefined, ...objects);
this.objects = this._objects;
this._npcs = list_property(undefined, ...npcs); this._npcs = list_property(undefined, ...npcs);
this.npcs = this._npcs; this.npcs = this._npcs;
this.dat_unknowns = dat_unknowns; this.dat_unknowns = dat_unknowns;
@ -179,15 +182,31 @@ export class QuestModel {
this.map_designations.observe(this.update_area_variants); this.map_designations.observe(this.update_area_variants);
} }
add_entity(entity: QuestEntityModel): void {
if (entity instanceof QuestObjectModel) {
this.add_object(entity);
} else if (entity instanceof QuestNpcModel) {
this.add_npc(entity);
} else {
throw new Error(`${entity_type_to_string(entity.type)} not supported.`);
}
}
add_object(object: QuestObjectModel): void {
this._objects.push(object);
}
add_npc(npc: QuestNpcModel): void { add_npc(npc: QuestNpcModel): void {
this._npcs.push(npc); this._npcs.push(npc);
} }
remove_entity(entity: QuestEntityModel): void { remove_entity(entity: QuestEntityModel): void {
if (entity instanceof QuestNpcModel) { if (entity instanceof QuestObjectModel) {
this._objects.remove(entity);
} else if (entity instanceof QuestNpcModel) {
this._npcs.remove(entity); this._npcs.remove(entity);
} else { } else {
// TODO: objects throw new Error(`${entity_type_to_string(entity.type)} not supported.`);
} }
} }

View File

@ -61,6 +61,8 @@ export class QuestEntityControls implements Disposable {
* Iff defined, the user is transforming the selected entity. * Iff defined, the user is transforming the selected entity.
*/ */
private pick?: Pick; private pick?: Pick;
private pointer_position = new Vector2(0, 0);
private pointer_device_position = new Vector2(0, 0);
private last_pointer_position = new Vector2(0, 0); private last_pointer_position = new Vector2(0, 0);
private moved_since_last_mouse_down = false; private moved_since_last_mouse_down = false;
private disposer = new Disposer(); private disposer = new Disposer();
@ -82,8 +84,6 @@ export class QuestEntityControls implements Disposable {
); );
renderer.dom_element.addEventListener("mousedown", this.mousedown); renderer.dom_element.addEventListener("mousedown", this.mousedown);
renderer.dom_element.addEventListener("mousemove", this.mousemove);
renderer.dom_element.addEventListener("mouseup", this.mouseup);
add_entity_dnd_listener(renderer.dom_element, "dragenter", this.dragenter); add_entity_dnd_listener(renderer.dom_element, "dragenter", this.dragenter);
add_entity_dnd_listener(renderer.dom_element, "dragover", this.dragover); add_entity_dnd_listener(renderer.dom_element, "dragover", this.dragover);
add_entity_dnd_listener(renderer.dom_element, "dragleave", this.dragleave); add_entity_dnd_listener(renderer.dom_element, "dragleave", this.dragleave);
@ -92,8 +92,8 @@ export class QuestEntityControls implements Disposable {
dispose(): void { dispose(): void {
this.renderer.dom_element.removeEventListener("mousedown", this.mousedown); this.renderer.dom_element.removeEventListener("mousedown", this.mousedown);
this.renderer.dom_element.removeEventListener("mousemove", this.mousemove); document.removeEventListener("mousemove", this.doc_mousemove);
this.renderer.dom_element.removeEventListener("mouseup", this.mouseup); document.removeEventListener("mouseup", this.doc_mouseup);
remove_entity_dnd_listener(this.renderer.dom_element, "dragenter", this.dragenter); remove_entity_dnd_listener(this.renderer.dom_element, "dragenter", this.dragenter);
remove_entity_dnd_listener(this.renderer.dom_element, "dragover", this.dragover); remove_entity_dnd_listener(this.renderer.dom_element, "dragover", this.dragover);
remove_entity_dnd_listener(this.renderer.dom_element, "dragleave", this.dragleave); remove_entity_dnd_listener(this.renderer.dom_element, "dragleave", this.dragleave);
@ -120,9 +120,13 @@ export class QuestEntityControls implements Disposable {
private mousedown = (e: MouseEvent) => { private mousedown = (e: MouseEvent) => {
this.process_event(e); this.process_event(e);
document.addEventListener("mouseup", this.doc_mouseup);
document.addEventListener("mousemove", this.doc_mousemove);
this.stop_transforming(); this.stop_transforming();
const new_pick = this.pick_entity(this.renderer.pointer_pos_to_device_coords(e)); const new_pick = this.pick_entity(this.pointer_device_position);
if (new_pick) { if (new_pick) {
// Disable camera controls while the user is transforming an entity. // Disable camera controls while the user is transforming an entity.
@ -137,11 +141,9 @@ export class QuestEntityControls implements Disposable {
this.renderer.schedule_render(); this.renderer.schedule_render();
}; };
private mousemove = (e: MouseEvent) => { private doc_mousemove = (e: MouseEvent) => {
this.process_event(e); this.process_event(e);
const pointer_device_pos = this.renderer.pointer_pos_to_device_coords(e);
if (this.selected && this.pick) { if (this.selected && this.pick) {
if (this.moved_since_last_mouse_down) { if (this.moved_since_last_mouse_down) {
// User is transforming selected entity. // User is transforming selected entity.
@ -151,7 +153,7 @@ export class QuestEntityControls implements Disposable {
} }
} else { } else {
// User is hovering. // User is hovering.
const new_pick = this.pick_entity(pointer_device_pos); const new_pick = this.pick_entity(this.pointer_device_position);
if (this.mark_hovered(new_pick)) { if (this.mark_hovered(new_pick)) {
this.renderer.schedule_render(); this.renderer.schedule_render();
@ -159,10 +161,12 @@ export class QuestEntityControls implements Disposable {
} }
}; };
// TODO: deal with mouseup outside of 3D-view private doc_mouseup = (e: MouseEvent) => {
private mouseup = (e: MouseEvent) => {
this.process_event(e); this.process_event(e);
document.removeEventListener("mousemove", this.doc_mousemove);
document.removeEventListener("mouseup", this.doc_mouseup);
if (!this.moved_since_last_mouse_down && !this.pick) { if (!this.moved_since_last_mouse_down && !this.pick) {
// If the user clicks on nothing, deselect the currently selected entity. // If the user clicks on nothing, deselect the currently selected entity.
this.deselect(); this.deselect();
@ -176,6 +180,8 @@ export class QuestEntityControls implements Disposable {
}; };
private dragenter = (e: EntityDragEvent) => { private dragenter = (e: EntityDragEvent) => {
this.process_event(e.event);
const area = quest_editor_store.current_area.val; const area = quest_editor_store.current_area.val;
const quest = quest_editor_store.current_quest.val; const quest = quest_editor_store.current_quest.val;
@ -208,7 +214,7 @@ export class QuestEntityControls implements Disposable {
); );
const grab_offset = new Vector3(0, 0, 0); const grab_offset = new Vector3(0, 0, 0);
const drag_adjust = new Vector3(0, 0, 0); const drag_adjust = new Vector3(0, 0, 0);
this.translate_entity_horizontally(npc, e.event, grab_offset, drag_adjust); this.translate_entity_horizontally(npc, grab_offset, drag_adjust);
quest.add_npc(npc); quest.add_npc(npc);
quest_editor_store.set_selected_entity(npc); quest_editor_store.set_selected_entity(npc);
@ -224,6 +230,8 @@ export class QuestEntityControls implements Disposable {
}; };
private dragover = (e: EntityDragEvent) => { private dragover = (e: EntityDragEvent) => {
this.process_event(e.event);
if (!quest_editor_store.current_area.val) return; if (!quest_editor_store.current_area.val) return;
if (this.pick && this.pick.mode === PickMode.Creating) { if (this.pick && this.pick.mode === PickMode.Creating) {
@ -242,6 +250,8 @@ export class QuestEntityControls implements Disposable {
}; };
private dragleave = (e: EntityDragEvent) => { private dragleave = (e: EntityDragEvent) => {
this.process_event(e.event);
if (!quest_editor_store.current_area.val) return; if (!quest_editor_store.current_area.val) return;
e.drag_element.style.display = "flex"; e.drag_element.style.display = "flex";
@ -253,24 +263,31 @@ export class QuestEntityControls implements Disposable {
} }
}; };
private drop = () => { private drop = (e: EntityDragEvent) => {
// TODO: push onto undo stack. this.process_event(e.event);
this.pick = undefined;
if (this.selected && this.pick && this.pick.mode === PickMode.Creating) {
quest_editor_store.push_create_entity_action(this.selected.entity);
this.pick = undefined;
}
}; };
private process_event(e: MouseEvent): void { private process_event(e: MouseEvent): void {
const { left, top } = this.renderer.dom_element.getBoundingClientRect();
this.pointer_position.set(e.clientX - left, e.clientY - top);
this.pointer_device_position.copy(this.pointer_position);
this.renderer.pointer_pos_to_device_coords(this.pointer_device_position);
if (e.type === "mousedown") { if (e.type === "mousedown") {
this.moved_since_last_mouse_down = false; this.moved_since_last_mouse_down = false;
} else { } else if (e.type === "mousemove" || e.type === "mouseup") {
if ( if (!this.pointer_position.equals(this.last_pointer_position)) {
e.offsetX !== this.last_pointer_position.x ||
e.offsetY !== this.last_pointer_position.y
) {
this.moved_since_last_mouse_down = true; this.moved_since_last_mouse_down = true;
} }
} }
this.last_pointer_position.set(e.offsetX, e.offsetY); this.last_pointer_position.copy(this.pointer_position);
} }
/** /**
@ -330,20 +347,10 @@ export class QuestEntityControls implements Disposable {
private translate_entity(e: MouseEvent, selected: Highlighted, pick: Pick): void { private translate_entity(e: MouseEvent, selected: Highlighted, pick: Pick): void {
if (e.shiftKey) { if (e.shiftKey) {
// Vertical movement. // Vertical movement.
this.translate_entity_vertically( this.translate_entity_vertically(selected.entity, pick.drag_adjust, pick.grab_offset);
selected.entity,
e,
pick.drag_adjust,
pick.grab_offset,
);
} else { } else {
// Horizontal movement across the ground. // Horizontal movement across the ground.
this.translate_entity_horizontally( this.translate_entity_horizontally(selected.entity, pick.drag_adjust, pick.grab_offset);
selected.entity,
e,
pick.drag_adjust,
pick.grab_offset,
);
} }
this.renderer.schedule_render(); this.renderer.schedule_render();
@ -351,15 +358,12 @@ export class QuestEntityControls implements Disposable {
private translate_entity_vertically( private translate_entity_vertically(
entity: QuestEntityModel, entity: QuestEntityModel,
e: MouseEvent,
drag_adjust: Vector3, drag_adjust: Vector3,
grab_offset: Vector3, grab_offset: Vector3,
): void { ): void {
const pointer_position = this.renderer.pointer_pos_to_device_coords(e);
// We intersect with a plane that's oriented toward the camera and that's coplanar with the // We intersect with a plane that's oriented toward the camera and that's coplanar with the
// point where the entity was grabbed. // point where the entity was grabbed.
this.raycaster.setFromCamera(pointer_position, this.renderer.camera); this.raycaster.setFromCamera(this.pointer_device_position, this.renderer.camera);
const ray = this.raycaster.ray; const ray = this.raycaster.ray;
const negative_world_dir = this.renderer.camera.getWorldDirection(new Vector3()).negate(); const negative_world_dir = this.renderer.camera.getWorldDirection(new Vector3()).negate();
@ -386,14 +390,14 @@ export class QuestEntityControls implements Disposable {
*/ */
private translate_entity_horizontally( private translate_entity_horizontally(
entity: QuestEntityModel, entity: QuestEntityModel,
e: MouseEvent,
drag_adjust: Vector3, drag_adjust: Vector3,
grab_offset: Vector3, grab_offset: Vector3,
): void { ): void {
const pointer_position = this.renderer.pointer_pos_to_device_coords(e);
// Cast ray adjusted for dragging entities. // Cast ray adjusted for dragging entities.
const { intersection, section } = this.pick_ground(pointer_position, drag_adjust); const { intersection, section } = this.pick_ground(
this.pointer_device_position,
drag_adjust,
);
if (intersection) { if (intersection) {
entity.set_world_position( entity.set_world_position(
@ -410,7 +414,7 @@ export class QuestEntityControls implements Disposable {
} else { } else {
// If the pointer is not over the ground, we translate the entity across the horizontal // If the pointer is not over the ground, we translate the entity across the horizontal
// plane in which the entity's origin lies. // plane in which the entity's origin lies.
this.raycaster.setFromCamera(pointer_position, this.renderer.camera); this.raycaster.setFromCamera(this.pointer_device_position, this.renderer.camera);
const ray = this.raycaster.ray; const ray = this.raycaster.ray;
const plane = new Plane( const plane = new Plane(
new Vector3(0, 1, 0), new Vector3(0, 1, 0),

View File

@ -8,6 +8,13 @@ import { QuestEntityControls } from "./QuestEntityControls";
import { EntityUserData } from "./conversion/entities"; import { EntityUserData } from "./conversion/entities";
export class QuestRenderer extends Renderer { export class QuestRenderer extends Renderer {
private _collision_geometry = new Object3D();
private _render_geometry = new Object3D();
private _entity_models = new Object3D();
private readonly disposer = new Disposer();
private readonly entity_to_mesh = new Map<QuestEntityModel, Mesh>();
private readonly entity_controls = this.disposer.add(new QuestEntityControls(this));
get debug(): boolean { get debug(): boolean {
return super.debug; return super.debug;
} }
@ -20,7 +27,7 @@ export class QuestRenderer extends Renderer {
} }
} }
private _collision_geometry = new Object3D(); readonly camera = new PerspectiveCamera(60, 1, 10, 10000);
get collision_geometry(): Object3D { get collision_geometry(): Object3D {
return this._collision_geometry; return this._collision_geometry;
@ -32,8 +39,6 @@ export class QuestRenderer extends Renderer {
this.scene.add(collision_geometry); this.scene.add(collision_geometry);
} }
private _render_geometry = new Object3D();
set render_geometry(render_geometry: Object3D) { set render_geometry(render_geometry: Object3D) {
this.scene.remove(this._render_geometry); this.scene.remove(this._render_geometry);
this._render_geometry = render_geometry; this._render_geometry = render_geometry;
@ -41,27 +46,24 @@ export class QuestRenderer extends Renderer {
this.scene.add(render_geometry); this.scene.add(render_geometry);
} }
private _entity_models = new Object3D();
get entity_models(): Object3D { get entity_models(): Object3D {
return this._entity_models; return this._entity_models;
} }
private readonly disposer = new Disposer();
private readonly perspective_camera: PerspectiveCamera;
private readonly entity_to_mesh = new Map<QuestEntityModel, Mesh>();
private readonly entity_controls = this.disposer.add(new QuestEntityControls(this));
constructor() { constructor() {
super(new PerspectiveCamera(60, 1, 10, 10000)); super();
this.perspective_camera = this.camera as PerspectiveCamera;
this.disposer.add_all( this.disposer.add_all(
new QuestModelManager(this), new QuestModelManager(this),
quest_editor_store.debug.observe(({ value }) => (this.debug = value)), quest_editor_store.debug.observe(({ value }) => (this.debug = value)),
); );
// Initialize camera-controls after QuestEntityControls to ensure correct order of event
// listener registration. This is a fragile work-around for the fact that camera-controls
// doesn't support intercepting pointer events.
this.init_camera_controls();
} }
dispose(): void { dispose(): void {
@ -70,8 +72,8 @@ export class QuestRenderer extends Renderer {
} }
set_size(width: number, height: number): void { set_size(width: number, height: number): void {
this.perspective_camera.aspect = width / height; this.camera.aspect = width / height;
this.perspective_camera.updateProjectionMatrix(); this.camera.updateProjectionMatrix();
super.set_size(width, height); super.set_size(width, height);
} }
@ -99,6 +101,7 @@ export class QuestRenderer extends Renderer {
const mesh = this.entity_to_mesh.get(entity); const mesh = this.entity_to_mesh.get(entity);
if (mesh) { if (mesh) {
this.entity_to_mesh.delete(entity);
this._entity_models.remove(mesh); this._entity_models.remove(mesh);
this.schedule_render(); this.schedule_render();
} }

View File

@ -25,6 +25,7 @@ import { EditIdAction } from "../actions/EditIdAction";
import { Episode } from "../../core/data_formats/parsing/quest/Episode"; import { Episode } from "../../core/data_formats/parsing/quest/Episode";
import { create_new_quest } from "./quest_creation"; import { create_new_quest } from "./quest_creation";
import Logger = require("js-logger"); import Logger = require("js-logger");
import { CreateEntityAction } from "../actions/CreateEntityAction";
const logger = Logger.get("quest_editor/gui/QuestEditorStore"); const logger = Logger.get("quest_editor/gui/QuestEditorStore");
@ -269,6 +270,10 @@ export class QuestEditorStore implements Disposable {
.redo(); .redo();
}; };
push_create_entity_action = (entity: QuestEntityModel) => {
this.undo.push(new CreateEntityAction(entity));
};
private async set_quest(quest?: QuestModel, filename?: string): Promise<void> { private async set_quest(quest?: QuestModel, filename?: string): Promise<void> {
this.undo.reset(); this.undo.reset();

View File

@ -27,7 +27,6 @@ import { Disposer } from "../../core/observable/Disposer";
import { ChangeEvent } from "../../core/observable/Observable"; import { ChangeEvent } from "../../core/observable/Observable";
export class Model3DRenderer extends Renderer implements Disposable { export class Model3DRenderer extends Renderer implements Disposable {
private readonly perspective_camera: PerspectiveCamera;
private readonly disposer = new Disposer(); private readonly disposer = new Disposer();
private readonly clock = new Clock(); private readonly clock = new Clock();
private mesh?: Object3D; private mesh?: Object3D;
@ -39,10 +38,10 @@ export class Model3DRenderer extends Renderer implements Disposable {
}; };
private update_animation_time = true; private update_animation_time = true;
constructor() { readonly camera = new PerspectiveCamera(75, 1, 1, 200);
super(new PerspectiveCamera(75, 1, 1, 200));
this.perspective_camera = this.camera as PerspectiveCamera; constructor() {
super();
this.disposer.add_all( this.disposer.add_all(
model_store.current_nj_data.observe(this.nj_data_or_xvm_changed), model_store.current_nj_data.observe(this.nj_data_or_xvm_changed),
@ -53,11 +52,13 @@ export class Model3DRenderer extends Renderer implements Disposable {
model_store.animation_frame_rate.observe(this.animation_frame_rate_changed), model_store.animation_frame_rate.observe(this.animation_frame_rate_changed),
model_store.animation_frame.observe(this.animation_frame_changed), model_store.animation_frame.observe(this.animation_frame_changed),
); );
this.init_camera_controls();
} }
set_size(width: number, height: number): void { set_size(width: number, height: number): void {
this.perspective_camera.aspect = width / height; this.camera.aspect = width / height;
this.perspective_camera.updateProjectionMatrix(); this.camera.updateProjectionMatrix();
super.set_size(width, height); super.set_size(width, height);
} }
@ -71,7 +72,7 @@ export class Model3DRenderer extends Renderer implements Disposable {
this.animation.mixer.update(this.clock.getDelta()); this.animation.mixer.update(this.clock.getDelta());
} }
this.light_holder.quaternion.copy(this.perspective_camera.quaternion); this.light_holder.quaternion.copy(this.camera.quaternion);
super.render(); super.render();
if (this.animation && !this.animation.action.paused) { if (this.animation && !this.animation.action.paused) {

View File

@ -12,24 +12,19 @@ import { Renderer } from "../../core/rendering/Renderer";
import { Disposer } from "../../core/observable/Disposer"; import { Disposer } from "../../core/observable/Disposer";
import { Xvm } from "../../core/data_formats/parsing/ninja/texture"; import { Xvm } from "../../core/data_formats/parsing/ninja/texture";
import { xvm_texture_to_texture } from "../../core/rendering/conversion/ninja_textures"; import { xvm_texture_to_texture } from "../../core/rendering/conversion/ninja_textures";
import Logger = require("js-logger");
import { texture_store } from "../stores/TextureStore"; import { texture_store } from "../stores/TextureStore";
import Logger = require("js-logger");
const logger = Logger.get("viewer/rendering/TextureRenderer"); const logger = Logger.get("viewer/rendering/TextureRenderer");
export class TextureRenderer extends Renderer implements Disposable { export class TextureRenderer extends Renderer implements Disposable {
private readonly ortho_camera: OrthographicCamera;
private readonly disposer = new Disposer(); private readonly disposer = new Disposer();
private readonly quad_meshes: Mesh[] = []; private readonly quad_meshes: Mesh[] = [];
readonly camera = new OrthographicCamera(-400, 400, 300, -300, 1, 10);
constructor() { constructor() {
super(new OrthographicCamera(-400, 400, 300, -300, 1, 10)); super();
this.ortho_camera = this.camera as OrthographicCamera;
this.controls.dollySpeed = -1;
this.controls.azimuthRotateSpeed = 0;
this.controls.polarRotateSpeed = 0;
this.disposer.add_all( this.disposer.add_all(
texture_store.current_xvm.observe(({ value: xvm }) => { texture_store.current_xvm.observe(({ value: xvm }) => {
@ -43,14 +38,19 @@ export class TextureRenderer extends Renderer implements Disposable {
this.schedule_render(); this.schedule_render();
}), }),
); );
this.init_camera_controls();
this.controls.dollySpeed = -1;
this.controls.azimuthRotateSpeed = 0;
this.controls.polarRotateSpeed = 0;
} }
set_size(width: number, height: number): void { set_size(width: number, height: number): void {
this.ortho_camera.left = -Math.floor(width / 2); this.camera.left = -Math.floor(width / 2);
this.ortho_camera.right = Math.ceil(width / 2); this.camera.right = Math.ceil(width / 2);
this.ortho_camera.top = Math.floor(height / 2); this.camera.top = Math.floor(height / 2);
this.ortho_camera.bottom = -Math.ceil(height / 2); this.camera.bottom = -Math.ceil(height / 2);
this.ortho_camera.updateProjectionMatrix(); this.camera.updateProjectionMatrix();
super.set_size(width, height); super.set_size(width, height);
} }