mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 15:28:29 +08:00
Simplified mesh creation. Some performance improvements. Added debug mode to quest viewer that shows per-section colored area render geometry.
This commit is contained in:
parent
a181847647
commit
f670718637
@ -4,18 +4,12 @@ import { Vec3 } from "../data_formats/vector";
|
|||||||
import { QuestEntity, QuestNpc, QuestObject, Section } from "../domain";
|
import { QuestEntity, QuestNpc, QuestObject, Section } from "../domain";
|
||||||
import { quest_editor_store } from "../stores/QuestEditorStore";
|
import { quest_editor_store } from "../stores/QuestEditorStore";
|
||||||
import { AreaUserData } from "./conversion/areas";
|
import { AreaUserData } from "./conversion/areas";
|
||||||
import {
|
import { ColorType, EntityUserData, NPC_COLORS, OBJECT_COLORS } from "./conversion/entities";
|
||||||
EntityUserData,
|
|
||||||
NPC_COLOR,
|
|
||||||
NPC_HIGHLIGHTED_COLOR,
|
|
||||||
NPC_SELECTED_COLOR,
|
|
||||||
OBJECT_COLOR,
|
|
||||||
OBJECT_HIGHLIGHTED_COLOR,
|
|
||||||
OBJECT_SELECTED_COLOR,
|
|
||||||
} from "./conversion/entities";
|
|
||||||
import { QuestRenderer } from "./QuestRenderer";
|
import { QuestRenderer } from "./QuestRenderer";
|
||||||
|
|
||||||
type Selection = {
|
const DOWN_VECTOR = new Vector3(0, -1, 0);
|
||||||
|
|
||||||
|
type Highlighted = {
|
||||||
entity: QuestEntity;
|
entity: QuestEntity;
|
||||||
mesh: Mesh;
|
mesh: Mesh;
|
||||||
};
|
};
|
||||||
@ -32,16 +26,10 @@ type PickResult = Pick & {
|
|||||||
mesh: Mesh;
|
mesh: Mesh;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ColorType {
|
|
||||||
Normal,
|
|
||||||
Highlighted,
|
|
||||||
Selected,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class QuestEntityControls {
|
export class QuestEntityControls {
|
||||||
private raycaster = new Raycaster();
|
private raycaster = new Raycaster();
|
||||||
private selected?: Selection;
|
private selected?: Highlighted;
|
||||||
private highlighted?: Selection;
|
private hovered?: Highlighted;
|
||||||
/**
|
/**
|
||||||
* Iff defined, the user is transforming the selected entity.
|
* Iff defined, the user is transforming the selected entity.
|
||||||
*/
|
*/
|
||||||
@ -118,8 +106,9 @@ export class QuestEntityControls {
|
|||||||
const pointer_device_pos = this.renderer.pointer_pos_to_device_coords(e);
|
const pointer_device_pos = this.renderer.pointer_pos_to_device_coords(e);
|
||||||
|
|
||||||
if (this.selected && this.pick) {
|
if (this.selected && this.pick) {
|
||||||
// User is tranforming selected entity.
|
if (this.moved_since_last_mouse_down) {
|
||||||
if (e.buttons === 1) {
|
if (e.buttons === 1) {
|
||||||
|
// User is tranforming selected entity.
|
||||||
// User is dragging selected entity.
|
// User is dragging selected entity.
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
// Vertical movement.
|
// Vertical movement.
|
||||||
@ -131,11 +120,12 @@ export class QuestEntityControls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.renderer.schedule_render();
|
this.renderer.schedule_render();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// User is hovering.
|
// User is hovering.
|
||||||
const new_pick = this.pick_entity(pointer_device_pos);
|
const new_pick = this.pick_entity(pointer_device_pos);
|
||||||
|
|
||||||
if (this.highlight(new_pick)) {
|
if (this.mark_hovered(new_pick)) {
|
||||||
this.renderer.schedule_render();
|
this.renderer.schedule_render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,32 +149,32 @@ export class QuestEntityControls {
|
|||||||
/**
|
/**
|
||||||
* @returns true if a render is required.
|
* @returns true if a render is required.
|
||||||
*/
|
*/
|
||||||
private highlight(selection?: Selection): boolean {
|
private mark_hovered(selection?: Highlighted): boolean {
|
||||||
let render_required = false;
|
let render_required = false;
|
||||||
|
|
||||||
if (!this.selected || !selection_equals(selection, this.selected)) {
|
if (!this.selected || !selection_equals(selection, this.selected)) {
|
||||||
if (!selection_equals(selection, this.highlighted)) {
|
if (!selection_equals(selection, this.hovered)) {
|
||||||
if (this.highlighted) {
|
if (this.hovered) {
|
||||||
set_color(this.highlighted, ColorType.Normal);
|
set_color(this.hovered, ColorType.Normal);
|
||||||
this.highlighted = undefined;
|
this.hovered = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection) {
|
if (selection) {
|
||||||
set_color(selection, ColorType.Highlighted);
|
set_color(selection, ColorType.Hovered);
|
||||||
}
|
}
|
||||||
|
|
||||||
render_required = true;
|
render_required = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.highlighted = selection;
|
this.hovered = selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
return render_required;
|
return render_required;
|
||||||
}
|
}
|
||||||
|
|
||||||
private select(selection: Selection): void {
|
private select(selection: Highlighted): void {
|
||||||
if (selection_equals(selection, this.highlighted)) {
|
if (selection_equals(selection, this.hovered)) {
|
||||||
this.highlighted = undefined;
|
this.hovered = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selection_equals(selection, this.selected)) {
|
if (!selection_equals(selection, this.selected)) {
|
||||||
@ -211,7 +201,7 @@ export class QuestEntityControls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private translate_vertically(
|
private translate_vertically(
|
||||||
selection: Selection,
|
selection: Highlighted,
|
||||||
pick: Pick,
|
pick: Pick,
|
||||||
pointer_position: Vector2
|
pointer_position: Vector2
|
||||||
): void {
|
): void {
|
||||||
@ -241,7 +231,7 @@ export class QuestEntityControls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private translate_horizontally(
|
private translate_horizontally(
|
||||||
selection: Selection,
|
selection: Highlighted,
|
||||||
pick: Pick,
|
pick: Pick,
|
||||||
pointer_position: Vector2
|
pointer_position: Vector2
|
||||||
): void {
|
): void {
|
||||||
@ -325,15 +315,15 @@ export class QuestEntityControls {
|
|||||||
let drag_y = 0;
|
let drag_y = 0;
|
||||||
|
|
||||||
// Find vertical distance to terrain.
|
// Find vertical distance to terrain.
|
||||||
this.raycaster.set(intersection.object.position, new Vector3(0, -1, 0));
|
this.raycaster.set(intersection.object.position, DOWN_VECTOR);
|
||||||
const [terrain] = this.raycaster.intersectObjects(
|
const [collision_geom_intersection] = this.raycaster.intersectObjects(
|
||||||
this.renderer.collision_geometry.children,
|
this.renderer.collision_geometry.children,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (terrain) {
|
if (collision_geom_intersection) {
|
||||||
drag_adjust.sub(new Vector3(0, terrain.distance, 0));
|
drag_adjust.y -= collision_geom_intersection.distance;
|
||||||
drag_y += terrain.distance;
|
drag_y += collision_geom_intersection.distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -358,7 +348,7 @@ export class QuestEntityControls {
|
|||||||
} {
|
} {
|
||||||
this.raycaster.setFromCamera(pointer_pos, this.renderer.camera);
|
this.raycaster.setFromCamera(pointer_pos, this.renderer.camera);
|
||||||
this.raycaster.ray.origin.add(data.drag_adjust);
|
this.raycaster.ray.origin.add(data.drag_adjust);
|
||||||
const terrains = this.raycaster.intersectObjects(
|
const intersections = this.raycaster.intersectObjects(
|
||||||
this.renderer.collision_geometry.children,
|
this.renderer.collision_geometry.children,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@ -366,19 +356,11 @@ export class QuestEntityControls {
|
|||||||
// Don't allow entities to be placed on very steep terrain.
|
// Don't allow entities to be placed on very steep terrain.
|
||||||
// E.g. walls.
|
// E.g. walls.
|
||||||
// TODO: make use of the flags field in the collision data.
|
// TODO: make use of the flags field in the collision data.
|
||||||
for (const terrain of terrains) {
|
for (const intersection of intersections) {
|
||||||
if (terrain.face!.normal.y > 0.75) {
|
if (intersection.face!.normal.y > 0.75) {
|
||||||
// Find section ID.
|
|
||||||
this.raycaster.set(terrain.point.clone().setY(1000), new Vector3(0, -1, 0));
|
|
||||||
const render_terrains = this.raycaster
|
|
||||||
.intersectObjects(this.renderer.render_geometry.children, true)
|
|
||||||
.filter(rt => (rt.object.userData as AreaUserData).section.id >= 0);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
intersection: terrain,
|
intersection,
|
||||||
section:
|
section: (intersection.object.userData as AreaUserData).section,
|
||||||
render_terrains[0] &&
|
|
||||||
(render_terrains[0].object.userData as AreaUserData).section,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,34 +369,24 @@ export class QuestEntityControls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_color({ entity, mesh }: Selection, type: ColorType): void {
|
function set_color({ entity, mesh }: Highlighted, type: ColorType): void {
|
||||||
const color = get_color(entity, type);
|
const color = entity instanceof QuestNpc ? NPC_COLORS[type] : OBJECT_COLORS[type];
|
||||||
|
|
||||||
if (mesh) {
|
if (mesh) {
|
||||||
for (const material of mesh.material as MeshLambertMaterial[]) {
|
if (Array.isArray(mesh.material)) {
|
||||||
if (type === ColorType.Normal && material.map) {
|
for (const mat of mesh.material as MeshLambertMaterial[]) {
|
||||||
material.color.set(0xffffff);
|
if (type === ColorType.Normal && mat.map) {
|
||||||
|
mat.color.set(0xffffff);
|
||||||
} else {
|
} else {
|
||||||
material.color.set(color);
|
mat.color.set(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
(mesh.material as MeshLambertMaterial).color.set(color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selection_equals(a?: Selection, b?: Selection): boolean {
|
function selection_equals(a?: Highlighted, b?: Highlighted): boolean {
|
||||||
return a && b ? a.entity === b.entity : a === b;
|
return a && b ? a.entity === b.entity : a === b;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_color(entity: QuestEntity, type: ColorType): number {
|
|
||||||
const is_npc = entity instanceof QuestNpc;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
case ColorType.Normal:
|
|
||||||
return is_npc ? NPC_COLOR : OBJECT_COLOR;
|
|
||||||
case ColorType.Highlighted:
|
|
||||||
return is_npc ? NPC_HIGHLIGHTED_COLOR : OBJECT_HIGHLIGHTED_COLOR;
|
|
||||||
case ColorType.Selected:
|
|
||||||
return is_npc ? NPC_SELECTED_COLOR : OBJECT_SELECTED_COLOR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Logger from "js-logger";
|
import Logger from "js-logger";
|
||||||
import { autorun, IReactionDisposer } from "mobx";
|
import { autorun, IReactionDisposer } from "mobx";
|
||||||
import { Mesh, Object3D, Vector3 } from "three";
|
import { Mesh, Object3D, Vector3, Raycaster, Intersection } from "three";
|
||||||
import { Area, Quest, QuestEntity } from "../domain";
|
import { Area, Quest, QuestEntity } from "../domain";
|
||||||
import { load_area_collision_geometry, load_area_render_geometry } from "../loading/areas";
|
import { load_area_collision_geometry, load_area_render_geometry } from "../loading/areas";
|
||||||
import {
|
import {
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
} from "../loading/entities";
|
} from "../loading/entities";
|
||||||
import { create_npc_mesh, create_object_mesh } from "./conversion/entities";
|
import { create_npc_mesh, create_object_mesh } from "./conversion/entities";
|
||||||
import { QuestRenderer } from "./QuestRenderer";
|
import { QuestRenderer } from "./QuestRenderer";
|
||||||
|
import { AreaUserData } from "./conversion/areas";
|
||||||
|
|
||||||
const logger = Logger.get("rendering/QuestModelManager");
|
const logger = Logger.get("rendering/QuestModelManager");
|
||||||
|
|
||||||
@ -55,6 +56,8 @@ export class QuestModelManager {
|
|||||||
variant_id
|
variant_id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.add_sections_to_collision_geometry(collision_geometry, render_geometry);
|
||||||
|
|
||||||
if (this.quest !== quest || this.area !== area) return;
|
if (this.quest !== quest || this.area !== area) return;
|
||||||
|
|
||||||
this.renderer.collision_geometry = collision_geometry;
|
this.renderer.collision_geometry = collision_geometry;
|
||||||
@ -101,6 +104,47 @@ export class QuestModelManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private add_sections_to_collision_geometry(
|
||||||
|
collision_geom: Object3D,
|
||||||
|
render_geom: Object3D
|
||||||
|
): void {
|
||||||
|
const raycaster = new Raycaster();
|
||||||
|
const origin = new Vector3();
|
||||||
|
const down = new Vector3(0, -1, 0);
|
||||||
|
const up = new Vector3(0, 1, 0);
|
||||||
|
|
||||||
|
for (const collision_area of collision_geom.children) {
|
||||||
|
(collision_area as Mesh).geometry.boundingBox.getCenter(origin);
|
||||||
|
|
||||||
|
raycaster.set(origin, down);
|
||||||
|
const intersection1 = raycaster
|
||||||
|
.intersectObject(render_geom, true)
|
||||||
|
.find(i => (i.object.userData as AreaUserData).section != null);
|
||||||
|
|
||||||
|
raycaster.set(origin, up);
|
||||||
|
const intersection2 = raycaster
|
||||||
|
.intersectObject(render_geom, true)
|
||||||
|
.find(i => (i.object.userData as AreaUserData).section != null);
|
||||||
|
|
||||||
|
let intersection: Intersection | undefined;
|
||||||
|
|
||||||
|
if (intersection1 && intersection2) {
|
||||||
|
intersection =
|
||||||
|
intersection1.distance <= intersection2.distance
|
||||||
|
? intersection1
|
||||||
|
: intersection2;
|
||||||
|
} else {
|
||||||
|
intersection = intersection1 || intersection2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersection) {
|
||||||
|
const cud = collision_area.userData as AreaUserData;
|
||||||
|
const rud = intersection.object.userData as AreaUserData;
|
||||||
|
cud.section = rud.section;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private update_entity_geometry(entity: QuestEntity, model: Mesh): void {
|
private update_entity_geometry(entity: QuestEntity, model: Mesh): void {
|
||||||
this.renderer.add_entity_model(model);
|
this.renderer.add_entity_model(model);
|
||||||
|
|
||||||
|
@ -15,6 +15,18 @@ export function get_quest_renderer(): QuestRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class QuestRenderer extends Renderer<PerspectiveCamera> {
|
export class QuestRenderer extends Renderer<PerspectiveCamera> {
|
||||||
|
get debug(): boolean {
|
||||||
|
return this._debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
set debug(debug: boolean) {
|
||||||
|
if (this._debug !== debug) {
|
||||||
|
this._debug = debug;
|
||||||
|
this._render_geometry.visible = debug;
|
||||||
|
this.schedule_render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _collision_geometry = new Object3D();
|
private _collision_geometry = new Object3D();
|
||||||
|
|
||||||
get collision_geometry(): Object3D {
|
get collision_geometry(): Object3D {
|
||||||
@ -34,9 +46,10 @@ export class QuestRenderer extends Renderer<PerspectiveCamera> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
// this.scene.add(render_geometry);
|
render_geometry.visible = this.debug;
|
||||||
|
this.scene.add(render_geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _entity_models = new Object3D();
|
private _entity_models = new Object3D();
|
||||||
|
@ -15,6 +15,16 @@ import OrbitControlsCreator from "three-orbit-controls";
|
|||||||
const OrbitControls = OrbitControlsCreator(THREE);
|
const OrbitControls = OrbitControlsCreator(THREE);
|
||||||
|
|
||||||
export class Renderer<C extends Camera> {
|
export class Renderer<C extends Camera> {
|
||||||
|
protected _debug = false;
|
||||||
|
|
||||||
|
get debug(): boolean {
|
||||||
|
return this._debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
set debug(debug: boolean) {
|
||||||
|
this._debug = debug;
|
||||||
|
}
|
||||||
|
|
||||||
readonly camera: C;
|
readonly camera: C;
|
||||||
readonly controls: any;
|
readonly controls: any;
|
||||||
readonly scene = new Scene();
|
readonly scene = new Scene();
|
||||||
|
@ -1,4 +1,19 @@
|
|||||||
import { BufferGeometry, Float32BufferAttribute, Uint16BufferAttribute, Vector3 } from "three";
|
import {
|
||||||
|
BufferGeometry,
|
||||||
|
Float32BufferAttribute,
|
||||||
|
Uint16BufferAttribute,
|
||||||
|
Vector3,
|
||||||
|
Bone,
|
||||||
|
} from "three";
|
||||||
|
|
||||||
|
export type BuilderData = {
|
||||||
|
created_by_geometry_builder: boolean;
|
||||||
|
/**
|
||||||
|
* Maps material indices to normalized material indices.
|
||||||
|
*/
|
||||||
|
normalized_material_indices: Map<number, number>;
|
||||||
|
bones: Bone[];
|
||||||
|
};
|
||||||
|
|
||||||
export type BuilderVec2 = {
|
export type BuilderVec2 = {
|
||||||
x: number;
|
x: number;
|
||||||
@ -22,10 +37,14 @@ export class GeometryBuilder {
|
|||||||
private normals: number[] = [];
|
private normals: number[] = [];
|
||||||
private uvs: number[] = [];
|
private uvs: number[] = [];
|
||||||
private indices: number[] = [];
|
private indices: number[] = [];
|
||||||
|
private bones: Bone[] = [];
|
||||||
private bone_indices: number[] = [];
|
private bone_indices: number[] = [];
|
||||||
private bone_weights: number[] = [];
|
private bone_weights: number[] = [];
|
||||||
private groups: VertexGroup[] = [];
|
private groups: VertexGroup[] = [];
|
||||||
private _max_material_index?: number;
|
/**
|
||||||
|
* Will contain all material indices used in {@link this.groups} and -1 for the dummy material.
|
||||||
|
*/
|
||||||
|
private material_indices = new Set<number>([-1]);
|
||||||
|
|
||||||
get vertex_count(): number {
|
get vertex_count(): number {
|
||||||
return this.positions.length / 3;
|
return this.positions.length / 3;
|
||||||
@ -35,10 +54,6 @@ export class GeometryBuilder {
|
|||||||
return this.indices.length;
|
return this.indices.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get max_material_index(): number | undefined {
|
|
||||||
return this._max_material_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_position(index: number): Vector3 {
|
get_position(index: number): Vector3 {
|
||||||
return new Vector3(
|
return new Vector3(
|
||||||
this.positions[3 * index],
|
this.positions[3 * index],
|
||||||
@ -65,14 +80,18 @@ export class GeometryBuilder {
|
|||||||
this.indices.push(index);
|
this.indices.push(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_bone(index: number, weight: number): void {
|
add_bone(bone: Bone): void {
|
||||||
|
this.bones.push(bone);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_bone_weight(index: number, weight: number): void {
|
||||||
this.bone_indices.push(index);
|
this.bone_indices.push(index);
|
||||||
this.bone_weights.push(weight);
|
this.bone_weights.push(weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_group(offset: number, size: number, material_id?: number): void {
|
add_group(offset: number, size: number, material_index?: number): void {
|
||||||
const last_group = this.groups[this.groups.length - 1];
|
const last_group = this.groups[this.groups.length - 1];
|
||||||
const mat_idx = material_id == null ? 0 : material_id + 1;
|
const mat_idx = material_index == null ? -1 : material_index;
|
||||||
|
|
||||||
if (last_group && last_group.material_index === mat_idx) {
|
if (last_group && last_group.material_index === mat_idx) {
|
||||||
last_group.size += size;
|
last_group.size += size;
|
||||||
@ -82,15 +101,14 @@ export class GeometryBuilder {
|
|||||||
size,
|
size,
|
||||||
material_index: mat_idx,
|
material_index: mat_idx,
|
||||||
});
|
});
|
||||||
|
this.material_indices.add(mat_idx);
|
||||||
this._max_material_index = this._max_material_index
|
|
||||||
? Math.max(this._max_material_index, mat_idx)
|
|
||||||
: mat_idx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
build(): BufferGeometry {
|
build(): BufferGeometry {
|
||||||
const geom = new BufferGeometry();
|
const geom = new BufferGeometry();
|
||||||
|
const data = geom.userData as BuilderData;
|
||||||
|
data.created_by_geometry_builder = true;
|
||||||
|
|
||||||
geom.addAttribute("position", new Float32BufferAttribute(this.positions, 3));
|
geom.addAttribute("position", new Float32BufferAttribute(this.positions, 3));
|
||||||
geom.addAttribute("normal", new Float32BufferAttribute(this.normals, 3));
|
geom.addAttribute("normal", new Float32BufferAttribute(this.normals, 3));
|
||||||
@ -98,15 +116,29 @@ export class GeometryBuilder {
|
|||||||
|
|
||||||
geom.setIndex(new Uint16BufferAttribute(this.indices, 1));
|
geom.setIndex(new Uint16BufferAttribute(this.indices, 1));
|
||||||
|
|
||||||
for (const group of this.groups) {
|
if (this.bone_indices.length && this.bones.length) {
|
||||||
geom.addGroup(group.offset, group.size, group.material_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.bone_indices.length) {
|
|
||||||
geom.addAttribute("skinIndex", new Uint16BufferAttribute(this.bone_indices, 4));
|
geom.addAttribute("skinIndex", new Uint16BufferAttribute(this.bone_indices, 4));
|
||||||
geom.addAttribute("skinWeight", new Float32BufferAttribute(this.bone_weights, 4));
|
geom.addAttribute("skinWeight", new Float32BufferAttribute(this.bone_weights, 4));
|
||||||
|
data.bones = this.bones;
|
||||||
|
} else {
|
||||||
|
data.bones = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize material indices.
|
||||||
|
const normalized_mat_idxs = new Map<number, number>();
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for (const mat_idx of [...this.material_indices].sort((a, b) => a - b)) {
|
||||||
|
normalized_mat_idxs.set(mat_idx, i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use normalized material indices in Three.js groups.
|
||||||
|
for (const group of this.groups) {
|
||||||
|
geom.addGroup(group.offset, group.size, normalized_mat_idxs.get(group.material_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
data.normalized_material_indices = normalized_mat_idxs;
|
||||||
|
|
||||||
geom.computeBoundingSphere();
|
geom.computeBoundingSphere();
|
||||||
geom.computeBoundingBox();
|
geom.computeBoundingBox();
|
||||||
|
|
||||||
|
@ -8,12 +8,13 @@ import {
|
|||||||
MeshLambertMaterial,
|
MeshLambertMaterial,
|
||||||
Object3D,
|
Object3D,
|
||||||
Vector3,
|
Vector3,
|
||||||
|
Color,
|
||||||
} from "three";
|
} from "three";
|
||||||
import { CollisionObject } from "../../data_formats/parsing/area_collision_geometry";
|
import { CollisionObject } from "../../data_formats/parsing/area_collision_geometry";
|
||||||
import { RenderObject } from "../../data_formats/parsing/area_geometry";
|
import { RenderObject } from "../../data_formats/parsing/area_geometry";
|
||||||
import { Section } from "../../domain";
|
import { Section } from "../../domain";
|
||||||
import { ninja_object_to_mesh, ninja_object_to_geometry_builder } from "./ninja_geometry";
|
|
||||||
import { GeometryBuilder } from "./GeometryBuilder";
|
import { GeometryBuilder } from "./GeometryBuilder";
|
||||||
|
import { ninja_object_to_geometry_builder } from "./ninja_geometry";
|
||||||
|
|
||||||
const materials = [
|
const materials = [
|
||||||
// Wall
|
// Wall
|
||||||
@ -65,6 +66,10 @@ const wireframe_materials = [
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export type AreaUserData = {
|
||||||
|
section?: Section;
|
||||||
|
};
|
||||||
|
|
||||||
export function area_collision_geometry_to_object_3d(object: CollisionObject): Object3D {
|
export function area_collision_geometry_to_object_3d(object: CollisionObject): Object3D {
|
||||||
const group = new Group();
|
const group = new Group();
|
||||||
|
|
||||||
@ -94,6 +99,9 @@ export function area_collision_geometry_to_object_3d(object: CollisionObject): O
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
geom.computeBoundingBox();
|
||||||
|
geom.computeBoundingSphere();
|
||||||
|
|
||||||
const mesh = new Mesh(geom, materials);
|
const mesh = new Mesh(geom, materials);
|
||||||
mesh.renderOrder = 1;
|
mesh.renderOrder = 1;
|
||||||
group.add(mesh);
|
group.add(mesh);
|
||||||
@ -106,20 +114,14 @@ export function area_collision_geometry_to_object_3d(object: CollisionObject): O
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AreaUserData = {
|
|
||||||
section: Section;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function area_geometry_to_sections_and_object_3d(
|
export function area_geometry_to_sections_and_object_3d(
|
||||||
object: RenderObject
|
object: RenderObject
|
||||||
): [Section[], Object3D] {
|
): [Section[], Object3D] {
|
||||||
const sections: Section[] = [];
|
const sections: Section[] = [];
|
||||||
const group = new Group();
|
const group = new Group();
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
for (const section of object.sections) {
|
for (const section of object.sections) {
|
||||||
const sec = new Section(section.id, section.position, section.rotation.y);
|
|
||||||
sections.push(sec);
|
|
||||||
|
|
||||||
const builder = new GeometryBuilder();
|
const builder = new GeometryBuilder();
|
||||||
|
|
||||||
for (const object of section.objects) {
|
for (const object of section.objects) {
|
||||||
@ -128,16 +130,23 @@ export function area_geometry_to_sections_and_object_3d(
|
|||||||
|
|
||||||
const mesh = new Mesh(
|
const mesh = new Mesh(
|
||||||
builder.build(),
|
builder.build(),
|
||||||
new MeshLambertMaterial({
|
new MeshBasicMaterial({
|
||||||
color: 0x44aaff,
|
color: new Color().setHSL((i++ % 7) / 7, 1, 0.5),
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.75,
|
opacity: 0.25,
|
||||||
side: DoubleSide,
|
side: DoubleSide,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
(mesh.userData as AreaUserData).section = sec;
|
|
||||||
group.add(mesh);
|
group.add(mesh);
|
||||||
|
|
||||||
|
mesh.position.set(section.position.x, section.position.y, section.position.z);
|
||||||
|
mesh.rotation.set(section.rotation.x, section.rotation.y, section.rotation.z);
|
||||||
|
|
||||||
|
if (section.id >= 0) {
|
||||||
|
const sec = new Section(section.id, section.position, section.rotation.y);
|
||||||
|
sections.push(sec);
|
||||||
|
(mesh.userData as AreaUserData).section = sec;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [sections, group];
|
return [sections, group];
|
||||||
|
82
src/rendering/conversion/create_mesh.ts
Normal file
82
src/rendering/conversion/create_mesh.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import {
|
||||||
|
BufferGeometry,
|
||||||
|
DoubleSide,
|
||||||
|
Material,
|
||||||
|
Mesh,
|
||||||
|
MeshLambertMaterial,
|
||||||
|
Skeleton,
|
||||||
|
SkinnedMesh,
|
||||||
|
} from "three";
|
||||||
|
import { BuilderData } from "./GeometryBuilder";
|
||||||
|
|
||||||
|
const DUMMY_MATERIAL = new MeshLambertMaterial({
|
||||||
|
color: 0x00ff00,
|
||||||
|
side: DoubleSide,
|
||||||
|
});
|
||||||
|
const DEFAULT_MATERIAL = new MeshLambertMaterial({
|
||||||
|
color: 0xff00ff,
|
||||||
|
side: DoubleSide,
|
||||||
|
});
|
||||||
|
const DEFAULT_SKINNED_MATERIAL = new MeshLambertMaterial({
|
||||||
|
skinning: true,
|
||||||
|
color: 0xff00ff,
|
||||||
|
side: DoubleSide,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function create_mesh(
|
||||||
|
geometry: BufferGeometry,
|
||||||
|
material?: Material | Material[],
|
||||||
|
default_material: Material = DEFAULT_MATERIAL
|
||||||
|
): Mesh {
|
||||||
|
return create(geometry, material, default_material, Mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create_skinned_mesh(
|
||||||
|
geometry: BufferGeometry,
|
||||||
|
material?: Material | Material[],
|
||||||
|
default_material: Material = DEFAULT_SKINNED_MATERIAL
|
||||||
|
): SkinnedMesh {
|
||||||
|
return create(geometry, material, default_material, SkinnedMesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
function create<M extends Mesh>(
|
||||||
|
geometry: BufferGeometry,
|
||||||
|
material: Material | Material[] | undefined,
|
||||||
|
default_material: Material,
|
||||||
|
mesh_constructor: new (geometry: BufferGeometry, material: Material | Material[]) => M
|
||||||
|
): M {
|
||||||
|
const {
|
||||||
|
created_by_geometry_builder,
|
||||||
|
normalized_material_indices: mat_idxs,
|
||||||
|
bones,
|
||||||
|
} = geometry.userData as BuilderData;
|
||||||
|
|
||||||
|
let mat: Material | Material[];
|
||||||
|
|
||||||
|
if (Array.isArray(material)) {
|
||||||
|
if (created_by_geometry_builder) {
|
||||||
|
mat = [DUMMY_MATERIAL];
|
||||||
|
|
||||||
|
for (const [idx, normalized_idx] of mat_idxs.entries()) {
|
||||||
|
if (normalized_idx > 0) {
|
||||||
|
mat[normalized_idx] = material[idx] || default_material;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mat = material;
|
||||||
|
}
|
||||||
|
} else if (material) {
|
||||||
|
mat = material;
|
||||||
|
} else {
|
||||||
|
mat = default_material;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mesh = new mesh_constructor(geometry, mat);
|
||||||
|
|
||||||
|
if (created_by_geometry_builder && bones.length && mesh instanceof SkinnedMesh) {
|
||||||
|
mesh.add(bones[0]);
|
||||||
|
mesh.bind(new Skeleton(bones));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
@ -1,20 +1,22 @@
|
|||||||
import {
|
import { BufferGeometry, DoubleSide, Mesh, MeshLambertMaterial, Texture } from "three";
|
||||||
BufferGeometry,
|
|
||||||
DoubleSide,
|
|
||||||
Mesh,
|
|
||||||
MeshBasicMaterial,
|
|
||||||
MeshLambertMaterial,
|
|
||||||
Texture,
|
|
||||||
Material,
|
|
||||||
} from "three";
|
|
||||||
import { QuestEntity, QuestNpc, QuestObject } from "../../domain";
|
import { QuestEntity, QuestNpc, QuestObject } from "../../domain";
|
||||||
|
import { create_mesh } from "./create_mesh";
|
||||||
|
|
||||||
export const OBJECT_COLOR = 0xffff00;
|
export enum ColorType {
|
||||||
export const OBJECT_HIGHLIGHTED_COLOR = 0xffdf3f;
|
Normal,
|
||||||
export const OBJECT_SELECTED_COLOR = 0xffaa00;
|
Hovered,
|
||||||
export const NPC_COLOR = 0xff0000;
|
Selected,
|
||||||
export const NPC_HIGHLIGHTED_COLOR = 0xff3f5f;
|
}
|
||||||
export const NPC_SELECTED_COLOR = 0xff0054;
|
|
||||||
|
export const OBJECT_COLORS: number[] = [];
|
||||||
|
OBJECT_COLORS[ColorType.Normal] = 0xffff00;
|
||||||
|
OBJECT_COLORS[ColorType.Hovered] = 0xffdf3f;
|
||||||
|
OBJECT_COLORS[ColorType.Selected] = 0xffaa00;
|
||||||
|
|
||||||
|
export const NPC_COLORS: number[] = [];
|
||||||
|
NPC_COLORS[ColorType.Normal] = 0xff0000;
|
||||||
|
NPC_COLORS[ColorType.Hovered] = 0xff3f5f;
|
||||||
|
NPC_COLORS[ColorType.Selected] = 0xff0054;
|
||||||
|
|
||||||
export type EntityUserData = {
|
export type EntityUserData = {
|
||||||
entity: QuestEntity;
|
entity: QuestEntity;
|
||||||
@ -25,7 +27,7 @@ export function create_object_mesh(
|
|||||||
geometry: BufferGeometry,
|
geometry: BufferGeometry,
|
||||||
textures: Texture[]
|
textures: Texture[]
|
||||||
): Mesh {
|
): Mesh {
|
||||||
return create_mesh(object, geometry, textures, OBJECT_COLOR, "Object");
|
return create(object, geometry, textures, OBJECT_COLORS[ColorType.Normal], object.type.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function create_npc_mesh(
|
export function create_npc_mesh(
|
||||||
@ -33,27 +35,25 @@ export function create_npc_mesh(
|
|||||||
geometry: BufferGeometry,
|
geometry: BufferGeometry,
|
||||||
textures: Texture[]
|
textures: Texture[]
|
||||||
): Mesh {
|
): Mesh {
|
||||||
return create_mesh(npc, geometry, textures, NPC_COLOR, "NPC");
|
return create(npc, geometry, textures, NPC_COLORS[ColorType.Normal], npc.type.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_mesh(
|
function create(
|
||||||
entity: QuestEntity,
|
entity: QuestEntity,
|
||||||
geometry: BufferGeometry,
|
geometry: BufferGeometry,
|
||||||
textures: Texture[],
|
textures: Texture[],
|
||||||
color: number,
|
color: number,
|
||||||
type: string
|
name: string
|
||||||
): Mesh {
|
): Mesh {
|
||||||
const max_mat_idx = geometry.groups.reduce((max, g) => Math.max(max, g.materialIndex || 0), 0);
|
const default_material = new MeshLambertMaterial({
|
||||||
|
|
||||||
const materials: Material[] = [
|
|
||||||
new MeshBasicMaterial({
|
|
||||||
color,
|
color,
|
||||||
side: DoubleSide,
|
side: DoubleSide,
|
||||||
}),
|
});
|
||||||
];
|
|
||||||
|
|
||||||
materials.push(
|
const mesh = create_mesh(
|
||||||
...textures.map(
|
geometry,
|
||||||
|
textures.length
|
||||||
|
? textures.map(
|
||||||
tex =>
|
tex =>
|
||||||
new MeshLambertMaterial({
|
new MeshLambertMaterial({
|
||||||
map: tex,
|
map: tex,
|
||||||
@ -61,19 +61,11 @@ function create_mesh(
|
|||||||
alphaTest: 0.5,
|
alphaTest: 0.5,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
: default_material,
|
||||||
|
default_material
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let i = materials.length - 1; i < max_mat_idx; ++i) {
|
mesh.name = name;
|
||||||
materials.push(
|
|
||||||
new MeshLambertMaterial({
|
|
||||||
color,
|
|
||||||
side: DoubleSide,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mesh = new Mesh(geometry, materials);
|
|
||||||
mesh.name = type;
|
|
||||||
(mesh.userData as EntityUserData).entity = entity;
|
(mesh.userData as EntityUserData).entity = entity;
|
||||||
|
|
||||||
const { x, y, z } = entity.position;
|
const { x, y, z } = entity.position;
|
||||||
|
@ -1,39 +1,10 @@
|
|||||||
import {
|
import { Bone, BufferGeometry, Euler, Matrix3, Matrix4, Quaternion, Vector2, Vector3 } from "three";
|
||||||
Bone,
|
|
||||||
BufferGeometry,
|
|
||||||
DoubleSide,
|
|
||||||
Euler,
|
|
||||||
Material,
|
|
||||||
Matrix3,
|
|
||||||
Matrix4,
|
|
||||||
Mesh,
|
|
||||||
MeshBasicMaterial,
|
|
||||||
MeshLambertMaterial,
|
|
||||||
Quaternion,
|
|
||||||
Skeleton,
|
|
||||||
SkinnedMesh,
|
|
||||||
Vector2,
|
|
||||||
Vector3,
|
|
||||||
} from "three";
|
|
||||||
import { vec3_to_threejs } from ".";
|
import { vec3_to_threejs } from ".";
|
||||||
import { is_njcm_model, NjModel, NjObject } from "../../data_formats/parsing/ninja";
|
import { is_njcm_model, NjModel, NjObject } from "../../data_formats/parsing/ninja";
|
||||||
import { NjcmModel } from "../../data_formats/parsing/ninja/njcm";
|
import { NjcmModel } from "../../data_formats/parsing/ninja/njcm";
|
||||||
import { XjModel } from "../../data_formats/parsing/ninja/xj";
|
import { XjModel } from "../../data_formats/parsing/ninja/xj";
|
||||||
import { GeometryBuilder } from "./GeometryBuilder";
|
import { GeometryBuilder } from "./GeometryBuilder";
|
||||||
|
|
||||||
const DUMMY_MATERIAL = new MeshBasicMaterial({
|
|
||||||
color: 0x00ff00,
|
|
||||||
side: DoubleSide,
|
|
||||||
});
|
|
||||||
const DEFAULT_MATERIAL = new MeshBasicMaterial({
|
|
||||||
color: 0xff00ff,
|
|
||||||
side: DoubleSide,
|
|
||||||
});
|
|
||||||
const DEFAULT_SKINNED_MATERIAL = new MeshLambertMaterial({
|
|
||||||
skinning: true,
|
|
||||||
color: 0xff00ff,
|
|
||||||
side: DoubleSide,
|
|
||||||
});
|
|
||||||
const DEFAULT_NORMAL = new Vector3(0, 1, 0);
|
const DEFAULT_NORMAL = new Vector3(0, 1, 0);
|
||||||
const DEFAULT_UV = new Vector2(0, 0);
|
const DEFAULT_UV = new Vector2(0, 0);
|
||||||
const NO_TRANSLATION = new Vector3(0, 0, 0);
|
const NO_TRANSLATION = new Vector3(0, 0, 0);
|
||||||
@ -43,32 +14,12 @@ const NO_SCALE = new Vector3(1, 1, 1);
|
|||||||
export function ninja_object_to_geometry_builder(
|
export function ninja_object_to_geometry_builder(
|
||||||
object: NjObject<NjModel>,
|
object: NjObject<NjModel>,
|
||||||
builder: GeometryBuilder
|
builder: GeometryBuilder
|
||||||
) {
|
): void {
|
||||||
new ModelCreator(builder).to_geometry_builder(object);
|
new GeometryCreator(builder).to_geometry_builder(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ninja_object_to_buffer_geometry(object: NjObject<NjModel>): BufferGeometry {
|
export function ninja_object_to_buffer_geometry(object: NjObject<NjModel>): BufferGeometry {
|
||||||
return new ModelCreator(new GeometryBuilder()).create_buffer_geometry(object);
|
return new GeometryCreator(new GeometryBuilder()).create_buffer_geometry(object);
|
||||||
}
|
|
||||||
|
|
||||||
export function ninja_object_to_mesh(
|
|
||||||
object: NjObject<NjModel>,
|
|
||||||
materials: Material[] = [],
|
|
||||||
default_material: Material = DEFAULT_MATERIAL
|
|
||||||
): Mesh {
|
|
||||||
return new ModelCreator(new GeometryBuilder()).create_mesh(object, materials, default_material);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ninja_object_to_skinned_mesh(
|
|
||||||
object: NjObject<NjModel>,
|
|
||||||
materials: Material[] = [],
|
|
||||||
default_material: Material = DEFAULT_SKINNED_MATERIAL
|
|
||||||
): SkinnedMesh {
|
|
||||||
return new ModelCreator(new GeometryBuilder()).create_skinned_mesh(
|
|
||||||
object,
|
|
||||||
materials,
|
|
||||||
default_material
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Vertex = {
|
type Vertex = {
|
||||||
@ -102,17 +53,16 @@ class VerticesHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModelCreator {
|
class GeometryCreator {
|
||||||
private vertices = new VerticesHolder();
|
private vertices = new VerticesHolder();
|
||||||
private bone_id: number = 0;
|
private bone_id: number = 0;
|
||||||
private bones: Bone[] = [];
|
|
||||||
private builder: GeometryBuilder;
|
private builder: GeometryBuilder;
|
||||||
|
|
||||||
constructor(builder: GeometryBuilder) {
|
constructor(builder: GeometryBuilder) {
|
||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
to_geometry_builder(object: NjObject<NjModel>) {
|
to_geometry_builder(object: NjObject<NjModel>): void {
|
||||||
this.object_to_geometry(object, undefined, new Matrix4());
|
this.object_to_geometry(object, undefined, new Matrix4());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,44 +71,6 @@ class ModelCreator {
|
|||||||
return this.builder.build();
|
return this.builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
create_mesh(
|
|
||||||
object: NjObject<NjModel>,
|
|
||||||
materials: Material[],
|
|
||||||
default_material: Material
|
|
||||||
): Mesh {
|
|
||||||
const geom = this.create_buffer_geometry(object);
|
|
||||||
|
|
||||||
materials = [DUMMY_MATERIAL, ...materials];
|
|
||||||
const max_mat_idx = this.builder.max_material_index || 0;
|
|
||||||
|
|
||||||
for (let i = materials.length - 1; i < max_mat_idx; ++i) {
|
|
||||||
materials.push(default_material);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Mesh(geom, materials);
|
|
||||||
}
|
|
||||||
|
|
||||||
create_skinned_mesh(
|
|
||||||
object: NjObject<NjModel>,
|
|
||||||
materials: Material[],
|
|
||||||
default_material: Material
|
|
||||||
): SkinnedMesh {
|
|
||||||
const geom = this.create_buffer_geometry(object);
|
|
||||||
|
|
||||||
materials = [DUMMY_MATERIAL, ...materials];
|
|
||||||
const max_mat_idx = this.builder.max_material_index || 0;
|
|
||||||
|
|
||||||
for (let i = materials.length - 1; i < max_mat_idx; ++i) {
|
|
||||||
materials.push(default_material);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mesh = new SkinnedMesh(geom, materials);
|
|
||||||
mesh.add(this.bones[0]);
|
|
||||||
mesh.bind(new Skeleton(this.bones));
|
|
||||||
|
|
||||||
return mesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
private object_to_geometry(
|
private object_to_geometry(
|
||||||
object: NjObject<NjModel>,
|
object: NjObject<NjModel>,
|
||||||
parent_bone: Bone | undefined,
|
parent_bone: Bone | undefined,
|
||||||
@ -201,7 +113,7 @@ class ModelCreator {
|
|||||||
bone.setRotationFromEuler(euler);
|
bone.setRotationFromEuler(euler);
|
||||||
bone.scale.set(scale.x, scale.y, scale.z);
|
bone.scale.set(scale.x, scale.y, scale.z);
|
||||||
|
|
||||||
this.bones.push(bone);
|
this.builder.add_bone(bone);
|
||||||
|
|
||||||
if (parent_bone) {
|
if (parent_bone) {
|
||||||
parent_bone.add(bone);
|
parent_bone.add(bone);
|
||||||
@ -289,7 +201,7 @@ class ModelCreator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const [bone_index, bone_weight] of bones) {
|
for (const [bone_index, bone_weight] of bones) {
|
||||||
this.builder.add_bone(bone_index, bone_weight);
|
this.builder.add_bone_weight(bone_index, bone_weight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
MeshLambertMaterial,
|
MeshLambertMaterial,
|
||||||
SkinnedMesh,
|
SkinnedMesh,
|
||||||
Texture,
|
Texture,
|
||||||
Vector3,
|
|
||||||
} from "three";
|
} from "three";
|
||||||
import { Endianness } from "../data_formats";
|
import { Endianness } from "../data_formats";
|
||||||
import { ArrayBufferCursor } from "../data_formats/cursor/ArrayBufferCursor";
|
import { ArrayBufferCursor } from "../data_formats/cursor/ArrayBufferCursor";
|
||||||
@ -18,14 +17,12 @@ import { NjModel, NjObject, parse_nj, parse_xj } from "../data_formats/parsing/n
|
|||||||
import { NjMotion, parse_njm } from "../data_formats/parsing/ninja/motion";
|
import { NjMotion, parse_njm } from "../data_formats/parsing/ninja/motion";
|
||||||
import { parse_xvm } from "../data_formats/parsing/ninja/texture";
|
import { parse_xvm } from "../data_formats/parsing/ninja/texture";
|
||||||
import { PlayerAnimation, PlayerModel } from "../domain";
|
import { PlayerAnimation, PlayerModel } from "../domain";
|
||||||
import { read_file } from "../read_file";
|
|
||||||
import { create_animation_clip, PSO_FRAME_RATE } from "../rendering/conversion/ninja_animation";
|
|
||||||
import {
|
|
||||||
ninja_object_to_mesh,
|
|
||||||
ninja_object_to_skinned_mesh,
|
|
||||||
} from "../rendering/conversion/ninja_geometry";
|
|
||||||
import { xvm_to_textures } from "../rendering/conversion/ninja_textures";
|
|
||||||
import { get_player_animation_data, get_player_data } from "../loading/player";
|
import { get_player_animation_data, get_player_data } from "../loading/player";
|
||||||
|
import { read_file } from "../read_file";
|
||||||
|
import { create_skinned_mesh, create_mesh } from "../rendering/conversion/create_mesh";
|
||||||
|
import { create_animation_clip, PSO_FRAME_RATE } from "../rendering/conversion/ninja_animation";
|
||||||
|
import { ninja_object_to_buffer_geometry } from "../rendering/conversion/ninja_geometry";
|
||||||
|
import { xvm_to_textures } from "../rendering/conversion/ninja_textures";
|
||||||
|
|
||||||
const logger = Logger.get("stores/ModelViewerStore");
|
const logger = Logger.get("stores/ModelViewerStore");
|
||||||
const nj_object_cache: Map<string, Promise<NjObject<NjModel>>> = new Map();
|
const nj_object_cache: Map<string, Promise<NjObject<NjModel>>> = new Map();
|
||||||
@ -294,7 +291,6 @@ class ModelViewerStore {
|
|||||||
private set_obj3d = (textures?: Texture[]) => {
|
private set_obj3d = (textures?: Texture[]) => {
|
||||||
if (this.current_model) {
|
if (this.current_model) {
|
||||||
let mesh: Mesh;
|
let mesh: Mesh;
|
||||||
let bb_size = new Vector3();
|
|
||||||
|
|
||||||
const materials =
|
const materials =
|
||||||
textures &&
|
textures &&
|
||||||
@ -309,13 +305,19 @@ class ModelViewerStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (this.has_skeleton) {
|
if (this.has_skeleton) {
|
||||||
mesh = ninja_object_to_skinned_mesh(this.current_model, materials);
|
mesh = create_skinned_mesh(
|
||||||
|
ninja_object_to_buffer_geometry(this.current_model),
|
||||||
|
materials
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
mesh = ninja_object_to_mesh(this.current_model, materials);
|
mesh = create_mesh(ninja_object_to_buffer_geometry(this.current_model), materials);
|
||||||
}
|
}
|
||||||
|
|
||||||
mesh.geometry.boundingBox.getSize(bb_size);
|
// Make sure we rotate around the center of the model.
|
||||||
mesh.translateY(-bb_size.y / 2);
|
const bb = mesh.geometry.boundingBox;
|
||||||
|
const height = bb.max.y - bb.min.y;
|
||||||
|
mesh.translateY(-height / 2 - bb.min.y);
|
||||||
|
|
||||||
this.current_obj3d = mesh;
|
this.current_obj3d = mesh;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,11 +3,14 @@ import { Renderer } from "../rendering/Renderer";
|
|||||||
import "./RendererComponent.less";
|
import "./RendererComponent.less";
|
||||||
import { Camera } from "three";
|
import { Camera } from "three";
|
||||||
|
|
||||||
export class RendererComponent extends Component<{
|
type Props = {
|
||||||
renderer: Renderer<Camera>;
|
renderer: Renderer<Camera>;
|
||||||
|
debug?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
on_will_unmount?: () => void;
|
on_will_unmount?: () => void;
|
||||||
}> {
|
};
|
||||||
|
|
||||||
|
export class RendererComponent extends Component<Props> {
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
let className = "RendererComponent";
|
let className = "RendererComponent";
|
||||||
if (this.props.className) className += " " + this.props.className;
|
if (this.props.className) className += " " + this.props.className;
|
||||||
@ -15,6 +18,10 @@ export class RendererComponent extends Component<{
|
|||||||
return <div className={className} ref={this.modifyDom} />;
|
return <div className={className} ref={this.modifyDom} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(props: Props): void {
|
||||||
|
this.props.renderer.debug = !!props.debug;
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
window.addEventListener("resize", this.onResize);
|
window.addEventListener("resize", this.onResize);
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,14 @@ import { application_store } from "../../stores/ApplicationStore";
|
|||||||
export class QuestEditorComponent extends Component<
|
export class QuestEditorComponent extends Component<
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
debug: boolean;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
save_dialog_open: boolean;
|
save_dialog_open: boolean;
|
||||||
save_dialog_filename: string;
|
save_dialog_filename: string;
|
||||||
}
|
}
|
||||||
> {
|
> {
|
||||||
state = {
|
state = {
|
||||||
|
debug: false,
|
||||||
save_dialog_open: false,
|
save_dialog_open: false,
|
||||||
save_dialog_filename: "Untitled",
|
save_dialog_filename: "Untitled",
|
||||||
};
|
};
|
||||||
@ -37,7 +39,7 @@ export class QuestEditorComponent extends Component<
|
|||||||
<Toolbar on_save_as_clicked={this.save_as_clicked} />
|
<Toolbar on_save_as_clicked={this.save_as_clicked} />
|
||||||
<div className="qe-QuestEditorComponent-main">
|
<div className="qe-QuestEditorComponent-main">
|
||||||
<QuestInfoComponent quest={quest} />
|
<QuestInfoComponent quest={quest} />
|
||||||
<RendererComponent renderer={get_quest_renderer()} />
|
<RendererComponent renderer={get_quest_renderer()} debug={this.state.debug} />
|
||||||
<EntityInfoComponent entity={quest_editor_store.selected_entity} />
|
<EntityInfoComponent entity={quest_editor_store.selected_entity} />
|
||||||
</div>
|
</div>
|
||||||
<SaveAsForm
|
<SaveAsForm
|
||||||
@ -82,6 +84,8 @@ export class QuestEditorComponent extends Component<
|
|||||||
quest_editor_store.undo_stack.undo();
|
quest_editor_store.undo_stack.undo();
|
||||||
} else if (e.ctrlKey && e.key === "Z" && !e.altKey) {
|
} else if (e.ctrlKey && e.key === "Z" && !e.altKey) {
|
||||||
quest_editor_store.undo_stack.redo();
|
quest_editor_store.undo_stack.redo();
|
||||||
|
} else if (e.ctrlKey && e.altKey && e.key === "d") {
|
||||||
|
this.setState(state => ({ debug: !state.debug }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user