From f0d474ad4065b0d9aa55ed951dea51ec9e3288cb Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Sat, 21 Sep 2019 21:47:00 +0200 Subject: [PATCH] The EntityListView now shows renders of entities instead of green squares. --- src/core/gui/dom.ts | 32 ++- src/core/rendering/Renderer.ts | 8 +- src/quest_editor/gui/EntityListView.css | 8 +- src/quest_editor/gui/EntityListView.ts | 47 +++- src/quest_editor/gui/entity_dnd.ts | 4 +- src/quest_editor/loading/entities.ts | 252 ++++++++---------- .../rendering/QuestModelManager.ts | 25 +- .../rendering/conversion/entities.ts | 52 ++-- .../rendering/render_entity_to_image.ts | 43 +++ 9 files changed, 249 insertions(+), 222 deletions(-) create mode 100644 src/quest_editor/rendering/render_entity_to_image.ts diff --git a/src/core/gui/dom.ts b/src/core/gui/dom.ts index b7cc32b3..c7d573ab 100644 --- a/src/core/gui/dom.ts +++ b/src/core/gui/dom.ts @@ -46,6 +46,16 @@ export const el = { 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 => create_element("table", attributes, ...children), @@ -82,17 +92,25 @@ export function create_element( tag_name: string, attributes?: ElementAttributes & { href?: string; + src?: string; + width?: number; + height?: number; + alt?: string; col_span?: number; }, ...children: HTMLElement[] ): T { - const element = document.createElement(tag_name) as (HTMLTableCellElement & HTMLAnchorElement); + const element = document.createElement(tag_name) as any; if (attributes) { - if (attributes.class) element.className = attributes.class; - if (attributes.text) element.textContent = attributes.text; - if (attributes.title) element.title = attributes.title; - if (attributes.href) element.href = attributes.href; + if (attributes.class != undefined) element.className = attributes.class; + if (attributes.text != undefined) element.textContent = attributes.text; + if (attributes.title != undefined) element.title = attributes.title; + 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) { for (const [key, val] of Object.entries(attributes.data)) { @@ -100,9 +118,9 @@ export function create_element( } } - 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); diff --git a/src/core/rendering/Renderer.ts b/src/core/rendering/Renderer.ts index ceed3d1f..7bd6df25 100644 --- a/src/core/rendering/Renderer.ts +++ b/src/core/rendering/Renderer.ts @@ -38,12 +38,12 @@ export abstract class Renderer implements Disposable { readonly scene = new Scene(); readonly light_holder = new Group(); - private renderer = new WebGLRenderer({ antialias: true }); + private readonly renderer = new WebGLRenderer({ antialias: true }); private render_scheduled = false; private animation_frame_handle?: number = undefined; - private light = new HemisphereLight(0xffffff, 0x505050, 1.2); - private controls_clock = new Clock(); - private size = new Vector2(); + private readonly light = new HemisphereLight(0xffffff, 0x505050, 1.2); + private readonly controls_clock = new Clock(); + private readonly size = new Vector2(); protected constructor() { this.dom_element.tabIndex = 0; diff --git a/src/quest_editor/gui/EntityListView.css b/src/quest_editor/gui/EntityListView.css index 158b7ec6..ec75f860 100644 --- a/src/quest_editor/gui/EntityListView.css +++ b/src/quest_editor/gui/EntityListView.css @@ -5,6 +5,8 @@ .quest_editor_EntityListView_entity_list { display: grid; grid-template-columns: repeat(auto-fill, 100px); + grid-column-gap: 6px; + grid-row-gap: 6px; justify-content: center; } @@ -12,11 +14,5 @@ box-sizing: border-box; display: flex; flex-direction: column; - justify-content: center; text-align: center; - height: 100px; - padding: 5px; - border: solid 2px olivedrab; - background-color: darkolivegreen; - color: greenyellow; } diff --git a/src/quest_editor/gui/EntityListView.ts b/src/quest_editor/gui/EntityListView.ts index a34f6253..796e9e87 100644 --- a/src/quest_editor/gui/EntityListView.ts +++ b/src/quest_editor/gui/EntityListView.ts @@ -4,6 +4,7 @@ import "./EntityListView.css"; import { entity_data, EntityType } from "../../core/data_formats/parsing/quest/entities"; import { ListProperty } from "../../core/observable/property/list/ListProperty"; import { entity_dnd_source } from "./entity_dnd"; +import { render_entity_to_image } from "../rendering/render_entity_to_image"; export abstract class EntityListView extends ResizableWidget { readonly element: HTMLElement; @@ -19,26 +20,50 @@ export abstract class EntityListView extends ResizableWidg bind_children_to(list_element, entities, this.create_entity_element), entity_dnd_source(list_element, target => { - if (target !== list_element) { - const drag_element = target.cloneNode(true) as HTMLElement; - drag_element.style.width = "100px"; - return [drag_element, entities.get(parseInt(target.dataset.index!, 10))]; - } else { - return undefined; - } + let element: HTMLElement | null = target; + + do { + const index = target.dataset.index; + + 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 => { - const div = el.div({ + const entity_element = el.div({ class: "quest_editor_EntityListView_entity", - text: entity_data(entity).name, 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; }; } diff --git a/src/quest_editor/gui/entity_dnd.ts b/src/quest_editor/gui/entity_dnd.ts index 0964e342..7aa0f27b 100644 --- a/src/quest_editor/gui/entity_dnd.ts +++ b/src/quest_editor/gui/entity_dnd.ts @@ -51,7 +51,7 @@ export function entity_dnd_source( const result = start(e.target); if (result) { - grab_point.set(e.offsetX + 2, e.offsetY + 2); + grab_point.set(e.offsetX, e.offsetY); dragging_details = { drag_element: result[0], @@ -76,6 +76,8 @@ export function entity_dnd_source( entity_data(dragging_details.entity_type).name, ); } + } else { + e.preventDefault(); } } } diff --git a/src/quest_editor/loading/entities.ts b/src/quest_editor/loading/entities.ts index 32f13aca..497933d4 100644 --- a/src/quest_editor/loading/entities.ts +++ b/src/quest_editor/loading/entities.ts @@ -10,8 +10,13 @@ import { xvm_to_textures } from "../../core/rendering/conversion/ninja_textures" import { load_array_buffer } from "../../core/loading"; import { object_data, ObjectType } from "../../core/data_formats/parsing/quest/object_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); DEFAULT_ENTITY.translate(0, 10, 0); @@ -26,16 +31,22 @@ const DEFAULT_ENTITY_TEX_PROMISE: Promise = new Promise(resolve => resolve(DEFAULT_ENTITY_TEX), ); -const npc_cache = new LoadingCache>(); -npc_cache.set(NpcType.Unknown, DEFAULT_ENTITY_PROMISE); +const geom_cache = new LoadingCache>(); -const npc_tex_cache = new LoadingCache>(); -npc_tex_cache.set(NpcType.Unknown, DEFAULT_ENTITY_TEX_PROMISE); - -const object_cache = new LoadingCache>(); -const object_tex_cache = new LoadingCache>(); +const tex_cache = new LoadingCache>(); 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.PlayerSet, ObjectType.FogCollision, @@ -56,92 +67,49 @@ for (const type of [ ObjectType.TempleMapDetect, ObjectType.LabInvisibleObject, ]) { - object_cache.set(type, DEFAULT_ENTITY_PROMISE); - object_tex_cache.set(type, DEFAULT_ENTITY_TEX_PROMISE); + geom_cache.set(type, DEFAULT_ENTITY_PROMISE); + tex_cache.set(type, DEFAULT_ENTITY_TEX_PROMISE); } -export async function load_npc_geometry(npc_type: NpcType): Promise { - return npc_cache.get_or_set(npc_type, async () => { +export async function load_entity_geometry(type: EntityType): Promise { + return geom_cache.get_or_set(type, async () => { 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 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 ${NpcType[npc_type]}.`); + logger.warn(`Couldn't parse ${url} for ${entity_type_to_string(type)}.`); return DEFAULT_ENTITY; } } 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; } }); } -export async function load_npc_textures(npc_type: NpcType): Promise { - return npc_tex_cache.get_or_set(npc_type, async () => { +export async function load_entity_textures(type: EntityType): Promise { + return tex_cache.get_or_set(type, async () => { 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 xvm = parse_xvm(cursor); return xvm_to_textures(xvm); } 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; } }); } -export async function load_object_geometry(object_type: ObjectType): Promise { - return object_cache.get_or_set(object_type, async () => { - try { - 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 { - 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, +export async function load_entity_data( + type: EntityType, + asset_type: AssetType, ): Promise<{ url: string; data: ArrayBuffer }> { - const url = npc_type_to_url(npc_type, 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 url = entity_type_to_url(type, asset_type); const data = await load_array_buffer(url); return { url, data }; } @@ -151,88 +119,90 @@ enum AssetType { Texture, } -function npc_type_to_url(npc_type: NpcType, type: AssetType): string { - switch (npc_type) { - // The dubswitch model is in XJ format. - case NpcType.Dubswitch: - return `/npcs/${NpcType[npc_type]}.${type === AssetType.Geometry ? "xj" : "xvm"}`; +function entity_type_to_url(type: EntityType, asset_type: AssetType): string { + if (is_npc_type(type)) { + switch (type) { + // The dubswitch model is in XJ format. + case NpcType.Dubswitch: + return `/npcs/${NpcType[type]}.${asset_type === AssetType.Geometry ? "xj" : "xvm"}`; - // Episode II VR Temple + // Episode II VR Temple - case NpcType.Hildebear2: - return npc_type_to_url(NpcType.Hildebear, type); - case NpcType.Hildeblue2: - return npc_type_to_url(NpcType.Hildeblue, type); - case NpcType.RagRappy2: - return npc_type_to_url(NpcType.RagRappy, type); - case NpcType.Monest2: - return npc_type_to_url(NpcType.Monest, type); - case NpcType.PoisonLily2: - return npc_type_to_url(NpcType.PoisonLily, type); - case NpcType.NarLily2: - return npc_type_to_url(NpcType.NarLily, type); - case NpcType.GrassAssassin2: - return npc_type_to_url(NpcType.GrassAssassin, type); - case NpcType.Dimenian2: - return npc_type_to_url(NpcType.Dimenian, type); - case NpcType.LaDimenian2: - return npc_type_to_url(NpcType.LaDimenian, type); - case NpcType.SoDimenian2: - return npc_type_to_url(NpcType.SoDimenian, type); - case NpcType.DarkBelra2: - return npc_type_to_url(NpcType.DarkBelra, type); + case NpcType.Hildebear2: + return entity_type_to_url(NpcType.Hildebear, asset_type); + case NpcType.Hildeblue2: + return entity_type_to_url(NpcType.Hildeblue, asset_type); + case NpcType.RagRappy2: + return entity_type_to_url(NpcType.RagRappy, asset_type); + case NpcType.Monest2: + return entity_type_to_url(NpcType.Monest, asset_type); + case NpcType.Mothmant2: + return entity_type_to_url(NpcType.Mothmant, asset_type); + case NpcType.PoisonLily2: + return entity_type_to_url(NpcType.PoisonLily, asset_type); + case NpcType.NarLily2: + return entity_type_to_url(NpcType.NarLily, asset_type); + case NpcType.GrassAssassin2: + return entity_type_to_url(NpcType.GrassAssassin, asset_type); + case NpcType.Dimenian2: + return entity_type_to_url(NpcType.Dimenian, asset_type); + case NpcType.LaDimenian2: + return entity_type_to_url(NpcType.LaDimenian, asset_type); + case NpcType.SoDimenian2: + 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: - return npc_type_to_url(NpcType.SavageWolf, type); - case NpcType.BarbarousWolf2: - return npc_type_to_url(NpcType.BarbarousWolf, type); - case NpcType.PanArms2: - return npc_type_to_url(NpcType.PanArms, type); - case NpcType.Dubchic2: - return npc_type_to_url(NpcType.Dubchic, type); - case NpcType.Gilchic2: - return npc_type_to_url(NpcType.Gilchic, type); - case NpcType.Garanz2: - return npc_type_to_url(NpcType.Garanz, type); - case NpcType.Dubswitch2: - return npc_type_to_url(NpcType.Dubswitch, type); - case NpcType.Delsaber2: - return npc_type_to_url(NpcType.Delsaber, type); - case NpcType.ChaosSorcerer2: - return npc_type_to_url(NpcType.ChaosSorcerer, 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`; + case NpcType.SavageWolf2: + return entity_type_to_url(NpcType.SavageWolf, asset_type); + case NpcType.BarbarousWolf2: + return entity_type_to_url(NpcType.BarbarousWolf, asset_type); + case NpcType.PanArms2: + return entity_type_to_url(NpcType.PanArms, asset_type); + case NpcType.Dubchic2: + return entity_type_to_url(NpcType.Dubchic, asset_type); + case NpcType.Gilchic2: + return entity_type_to_url(NpcType.Gilchic, asset_type); + case NpcType.Garanz2: + return entity_type_to_url(NpcType.Garanz, asset_type); + case NpcType.Dubswitch2: + return entity_type_to_url(NpcType.Dubswitch, asset_type); + case NpcType.Delsaber2: + return entity_type_to_url(NpcType.Delsaber, asset_type); + case NpcType.ChaosSorcerer2: + return entity_type_to_url(NpcType.ChaosSorcerer, asset_type); default: - return `/objects/${object_data(object_type).pso_id}.xj`; + return `/npcs/${NpcType[type]}.${asset_type === AssetType.Geometry ? "nj" : "xvm"}`; } } 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`; + } } } diff --git a/src/quest_editor/rendering/QuestModelManager.ts b/src/quest_editor/rendering/QuestModelManager.ts index d3144d45..d9ace9c5 100644 --- a/src/quest_editor/rendering/QuestModelManager.ts +++ b/src/quest_editor/rendering/QuestModelManager.ts @@ -2,18 +2,13 @@ import Logger from "js-logger"; import { Intersection, Mesh, Object3D, Raycaster, Vector3 } from "three"; import { QuestRenderer } from "./QuestRenderer"; import { QuestModel } from "../model/QuestModel"; -import { - load_npc_geometry, - load_npc_textures, - load_object_geometry, - load_object_textures, -} from "../loading/entities"; +import { load_entity_geometry, load_entity_textures } from "../loading/entities"; import { load_area_collision_geometry, load_area_render_geometry } from "../loading/areas"; import { QuestEntityModel } from "../model/QuestEntityModel"; import { Disposer } from "../../core/observable/Disposer"; import { Disposable } from "../../core/observable/Disposable"; 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 { quest_editor_store } from "../stores/QuestEditorStore"; import { @@ -271,19 +266,9 @@ class EntityModelManager { } private async load(entity: QuestEntityModel): Promise { - let model: Mesh; - - if (entity instanceof QuestNpcModel) { - 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}.`); - } + const geom = await load_entity_geometry(entity.type); + const tex = await load_entity_textures(entity.type); + const model = create_entity_mesh(entity, geom, tex); // The model load might be cancelled by now. if (this.queue.includes(entity)) { diff --git a/src/quest_editor/rendering/conversion/entities.ts b/src/quest_editor/rendering/conversion/entities.ts index 4fdebc68..8f36925c 100644 --- a/src/quest_editor/rendering/conversion/entities.ts +++ b/src/quest_editor/rendering/conversion/entities.ts @@ -1,10 +1,11 @@ import { QuestEntityModel } from "../../model/QuestEntityModel"; -import { QuestObjectModel } from "../../model/QuestObjectModel"; 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 { + entity_type_to_string, + EntityType, + is_npc_type, +} from "../../../core/data_formats/parsing/quest/entities"; export enum ColorType { Normal, @@ -26,37 +27,13 @@ export type EntityUserData = { entity: QuestEntityModel; }; -export function create_object_mesh( - object: QuestObjectModel, +export function create_entity_type_mesh( + type: EntityType, geometry: BufferGeometry, 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 { const default_material = new MeshLambertMaterial({ - color, + color: is_npc_type(type) ? NPC_COLORS[ColorType.Normal] : OBJECT_COLORS[ColorType.Normal], side: DoubleSide, }); @@ -75,7 +52,18 @@ function create( 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; const { x, y, z } = entity.world_position.val; diff --git a/src/quest_editor/rendering/render_entity_to_image.ts b/src/quest_editor/rendering/render_entity_to_image.ts new file mode 100644 index 00000000..30d43908 --- /dev/null +++ b/src/quest_editor/rendering/render_entity_to_image.ts @@ -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> = new Map(); + +export async function render_entity_to_image(entity: EntityType): Promise { + let url = cache.get(entity); + + if (!url) { + url = render(entity); + cache.set(entity, url); + } + + return url; +} + +const render = sequential( + async (entity: EntityType): Promise => { + 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(); + }, +);