The EntityListView now shows renders of entities instead of green squares.

This commit is contained in:
Daan Vanden Bosch 2019-09-21 21:47:00 +02:00
parent 3d9b003e39
commit f0d474ad40
9 changed files with 249 additions and 222 deletions

View File

@ -46,6 +46,16 @@ export const el = {
return element; return element;
}, },
img: (
attributes?: ElementAttributes & {
src?: string;
width?: number;
height?: number;
alt?: string;
},
...children: HTMLImageElement[]
): HTMLImageElement => create_element("img", attributes, ...children),
table: (attributes?: ElementAttributes, ...children: HTMLElement[]): HTMLTableElement => table: (attributes?: ElementAttributes, ...children: HTMLElement[]): HTMLTableElement =>
create_element("table", attributes, ...children), create_element("table", attributes, ...children),
@ -82,17 +92,25 @@ export function create_element<T extends HTMLElement>(
tag_name: string, tag_name: string,
attributes?: ElementAttributes & { attributes?: ElementAttributes & {
href?: string; href?: string;
src?: string;
width?: number;
height?: number;
alt?: string;
col_span?: number; col_span?: number;
}, },
...children: HTMLElement[] ...children: HTMLElement[]
): T { ): T {
const element = document.createElement(tag_name) as (HTMLTableCellElement & HTMLAnchorElement); const element = document.createElement(tag_name) as any;
if (attributes) { if (attributes) {
if (attributes.class) element.className = attributes.class; if (attributes.class != undefined) element.className = attributes.class;
if (attributes.text) element.textContent = attributes.text; if (attributes.text != undefined) element.textContent = attributes.text;
if (attributes.title) element.title = attributes.title; if (attributes.title != undefined) element.title = attributes.title;
if (attributes.href) element.href = attributes.href; if (attributes.href != undefined) element.href = attributes.href;
if (attributes.src != undefined) element.src = attributes.src;
if (attributes.width != undefined) element.width = attributes.width;
if (attributes.height != undefined) element.height = attributes.height;
if (attributes.alt != undefined) element.alt = attributes.alt;
if (attributes.data) { if (attributes.data) {
for (const [key, val] of Object.entries(attributes.data)) { for (const [key, val] of Object.entries(attributes.data)) {
@ -100,9 +118,9 @@ export function create_element<T extends HTMLElement>(
} }
} }
if (attributes.col_span) element.colSpan = attributes.col_span; if (attributes.col_span != undefined) element.colSpan = attributes.col_span;
if (attributes.tab_index) element.tabIndex = attributes.tab_index; if (attributes.tab_index != undefined) element.tabIndex = attributes.tab_index;
} }
element.append(...children); element.append(...children);

View File

@ -38,12 +38,12 @@ export abstract class Renderer implements Disposable {
readonly scene = new Scene(); readonly scene = new Scene();
readonly light_holder = new Group(); readonly light_holder = new Group();
private renderer = new WebGLRenderer({ antialias: true }); private readonly renderer = new WebGLRenderer({ antialias: true });
private render_scheduled = false; private render_scheduled = false;
private animation_frame_handle?: number = undefined; private animation_frame_handle?: number = undefined;
private light = new HemisphereLight(0xffffff, 0x505050, 1.2); private readonly light = new HemisphereLight(0xffffff, 0x505050, 1.2);
private controls_clock = new Clock(); private readonly controls_clock = new Clock();
private size = new Vector2(); private readonly size = new Vector2();
protected constructor() { protected constructor() {
this.dom_element.tabIndex = 0; this.dom_element.tabIndex = 0;

View File

@ -5,6 +5,8 @@
.quest_editor_EntityListView_entity_list { .quest_editor_EntityListView_entity_list {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, 100px); grid-template-columns: repeat(auto-fill, 100px);
grid-column-gap: 6px;
grid-row-gap: 6px;
justify-content: center; justify-content: center;
} }
@ -12,11 +14,5 @@
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
text-align: center; text-align: center;
height: 100px;
padding: 5px;
border: solid 2px olivedrab;
background-color: darkolivegreen;
color: greenyellow;
} }

View File

@ -4,6 +4,7 @@ import "./EntityListView.css";
import { entity_data, EntityType } from "../../core/data_formats/parsing/quest/entities"; import { entity_data, EntityType } from "../../core/data_formats/parsing/quest/entities";
import { ListProperty } from "../../core/observable/property/list/ListProperty"; import { ListProperty } from "../../core/observable/property/list/ListProperty";
import { entity_dnd_source } from "./entity_dnd"; import { entity_dnd_source } from "./entity_dnd";
import { render_entity_to_image } from "../rendering/render_entity_to_image";
export abstract class EntityListView<T extends EntityType> extends ResizableWidget { export abstract class EntityListView<T extends EntityType> extends ResizableWidget {
readonly element: HTMLElement; readonly element: HTMLElement;
@ -19,26 +20,50 @@ export abstract class EntityListView<T extends EntityType> extends ResizableWidg
bind_children_to(list_element, entities, this.create_entity_element), bind_children_to(list_element, entities, this.create_entity_element),
entity_dnd_source(list_element, target => { entity_dnd_source(list_element, target => {
if (target !== list_element) { let element: HTMLElement | null = target;
const drag_element = target.cloneNode(true) as HTMLElement;
drag_element.style.width = "100px"; do {
return [drag_element, entities.get(parseInt(target.dataset.index!, 10))]; const index = target.dataset.index;
} else {
return undefined; if (index != undefined) {
} return [
element.querySelector("img")!.cloneNode(true) as HTMLElement,
entities.get(parseInt(index, 10)),
];
}
element = element.parentElement;
} while (element && element !== list_element);
return undefined;
}), }),
); );
} }
private create_entity_element = (entity: T, index: number): HTMLElement => { private create_entity_element = (entity: T, index: number): HTMLElement => {
const div = el.div({ const entity_element = el.div({
class: "quest_editor_EntityListView_entity", class: "quest_editor_EntityListView_entity",
text: entity_data(entity).name,
data: { index: index.toString() }, data: { index: index.toString() },
}); });
entity_element.draggable = true;
div.draggable = true; const img_element = el.img({ width: 100, height: 100 });
img_element.style.visibility = "hidden";
// Workaround for Chrome bug: when dragging an image, calling setDragImage on a DragEvent
// has no effect.
img_element.style.pointerEvents = "none";
entity_element.append(img_element);
return div; render_entity_to_image(entity).then(url => {
img_element.src = url;
img_element.style.visibility = "visible";
});
const name_element = el.span({
text: entity_data(entity).name,
});
entity_element.append(name_element);
return entity_element;
}; };
} }

View File

@ -51,7 +51,7 @@ export function entity_dnd_source(
const result = start(e.target); const result = start(e.target);
if (result) { if (result) {
grab_point.set(e.offsetX + 2, e.offsetY + 2); grab_point.set(e.offsetX, e.offsetY);
dragging_details = { dragging_details = {
drag_element: result[0], drag_element: result[0],
@ -76,6 +76,8 @@ export function entity_dnd_source(
entity_data(dragging_details.entity_type).name, entity_data(dragging_details.entity_type).name,
); );
} }
} else {
e.preventDefault();
} }
} }
} }

View File

@ -10,8 +10,13 @@ import { xvm_to_textures } from "../../core/rendering/conversion/ninja_textures"
import { load_array_buffer } from "../../core/loading"; import { load_array_buffer } from "../../core/loading";
import { object_data, ObjectType } from "../../core/data_formats/parsing/quest/object_types"; import { object_data, ObjectType } from "../../core/data_formats/parsing/quest/object_types";
import { NpcType } from "../../core/data_formats/parsing/quest/npc_types"; import { NpcType } from "../../core/data_formats/parsing/quest/npc_types";
import {
entity_type_to_string,
EntityType,
is_npc_type,
} from "../../core/data_formats/parsing/quest/entities";
const logger = Logger.get("loading/entities"); const logger = Logger.get("quest_editor/loading/entities");
const DEFAULT_ENTITY = new CylinderBufferGeometry(3, 3, 20); const DEFAULT_ENTITY = new CylinderBufferGeometry(3, 3, 20);
DEFAULT_ENTITY.translate(0, 10, 0); DEFAULT_ENTITY.translate(0, 10, 0);
@ -26,16 +31,22 @@ const DEFAULT_ENTITY_TEX_PROMISE: Promise<Texture[]> = new Promise(resolve =>
resolve(DEFAULT_ENTITY_TEX), resolve(DEFAULT_ENTITY_TEX),
); );
const npc_cache = new LoadingCache<NpcType, Promise<BufferGeometry>>(); const geom_cache = new LoadingCache<EntityType, Promise<BufferGeometry>>();
npc_cache.set(NpcType.Unknown, DEFAULT_ENTITY_PROMISE);
const npc_tex_cache = new LoadingCache<NpcType, Promise<Texture[]>>(); const tex_cache = new LoadingCache<EntityType, Promise<Texture[]>>();
npc_tex_cache.set(NpcType.Unknown, DEFAULT_ENTITY_TEX_PROMISE);
const object_cache = new LoadingCache<ObjectType, Promise<BufferGeometry>>();
const object_tex_cache = new LoadingCache<ObjectType, Promise<Texture[]>>();
for (const type of [ for (const type of [
NpcType.Unknown,
NpcType.Migium,
NpcType.Hidoom,
NpcType.DeathGunner,
NpcType.StRappy,
NpcType.HalloRappy,
NpcType.EggRappy,
NpcType.Migium2,
NpcType.Hidoom2,
NpcType.Recon,
ObjectType.Unknown, ObjectType.Unknown,
ObjectType.PlayerSet, ObjectType.PlayerSet,
ObjectType.FogCollision, ObjectType.FogCollision,
@ -56,92 +67,49 @@ for (const type of [
ObjectType.TempleMapDetect, ObjectType.TempleMapDetect,
ObjectType.LabInvisibleObject, ObjectType.LabInvisibleObject,
]) { ]) {
object_cache.set(type, DEFAULT_ENTITY_PROMISE); geom_cache.set(type, DEFAULT_ENTITY_PROMISE);
object_tex_cache.set(type, DEFAULT_ENTITY_TEX_PROMISE); tex_cache.set(type, DEFAULT_ENTITY_TEX_PROMISE);
} }
export async function load_npc_geometry(npc_type: NpcType): Promise<BufferGeometry> { export async function load_entity_geometry(type: EntityType): Promise<BufferGeometry> {
return npc_cache.get_or_set(npc_type, async () => { return geom_cache.get_or_set(type, async () => {
try { try {
const { url, data } = await load_npc_data(npc_type, AssetType.Geometry); const { url, data } = await load_entity_data(type, AssetType.Geometry);
const cursor = new ArrayBufferCursor(data, Endianness.Little); const cursor = new ArrayBufferCursor(data, Endianness.Little);
const nj_objects = url.endsWith(".nj") ? parse_nj(cursor) : parse_xj(cursor); const nj_objects = url.endsWith(".nj") ? parse_nj(cursor) : parse_xj(cursor);
if (nj_objects.length) { if (nj_objects.length) {
return ninja_object_to_buffer_geometry(nj_objects[0]); return ninja_object_to_buffer_geometry(nj_objects[0]);
} else { } else {
logger.warn(`Couldn't parse ${url} for ${NpcType[npc_type]}.`); logger.warn(`Couldn't parse ${url} for ${entity_type_to_string(type)}.`);
return DEFAULT_ENTITY; return DEFAULT_ENTITY;
} }
} catch (e) { } catch (e) {
logger.warn(`Couldn't load geometry file for ${NpcType[npc_type]}.`, e); logger.warn(`Couldn't load geometry file for ${entity_type_to_string(type)}.`, e);
return DEFAULT_ENTITY; return DEFAULT_ENTITY;
} }
}); });
} }
export async function load_npc_textures(npc_type: NpcType): Promise<Texture[]> { export async function load_entity_textures(type: EntityType): Promise<Texture[]> {
return npc_tex_cache.get_or_set(npc_type, async () => { return tex_cache.get_or_set(type, async () => {
try { try {
const { data } = await load_npc_data(npc_type, AssetType.Texture); const { data } = await load_entity_data(type, AssetType.Texture);
const cursor = new ArrayBufferCursor(data, Endianness.Little); const cursor = new ArrayBufferCursor(data, Endianness.Little);
const xvm = parse_xvm(cursor); const xvm = parse_xvm(cursor);
return xvm_to_textures(xvm); return xvm_to_textures(xvm);
} catch (e) { } catch (e) {
logger.warn(`Couldn't load texture file for ${NpcType[npc_type]}.`, e); logger.warn(`Couldn't load texture file for ${entity_type_to_string(type)}.`, e);
return DEFAULT_ENTITY_TEX; return DEFAULT_ENTITY_TEX;
} }
}); });
} }
export async function load_object_geometry(object_type: ObjectType): Promise<BufferGeometry> { export async function load_entity_data(
return object_cache.get_or_set(object_type, async () => { type: EntityType,
try { asset_type: AssetType,
const { url, data } = await load_object_data(object_type, AssetType.Geometry);
const cursor = new ArrayBufferCursor(data, Endianness.Little);
const nj_objects = url.endsWith(".nj") ? parse_nj(cursor) : parse_xj(cursor);
if (nj_objects.length) {
return ninja_object_to_buffer_geometry(nj_objects[0]);
} else {
logger.warn(`Couldn't parse ${url} for ${ObjectType[object_type]}.`);
return DEFAULT_ENTITY;
}
} catch (e) {
logger.warn(`Couldn't load geometry file for ${ObjectType[object_type]}.`, e);
return DEFAULT_ENTITY;
}
});
}
export async function load_object_textures(object_type: ObjectType): Promise<Texture[]> {
return object_tex_cache.get_or_set(object_type, async () => {
try {
const { data } = await load_object_data(object_type, AssetType.Texture);
const cursor = new ArrayBufferCursor(data, Endianness.Little);
const xvm = parse_xvm(cursor);
return xvm_to_textures(xvm);
} catch (e) {
logger.warn(`Couldn't load texture file for ${ObjectType[object_type]}.`, e);
return DEFAULT_ENTITY_TEX;
}
});
}
export async function load_npc_data(
npc_type: NpcType,
type: AssetType,
): Promise<{ url: string; data: ArrayBuffer }> { ): Promise<{ url: string; data: ArrayBuffer }> {
const url = npc_type_to_url(npc_type, type); const url = entity_type_to_url(type, asset_type);
const data = await load_array_buffer(url);
return { url, data };
}
export async function load_object_data(
object_type: ObjectType,
type: AssetType,
): Promise<{ url: string; data: ArrayBuffer }> {
const url = object_type_to_url(object_type, type);
const data = await load_array_buffer(url); const data = await load_array_buffer(url);
return { url, data }; return { url, data };
} }
@ -151,88 +119,90 @@ enum AssetType {
Texture, Texture,
} }
function npc_type_to_url(npc_type: NpcType, type: AssetType): string { function entity_type_to_url(type: EntityType, asset_type: AssetType): string {
switch (npc_type) { if (is_npc_type(type)) {
// The dubswitch model is in XJ format. switch (type) {
case NpcType.Dubswitch: // The dubswitch model is in XJ format.
return `/npcs/${NpcType[npc_type]}.${type === AssetType.Geometry ? "xj" : "xvm"}`; case NpcType.Dubswitch:
return `/npcs/${NpcType[type]}.${asset_type === AssetType.Geometry ? "xj" : "xvm"}`;
// Episode II VR Temple // Episode II VR Temple
case NpcType.Hildebear2: case NpcType.Hildebear2:
return npc_type_to_url(NpcType.Hildebear, type); return entity_type_to_url(NpcType.Hildebear, asset_type);
case NpcType.Hildeblue2: case NpcType.Hildeblue2:
return npc_type_to_url(NpcType.Hildeblue, type); return entity_type_to_url(NpcType.Hildeblue, asset_type);
case NpcType.RagRappy2: case NpcType.RagRappy2:
return npc_type_to_url(NpcType.RagRappy, type); return entity_type_to_url(NpcType.RagRappy, asset_type);
case NpcType.Monest2: case NpcType.Monest2:
return npc_type_to_url(NpcType.Monest, type); return entity_type_to_url(NpcType.Monest, asset_type);
case NpcType.PoisonLily2: case NpcType.Mothmant2:
return npc_type_to_url(NpcType.PoisonLily, type); return entity_type_to_url(NpcType.Mothmant, asset_type);
case NpcType.NarLily2: case NpcType.PoisonLily2:
return npc_type_to_url(NpcType.NarLily, type); return entity_type_to_url(NpcType.PoisonLily, asset_type);
case NpcType.GrassAssassin2: case NpcType.NarLily2:
return npc_type_to_url(NpcType.GrassAssassin, type); return entity_type_to_url(NpcType.NarLily, asset_type);
case NpcType.Dimenian2: case NpcType.GrassAssassin2:
return npc_type_to_url(NpcType.Dimenian, type); return entity_type_to_url(NpcType.GrassAssassin, asset_type);
case NpcType.LaDimenian2: case NpcType.Dimenian2:
return npc_type_to_url(NpcType.LaDimenian, type); return entity_type_to_url(NpcType.Dimenian, asset_type);
case NpcType.SoDimenian2: case NpcType.LaDimenian2:
return npc_type_to_url(NpcType.SoDimenian, type); return entity_type_to_url(NpcType.LaDimenian, asset_type);
case NpcType.DarkBelra2: case NpcType.SoDimenian2:
return npc_type_to_url(NpcType.DarkBelra, type); return entity_type_to_url(NpcType.SoDimenian, asset_type);
case NpcType.DarkBelra2:
return entity_type_to_url(NpcType.DarkBelra, asset_type);
// Episode II VR Spaceship // Episode II VR Spaceship
case NpcType.SavageWolf2: case NpcType.SavageWolf2:
return npc_type_to_url(NpcType.SavageWolf, type); return entity_type_to_url(NpcType.SavageWolf, asset_type);
case NpcType.BarbarousWolf2: case NpcType.BarbarousWolf2:
return npc_type_to_url(NpcType.BarbarousWolf, type); return entity_type_to_url(NpcType.BarbarousWolf, asset_type);
case NpcType.PanArms2: case NpcType.PanArms2:
return npc_type_to_url(NpcType.PanArms, type); return entity_type_to_url(NpcType.PanArms, asset_type);
case NpcType.Dubchic2: case NpcType.Dubchic2:
return npc_type_to_url(NpcType.Dubchic, type); return entity_type_to_url(NpcType.Dubchic, asset_type);
case NpcType.Gilchic2: case NpcType.Gilchic2:
return npc_type_to_url(NpcType.Gilchic, type); return entity_type_to_url(NpcType.Gilchic, asset_type);
case NpcType.Garanz2: case NpcType.Garanz2:
return npc_type_to_url(NpcType.Garanz, type); return entity_type_to_url(NpcType.Garanz, asset_type);
case NpcType.Dubswitch2: case NpcType.Dubswitch2:
return npc_type_to_url(NpcType.Dubswitch, type); return entity_type_to_url(NpcType.Dubswitch, asset_type);
case NpcType.Delsaber2: case NpcType.Delsaber2:
return npc_type_to_url(NpcType.Delsaber, type); return entity_type_to_url(NpcType.Delsaber, asset_type);
case NpcType.ChaosSorcerer2: case NpcType.ChaosSorcerer2:
return npc_type_to_url(NpcType.ChaosSorcerer, type); return entity_type_to_url(NpcType.ChaosSorcerer, asset_type);
default:
return `/npcs/${NpcType[npc_type]}.${type === AssetType.Geometry ? "nj" : "xvm"}`;
}
}
function object_type_to_url(object_type: ObjectType, type: AssetType): string {
if (type === AssetType.Geometry) {
switch (object_type) {
case ObjectType.EasterEgg:
case ObjectType.ChristmasTree:
case ObjectType.ChristmasWreath:
case ObjectType.TwentyFirstCentury:
case ObjectType.Sonic:
case ObjectType.WelcomeBoard:
case ObjectType.FloatingJelifish:
case ObjectType.RuinsSeal:
case ObjectType.Dolphin:
case ObjectType.Cacti:
case ObjectType.BigBrownRock:
case ObjectType.PoisonPlant:
case ObjectType.BigBlackRocks:
case ObjectType.FallingRock:
case ObjectType.DesertFixedTypeBoxBreakableCrystals:
case ObjectType.BeeHive:
return `/objects/${object_data(object_type).pso_id}.nj`;
default: default:
return `/objects/${object_data(object_type).pso_id}.xj`; return `/npcs/${NpcType[type]}.${asset_type === AssetType.Geometry ? "nj" : "xvm"}`;
} }
} else { } else {
return `/objects/${object_data(object_type).pso_id}.xvm`; if (asset_type === AssetType.Geometry) {
switch (type) {
case ObjectType.EasterEgg:
case ObjectType.ChristmasTree:
case ObjectType.ChristmasWreath:
case ObjectType.TwentyFirstCentury:
case ObjectType.Sonic:
case ObjectType.WelcomeBoard:
case ObjectType.FloatingJelifish:
case ObjectType.RuinsSeal:
case ObjectType.Dolphin:
case ObjectType.Cacti:
case ObjectType.BigBrownRock:
case ObjectType.PoisonPlant:
case ObjectType.BigBlackRocks:
case ObjectType.FallingRock:
case ObjectType.DesertFixedTypeBoxBreakableCrystals:
case ObjectType.BeeHive:
return `/objects/${object_data(type).pso_id}.nj`;
default:
return `/objects/${object_data(type).pso_id}.xj`;
}
} else {
return `/objects/${object_data(type).pso_id}.xvm`;
}
} }
} }

View File

@ -2,18 +2,13 @@ import Logger from "js-logger";
import { Intersection, Mesh, Object3D, Raycaster, Vector3 } from "three"; import { Intersection, Mesh, Object3D, Raycaster, Vector3 } from "three";
import { QuestRenderer } from "./QuestRenderer"; import { QuestRenderer } from "./QuestRenderer";
import { QuestModel } from "../model/QuestModel"; import { QuestModel } from "../model/QuestModel";
import { import { load_entity_geometry, load_entity_textures } from "../loading/entities";
load_npc_geometry,
load_npc_textures,
load_object_geometry,
load_object_textures,
} from "../loading/entities";
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 { QuestEntityModel } from "../model/QuestEntityModel"; import { QuestEntityModel } from "../model/QuestEntityModel";
import { Disposer } from "../../core/observable/Disposer"; import { Disposer } from "../../core/observable/Disposer";
import { Disposable } from "../../core/observable/Disposable"; import { Disposable } from "../../core/observable/Disposable";
import { AreaModel } from "../model/AreaModel"; import { AreaModel } from "../model/AreaModel";
import { create_npc_mesh, create_object_mesh } from "./conversion/entities"; import { create_entity_mesh } from "./conversion/entities";
import { AreaUserData } from "./conversion/areas"; import { AreaUserData } from "./conversion/areas";
import { quest_editor_store } from "../stores/QuestEditorStore"; import { quest_editor_store } from "../stores/QuestEditorStore";
import { import {
@ -271,19 +266,9 @@ class EntityModelManager {
} }
private async load(entity: QuestEntityModel): Promise<void> { private async load(entity: QuestEntityModel): Promise<void> {
let model: Mesh; const geom = await load_entity_geometry(entity.type);
const tex = await load_entity_textures(entity.type);
if (entity instanceof QuestNpcModel) { const model = create_entity_mesh(entity, geom, tex);
const npc_geom = await load_npc_geometry(entity.type);
const npc_tex = await load_npc_textures(entity.type);
model = create_npc_mesh(entity, npc_geom, npc_tex);
} else if (entity instanceof QuestObjectModel) {
const object_geom = await load_object_geometry(entity.type);
const object_tex = await load_object_textures(entity.type);
model = create_object_mesh(entity, object_geom, object_tex);
} else {
throw new Error(`Unknown entity type ${entity.type}.`);
}
// The model load might be cancelled by now. // The model load might be cancelled by now.
if (this.queue.includes(entity)) { if (this.queue.includes(entity)) {

View File

@ -1,10 +1,11 @@
import { QuestEntityModel } from "../../model/QuestEntityModel"; import { QuestEntityModel } from "../../model/QuestEntityModel";
import { QuestObjectModel } from "../../model/QuestObjectModel";
import { BufferGeometry, DoubleSide, Mesh, MeshLambertMaterial, Texture } from "three"; import { BufferGeometry, DoubleSide, Mesh, MeshLambertMaterial, Texture } from "three";
import { ObjectType } from "../../../core/data_formats/parsing/quest/object_types";
import { QuestNpcModel } from "../../model/QuestNpcModel";
import { NpcType } from "../../../core/data_formats/parsing/quest/npc_types";
import { create_mesh } from "../../../core/rendering/conversion/create_mesh"; import { create_mesh } from "../../../core/rendering/conversion/create_mesh";
import {
entity_type_to_string,
EntityType,
is_npc_type,
} from "../../../core/data_formats/parsing/quest/entities";
export enum ColorType { export enum ColorType {
Normal, Normal,
@ -26,37 +27,13 @@ export type EntityUserData = {
entity: QuestEntityModel; entity: QuestEntityModel;
}; };
export function create_object_mesh( export function create_entity_type_mesh(
object: QuestObjectModel, type: EntityType,
geometry: BufferGeometry, geometry: BufferGeometry,
textures: Texture[], textures: Texture[],
): Mesh {
return create(
object,
geometry,
textures,
OBJECT_COLORS[ColorType.Normal],
ObjectType[object.type],
);
}
export function create_npc_mesh(
npc: QuestNpcModel,
geometry: BufferGeometry,
textures: Texture[],
): Mesh {
return create(npc, geometry, textures, NPC_COLORS[ColorType.Normal], NpcType[npc.type]);
}
function create(
entity: QuestEntityModel,
geometry: BufferGeometry,
textures: Texture[],
color: number,
name: string,
): Mesh { ): Mesh {
const default_material = new MeshLambertMaterial({ const default_material = new MeshLambertMaterial({
color, color: is_npc_type(type) ? NPC_COLORS[ColorType.Normal] : OBJECT_COLORS[ColorType.Normal],
side: DoubleSide, side: DoubleSide,
}); });
@ -75,7 +52,18 @@ function create(
default_material, default_material,
); );
mesh.name = name; mesh.name = entity_type_to_string(type);
return mesh;
}
export function create_entity_mesh(
entity: QuestEntityModel,
geometry: BufferGeometry,
textures: Texture[],
): Mesh {
const mesh = create_entity_type_mesh(entity.type, geometry, textures);
(mesh.userData as EntityUserData).entity = entity; (mesh.userData as EntityUserData).entity = entity;
const { x, y, z } = entity.world_position.val; const { x, y, z } = entity.world_position.val;

View File

@ -0,0 +1,43 @@
import { HemisphereLight, PerspectiveCamera, Scene, WebGLRenderer } from "three";
import { EntityType } from "../../core/data_formats/parsing/quest/entities";
import { load_entity_geometry, load_entity_textures } from "../loading/entities";
import { create_entity_type_mesh } from "./conversion/entities";
import { sequential } from "../../core/sequential";
const renderer = new WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(100, 100);
const camera = new PerspectiveCamera(60, 1, 10, 1000);
const light = new HemisphereLight(0xffffff, 0x505050, 1.2);
const scene = new Scene();
const cache: Map<EntityType, Promise<string>> = new Map();
export async function render_entity_to_image(entity: EntityType): Promise<string> {
let url = cache.get(entity);
if (!url) {
url = render(entity);
cache.set(entity, url);
}
return url;
}
const render = sequential(
async (entity: EntityType): Promise<string> => {
const geometry = await load_entity_geometry(entity);
const textures = await load_entity_textures(entity);
scene.remove(...scene.children);
scene.add(light);
scene.add(create_entity_type_mesh(entity, geometry, textures));
camera.position.set(10, 25, 20);
camera.lookAt(0, 10, 0);
renderer.render(scene, camera);
return renderer.domElement.toDataURL();
},
);