mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +08:00
Texture viewer now uses the new UI system.
This commit is contained in:
parent
c4865ee510
commit
844e63735e
@ -1,4 +1,5 @@
|
|||||||
import { Disposable } from "../observable/Disposable";
|
import { Disposable } from "../observable/Disposable";
|
||||||
|
import { Disposer } from "../observable/Disposer";
|
||||||
|
|
||||||
export abstract class View implements Disposable {
|
export abstract class View implements Disposable {
|
||||||
abstract readonly element: HTMLElement;
|
abstract readonly element: HTMLElement;
|
||||||
@ -11,19 +12,19 @@ export abstract class View implements Disposable {
|
|||||||
this.element.id = id;
|
this.element.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private disposable_list: Disposable[] = [];
|
private disposer = new Disposer();
|
||||||
|
|
||||||
protected disposable<T extends Disposable>(disposable: T): T {
|
protected disposable<T extends Disposable>(disposable: T): T {
|
||||||
this.disposable_list.push(disposable);
|
this.disposer.add(disposable);
|
||||||
return disposable;
|
return disposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected disposables(...disposables: Disposable[]): void {
|
protected disposables(...disposables: Disposable[]): void {
|
||||||
this.disposable_list.push(...disposables);
|
this.disposer.add(...disposables);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
this.element.remove();
|
this.element.remove();
|
||||||
this.disposable_list.splice(0, this.disposable_list.length).forEach(d => d.dispose());
|
this.disposer.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
src/core/observable/Disposer.ts
Normal file
23
src/core/observable/Disposer.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Disposable } from "./Disposable";
|
||||||
|
import Logger = require("js-logger");
|
||||||
|
|
||||||
|
const logger = Logger.get("core/observable/Disposer");
|
||||||
|
|
||||||
|
export class Disposer implements Disposable {
|
||||||
|
private readonly disposables: Disposable[] = [];
|
||||||
|
|
||||||
|
add(...disposable: Disposable[]): this {
|
||||||
|
this.disposables.push(...disposable);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
for (const disposable of this.disposables.splice(0, this.disposables.length)) {
|
||||||
|
try {
|
||||||
|
disposable.dispose();
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn("Error while disposing.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import {
|
|||||||
Vector3,
|
Vector3,
|
||||||
WebGLRenderer,
|
WebGLRenderer,
|
||||||
} from "three";
|
} from "three";
|
||||||
|
import { Disposable } from "../observable/Disposable";
|
||||||
|
|
||||||
CameraControls.install({
|
CameraControls.install({
|
||||||
// Hack to make panning and orbiting work the way we want.
|
// Hack to make panning and orbiting work the way we want.
|
||||||
@ -22,7 +23,7 @@ CameraControls.install({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export abstract class Renderer {
|
export abstract class Renderer implements Disposable {
|
||||||
protected _debug = false;
|
protected _debug = false;
|
||||||
|
|
||||||
get debug(): boolean {
|
get debug(): boolean {
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
--scrollbar-color: hsl(0, 0%, 17%);
|
--scrollbar-color: hsl(0, 0%, 17%);
|
||||||
--scrollbar-thumb-color: hsl(0, 0%, 23%);
|
--scrollbar-thumb-color: hsl(0, 0%, 23%);
|
||||||
|
|
||||||
--input-bg-color: hsl(0, 0%, 10%);
|
--input-bg-color: hsl(0, 0%, 15%);
|
||||||
--input-bg-color-disabled: hsl(0, 0%, 15%);
|
--input-bg-color-disabled: hsl(0, 0%, 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
@ -18,7 +18,7 @@ const MODEL_LIST_WIDTH = 100;
|
|||||||
const ANIMATION_LIST_WIDTH = 150;
|
const ANIMATION_LIST_WIDTH = 150;
|
||||||
|
|
||||||
export class ModelView extends ResizableView {
|
export class ModelView extends ResizableView {
|
||||||
element = create_el("div", "viewer_ModelView");
|
readonly element = create_el("div", "viewer_ModelView");
|
||||||
|
|
||||||
private tool_bar_view = this.disposable(new ToolBarView());
|
private tool_bar_view = this.disposable(new ToolBarView());
|
||||||
private container_element = create_el("div", "viewer_ModelView_container");
|
private container_element = create_el("div", "viewer_ModelView_container");
|
||||||
|
@ -1,6 +1,50 @@
|
|||||||
import { create_el } from "../../core/gui/dom";
|
import { create_el } from "../../core/gui/dom";
|
||||||
import { ResizableView } from "../../core/gui/ResizableView";
|
import { ResizableView } from "../../core/gui/ResizableView";
|
||||||
|
import { FileButton } from "../../core/gui/FileButton";
|
||||||
|
import { ToolBar } from "../../core/gui/ToolBar";
|
||||||
|
import { texture_store } from "../stores/TextureStore";
|
||||||
|
import { RendererView } from "../../core/gui/RendererView";
|
||||||
|
import { TextureRenderer } from "../rendering/TextureRenderer";
|
||||||
|
import { gui_store, GuiTool } from "../../core/stores/GuiStore";
|
||||||
|
|
||||||
export class TextureView extends ResizableView {
|
export class TextureView extends ResizableView {
|
||||||
element = create_el("div", "viewer_TextureView", el => (el.textContent = "Texture"));
|
readonly element = create_el("div", "viewer_TextureView");
|
||||||
|
|
||||||
|
private readonly open_file_button = new FileButton("Open file...", ".xvm");
|
||||||
|
|
||||||
|
private readonly tool_bar = this.disposable(new ToolBar(this.open_file_button));
|
||||||
|
|
||||||
|
private readonly renderer_view = this.disposable(new RendererView(new TextureRenderer()));
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.element.append(this.tool_bar.element, this.renderer_view.element);
|
||||||
|
|
||||||
|
this.disposable(
|
||||||
|
this.open_file_button.files.observe(files => {
|
||||||
|
if (files.length) texture_store.load_file(files[0]);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.renderer_view.start_rendering();
|
||||||
|
|
||||||
|
this.disposable(
|
||||||
|
gui_store.tool.observe(tool => {
|
||||||
|
if (tool === GuiTool.Viewer) {
|
||||||
|
this.renderer_view.start_rendering();
|
||||||
|
} else {
|
||||||
|
this.renderer_view.stop_rendering();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resize(width: number, height: number): this {
|
||||||
|
super.resize(width, height);
|
||||||
|
|
||||||
|
this.renderer_view.resize(width, Math.max(0, height - this.tool_bar.height));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,11 @@ import {
|
|||||||
PSO_FRAME_RATE,
|
PSO_FRAME_RATE,
|
||||||
} from "../../core/rendering/conversion/ninja_animation";
|
} from "../../core/rendering/conversion/ninja_animation";
|
||||||
import { Renderer } from "../../core/rendering/Renderer";
|
import { Renderer } from "../../core/rendering/Renderer";
|
||||||
|
import { Disposer } from "../../core/observable/Disposer";
|
||||||
|
|
||||||
export class ModelRenderer extends Renderer implements Disposable {
|
export class ModelRenderer extends Renderer implements Disposable {
|
||||||
private readonly perspective_camera: PerspectiveCamera;
|
private readonly perspective_camera: PerspectiveCamera;
|
||||||
private readonly disposables: Disposable[] = [];
|
private readonly disposer = new Disposer();
|
||||||
private readonly clock = new Clock();
|
private readonly clock = new Clock();
|
||||||
private mesh?: Object3D;
|
private mesh?: Object3D;
|
||||||
private skeleton_helper?: SkeletonHelper;
|
private skeleton_helper?: SkeletonHelper;
|
||||||
@ -41,7 +42,7 @@ export class ModelRenderer extends Renderer implements Disposable {
|
|||||||
|
|
||||||
this.perspective_camera = this.camera as PerspectiveCamera;
|
this.perspective_camera = this.camera as PerspectiveCamera;
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposer.add(
|
||||||
model_store.current_nj_data.observe(this.nj_data_or_xvm_changed),
|
model_store.current_nj_data.observe(this.nj_data_or_xvm_changed),
|
||||||
model_store.current_xvm.observe(this.nj_data_or_xvm_changed),
|
model_store.current_xvm.observe(this.nj_data_or_xvm_changed),
|
||||||
model_store.current_nj_motion.observe(this.nj_motion_changed),
|
model_store.current_nj_motion.observe(this.nj_motion_changed),
|
||||||
@ -60,7 +61,7 @@ export class ModelRenderer extends Renderer implements Disposable {
|
|||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
this.disposables.forEach(d => d.dispose());
|
this.disposer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): void {
|
protected render(): void {
|
||||||
|
118
src/viewer/rendering/TextureRenderer.ts
Normal file
118
src/viewer/rendering/TextureRenderer.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
Mesh,
|
||||||
|
MeshBasicMaterial,
|
||||||
|
OrthographicCamera,
|
||||||
|
PlaneGeometry,
|
||||||
|
Texture,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
} from "three";
|
||||||
|
import { Disposable } from "../../core/observable/Disposable";
|
||||||
|
import { Renderer } from "../../core/rendering/Renderer";
|
||||||
|
import { Disposer } from "../../core/observable/Disposer";
|
||||||
|
import { Xvm } from "../../core/data_formats/parsing/ninja/texture";
|
||||||
|
import { xvm_texture_to_texture } from "../../core/rendering/conversion/ninja_textures";
|
||||||
|
import Logger = require("js-logger");
|
||||||
|
import { texture_store } from "../stores/TextureStore";
|
||||||
|
|
||||||
|
const logger = Logger.get("viewer/rendering/TextureRenderer");
|
||||||
|
|
||||||
|
export class TextureRenderer extends Renderer implements Disposable {
|
||||||
|
private readonly ortho_camera: OrthographicCamera;
|
||||||
|
private readonly disposer = new Disposer();
|
||||||
|
private readonly quad_meshes: Mesh[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(new OrthographicCamera(-400, 400, 300, -300, 1, 10));
|
||||||
|
|
||||||
|
this.ortho_camera = this.camera as OrthographicCamera;
|
||||||
|
this.controls.dollySpeed = -1;
|
||||||
|
|
||||||
|
this.controls.azimuthRotateSpeed = 0;
|
||||||
|
this.controls.polarRotateSpeed = 0;
|
||||||
|
|
||||||
|
this.disposer.add(
|
||||||
|
texture_store.current_xvm.observe(xvm => {
|
||||||
|
this.scene.remove(...this.quad_meshes);
|
||||||
|
|
||||||
|
if (xvm) {
|
||||||
|
this.render_textures(xvm);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reset_camera(new Vector3(0, 0, 5), new Vector3());
|
||||||
|
this.schedule_render();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_size(width: number, height: number): void {
|
||||||
|
this.ortho_camera.left = -Math.floor(width / 2);
|
||||||
|
this.ortho_camera.right = Math.ceil(width / 2);
|
||||||
|
this.ortho_camera.top = Math.floor(height / 2);
|
||||||
|
this.ortho_camera.bottom = -Math.ceil(height / 2);
|
||||||
|
this.ortho_camera.updateProjectionMatrix();
|
||||||
|
super.set_size(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
super.dispose();
|
||||||
|
this.disposer.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private render_textures(xvm: Xvm): void {
|
||||||
|
let total_width = 10 * (xvm.textures.length - 1); // 10px spacing between textures.
|
||||||
|
let total_height = 0;
|
||||||
|
|
||||||
|
for (const tex of xvm.textures) {
|
||||||
|
total_width += tex.width;
|
||||||
|
total_height = Math.max(total_height, tex.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = -Math.floor(total_width / 2);
|
||||||
|
const y = -Math.floor(total_height / 2);
|
||||||
|
|
||||||
|
for (const tex of xvm.textures) {
|
||||||
|
let tex_3js: Texture | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
tex_3js = xvm_texture_to_texture(tex);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn("Couldn't convert XVM texture.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const quad_mesh = new Mesh(
|
||||||
|
this.create_quad(
|
||||||
|
x,
|
||||||
|
y + Math.floor((total_height - tex.height) / 2),
|
||||||
|
tex.width,
|
||||||
|
tex.height,
|
||||||
|
),
|
||||||
|
tex_3js
|
||||||
|
? new MeshBasicMaterial({
|
||||||
|
map: tex_3js,
|
||||||
|
transparent: true,
|
||||||
|
})
|
||||||
|
: new MeshBasicMaterial({
|
||||||
|
color: 0xff00ff,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.quad_meshes.push(quad_mesh);
|
||||||
|
this.scene.add(quad_mesh);
|
||||||
|
|
||||||
|
x += 10 + tex.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private create_quad(x: number, y: number, width: number, height: number): PlaneGeometry {
|
||||||
|
const quad = new PlaneGeometry(width, height, 1, 1);
|
||||||
|
quad.faceVertexUvs = [
|
||||||
|
[
|
||||||
|
[new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0)],
|
||||||
|
[new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0)],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
quad.translate(x + width / 2, y + height / 2, -5);
|
||||||
|
return quad;
|
||||||
|
}
|
||||||
|
}
|
25
src/viewer/stores/TextureStore.ts
Normal file
25
src/viewer/stores/TextureStore.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { property } from "../../core/observable";
|
||||||
|
import { parse_xvm, Xvm } from "../../core/data_formats/parsing/ninja/texture";
|
||||||
|
import { Property } from "../../core/observable/Property";
|
||||||
|
import { read_file } from "../../core/read_file";
|
||||||
|
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
||||||
|
import { Endianness } from "../../core/data_formats/Endianness";
|
||||||
|
import Logger = require("js-logger");
|
||||||
|
|
||||||
|
const logger = Logger.get("viewer/stores/TextureStore");
|
||||||
|
|
||||||
|
export class TextureStore {
|
||||||
|
private readonly _current_xvm = property<Xvm | undefined>(undefined);
|
||||||
|
readonly current_xvm: Property<Xvm | undefined> = this._current_xvm;
|
||||||
|
|
||||||
|
load_file = async (file: File) => {
|
||||||
|
try {
|
||||||
|
const buffer = await read_file(file);
|
||||||
|
this._current_xvm.set(parse_xvm(new ArrayBufferCursor(buffer, Endianness.Little)));
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Couldn't read file.", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const texture_store = new TextureStore();
|
Loading…
Reference in New Issue
Block a user