Texture viewer now uses the new UI system.

This commit is contained in:
Daan Vanden Bosch 2019-08-21 18:59:56 +02:00
parent c4865ee510
commit 844e63735e
9 changed files with 225 additions and 12 deletions

View File

@ -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();
}
}

View 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);
}
}
}
}

View File

@ -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 {

View File

@ -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%);
}
* {

View File

@ -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");

View File

@ -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;
}
}

View File

@ -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 {

View 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;
}
}

View 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();