mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58: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 { Disposer } from "../observable/Disposer";
|
||||
|
||||
export abstract class View implements Disposable {
|
||||
abstract readonly element: HTMLElement;
|
||||
@ -11,19 +12,19 @@ export abstract class View implements Disposable {
|
||||
this.element.id = id;
|
||||
}
|
||||
|
||||
private disposable_list: Disposable[] = [];
|
||||
private disposer = new Disposer();
|
||||
|
||||
protected disposable<T extends Disposable>(disposable: T): T {
|
||||
this.disposable_list.push(disposable);
|
||||
this.disposer.add(disposable);
|
||||
return disposable;
|
||||
}
|
||||
|
||||
protected disposables(...disposables: Disposable[]): void {
|
||||
this.disposable_list.push(...disposables);
|
||||
this.disposer.add(...disposables);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
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,
|
||||
WebGLRenderer,
|
||||
} from "three";
|
||||
import { Disposable } from "../observable/Disposable";
|
||||
|
||||
CameraControls.install({
|
||||
// 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;
|
||||
|
||||
get debug(): boolean {
|
||||
|
@ -9,8 +9,8 @@
|
||||
--scrollbar-color: hsl(0, 0%, 17%);
|
||||
--scrollbar-thumb-color: hsl(0, 0%, 23%);
|
||||
|
||||
--input-bg-color: hsl(0, 0%, 10%);
|
||||
--input-bg-color-disabled: hsl(0, 0%, 15%);
|
||||
--input-bg-color: 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;
|
||||
|
||||
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 container_element = create_el("div", "viewer_ModelView_container");
|
||||
|
@ -1,6 +1,50 @@
|
||||
import { create_el } from "../../core/gui/dom";
|
||||
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 {
|
||||
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,
|
||||
} from "../../core/rendering/conversion/ninja_animation";
|
||||
import { Renderer } from "../../core/rendering/Renderer";
|
||||
import { Disposer } from "../../core/observable/Disposer";
|
||||
|
||||
export class ModelRenderer extends Renderer implements Disposable {
|
||||
private readonly perspective_camera: PerspectiveCamera;
|
||||
private readonly disposables: Disposable[] = [];
|
||||
private readonly disposer = new Disposer();
|
||||
private readonly clock = new Clock();
|
||||
private mesh?: Object3D;
|
||||
private skeleton_helper?: SkeletonHelper;
|
||||
@ -41,7 +42,7 @@ export class ModelRenderer extends Renderer implements Disposable {
|
||||
|
||||
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_xvm.observe(this.nj_data_or_xvm_changed),
|
||||
model_store.current_nj_motion.observe(this.nj_motion_changed),
|
||||
@ -60,7 +61,7 @@ export class ModelRenderer extends Renderer implements Disposable {
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
this.disposer.dispose();
|
||||
}
|
||||
|
||||
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