mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-07-03 06:18:11 +00:00
feat: xmodel preview in ModMan (#835)
* chore: upgrade webwindowed for dynamic assets * chore: make enums in ModMan lowercase * chore: add missing platform wiiu in ModMan * fix: register asset handler on all windows * chore: properly localize game and platform * chore: render example cube as xmodel preview * chore: allow origin * in debug * feat: show preview of xmodels with ModMan * feat: show images in xmodel preview * feat: auto load search paths in ModMan * chore: load objcontainer of loaded zones in ModMan * chore: add iw4x specific recognized zone dirs * chore: show when models are loading * fix: make sure webwindowed handles window and app destruction in correct order * chore: track and properly free threejs resources * chore: add skybox for 3d preview * chore: add small border radius to preview * fix: linting * fix: linux compilation * chore: update package lock
This commit is contained in:
@@ -16,8 +16,10 @@
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "5.2.8",
|
||||
"@primeuix/themes": "2.0.3",
|
||||
"@vueuse/core": "14.3.0",
|
||||
"pinia": "3.0.4",
|
||||
"primevue": "4.5.5",
|
||||
"three": "0.184.0",
|
||||
"vue": "3.5.35",
|
||||
"vue-router": "5.1.0"
|
||||
},
|
||||
@@ -25,13 +27,14 @@
|
||||
"@tsconfig/node24": "24.0.4",
|
||||
"@types/jsdom": "28.0.3",
|
||||
"@types/node": "25.9.2",
|
||||
"@types/three": "0.184.1",
|
||||
"@vitejs/plugin-vue": "6.0.7",
|
||||
"@vitest/eslint-plugin": "1.6.19",
|
||||
"@vue/eslint-config-prettier": "10.2.0",
|
||||
"@vue/eslint-config-typescript": "14.8.0",
|
||||
"@vue/test-utils": "2.4.11",
|
||||
"@vue/tsconfig": "0.9.1",
|
||||
"@webwindowed/vite-plugin-cpp-header": "1.0.0",
|
||||
"@webwindowed/vite-plugin-cpp-header": "1.1.0",
|
||||
"@webwindowed/web-api": "1.0.0",
|
||||
"eslint": "10.4.1",
|
||||
"eslint-plugin-vue": "10.9.2",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 990 KiB |
@@ -0,0 +1 @@
|
||||
https://polyhaven.com/a/citrus_orchard_puresky
|
||||
@@ -0,0 +1,169 @@
|
||||
import { BufferGeometry, Material, Object3D, Texture } from "three";
|
||||
import { type IUniform } from "three/src/renderers/shaders/UniformsLib.js";
|
||||
|
||||
function getMaterialsOfObject(object: Object3D) {
|
||||
if ("material" in object) {
|
||||
const value = object.material as Material | Material[];
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export class ThreeResourceTracker {
|
||||
private readonly textures: Record<number, number>;
|
||||
private readonly materials: Record<string, number>;
|
||||
private readonly geometries: Record<number, number>;
|
||||
private readonly objects: Record<number, number>;
|
||||
|
||||
constructor() {
|
||||
this.textures = {};
|
||||
this.materials = {};
|
||||
this.geometries = {};
|
||||
this.objects = {};
|
||||
}
|
||||
|
||||
refTexture(texture: Texture) {
|
||||
if (!this.textures[texture.id]) {
|
||||
this.textures[texture.id] = 1;
|
||||
} else {
|
||||
this.textures[texture.id]++;
|
||||
}
|
||||
}
|
||||
|
||||
refMaterial(material: Material) {
|
||||
if (!this.materials[material.uuid]) {
|
||||
this.materials[material.uuid] = 1;
|
||||
for (const property of Object.values(material)) {
|
||||
if (property instanceof Texture) {
|
||||
this.refTexture(property);
|
||||
}
|
||||
if ("uniforms" in material) {
|
||||
for (const value of Object.values(material.uniforms as Record<string, IUniform>)) {
|
||||
if (value) {
|
||||
const uniformValue = value.value;
|
||||
const uniformValues = Array.isArray(uniformValue) ? uniformValue : [uniformValue];
|
||||
|
||||
for (const maybeTexture of uniformValues) {
|
||||
if (maybeTexture instanceof Texture) {
|
||||
this.refTexture(maybeTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.materials[material.uuid]++;
|
||||
}
|
||||
}
|
||||
|
||||
refGeometry(geometry: BufferGeometry) {
|
||||
if (!this.geometries[geometry.id]) {
|
||||
this.geometries[geometry.id] = 1;
|
||||
} else {
|
||||
this.geometries[geometry.id]++;
|
||||
}
|
||||
}
|
||||
|
||||
refObject(object: Object3D) {
|
||||
if (!this.objects[object.id]) {
|
||||
this.objects[object.id] = 1;
|
||||
for (const material of getMaterialsOfObject(object)) {
|
||||
this.refMaterial(material);
|
||||
}
|
||||
if ("geometry" in object) {
|
||||
this.refGeometry(object.geometry as BufferGeometry);
|
||||
}
|
||||
for (const child of object.children) {
|
||||
this.refObject(child);
|
||||
}
|
||||
} else {
|
||||
this.objects[object.id]++;
|
||||
}
|
||||
}
|
||||
|
||||
unrefTexture(texture: Texture) {
|
||||
const refCount = this.textures[texture.id];
|
||||
if (refCount === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refCount > 1) {
|
||||
this.textures[texture.id] = refCount - 1;
|
||||
} else {
|
||||
delete this.textures[texture.id];
|
||||
texture.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
unrefMaterial(material: Material) {
|
||||
const refCount = this.materials[material.uuid];
|
||||
if (refCount === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refCount > 1) {
|
||||
this.materials[material.uuid] = refCount - 1;
|
||||
} else {
|
||||
for (const property of Object.values(material)) {
|
||||
if (property instanceof Texture) {
|
||||
this.unrefTexture(property);
|
||||
}
|
||||
if ("uniforms" in material) {
|
||||
for (const value of Object.values(material.uniforms as Record<string, IUniform>)) {
|
||||
if (value) {
|
||||
const uniformValue = value.value;
|
||||
const uniformValues = Array.isArray(uniformValue) ? uniformValue : [uniformValue];
|
||||
|
||||
for (const maybeTexture of uniformValues) {
|
||||
if (maybeTexture instanceof Texture) {
|
||||
this.unrefTexture(maybeTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
material.dispose();
|
||||
delete this.materials[material.uuid];
|
||||
}
|
||||
}
|
||||
|
||||
unrefGeometry(geometry: BufferGeometry) {
|
||||
const refCount = this.geometries[geometry.id];
|
||||
if (refCount === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refCount > 1) {
|
||||
this.geometries[geometry.id] = refCount - 1;
|
||||
} else {
|
||||
delete this.geometries[geometry.id];
|
||||
geometry.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
unrefObject(object: Object3D) {
|
||||
const refCount = this.objects[object.id];
|
||||
if (refCount === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refCount > 1) {
|
||||
this.objects[object.id] = refCount - 1;
|
||||
} else {
|
||||
for (const material of getMaterialsOfObject(object)) {
|
||||
this.unrefMaterial(material);
|
||||
}
|
||||
if ("geometry" in object) {
|
||||
this.unrefGeometry(object.geometry as BufferGeometry);
|
||||
}
|
||||
for (const child of object.children) {
|
||||
this.unrefObject(child);
|
||||
}
|
||||
|
||||
delete this.objects[object.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Scene,
|
||||
PerspectiveCamera,
|
||||
WebGLRenderer,
|
||||
AmbientLight,
|
||||
HemisphereLight,
|
||||
Box3,
|
||||
LoadingManager,
|
||||
Loader,
|
||||
TextureLoader,
|
||||
EquirectangularReflectionMapping,
|
||||
SRGBColorSpace,
|
||||
type Texture,
|
||||
} from "three";
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { GLTFLoader, type GLTF } from "three/addons/loaders/GLTFLoader.js";
|
||||
import { DDSLoader } from "three/examples/jsm/loaders/DDSLoader.js";
|
||||
import type { AssetDto } from "@/native/AssetBinds.ts";
|
||||
import {
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
useTemplateRef,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { useResizeObserver } from "@vueuse/core";
|
||||
import { ThreeResourceTracker } from "@/components/assets/xmodel/ThreeResourceTracker.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
asset: AssetDto;
|
||||
zoneName: string;
|
||||
}>();
|
||||
|
||||
const canvasWrapperRef = useTemplateRef<HTMLDivElement>("canvas-wrapper");
|
||||
const canvasRef = useTemplateRef<HTMLCanvasElement>("canvas");
|
||||
|
||||
const scene = new Scene();
|
||||
const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
|
||||
const color = 0xffffff;
|
||||
const intensity = 1;
|
||||
const light = new AmbientLight(color, intensity);
|
||||
scene.add(light);
|
||||
|
||||
const skyColor = 0xb1e1ff; // light blue
|
||||
const groundColor = 0xb97a20; // brownish orange
|
||||
const hemisphereLight = new HemisphereLight(skyColor, groundColor, intensity);
|
||||
scene.add(hemisphereLight);
|
||||
|
||||
camera.position.z = 3;
|
||||
|
||||
const IMAGE_REGEX = /\.\.[\\/]images[\\/](.+)\.(.+)$/m;
|
||||
class ProxyImageLoader extends Loader {
|
||||
private ddsLoader: DDSLoader;
|
||||
constructor() {
|
||||
super();
|
||||
this.ddsLoader = new DDSLoader();
|
||||
}
|
||||
load(
|
||||
url: string,
|
||||
onLoad: (data: unknown) => void,
|
||||
onProgress?: (event: ProgressEvent) => void,
|
||||
onError?: (err: unknown) => void,
|
||||
) {
|
||||
const match = IMAGE_REGEX.exec(url);
|
||||
if (!match) {
|
||||
onError?.("invalid url");
|
||||
return;
|
||||
}
|
||||
|
||||
this.ddsLoader.load(
|
||||
`modman://localhost/image/dds?zone=${encodeURIComponent(props.zoneName)}&name=${encodeURIComponent(match[1])}`,
|
||||
onLoad,
|
||||
onProgress,
|
||||
onError,
|
||||
);
|
||||
}
|
||||
loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<unknown> {
|
||||
const match = IMAGE_REGEX.exec(url);
|
||||
if (!match) {
|
||||
return Promise.reject("invalid url");
|
||||
}
|
||||
|
||||
return this.ddsLoader.loadAsync(
|
||||
`modman://localhost/image/dds?zone=${encodeURIComponent(props.zoneName)}&name=${encodeURIComponent(match[1])}`,
|
||||
onProgress,
|
||||
);
|
||||
}
|
||||
}
|
||||
const manager = new LoadingManager();
|
||||
manager.addHandler(IMAGE_REGEX, new ProxyImageLoader());
|
||||
|
||||
const gltfLoader = new GLTFLoader(manager);
|
||||
const modelUri = computed<string>(
|
||||
() =>
|
||||
`modman://localhost/xmodel/glb?zone=${encodeURIComponent(props.zoneName)}&name=${encodeURIComponent(props.asset.name)}`,
|
||||
);
|
||||
const model = shallowRef<GLTF | undefined>(undefined);
|
||||
const isLoading = ref(false);
|
||||
const resourceTracker = new ThreeResourceTracker();
|
||||
const modelBounds = computed<Box3 | undefined>(() => {
|
||||
const modelValue = model.value;
|
||||
if (!modelValue) return undefined;
|
||||
|
||||
const box = new Box3();
|
||||
box.expandByObject(modelValue.scene);
|
||||
return box;
|
||||
});
|
||||
|
||||
const loader = new TextureLoader();
|
||||
let skybox: Texture | undefined = undefined;
|
||||
loader.loadAsync("/skybox/citrus_orchard_puresky.jpg").then((res) => {
|
||||
skybox = res;
|
||||
skybox.mapping = EquirectangularReflectionMapping;
|
||||
skybox.colorSpace = SRGBColorSpace;
|
||||
scene.background = skybox;
|
||||
});
|
||||
|
||||
watch(
|
||||
modelUri,
|
||||
(uri) => {
|
||||
isLoading.value = true;
|
||||
gltfLoader
|
||||
.loadAsync(uri)
|
||||
.then((gltf) => (model.value = gltf))
|
||||
.finally(() => (isLoading.value = false));
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
let renderer: WebGLRenderer | undefined = undefined;
|
||||
let controls: OrbitControls | undefined;
|
||||
|
||||
function resetCameraPositionForObject() {
|
||||
const boundsValue = modelBounds.value;
|
||||
if (!boundsValue) return;
|
||||
|
||||
const sizeX = boundsValue.max.x - boundsValue.min.x;
|
||||
const sizeY = boundsValue.max.y - boundsValue.min.y;
|
||||
const sizeZ = boundsValue.max.z - boundsValue.min.z;
|
||||
const middleX = boundsValue.min.x + sizeX / 2;
|
||||
const middleY = boundsValue.min.y + sizeY / 2;
|
||||
const middleZ = boundsValue.min.z + sizeZ / 2;
|
||||
|
||||
const cameraX = Math.max(sizeY, sizeZ) / 2 + boundsValue.max.x;
|
||||
camera.position.set(cameraX, middleY, middleZ);
|
||||
camera.lookAt(middleX, middleY, middleZ);
|
||||
|
||||
camera.far = Math.max(cameraX + sizeX, sizeY, sizeZ, 1000) * 2;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
if (controls) {
|
||||
controls.target.set(middleX, middleY, middleZ);
|
||||
controls.update();
|
||||
}
|
||||
}
|
||||
|
||||
watch(model, (newVal, oldVal) => {
|
||||
if (newVal) {
|
||||
resourceTracker.refObject(newVal.scene);
|
||||
}
|
||||
|
||||
if (oldVal) {
|
||||
scene.remove(toRaw(oldVal).scene);
|
||||
}
|
||||
|
||||
if (newVal) {
|
||||
scene.add(toRaw(newVal.scene));
|
||||
resetCameraPositionForObject();
|
||||
}
|
||||
|
||||
if (oldVal) {
|
||||
resourceTracker.unrefObject(toRaw(oldVal).scene);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
renderer = new WebGLRenderer({ canvas: canvasRef.value! });
|
||||
const canvasWrapperBounds = canvasWrapperRef.value!.getBoundingClientRect();
|
||||
renderer.setSize(canvasWrapperBounds.width, canvasWrapperBounds.height);
|
||||
|
||||
function animate() {
|
||||
renderer!.render(scene, camera);
|
||||
}
|
||||
renderer.setAnimationLoop(animate);
|
||||
|
||||
controls = new OrbitControls(camera, canvasRef.value!);
|
||||
controls.target.set(0, 0, 0);
|
||||
controls.update();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (model.value) {
|
||||
resourceTracker.unrefObject(toRaw(model.value).scene);
|
||||
}
|
||||
if (skybox) {
|
||||
skybox.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
useResizeObserver(canvasWrapperRef, () => {
|
||||
const canvasWrapperBounds = canvasWrapperRef.value!.getBoundingClientRect();
|
||||
renderer?.setSize(canvasWrapperBounds.width, canvasWrapperBounds.height);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="preview-wrapper" ref="canvas-wrapper">
|
||||
<canvas class="preview" ref="canvas" />
|
||||
<div v-if="isLoading" class="loading-overlay">
|
||||
<span>Loading</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.preview-wrapper {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.preview {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
.loading-overlay {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
transition: opacity ease-in-out 500ms;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { GameId, GamePlatform } from "@/native/ZoneBinds.ts";
|
||||
import type { CommonAssetType } from "@/native/AssetBinds.ts";
|
||||
|
||||
export function localizeGame(game: GameId) {
|
||||
return game.toUpperCase();
|
||||
}
|
||||
|
||||
const GAME_PLATFORM_LOOKUP: Record<GamePlatform, string> = {
|
||||
pc: "PC",
|
||||
ps3: "PS3",
|
||||
xbox: "Xbox",
|
||||
wiiu: "WiiU",
|
||||
};
|
||||
export function localizePlatform(platform: GamePlatform) {
|
||||
return GAME_PLATFORM_LOOKUP[platform];
|
||||
}
|
||||
|
||||
const ASSET_TYPE_LOOKUP: Record<CommonAssetType, string> = {
|
||||
phys_preset: "Phys preset",
|
||||
xanim: "XAnim",
|
||||
xmodel: "XModel",
|
||||
material: "Material",
|
||||
technique_set: "Technique set",
|
||||
image: "Image",
|
||||
sound: "Sound",
|
||||
sound_curve: "Sound curve",
|
||||
loaded_sound: "Loaded sound",
|
||||
clip_map: "Clip map",
|
||||
com_world: "Com world",
|
||||
game_world_sp: "Game world SP",
|
||||
game_world_mp: "Game world MP",
|
||||
map_ents: "Map ents",
|
||||
gfx_world: "Gfx world",
|
||||
light_def: "Light def",
|
||||
ui_map: "UI map",
|
||||
font: "Font",
|
||||
menu_list: "Menu list",
|
||||
menu: "Menu",
|
||||
localize_entry: "Localize entry",
|
||||
weapon: "Weapon",
|
||||
sound_driver_globals: "Sound driver globals",
|
||||
fx: "FX",
|
||||
impact_fx: "Impact FX",
|
||||
ai_type: "AI type",
|
||||
mp_type: "MP type",
|
||||
character: "Character",
|
||||
xmodel_alias: "XModel alias",
|
||||
raw_file: "Raw file",
|
||||
string_table: "String table",
|
||||
xmodel_pieces: "XModel pieces",
|
||||
phys_coll_map: "Phys coll map",
|
||||
xmodel_surfs: "XModel surfs",
|
||||
pixel_shader: "Pixel shader",
|
||||
vertex_shader: "Vertex shader",
|
||||
vertex_decl: "Vertex decl",
|
||||
fx_world: "FX world",
|
||||
leaderboard: "Leaderboard",
|
||||
structured_data_def: "Structured data def",
|
||||
tracer: "Tracer",
|
||||
vehicle: "Vehicle",
|
||||
addon_map_ents: "Addon map ents",
|
||||
glass_world: "Glass world",
|
||||
path_data: "Path data",
|
||||
vehicle_track: "Vehicle track",
|
||||
attachment: "Attachment",
|
||||
surface_fx: "Surface FX",
|
||||
script: "Script",
|
||||
phys_constraints: "Phys constraints",
|
||||
destructible_def: "Destructible def",
|
||||
sound_patch: "Sound patch",
|
||||
weapon_def: "Weapon def",
|
||||
weapon_variant: "Weapon variant",
|
||||
mp_body: "MP body",
|
||||
mp_head: "MP head",
|
||||
pack_index: "Pack index",
|
||||
xglobals: "XGlobals",
|
||||
ddl: "DDL",
|
||||
glasses: "Glasses",
|
||||
emblem_set: "Emblem set",
|
||||
font_icon: "Font icon",
|
||||
weapon_full: "Weapon full",
|
||||
attachment_unique: "Attachment unique",
|
||||
weapon_camo: "Weapon camo",
|
||||
key_value_pairs: "Key value pairs",
|
||||
memory_block: "Memory block",
|
||||
skinned_verts: "Skinned verts",
|
||||
qdb: "Qdb",
|
||||
slug: "Slug",
|
||||
footstep_table: "Footstep table",
|
||||
footstep_fx_table: "Footstep FX table",
|
||||
zbarrier: "ZBarrier",
|
||||
};
|
||||
|
||||
export function localizeAssetType(assetType: CommonAssetType): string {
|
||||
return ASSET_TYPE_LOOKUP[assetType];
|
||||
}
|
||||
@@ -1,80 +1,79 @@
|
||||
import { getBinds } from "@webwindowed/web-api";
|
||||
|
||||
export enum CommonAssetType {
|
||||
PHYS_PRESET = "PHYS_PRESET",
|
||||
XANIM = "XANIM",
|
||||
XMODEL = "XMODEL",
|
||||
MATERIAL = "MATERIAL",
|
||||
TECHNIQUE_SET = "TECHNIQUE_SET",
|
||||
IMAGE = "IMAGE",
|
||||
SOUND = "SOUND",
|
||||
SOUND_CURVE = "SOUND_CURVE",
|
||||
LOADED_SOUND = "LOADED_SOUND",
|
||||
CLIP_MAP = "CLIP_MAP",
|
||||
COM_WORLD = "COM_WORLD",
|
||||
GAME_WORLD_SP = "GAME_WORLD_SP",
|
||||
GAME_WORLD_MP = "GAME_WORLD_MP",
|
||||
MAP_ENTS = "MAP_ENTS",
|
||||
GFX_WORLD = "GFX_WORLD",
|
||||
LIGHT_DEF = "LIGHT_DEF",
|
||||
UI_MAP = "UI_MAP",
|
||||
FONT = "FONT",
|
||||
MENU_LIST = "MENU_LIST",
|
||||
MENU = "MENU",
|
||||
LOCALIZE_ENTRY = "LOCALIZE_ENTRY",
|
||||
WEAPON = "WEAPON",
|
||||
SOUND_DRIVER_GLOBALS = "SOUND_DRIVER_GLOBALS",
|
||||
FX = "FX",
|
||||
IMPACT_FX = "IMPACT_FX",
|
||||
AI_TYPE = "AI_TYPE",
|
||||
MP_TYPE = "MP_TYPE",
|
||||
CHARACTER = "CHARACTER",
|
||||
XMODEL_ALIAS = "XMODEL_ALIAS",
|
||||
RAW_FILE = "RAW_FILE",
|
||||
STRING_TABLE = "STRING_TABLE",
|
||||
XMODEL_PIECES = "XMODEL_PIECES",
|
||||
PHYS_COLL_MAP = "PHYS_COLL_MAP",
|
||||
XMODEL_SURFS = "XMODEL_SURFS",
|
||||
PIXEL_SHADER = "PIXEL_SHADER",
|
||||
VERTEX_SHADER = "VERTEX_SHADER",
|
||||
VERTEX_DECL = "VERTEX_DECL",
|
||||
FX_WORLD = "FX_WORLD",
|
||||
LEADERBOARD = "LEADERBOARD",
|
||||
STRUCTURED_DATA_DEF = "STRUCTURED_DATA_DEF",
|
||||
TRACER = "TRACER",
|
||||
VEHICLE = "VEHICLE",
|
||||
ADDON_MAP_ENTS = "ADDON_MAP_ENTS",
|
||||
GLASS_WORLD = "GLASS_WORLD",
|
||||
PATH_DATA = "PATH_DATA",
|
||||
VEHICLE_TRACK = "VEHICLE_TRACK",
|
||||
ATTACHMENT = "ATTACHMENT",
|
||||
SURFACE_FX = "SURFACE_FX",
|
||||
SCRIPT = "SCRIPT",
|
||||
PHYS_CONSTRAINTS = "PHYS_CONSTRAINTS",
|
||||
DESTRUCTIBLE_DEF = "DESTRUCTIBLE_DEF",
|
||||
SOUND_PATCH = "SOUND_PATCH",
|
||||
WEAPON_DEF = "WEAPON_DEF",
|
||||
WEAPON_VARIANT = "WEAPON_VARIANT",
|
||||
MP_BODY = "MP_BODY",
|
||||
MP_HEAD = "MP_HEAD",
|
||||
PACK_INDEX = "PACK_INDEX",
|
||||
XGLOBALS = "XGLOBALS",
|
||||
DDL = "DDL",
|
||||
GLASSES = "GLASSES",
|
||||
EMBLEM_SET = "EMBLEM_SET",
|
||||
FONT_ICON = "FONT_ICON",
|
||||
WEAPON_FULL = "WEAPON_FULL",
|
||||
ATTACHMENT_UNIQUE = "ATTACHMENT_UNIQUE",
|
||||
WEAPON_CAMO = "WEAPON_CAMO",
|
||||
KEY_VALUE_PAIRS = "KEY_VALUE_PAIRS",
|
||||
MEMORY_BLOCK = "MEMORY_BLOCK",
|
||||
SKINNED_VERTS = "SKINNED_VERTS",
|
||||
QDB = "QDB",
|
||||
SLUG = "SLUG",
|
||||
FOOTSTEP_TABLE = "FOOTSTEP_TABLE",
|
||||
FOOTSTEP_FX_TABLE = "FOOTSTEP_FX_TABLE",
|
||||
ZBARRIER = "ZBARRIER",
|
||||
}
|
||||
export type CommonAssetType =
|
||||
| "phys_preset"
|
||||
| "xanim"
|
||||
| "xmodel"
|
||||
| "material"
|
||||
| "technique_set"
|
||||
| "image"
|
||||
| "sound"
|
||||
| "sound_curve"
|
||||
| "loaded_sound"
|
||||
| "clip_map"
|
||||
| "com_world"
|
||||
| "game_world_sp"
|
||||
| "game_world_mp"
|
||||
| "map_ents"
|
||||
| "gfx_world"
|
||||
| "light_def"
|
||||
| "ui_map"
|
||||
| "font"
|
||||
| "menu_list"
|
||||
| "menu"
|
||||
| "localize_entry"
|
||||
| "weapon"
|
||||
| "sound_driver_globals"
|
||||
| "fx"
|
||||
| "impact_fx"
|
||||
| "ai_type"
|
||||
| "mp_type"
|
||||
| "character"
|
||||
| "xmodel_alias"
|
||||
| "raw_file"
|
||||
| "string_table"
|
||||
| "xmodel_pieces"
|
||||
| "phys_coll_map"
|
||||
| "xmodel_surfs"
|
||||
| "pixel_shader"
|
||||
| "vertex_shader"
|
||||
| "vertex_decl"
|
||||
| "fx_world"
|
||||
| "leaderboard"
|
||||
| "structured_data_def"
|
||||
| "tracer"
|
||||
| "vehicle"
|
||||
| "addon_map_ents"
|
||||
| "glass_world"
|
||||
| "path_data"
|
||||
| "vehicle_track"
|
||||
| "attachment"
|
||||
| "surface_fx"
|
||||
| "script"
|
||||
| "phys_constraints"
|
||||
| "destructible_def"
|
||||
| "sound_patch"
|
||||
| "weapon_def"
|
||||
| "weapon_variant"
|
||||
| "mp_body"
|
||||
| "mp_head"
|
||||
| "pack_index"
|
||||
| "xglobals"
|
||||
| "ddl"
|
||||
| "glasses"
|
||||
| "emblem_set"
|
||||
| "font_icon"
|
||||
| "weapon_full"
|
||||
| "attachment_unique"
|
||||
| "weapon_camo"
|
||||
| "key_value_pairs"
|
||||
| "memory_block"
|
||||
| "skinned_verts"
|
||||
| "qdb"
|
||||
| "slug"
|
||||
| "footstep_table"
|
||||
| "footstep_fx_table"
|
||||
| "zbarrier";
|
||||
|
||||
export interface AssetDto {
|
||||
type: CommonAssetType;
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
import { getBinds } from "@webwindowed/web-api";
|
||||
|
||||
export enum GameId {
|
||||
IW3 = "IW3",
|
||||
IW4 = "IW4",
|
||||
IW5 = "IW5",
|
||||
T4 = "T4",
|
||||
T5 = "T5",
|
||||
T6 = "T6",
|
||||
}
|
||||
export type GameId = "iw3" | "iw4" | "iw5" | "t4" | "t5" | "t6";
|
||||
|
||||
export enum GamePlatform {
|
||||
PC = "PC",
|
||||
XBOX = "XBOX",
|
||||
PS3 = "PS3",
|
||||
}
|
||||
export type GamePlatform = "pc" | "xbox" | "ps3" | "wiiu";
|
||||
|
||||
export interface ZoneDto {
|
||||
name: string;
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import { CommonAssetType } from "@/native/AssetBinds";
|
||||
|
||||
const LOOKUP_CAPITALIZED: Record<CommonAssetType, string> = {
|
||||
[CommonAssetType.PHYS_PRESET]: "Phys preset",
|
||||
[CommonAssetType.XANIM]: "XAnim",
|
||||
[CommonAssetType.XMODEL]: "XModel",
|
||||
[CommonAssetType.MATERIAL]: "Material",
|
||||
[CommonAssetType.TECHNIQUE_SET]: "Technique set",
|
||||
[CommonAssetType.IMAGE]: "Image",
|
||||
[CommonAssetType.SOUND]: "Sound",
|
||||
[CommonAssetType.SOUND_CURVE]: "Sound curve",
|
||||
[CommonAssetType.LOADED_SOUND]: "Loaded sound",
|
||||
[CommonAssetType.CLIP_MAP]: "Clip map",
|
||||
[CommonAssetType.COM_WORLD]: "Com world",
|
||||
[CommonAssetType.GAME_WORLD_SP]: "Game world SP",
|
||||
[CommonAssetType.GAME_WORLD_MP]: "Game world MP",
|
||||
[CommonAssetType.MAP_ENTS]: "Map ents",
|
||||
[CommonAssetType.GFX_WORLD]: "Gfx world",
|
||||
[CommonAssetType.LIGHT_DEF]: "Light def",
|
||||
[CommonAssetType.UI_MAP]: "UI map",
|
||||
[CommonAssetType.FONT]: "Font",
|
||||
[CommonAssetType.MENU_LIST]: "Menu list",
|
||||
[CommonAssetType.MENU]: "Menu",
|
||||
[CommonAssetType.LOCALIZE_ENTRY]: "Localize entry",
|
||||
[CommonAssetType.WEAPON]: "Weapon",
|
||||
[CommonAssetType.SOUND_DRIVER_GLOBALS]: "Sound driver globals",
|
||||
[CommonAssetType.FX]: "FX",
|
||||
[CommonAssetType.IMPACT_FX]: "Impact FX",
|
||||
[CommonAssetType.AI_TYPE]: "AI type",
|
||||
[CommonAssetType.MP_TYPE]: "MP type",
|
||||
[CommonAssetType.CHARACTER]: "Character",
|
||||
[CommonAssetType.XMODEL_ALIAS]: "XModel alias",
|
||||
[CommonAssetType.RAW_FILE]: "Raw file",
|
||||
[CommonAssetType.STRING_TABLE]: "String table",
|
||||
[CommonAssetType.XMODEL_PIECES]: "XModel pieces",
|
||||
[CommonAssetType.PHYS_COLL_MAP]: "Phys coll map",
|
||||
[CommonAssetType.XMODEL_SURFS]: "XModel surfs",
|
||||
[CommonAssetType.PIXEL_SHADER]: "Pixel shader",
|
||||
[CommonAssetType.VERTEX_SHADER]: "Vertex shader",
|
||||
[CommonAssetType.VERTEX_DECL]: "Vertex decl",
|
||||
[CommonAssetType.FX_WORLD]: "FX world",
|
||||
[CommonAssetType.LEADERBOARD]: "Leaderboard",
|
||||
[CommonAssetType.STRUCTURED_DATA_DEF]: "Structured data def",
|
||||
[CommonAssetType.TRACER]: "Tracer",
|
||||
[CommonAssetType.VEHICLE]: "Vehicle",
|
||||
[CommonAssetType.ADDON_MAP_ENTS]: "Addon map ents",
|
||||
[CommonAssetType.GLASS_WORLD]: "Glass world",
|
||||
[CommonAssetType.PATH_DATA]: "Path data",
|
||||
[CommonAssetType.VEHICLE_TRACK]: "Vehicle track",
|
||||
[CommonAssetType.ATTACHMENT]: "Attachment",
|
||||
[CommonAssetType.SURFACE_FX]: "Surface FX",
|
||||
[CommonAssetType.SCRIPT]: "Script",
|
||||
[CommonAssetType.PHYS_CONSTRAINTS]: "Phys constraints",
|
||||
[CommonAssetType.DESTRUCTIBLE_DEF]: "Destructible def",
|
||||
[CommonAssetType.SOUND_PATCH]: "Sound patch",
|
||||
[CommonAssetType.WEAPON_DEF]: "Weapon def",
|
||||
[CommonAssetType.WEAPON_VARIANT]: "Weapon variant",
|
||||
[CommonAssetType.MP_BODY]: "MP body",
|
||||
[CommonAssetType.MP_HEAD]: "MP head",
|
||||
[CommonAssetType.PACK_INDEX]: "Pack index",
|
||||
[CommonAssetType.XGLOBALS]: "XGlobals",
|
||||
[CommonAssetType.DDL]: "DDL",
|
||||
[CommonAssetType.GLASSES]: "Glasses",
|
||||
[CommonAssetType.EMBLEM_SET]: "Emblem set",
|
||||
[CommonAssetType.FONT_ICON]: "Font icon",
|
||||
[CommonAssetType.WEAPON_FULL]: "Weapon full",
|
||||
[CommonAssetType.ATTACHMENT_UNIQUE]: "Attachment unique",
|
||||
[CommonAssetType.WEAPON_CAMO]: "Weapon camo",
|
||||
[CommonAssetType.KEY_VALUE_PAIRS]: "Key value pairs",
|
||||
[CommonAssetType.MEMORY_BLOCK]: "Memory block",
|
||||
[CommonAssetType.SKINNED_VERTS]: "Skinned verts",
|
||||
[CommonAssetType.QDB]: "Qdb",
|
||||
[CommonAssetType.SLUG]: "Slug",
|
||||
[CommonAssetType.FOOTSTEP_TABLE]: "Footstep table",
|
||||
[CommonAssetType.FOOTSTEP_FX_TABLE]: "Footstep FX table",
|
||||
[CommonAssetType.ZBARRIER]: "ZBarrier",
|
||||
};
|
||||
|
||||
export function getAssetTypeNameCapitalized(assetType: CommonAssetType): string {
|
||||
return LOOKUP_CAPITALIZED[assetType];
|
||||
}
|
||||
|
||||
export function getAssetTypeNameLower(assetType: CommonAssetType): string {
|
||||
return getAssetTypeNameCapitalized(assetType).toLocaleLowerCase();
|
||||
}
|
||||
@@ -8,11 +8,11 @@ import type { ZoneDto } from "@/native/ZoneBinds";
|
||||
import { useZoneStore } from "@/stores/ZoneStore";
|
||||
import { computed, watch } from "vue";
|
||||
import type { CommonAssetType } from "@/native/AssetBinds";
|
||||
import { getAssetTypeNameCapitalized } from "@/utils/AssetTypeName";
|
||||
import { useRouter } from "vue-router";
|
||||
import { PAGE } from "@/router/Page";
|
||||
import { useAssetStore } from "@/stores/AssetStore";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { localizeAssetType, localizeGame, localizePlatform } from "@/i18n/i18n.ts";
|
||||
|
||||
const assetStore = useAssetStore();
|
||||
const zoneStore = useZoneStore();
|
||||
@@ -52,7 +52,7 @@ const meterItems = computed<MeterItem[]>(() => {
|
||||
.filter((entry) => entry[1] > minItemCountForDisplay)
|
||||
.sort((e0, e1) => e1[1] - e0[1])
|
||||
.map((entry) => ({
|
||||
label: getAssetTypeNameCapitalized(entry[0] as CommonAssetType),
|
||||
label: localizeAssetType(entry[0] as CommonAssetType),
|
||||
value: Math.round((entry[1] / assetCount.value) * 100),
|
||||
}));
|
||||
|
||||
@@ -105,8 +105,8 @@ watch(
|
||||
<h2>{{ selectedZone ?? "No zone selected" }}</h2>
|
||||
<Button label="Show assets" :disabled="!selectedZone" @click="onClickShowAssets" />
|
||||
<div v-if="selectedZoneDetails" class="zone-tags">
|
||||
<Tag :value="selectedZoneDetails.game" />
|
||||
<Tag :value="selectedZoneDetails.platform" />
|
||||
<Tag :value="localizeGame(selectedZoneDetails.game)" />
|
||||
<Tag :value="localizePlatform(selectedZoneDetails.platform)" />
|
||||
</div>
|
||||
<div class="zone-assets">
|
||||
<template v-if="assetsOfZone">
|
||||
|
||||
@@ -16,7 +16,7 @@ const props = defineProps<{
|
||||
zoneName: string;
|
||||
}>();
|
||||
|
||||
const selectedAsset = ref<AssetDto | null>(null);
|
||||
const selectedAsset = ref<AssetDto | undefined>(undefined);
|
||||
|
||||
watch(
|
||||
() => props.zoneName,
|
||||
@@ -30,7 +30,7 @@ watch(
|
||||
<template>
|
||||
<div class="inspect-details">
|
||||
<template v-if="assetsOfZone">
|
||||
<InspectPreview class="inspect-area-preview" />
|
||||
<InspectPreview :asset="selectedAsset" :zone-name class="inspect-area-preview" />
|
||||
<InspectAssetDetails :selected-asset="selectedAsset" class="inspect-area-details" />
|
||||
<InspectZoneAssets
|
||||
v-model:selected-asset="selectedAsset"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed } from "vue";
|
||||
import type { ZoneDto } from "@/native/ZoneBinds.ts";
|
||||
import { useZoneStore } from "@/stores/ZoneStore.ts";
|
||||
import Tag from "primevue/tag";
|
||||
import { localizeGame, localizePlatform } from "@/i18n/i18n.ts";
|
||||
|
||||
const zoneStore = useZoneStore();
|
||||
const props = defineProps<{
|
||||
@@ -18,8 +19,12 @@ const zoneDetails = computed<ZoneDto | null>(() =>
|
||||
<span>
|
||||
<span>Inspect zone: {{ zoneName }}</span>
|
||||
<template v-if="zoneDetails">
|
||||
<Tag class="zone-header-tag" :value="zoneDetails.game" severity="secondary" />
|
||||
<Tag class="zone-header-tag" :value="zoneDetails.platform" severity="secondary" />
|
||||
<Tag class="zone-header-tag" :value="localizeGame(zoneDetails.game)" severity="secondary" />
|
||||
<Tag
|
||||
class="zone-header-tag"
|
||||
:value="localizePlatform(zoneDetails.platform)"
|
||||
severity="secondary"
|
||||
/>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { AssetDto } from "@/native/AssetBinds.ts";
|
||||
import { getAssetTypeNameCapitalized } from "@/utils/AssetTypeName.ts";
|
||||
import { localizeAssetType } from "@/i18n/i18n.ts";
|
||||
|
||||
defineProps<{
|
||||
asset: AssetDto;
|
||||
@@ -9,7 +9,7 @@ defineProps<{
|
||||
|
||||
<template>
|
||||
<div class="asset-option">
|
||||
<span class="asset-type">{{ getAssetTypeNameCapitalized(asset.type) }}</span>
|
||||
<span class="asset-type">{{ localizeAssetType(asset.type) }}</span>
|
||||
<span class="asset-name">{{ asset.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { AssetDto } from "@/native/AssetBinds.ts";
|
||||
import Tag from "primevue/tag";
|
||||
import { getAssetTypeNameCapitalized } from "@/utils/AssetTypeName.ts";
|
||||
import { computed } from "vue";
|
||||
import { localizeAssetType } from "@/i18n/i18n.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
selectedAsset: AssetDto | null;
|
||||
selectedAsset?: AssetDto;
|
||||
}>();
|
||||
|
||||
const assetTypeName = computed(() =>
|
||||
props.selectedAsset ? getAssetTypeNameCapitalized(props.selectedAsset.type) : "",
|
||||
props.selectedAsset ? localizeAssetType(props.selectedAsset.type) : "",
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import XModelPreview from "@/components/assets/xmodel/XModelPreview.vue";
|
||||
import type { AssetDto } from "@/native/AssetBinds.ts";
|
||||
|
||||
defineProps<{
|
||||
asset?: AssetDto;
|
||||
zoneName: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="preview">
|
||||
<span>No preview available</span>
|
||||
<XModelPreview v-if="asset?.type === 'xmodel'" :asset :zone-name />
|
||||
<span v-else>No preview available</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import Listbox from "primevue/listbox";
|
||||
import type { AssetDto } from "@/native/AssetBinds.ts";
|
||||
import AssetListOption from "@/view/inspect_details/components/AssetListOption.vue";
|
||||
|
||||
const selectedAsset = defineModel<AssetDto | null>("selectedAsset");
|
||||
const selectedAsset = defineModel<AssetDto | undefined>("selectedAsset", { required: true });
|
||||
defineProps<{
|
||||
assets: AssetDto[];
|
||||
}>();
|
||||
|
||||
@@ -17,6 +17,9 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
cors: false,
|
||||
},
|
||||
publicDir: fileURLToPath(new URL("./public", import.meta.url)),
|
||||
plugins: [
|
||||
vue(),
|
||||
|
||||
Reference in New Issue
Block a user