1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-07-03 09:17:45 +00:00
physis/src/tera.rs
Joshua Goins cdc39ad4b6
Partial map write support (#9)
This adds initial support for writing new/modified maps, which are made up of layer groups (LGB) and terrain (TERA). Neither one is really tested yet, but I started checking some simple examples and it seems to work.
2025-04-13 18:34:18 -04:00

115 lines
2.7 KiB
Rust

// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
use std::io::Cursor;
use crate::ByteBuffer;
use crate::ByteSpan;
use binrw::BinRead;
use binrw::BinWrite;
use binrw::binrw;
#[binrw]
#[derive(Debug, Clone, Copy)]
#[brw(little)]
struct PlatePosition {
x: i16,
y: i16,
}
#[binrw]
#[derive(Debug)]
#[brw(little)]
struct TerrainHeader {
// Example: 0x1000003
version: u32,
plate_count: u32,
plate_size: u32,
clip_distance: f32,
unknown: f32,
#[brw(pad_before = 32)]
#[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 })
}
pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
let mut buffer = ByteBuffer::new();
{
let mut cursor = Cursor::new(&mut buffer);
let plate_size = 128;
let header = TerrainHeader {
version: 0x1000003,
plate_count: self.plates.len() as u32,
plate_size,
clip_distance: 0.0, // TODO: make configurable
unknown: 1.0, // TODO: what is this
positions: self
.plates
.iter()
.map(|model| PlatePosition {
x: ((model.position.0 / plate_size as f32) - 0.5) as i16,
y: ((model.position.1 / plate_size as f32) - 0.5) as i16,
})
.collect(),
};
header.write_le(&mut cursor).ok()?;
}
Some(buffer)
}
}
#[cfg(test)]
mod tests {
use std::fs::read;
use std::path::PathBuf;
use super::*;
#[test]
fn test_invalid() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("random");
// Feeding it invalid data should not panic
Terrain::from_existing(&read(d).unwrap());
}
}