mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 15:28:29 +08:00
The texture viewer can now deal with textures in an AFS archive.
This commit is contained in:
parent
53ca625d43
commit
16b1d0934b
@ -3,7 +3,7 @@ import { LogManager } from "../../Logger";
|
|||||||
|
|
||||||
const logger = LogManager.get("core/data_formats/parsing/afs");
|
const logger = LogManager.get("core/data_formats/parsing/afs");
|
||||||
|
|
||||||
const AFS = 0x41465300;
|
const AFS = 0x00534641;
|
||||||
|
|
||||||
type AfsFileEntry = {
|
type AfsFileEntry = {
|
||||||
readonly offset: number;
|
readonly offset: number;
|
||||||
|
@ -5,10 +5,10 @@ import { LogManager } from "../../../Logger";
|
|||||||
const logger = LogManager.get("core/data_formats/parsing/ninja/texture");
|
const logger = LogManager.get("core/data_formats/parsing/ninja/texture");
|
||||||
|
|
||||||
export type Xvm = {
|
export type Xvm = {
|
||||||
textures: XvmTexture[];
|
textures: XvrTexture[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type XvmTexture = {
|
export type XvrTexture = {
|
||||||
id: number;
|
id: number;
|
||||||
format: [number, number];
|
format: [number, number];
|
||||||
width: number;
|
width: number;
|
||||||
@ -24,34 +24,7 @@ type Header = {
|
|||||||
const XVMH = 0x484d5658;
|
const XVMH = 0x484d5658;
|
||||||
const XVRT = 0x54525658;
|
const XVRT = 0x54525658;
|
||||||
|
|
||||||
export function parse_xvm(cursor: Cursor): Xvm {
|
export function parse_xvr(cursor: Cursor): XvrTexture {
|
||||||
const chunks = parse_iff(cursor);
|
|
||||||
const header_chunk = chunks.find(chunk => chunk.type === XVMH);
|
|
||||||
const header = header_chunk && parse_header(header_chunk.data);
|
|
||||||
|
|
||||||
const textures = chunks
|
|
||||||
.filter(chunk => chunk.type === XVRT)
|
|
||||||
.map(chunk => parse_texture(chunk.data));
|
|
||||||
|
|
||||||
if (!header) {
|
|
||||||
logger.warn("No header found.");
|
|
||||||
} else if (header.texture_count !== textures.length) {
|
|
||||||
logger.warn(
|
|
||||||
`Found ${textures.length} textures instead of ${header.texture_count} as defined in the header.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { textures };
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse_header(cursor: Cursor): Header {
|
|
||||||
const texture_count = cursor.u16();
|
|
||||||
return {
|
|
||||||
texture_count,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse_texture(cursor: Cursor): XvmTexture {
|
|
||||||
const format_1 = cursor.u32();
|
const format_1 = cursor.u32();
|
||||||
const format_2 = cursor.u32();
|
const format_2 = cursor.u32();
|
||||||
const id = cursor.u32();
|
const id = cursor.u32();
|
||||||
@ -69,3 +42,28 @@ function parse_texture(cursor: Cursor): XvmTexture {
|
|||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parse_xvm(cursor: Cursor): Xvm {
|
||||||
|
const chunks = parse_iff(cursor);
|
||||||
|
const header_chunk = chunks.find(chunk => chunk.type === XVMH);
|
||||||
|
const header = header_chunk && parse_header(header_chunk.data);
|
||||||
|
|
||||||
|
const textures = chunks
|
||||||
|
.filter(chunk => chunk.type === XVRT)
|
||||||
|
.map(chunk => parse_xvr(chunk.data));
|
||||||
|
|
||||||
|
if (header && header.texture_count !== textures.length) {
|
||||||
|
logger.warn(
|
||||||
|
`Found ${textures.length} textures instead of ${header.texture_count} as defined in the header.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { textures };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_header(cursor: Cursor): Header {
|
||||||
|
const texture_count = cursor.u16();
|
||||||
|
return {
|
||||||
|
texture_count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -7,13 +7,13 @@ import {
|
|||||||
Texture,
|
Texture,
|
||||||
CompressedPixelFormat,
|
CompressedPixelFormat,
|
||||||
} from "three";
|
} from "three";
|
||||||
import { Xvm, XvmTexture } from "../../data_formats/parsing/ninja/texture";
|
import { Xvm, XvrTexture } from "../../data_formats/parsing/ninja/texture";
|
||||||
|
|
||||||
export function xvm_to_textures(xvm: Xvm): Texture[] {
|
export function xvm_to_textures(xvm: Xvm): Texture[] {
|
||||||
return xvm.textures.map(xvm_texture_to_texture);
|
return xvm.textures.map(xvr_texture_to_texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function xvm_texture_to_texture(tex: XvmTexture): Texture {
|
export function xvr_texture_to_texture(tex: XvrTexture): Texture {
|
||||||
let format: CompressedPixelFormat;
|
let format: CompressedPixelFormat;
|
||||||
let data_size: number;
|
let data_size: number;
|
||||||
|
|
||||||
|
@ -42,6 +42,18 @@ export function basename(filename: string): string {
|
|||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filename_extension(filename: string): string {
|
||||||
|
const dot_idx = filename.lastIndexOf(".");
|
||||||
|
|
||||||
|
// < 0 means filenames doesn't contain any "."
|
||||||
|
// also skip index 0 because that would mean the basename is empty
|
||||||
|
if (dot_idx > 1) {
|
||||||
|
return filename.slice(dot_idx + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
export function assert(condition: any, msg?: string | (() => string)): asserts condition {
|
export function assert(condition: any, msg?: string | (() => string)): asserts condition {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
let full_msg = "Assertion Error";
|
let full_msg = "Assertion Error";
|
||||||
|
@ -13,7 +13,7 @@ export class TextureView extends ResizableWidget {
|
|||||||
|
|
||||||
private readonly open_file_button = new FileButton("Open file...", {
|
private readonly open_file_button = new FileButton("Open file...", {
|
||||||
icon_left: Icon.File,
|
icon_left: Icon.File,
|
||||||
accept: ".xvm",
|
accept: ".afs, .xvm",
|
||||||
});
|
});
|
||||||
|
|
||||||
private readonly tool_bar = this.disposable(new ToolBar(this.open_file_button));
|
private readonly tool_bar = this.disposable(new ToolBar(this.open_file_button));
|
||||||
@ -41,7 +41,7 @@ export class TextureView extends ResizableWidget {
|
|||||||
|
|
||||||
this.renderer_view.start_rendering();
|
this.renderer_view.start_rendering();
|
||||||
|
|
||||||
this.disposable(
|
this.disposables(
|
||||||
gui_store.tool.observe(({ value: tool }) => {
|
gui_store.tool.observe(({ value: tool }) => {
|
||||||
if (tool === GuiTool.Viewer) {
|
if (tool === GuiTool.Viewer) {
|
||||||
this.renderer_view.start_rendering();
|
this.renderer_view.start_rendering();
|
||||||
|
@ -3,19 +3,13 @@ import {
|
|||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
OrthographicCamera,
|
OrthographicCamera,
|
||||||
PlaneGeometry,
|
PlaneGeometry,
|
||||||
Texture,
|
|
||||||
Vector2,
|
Vector2,
|
||||||
Vector3,
|
Vector3,
|
||||||
} from "three";
|
} from "three";
|
||||||
import { Disposable } from "../../core/observable/Disposable";
|
import { Disposable } from "../../core/observable/Disposable";
|
||||||
import { DisposableThreeRenderer, Renderer } from "../../core/rendering/Renderer";
|
import { DisposableThreeRenderer, Renderer } from "../../core/rendering/Renderer";
|
||||||
import { Disposer } from "../../core/observable/Disposer";
|
import { Disposer } from "../../core/observable/Disposer";
|
||||||
import { Xvm } from "../../core/data_formats/parsing/ninja/texture";
|
import { TextureStore, TextureWithSize } from "../stores/TextureStore";
|
||||||
import { xvm_texture_to_texture } from "../../core/rendering/conversion/ninja_textures";
|
|
||||||
import { TextureStore } from "../stores/TextureStore";
|
|
||||||
import { LogManager } from "../../core/Logger";
|
|
||||||
|
|
||||||
const logger = LogManager.get("viewer/rendering/TextureRenderer");
|
|
||||||
|
|
||||||
export class TextureRenderer extends Renderer implements Disposable {
|
export class TextureRenderer extends Renderer implements Disposable {
|
||||||
private readonly disposer = new Disposer();
|
private readonly disposer = new Disposer();
|
||||||
@ -27,12 +21,10 @@ export class TextureRenderer extends Renderer implements Disposable {
|
|||||||
super(three_renderer);
|
super(three_renderer);
|
||||||
|
|
||||||
this.disposer.add_all(
|
this.disposer.add_all(
|
||||||
texture_store.current_xvm.observe(({ value: xvm }) => {
|
texture_store.textures.observe(({ value: textures }) => {
|
||||||
this.scene.remove(...this.quad_meshes);
|
this.scene.remove(...this.quad_meshes);
|
||||||
|
|
||||||
if (xvm) {
|
this.render_textures(textures);
|
||||||
this.render_textures(xvm);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reset_camera(new Vector3(0, 0, 5), new Vector3());
|
this.reset_camera(new Vector3(0, 0, 5), new Vector3());
|
||||||
this.schedule_render();
|
this.schedule_render();
|
||||||
@ -59,11 +51,11 @@ export class TextureRenderer extends Renderer implements Disposable {
|
|||||||
this.disposer.dispose();
|
this.disposer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_textures(xvm: Xvm): void {
|
private render_textures(textures: readonly TextureWithSize[]): void {
|
||||||
let total_width = 10 * (xvm.textures.length - 1); // 10px spacing between textures.
|
let total_width = 10 * (textures.length - 1); // 10px spacing between textures.
|
||||||
let total_height = 0;
|
let total_height = 0;
|
||||||
|
|
||||||
for (const tex of xvm.textures) {
|
for (const tex of textures) {
|
||||||
total_width += tex.width;
|
total_width += tex.width;
|
||||||
total_height = Math.max(total_height, tex.height);
|
total_height = Math.max(total_height, tex.height);
|
||||||
}
|
}
|
||||||
@ -71,15 +63,7 @@ export class TextureRenderer extends Renderer implements Disposable {
|
|||||||
let x = -Math.floor(total_width / 2);
|
let x = -Math.floor(total_width / 2);
|
||||||
const y = -Math.floor(total_height / 2);
|
const y = -Math.floor(total_height / 2);
|
||||||
|
|
||||||
for (const tex of xvm.textures) {
|
for (const tex of 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(
|
const quad_mesh = new Mesh(
|
||||||
this.create_quad(
|
this.create_quad(
|
||||||
x,
|
x,
|
||||||
@ -87,9 +71,9 @@ export class TextureRenderer extends Renderer implements Disposable {
|
|||||||
tex.width,
|
tex.width,
|
||||||
tex.height,
|
tex.height,
|
||||||
),
|
),
|
||||||
tex_3js
|
tex.texture
|
||||||
? new MeshBasicMaterial({
|
? new MeshBasicMaterial({
|
||||||
map: tex_3js,
|
map: tex.texture,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
})
|
})
|
||||||
: new MeshBasicMaterial({
|
: new MeshBasicMaterial({
|
||||||
|
@ -1,22 +1,82 @@
|
|||||||
import { property } from "../../core/observable";
|
import { list_property } from "../../core/observable";
|
||||||
import { parse_xvm, Xvm } from "../../core/data_formats/parsing/ninja/texture";
|
import { parse_xvm } from "../../core/data_formats/parsing/ninja/texture";
|
||||||
import { Property } from "../../core/observable/property/Property";
|
|
||||||
import { read_file } from "../../core/read_file";
|
import { read_file } from "../../core/read_file";
|
||||||
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
||||||
import { Endianness } from "../../core/data_formats/Endianness";
|
import { Endianness } from "../../core/data_formats/Endianness";
|
||||||
import { Store } from "../../core/stores/Store";
|
import { Store } from "../../core/stores/Store";
|
||||||
import { LogManager } from "../../core/Logger";
|
import { LogManager } from "../../core/Logger";
|
||||||
|
import { WritableListProperty } from "../../core/observable/property/list/WritableListProperty";
|
||||||
|
import { Texture } from "three";
|
||||||
|
import { ListProperty } from "../../core/observable/property/list/ListProperty";
|
||||||
|
import { filename_extension } from "../../core/util";
|
||||||
|
import { xvr_texture_to_texture } from "../../core/rendering/conversion/ninja_textures";
|
||||||
|
import { parse_afs } from "../../core/data_formats/parsing/afs";
|
||||||
|
|
||||||
const logger = LogManager.get("viewer/stores/TextureStore");
|
const logger = LogManager.get("viewer/stores/TextureStore");
|
||||||
|
|
||||||
|
export type TextureWithSize = {
|
||||||
|
readonly texture?: Texture;
|
||||||
|
readonly width: number;
|
||||||
|
readonly height: number;
|
||||||
|
};
|
||||||
|
|
||||||
export class TextureStore extends Store {
|
export class TextureStore extends Store {
|
||||||
private readonly _current_xvm = property<Xvm | undefined>(undefined);
|
private readonly _textures: WritableListProperty<TextureWithSize> = list_property();
|
||||||
readonly current_xvm: Property<Xvm | undefined> = this._current_xvm;
|
readonly textures: ListProperty<TextureWithSize> = this._textures;
|
||||||
|
|
||||||
load_file = async (file: File): Promise<void> => {
|
load_file = async (file: File): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
const ext = filename_extension(file.name);
|
||||||
const buffer = await read_file(file);
|
const buffer = await read_file(file);
|
||||||
this._current_xvm.val = parse_xvm(new ArrayBufferCursor(buffer, Endianness.Little));
|
|
||||||
|
if (ext === "xvm") {
|
||||||
|
const xvm = parse_xvm(new ArrayBufferCursor(buffer, Endianness.Little));
|
||||||
|
|
||||||
|
this._textures.splice(
|
||||||
|
0,
|
||||||
|
Infinity,
|
||||||
|
...xvm.textures.map(tex => {
|
||||||
|
let texture: Texture | undefined = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
texture = xvr_texture_to_texture(tex);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Couldn't convert XVR texture.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
texture,
|
||||||
|
width: tex.width,
|
||||||
|
height: tex.height,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (ext === "afs") {
|
||||||
|
const afs = parse_afs(new ArrayBufferCursor(buffer, Endianness.Little));
|
||||||
|
const textures: TextureWithSize[] = [];
|
||||||
|
|
||||||
|
for (const buffer of afs) {
|
||||||
|
const xvm = parse_xvm(new ArrayBufferCursor(buffer, Endianness.Little));
|
||||||
|
|
||||||
|
for (const tex of xvm.textures) {
|
||||||
|
let texture: Texture | undefined = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
texture = xvr_texture_to_texture(tex);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Couldn't convert XVR texture.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
textures.push({
|
||||||
|
texture,
|
||||||
|
width: tex.width,
|
||||||
|
height: tex.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._textures.splice(0, Infinity, ...textures);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Couldn't read file.", e);
|
logger.error("Couldn't read file.", e);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user