mirror of
https://github.com/redstrate/Physis.git
synced 2025-04-23 21:17:45 +00:00
Add basic support for LGB and TERA files
This is for the Novus map editor, although TERA is much more complete than LGB right now.
This commit is contained in:
parent
872cabb2fa
commit
256b3f9305
3 changed files with 604 additions and 0 deletions
531
src/lgb.rs
Normal file
531
src/lgb.rs
Normal file
|
@ -0,0 +1,531 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||||
|
|
||||||
|
use binrw::{BinRead, binread, BinReaderExt};
|
||||||
|
use binrw::binrw;
|
||||||
|
use crate::ByteSpan;
|
||||||
|
|
||||||
|
// From https://github.com/NotAdam/Lumina/tree/40dab50183eb7ddc28344378baccc2d63ae71d35/src/Lumina/Data/Parsing/Layer
|
||||||
|
|
||||||
|
// TODO: convert these all to magic
|
||||||
|
#[binrw]
|
||||||
|
#[repr(i32)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum LayerEntryType
|
||||||
|
{
|
||||||
|
#[brw(magic = 0x0i32)]
|
||||||
|
AssetNone,
|
||||||
|
#[brw(magic = 0x1i32)]
|
||||||
|
BG {
|
||||||
|
asset_path_offset: u32,
|
||||||
|
collision_asset_path_offset: i32,
|
||||||
|
collision_type: ModelCollisionType,
|
||||||
|
attribute_mask: u32,
|
||||||
|
attribute: u32,
|
||||||
|
collision_config: u32,
|
||||||
|
is_visible: u8,
|
||||||
|
render_shadow_enabled: u8,
|
||||||
|
render_light_shadow_enabled: u8,
|
||||||
|
padding: u8,
|
||||||
|
render_model_clip_range: f32
|
||||||
|
},
|
||||||
|
#[brw(magic = 0x2i32)]
|
||||||
|
Attribute,
|
||||||
|
#[brw(magic = 0x3i32)]
|
||||||
|
LayLight,
|
||||||
|
#[brw(magic = 0x4i32)]
|
||||||
|
VFX,
|
||||||
|
#[brw(magic = 0x5i32)]
|
||||||
|
PositionMarker,
|
||||||
|
#[brw(magic = 0x6i32)]
|
||||||
|
SharedGroup,
|
||||||
|
Sound = 0x7, // //
|
||||||
|
EventNPC = 0x8, // //
|
||||||
|
BattleNPC = 0x9, // //
|
||||||
|
RoutePath = 0xA,
|
||||||
|
Character = 0xB,
|
||||||
|
Aetheryte = 0xC, // //
|
||||||
|
EnvSet = 0xD, // //
|
||||||
|
Gathering = 0xE, // //
|
||||||
|
HelperObject = 0xF, //
|
||||||
|
Treasure = 0x10, // //
|
||||||
|
Clip = 0x11,
|
||||||
|
ClipCtrlPoint = 0x12,
|
||||||
|
ClipCamera = 0x13,
|
||||||
|
ClipLight = 0x14,
|
||||||
|
ClipReserve00 = 0x15,
|
||||||
|
ClipReserve01 = 0x16,
|
||||||
|
ClipReserve02 = 0x17,
|
||||||
|
ClipReserve03 = 0x18,
|
||||||
|
ClipReserve04 = 0x19,
|
||||||
|
ClipReserve05 = 0x1A,
|
||||||
|
ClipReserve06 = 0x1B,
|
||||||
|
ClipReserve07 = 0x1C,
|
||||||
|
ClipReserve08 = 0x1D,
|
||||||
|
ClipReserve09 = 0x1E,
|
||||||
|
ClipReserve10 = 0x1F,
|
||||||
|
ClipReserve11 = 0x20,
|
||||||
|
ClipReserve12 = 0x21,
|
||||||
|
ClipReserve13 = 0x22,
|
||||||
|
ClipReserve14 = 0x23,
|
||||||
|
CutAssetOnlySelectable = 0x24,
|
||||||
|
Player = 0x25,
|
||||||
|
Monster = 0x26,
|
||||||
|
Weapon = 0x27, //
|
||||||
|
PopRange = 0x28, // //
|
||||||
|
ExitRange = 0x29, // //
|
||||||
|
LVB = 0x2A,
|
||||||
|
MapRange = 0x2B, // //
|
||||||
|
NaviMeshRange = 0x2C, // //
|
||||||
|
EventObject = 0x2D, // //
|
||||||
|
DemiHuman = 0x2E,
|
||||||
|
EnvLocation = 0x2F, // //
|
||||||
|
ControlPoint = 0x30,
|
||||||
|
EventRange = 0x31, //? //
|
||||||
|
RestBonusRange = 0x32,
|
||||||
|
QuestMarker = 0x33, // //
|
||||||
|
Timeline = 0x34,
|
||||||
|
ObjectBehaviorSet = 0x35,
|
||||||
|
Movie = 0x36,
|
||||||
|
ScenarioExd = 0x37,
|
||||||
|
ScenarioText = 0x38,
|
||||||
|
CollisionBox = 0x39, // //
|
||||||
|
DoorRange = 0x3A, //
|
||||||
|
LineVFX = 0x3B, // //
|
||||||
|
SoundEnvSet = 0x3C,
|
||||||
|
CutActionTimeline = 0x3D,
|
||||||
|
CharaScene = 0x3E,
|
||||||
|
CutAction = 0x3F,
|
||||||
|
EquipPreset = 0x40,
|
||||||
|
ClientPath = 0x41, // //
|
||||||
|
ServerPath = 0x42, // //
|
||||||
|
GimmickRange = 0x43, // //
|
||||||
|
TargetMarker = 0x44, // //
|
||||||
|
ChairMarker = 0x45, // //
|
||||||
|
ClickableRange = 0x46, //
|
||||||
|
PrefetchRange = 0x47, // //
|
||||||
|
FateRange = 0x48, // //
|
||||||
|
PartyMember = 0x49,
|
||||||
|
KeepRange = 0x4A, //
|
||||||
|
SphereCastRange = 0x4B,
|
||||||
|
IndoorObject = 0x4C,
|
||||||
|
OutdoorObject = 0x4D,
|
||||||
|
EditGroup = 0x4E,
|
||||||
|
StableChocobo = 0x4F,
|
||||||
|
MaxAssetType = 0x50,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum DoorState
|
||||||
|
{
|
||||||
|
Auto = 0x1,
|
||||||
|
Open = 0x2,
|
||||||
|
Closed = 0x3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum RotationState
|
||||||
|
{
|
||||||
|
Rounding = 0x1,
|
||||||
|
Stopped = 0x2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum TransformState
|
||||||
|
{
|
||||||
|
TransformStatePlay = 0x0,
|
||||||
|
TransformStateStop = 0x1,
|
||||||
|
TransformStateReplay = 0x2,
|
||||||
|
TransformStateReset = 0x3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum ColourState
|
||||||
|
{
|
||||||
|
ColorStatePlay = 0x0,
|
||||||
|
ColorStateStop = 0x1,
|
||||||
|
ColorStateReplay = 0x2,
|
||||||
|
ColorStateReset = 0x3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum TriggerBoxShape
|
||||||
|
{
|
||||||
|
TriggerBoxShapeBox = 0x1,
|
||||||
|
TriggerBoxShapeSphere = 0x2,
|
||||||
|
TriggerBoxShapeCylinder = 0x3,
|
||||||
|
TriggerBoxShapeBoard = 0x4,
|
||||||
|
TriggerBoxShapeMesh = 0x5,
|
||||||
|
TriggerBoxShapeBoardBothSides = 0x6,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = i32)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum ModelCollisionType
|
||||||
|
{
|
||||||
|
None = 0x0,
|
||||||
|
Replace = 0x1,
|
||||||
|
Box = 0x2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum LightType
|
||||||
|
{
|
||||||
|
None = 0x0,
|
||||||
|
Directional = 0x1,
|
||||||
|
Point = 0x2,
|
||||||
|
Spot = 0x3,
|
||||||
|
Plane = 0x4,
|
||||||
|
Line = 0x5,
|
||||||
|
Specular = 0x6,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum PointLightType
|
||||||
|
{
|
||||||
|
Sphere = 0x0,
|
||||||
|
Hemisphere = 0x1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum PositionMarkerType
|
||||||
|
{
|
||||||
|
DebugZonePop = 0x1,
|
||||||
|
DebugJump = 0x2,
|
||||||
|
NaviMesh = 0x3,
|
||||||
|
LQEvent = 0x4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum EnvSetShape
|
||||||
|
{
|
||||||
|
EnvShapeEllipsoid = 0x1,
|
||||||
|
EnvShapeCuboid = 0x2,
|
||||||
|
EnvShapeCylinder = 0x3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum HelperObjectType
|
||||||
|
{
|
||||||
|
ProxyActor = 0x0,
|
||||||
|
NullObject = 0x1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum TargetType
|
||||||
|
{
|
||||||
|
None = 0x0,
|
||||||
|
ENPCInstanceID = 0x1,
|
||||||
|
Player = 0x2,
|
||||||
|
PartyMember = 0x3,
|
||||||
|
ENPCDirect = 0x4,
|
||||||
|
BNPCDirect = 0x5,
|
||||||
|
BGObjInstanceID = 0x6,
|
||||||
|
SharedGroupInstanceID = 0x7,
|
||||||
|
BGObj = 0x8,
|
||||||
|
SharedGroup = 0x9,
|
||||||
|
Weapon = 0xA,
|
||||||
|
StableChocobo = 0xB,
|
||||||
|
AllianceMember = 0xC,
|
||||||
|
Max = 0xD,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binread]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum PopType
|
||||||
|
{
|
||||||
|
#[br(magic = 0x1u8)]
|
||||||
|
PC = 0x1,
|
||||||
|
#[br(magic = 0x2u8)]
|
||||||
|
NPC,
|
||||||
|
#[br(magic = 0x2u8)]
|
||||||
|
BNPC,
|
||||||
|
#[br(magic = 0x3u8)]
|
||||||
|
Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum ExitType
|
||||||
|
{
|
||||||
|
ZoneLine = 0x1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum RangeType
|
||||||
|
{
|
||||||
|
Type01 = 0x1,
|
||||||
|
Type02 = 0x2,
|
||||||
|
Type03 = 0x3,
|
||||||
|
Type04 = 0x4,
|
||||||
|
Type05 = 0x5,
|
||||||
|
Type06 = 0x6,
|
||||||
|
Type07 = 0x7,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum LineStyle
|
||||||
|
{
|
||||||
|
Red = 0x1,
|
||||||
|
Blue = 0x2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum GimmickType
|
||||||
|
{
|
||||||
|
Fishing = 0x1,
|
||||||
|
Content = 0x2,
|
||||||
|
Room = 0x3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum TargetMarkerType
|
||||||
|
{
|
||||||
|
UiTarget = 0x0,
|
||||||
|
UiNameplate = 0x1,
|
||||||
|
LookAt = 0x2,
|
||||||
|
BodyDyn = 0x3,
|
||||||
|
Root = 0x4,
|
||||||
|
}
|
||||||
|
|
||||||
|
//For ChairMarker
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum ObjectType
|
||||||
|
{
|
||||||
|
ObjectChair = 0x0,
|
||||||
|
ObjectBed = 0x1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum CharacterSize
|
||||||
|
{
|
||||||
|
DefaultSize = 0x0,
|
||||||
|
VerySmall = 0x1,
|
||||||
|
Small = 0x2,
|
||||||
|
Medium = 0x3,
|
||||||
|
Large = 0x4,
|
||||||
|
VeryLarge = 0x5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum DrawHeadParts
|
||||||
|
{
|
||||||
|
Default = 0x0,
|
||||||
|
ForceOn = 0x1,
|
||||||
|
ForceOff = 0x2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum RotationType
|
||||||
|
{
|
||||||
|
NoRotate = 0x0,
|
||||||
|
AllAxis = 0x1,
|
||||||
|
YAxisOnly = 0x2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum MovePathMode
|
||||||
|
{
|
||||||
|
None = 0x0,
|
||||||
|
SharedGroupAction = 0x1,
|
||||||
|
Timeline = 0x2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = i32)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum LayerSetReferencedType
|
||||||
|
{
|
||||||
|
All = 0x0,
|
||||||
|
Include = 0x1,
|
||||||
|
Exclude = 0x2,
|
||||||
|
Undetermined = 0x3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum SoundEffectType
|
||||||
|
{
|
||||||
|
Point = 0x3,
|
||||||
|
PointDir = 0x4,
|
||||||
|
Line = 0x5,
|
||||||
|
PolyLine = 0x6,
|
||||||
|
Surface = 0x7,
|
||||||
|
BoardObstruction = 0x8,
|
||||||
|
BoxObstruction = 0x9,
|
||||||
|
PolyLineObstruction = 0xB,
|
||||||
|
PolygonObstruction = 0xC,
|
||||||
|
LineExtController = 0xD,
|
||||||
|
Polygon = 0xE,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binread]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[br(little)]
|
||||||
|
struct LayerHeader {
|
||||||
|
layer_id: u32,
|
||||||
|
name_offset: u32,
|
||||||
|
|
||||||
|
instance_object_offset: i32,
|
||||||
|
instance_object_count: i32,
|
||||||
|
|
||||||
|
tool_mode_visible: u8,
|
||||||
|
tool_mode_read_only: u8,
|
||||||
|
|
||||||
|
is_bush_layer: u8,
|
||||||
|
ps3_visible: u8,
|
||||||
|
layer_set_referenced_list_offset: i32,
|
||||||
|
festival_id: u16,
|
||||||
|
festival_phase_id: u16,
|
||||||
|
is_temporary: u8,
|
||||||
|
is_housing: u8,
|
||||||
|
version_mask: u16,
|
||||||
|
|
||||||
|
#[br(temp)]
|
||||||
|
padding: u32,
|
||||||
|
|
||||||
|
ob_set_referenced_list: i32,
|
||||||
|
ob_set_referenced_list_count: i32,
|
||||||
|
ob_set_enable_referenced_list: i32,
|
||||||
|
ob_set_enable_referenced_list_count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binread]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[br(little)]
|
||||||
|
struct LayerSetReferencedList {
|
||||||
|
referenced_type: LayerSetReferencedType,
|
||||||
|
layer_sets: i32,
|
||||||
|
layer_set_count: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binread]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[br(little)]
|
||||||
|
struct LgbHeader {
|
||||||
|
#[br(count = 4)]
|
||||||
|
file_id: Vec<u8>,
|
||||||
|
file_size: i32,
|
||||||
|
total_chunk_count: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binread]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[br(little)]
|
||||||
|
struct LayerChunk {
|
||||||
|
#[br(count = 4)]
|
||||||
|
chunk_id: Vec<u8>,
|
||||||
|
chunk_size: i32,
|
||||||
|
layer_group_id: i32,
|
||||||
|
name_offset: u32,
|
||||||
|
layer_offset: i32,
|
||||||
|
layer_count: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binread]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[br(little)]
|
||||||
|
struct InstanceObject {
|
||||||
|
asset_type: LayerEntryType,
|
||||||
|
instance_id: u32,
|
||||||
|
name_offset: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Layer {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layer {
|
||||||
|
/// Reads an existing PBD file
|
||||||
|
pub fn from_existing(buffer: ByteSpan) -> Option<Layer> {
|
||||||
|
let mut cursor = Cursor::new(buffer);
|
||||||
|
|
||||||
|
let mut file_header = LgbHeader::read(&mut cursor).unwrap();
|
||||||
|
|
||||||
|
let mut chunk_header = LayerChunk::read(&mut cursor).unwrap();
|
||||||
|
|
||||||
|
let old_pos = cursor.position();
|
||||||
|
|
||||||
|
let mut layer_offsets = vec![0i32; chunk_header.layer_count as usize];
|
||||||
|
for i in 0.. chunk_header.layer_count {
|
||||||
|
layer_offsets[i as usize] = cursor.read_le::<i32>().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0.. chunk_header.layer_count {
|
||||||
|
cursor.seek(SeekFrom::Start(old_pos + layer_offsets[i as usize] as u64)).unwrap();
|
||||||
|
|
||||||
|
let old_pos = cursor.position();
|
||||||
|
|
||||||
|
let mut header = LayerHeader::read(&mut cursor).unwrap();
|
||||||
|
|
||||||
|
println!("{:#?}", header);
|
||||||
|
|
||||||
|
cursor.seek(SeekFrom::Start(old_pos + header.instance_object_offset as u64)).unwrap();
|
||||||
|
|
||||||
|
let mut instance_offsets = vec![0i32; header.instance_object_count as usize];
|
||||||
|
for i in 0..header.instance_object_count {
|
||||||
|
instance_offsets[i as usize] = cursor.read_le::<i32>().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.seek(SeekFrom::Start(old_pos + header.layer_set_referenced_list_offset as u64)).unwrap();
|
||||||
|
let mut referenced_list = LayerSetReferencedList::read(&mut cursor).unwrap();
|
||||||
|
|
||||||
|
for i in 0..header.instance_object_count {
|
||||||
|
cursor.seek(SeekFrom::Start(old_pos + header.instance_object_offset as u64 + instance_offsets[i as usize] as u64)).unwrap();
|
||||||
|
|
||||||
|
let instance_object = InstanceObject::read(&mut cursor).unwrap();
|
||||||
|
println!("{:#?}", instance_object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Layer {
|
||||||
|
//header
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -113,5 +113,11 @@ mod model_file_operations;
|
||||||
#[cfg(feature = "visual_data")]
|
#[cfg(feature = "visual_data")]
|
||||||
mod model_vertex_declarations;
|
mod model_vertex_declarations;
|
||||||
|
|
||||||
|
#[cfg(feature = "visual_data")]
|
||||||
|
pub mod lgb;
|
||||||
|
|
||||||
|
#[cfg(feature = "visual_data")]
|
||||||
|
pub mod tera;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub const PHYSIS_VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const PHYSIS_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
67
src/tera.rs
Normal file
67
src/tera.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::io::{Cursor, Seek};
|
||||||
|
|
||||||
|
use binrw::BinRead;
|
||||||
|
use binrw::binrw;
|
||||||
|
use crate::ByteSpan;
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[brw(little)]
|
||||||
|
struct PlatePosition {
|
||||||
|
x: i16,
|
||||||
|
y: i16
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[brw(little)]
|
||||||
|
struct TerrainHeader {
|
||||||
|
version: u32,
|
||||||
|
plate_count: u32,
|
||||||
|
plate_size: u32,
|
||||||
|
clip_distance: f32,
|
||||||
|
|
||||||
|
unknown: f32,
|
||||||
|
|
||||||
|
#[br(count = 32)]
|
||||||
|
padding: Vec<u8>,
|
||||||
|
|
||||||
|
#[br(count = plate_count)]
|
||||||
|
positions: Vec<PlatePosition>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PlateModel {
|
||||||
|
pub position: (f32, f32),
|
||||||
|
pub filename: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Terrain {
|
||||||
|
pub plates: Vec<PlateModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Terrain {
|
||||||
|
/// Reads an existing TERA file
|
||||||
|
pub fn from_existing(buffer: ByteSpan) -> Option<Terrain> {
|
||||||
|
let mut cursor = Cursor::new(buffer);
|
||||||
|
let header = TerrainHeader::read(&mut cursor).ok()?;
|
||||||
|
|
||||||
|
let mut plates = vec![];
|
||||||
|
|
||||||
|
for i in 0..header.plate_count {
|
||||||
|
plates.push(PlateModel {
|
||||||
|
position: (header.plate_size as f32 * (header.positions[i as usize].x as f32 + 0.5),
|
||||||
|
header.plate_size as f32 * (header.positions[i as usize].y as f32 + 0.5)),
|
||||||
|
filename: format!("{:04}.mdl", i)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Terrain {
|
||||||
|
plates
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue