├── .gitignore ├── rust ├── .gitignore ├── typed_ints │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── libdif │ ├── src │ │ ├── ai_special_node.rs │ │ ├── game_entity.rs │ │ ├── dif-derive │ │ │ ├── Cargo.toml │ │ │ ├── Cargo.lock │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── lib.rs │ │ ├── sub_object.rs │ │ ├── interior_path_follower.rs │ │ ├── force_field.rs │ │ ├── vehicle_collision.rs │ │ ├── trigger.rs │ │ ├── static_mesh.rs │ │ ├── dif.rs │ │ ├── io.rs │ │ └── types.rs │ ├── Cargo.toml │ └── Cargo.lock ├── difbuilder │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── libdifbuilder │ ├── Cargo.toml │ └── src │ ├── lib.rs │ └── bsp.rs ├── .gitmodules ├── .github └── workflows │ └── rust.yml ├── blender_plugin └── io_dif │ ├── util.py │ ├── import_dif.py │ ├── import_csx.py │ ├── __init__.py │ └── export_dif.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | out 3 | release 4 | .vs 5 | .vscode 6 | .idea 7 | trash 8 | CMakeSettings.json -------------------------------------------------------------------------------- /rust/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | target 4 | libdifbuilder/src/bsp_alt.rs 5 | libdifbuilder/src/csx_ref.rs -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/DifBuilder"] 2 | path = 3rdparty/DifBuilder 3 | url = https://github.com/RandomityGuy/DifBuilder.git 4 | -------------------------------------------------------------------------------- /rust/typed_ints/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "typed_ints" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /rust/typed_ints/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "typed_ints" 3 | version = "0.1.0" 4 | authors = ["HiGuy Smith "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] -------------------------------------------------------------------------------- /rust/libdif/src/ai_special_node.rs: -------------------------------------------------------------------------------- 1 | use crate::io::*; 2 | use crate::types::*; 3 | use bytes::{Buf, BufMut}; 4 | use dif_derive::{Readable, Writable}; 5 | 6 | #[derive(Debug, Readable, Writable)] 7 | pub struct AISpecialNode { 8 | pub name: String, 9 | pub position: Point3F, 10 | } 11 | -------------------------------------------------------------------------------- /rust/difbuilder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "difbuilder" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["RandomityGuy"] 6 | 7 | [dependencies] 8 | libdifbuilder = { path = "../libdifbuilder" } 9 | libdif = { path = "../libdif" } 10 | cgmath = "0.17.0" 11 | indicatif = "0.17.6" 12 | 13 | [lib] 14 | crate-type = ["cdylib"] 15 | -------------------------------------------------------------------------------- /rust/libdif/src/game_entity.rs: -------------------------------------------------------------------------------- 1 | use crate::io::*; 2 | use crate::types::*; 3 | use bytes::{Buf, BufMut}; 4 | use dif_derive::{Readable, Writable}; 5 | 6 | #[derive(Debug, Readable, Writable)] 7 | pub struct GameEntity { 8 | pub datablock: String, 9 | pub game_class: String, 10 | pub position: Point3F, 11 | pub properties: Dictionary, 12 | } 13 | -------------------------------------------------------------------------------- /rust/libdif/src/dif-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dif-derive" 3 | version = "0.1.0" 4 | authors = ["HiGuy Smith "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | syn = "1.0.2" 14 | quote = "1.0.1" 15 | proc-macro2 = "1.0" 16 | -------------------------------------------------------------------------------- /rust/libdif/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ai_special_node; 2 | pub mod dif; 3 | pub mod force_field; 4 | pub mod game_entity; 5 | pub mod interior; 6 | pub mod interior_path_follower; 7 | pub mod io; 8 | pub mod static_mesh; 9 | pub mod sub_object; 10 | pub mod trigger; 11 | pub mod types; 12 | pub mod vehicle_collision; 13 | 14 | extern crate bytes; 15 | extern crate dif_derive; 16 | #[macro_use] 17 | extern crate bitflags; 18 | #[macro_use] 19 | extern crate typed_ints; 20 | extern crate typenum; -------------------------------------------------------------------------------- /rust/libdif/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libdif" 3 | version = "0.1.0" 4 | authors = ["HiGuy Smith "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | name = "dif" 11 | path = "src/lib.rs" 12 | 13 | [dependencies] 14 | bitflags = "1.2.1" 15 | bytes = "0.4.12" 16 | cgmath = "0.17.0" 17 | dif-derive = { path = "src/dif-derive" } 18 | typenum = "1.11.2" 19 | typed_ints = { path = "../typed_ints" } -------------------------------------------------------------------------------- /rust/libdif/src/sub_object.rs: -------------------------------------------------------------------------------- 1 | use crate::io::*; 2 | use crate::types::*; 3 | use bytes::{Buf, BufMut}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct SubObject {} 7 | 8 | impl Readable for SubObject { 9 | fn read(_from: &mut dyn Buf, _version: &mut Version) -> DifResult { 10 | unimplemented!() 11 | } 12 | } 13 | 14 | impl Writable for SubObject { 15 | fn write(&self, _from: &mut dyn BufMut, _version: &Version) -> DifResult<()> { 16 | unimplemented!() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /rust/libdifbuilder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libdifbuilder" 3 | version = "0.1.0" 4 | authors = ["RandomityGuy"] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | name = "difbuilder" 11 | path = "src/lib.rs" 12 | 13 | [dependencies] 14 | libdif = { path = "../libdif" } 15 | quick-xml = { version = "0.30.0", features = ["serialize"] } 16 | serde = { version = "1.0.175", features = ["derive"] } 17 | cgmath = "0.17.0" 18 | rand = "0.8.5" 19 | itertools = "0.11.0" 20 | rayon = "1.7.0" 21 | image = "0.25.1" 22 | rectangle-pack = "0.4.2" 23 | arrayvec = "0.7.4" 24 | nalgebra = "0.33.2" 25 | -------------------------------------------------------------------------------- /rust/libdif/src/interior_path_follower.rs: -------------------------------------------------------------------------------- 1 | use crate::io::*; 2 | use crate::types::*; 3 | use bytes::{Buf, BufMut}; 4 | use dif_derive::{Readable, Writable}; 5 | 6 | #[derive(Debug, Readable, Writable)] 7 | pub struct InteriorPathFollower { 8 | pub name: String, 9 | pub datablock: String, 10 | pub interior_res_index: u32, 11 | pub offset: Point3F, 12 | pub properties: Dictionary, 13 | pub trigger_ids: Vec, 14 | pub way_points: Vec, 15 | pub total_ms: u32, 16 | } 17 | 18 | #[derive(Debug, Readable, Writable, Copy, Clone)] 19 | pub struct WayPoint { 20 | pub position: Point3F, 21 | pub rotation: QuatF, 22 | pub ms_to_next: u32, 23 | pub smoothing_type: u32, 24 | } 25 | -------------------------------------------------------------------------------- /rust/libdif/src/force_field.rs: -------------------------------------------------------------------------------- 1 | use crate::io::*; 2 | use crate::types::*; 3 | use bytes::{Buf, BufMut}; 4 | use dif_derive::{Readable, Writable}; 5 | 6 | #[derive(Debug, Readable, Writable)] 7 | pub struct ForceField { 8 | pub version: u32, 9 | pub name: String, 10 | pub triggers: Vec, 11 | pub bounding_box: BoxF, 12 | pub bounding_sphere: SphereF, 13 | pub normals: Vec, 14 | pub planes: Vec, 15 | pub bsp_nodes: Vec, 16 | pub bsp_solid_leaves: Vec, 17 | pub indices: Vec, 18 | pub surfaces: Vec, 19 | pub solid_leaf_surfaces: Vec, 20 | pub color: ColorI, 21 | } 22 | 23 | #[derive(Debug, Readable, Writable)] 24 | pub struct Plane { 25 | pub normal_index: u32, 26 | pub plane_distance: f32, 27 | } 28 | 29 | #[derive(Debug, Readable, Writable)] 30 | pub struct BSPNode { 31 | pub front_index: u16, 32 | pub back_index: u16, 33 | } 34 | 35 | #[derive(Debug, Readable, Writable)] 36 | pub struct BSPSolidLeaf { 37 | pub surface_index: u32, 38 | pub surface_count: u16, 39 | } 40 | 41 | #[derive(Debug, Readable, Writable)] 42 | pub struct Surface { 43 | pub winding_start: u32, 44 | pub winding_count: u8, 45 | pub plane_index: u16, 46 | pub surface_flags: u8, 47 | pub fan_mask: u32, 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | os: [macos-latest, ubuntu-latest, windows-latest] 17 | 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Build 22 | run: cd rust/difbuilder && cargo build --release 23 | 24 | - name: Upload Artifact 25 | uses: actions/upload-artifact@v4 26 | if: matrix.os == 'macos-latest' 27 | with: 28 | name: DifBuilderLib-${{ matrix.os }} 29 | path: | 30 | ${{github.workspace}}/rust/difbuilder/target/release/libdifbuilder.dylib 31 | 32 | - name: Upload Artifact 33 | uses: actions/upload-artifact@v4 34 | if: matrix.os == 'ubuntu-latest' 35 | with: 36 | name: DifBuilderLib-${{ matrix.os }} 37 | path: | 38 | ${{github.workspace}}/rust/difbuilder/target/release/libdifbuilder.so 39 | 40 | - name: Upload Artifact 41 | uses: actions/upload-artifact@v4 42 | if: matrix.os == 'windows-latest' 43 | with: 44 | name: DifBuilderLib-${{ matrix.os }} 45 | path: | 46 | ${{github.workspace}}/rust/difbuilder/target/release/difbuilder.dll 47 | -------------------------------------------------------------------------------- /rust/libdif/src/vehicle_collision.rs: -------------------------------------------------------------------------------- 1 | use crate::io::*; 2 | use crate::types::*; 3 | use bytes::{Buf, BufMut}; 4 | use dif_derive::{Readable, Writable}; 5 | 6 | #[derive(Debug, Readable, Writable)] 7 | pub struct VehicleCollision { 8 | pub version: u32, 9 | pub convex_hulls: Vec, 10 | pub convex_hull_emit_string_characters: Vec, 11 | pub hull_indices: Vec, 12 | pub hull_plane_indices: Vec, 13 | pub hull_emit_string_indices: Vec, 14 | pub hull_surface_indices: Vec, 15 | pub poly_list_plane_indices: Vec, 16 | pub poly_list_point_indices: Vec, 17 | pub poly_list_string_characters: Vec, 18 | pub null_surfaces: Vec, 19 | pub points: Vec, 20 | pub planes: Vec, 21 | pub windings: Vec, 22 | pub winding_indices: Vec, 23 | } 24 | 25 | #[derive(Debug, Readable, Writable)] 26 | pub struct ConvexHull { 27 | pub hull_start: u32, 28 | pub hull_count: u16, 29 | pub min_x: f32, 30 | pub max_x: f32, 31 | pub min_y: f32, 32 | pub max_y: f32, 33 | pub min_z: f32, 34 | pub max_z: f32, 35 | pub surface_start: u32, 36 | pub surface_count: u16, 37 | pub plane_start: u32, 38 | pub poly_list_plane_start: u32, 39 | pub poly_list_point_start: u32, 40 | pub poly_list_string_start: u32, 41 | } 42 | 43 | #[derive(Debug, Readable, Writable)] 44 | pub struct NullSurface { 45 | pub winding_start: u32, 46 | pub plane_index: u16, 47 | pub surface_flags: u8, 48 | pub winding_count: u32, 49 | } 50 | 51 | #[derive(Debug, Readable, Writable)] 52 | pub struct WindingIndex { 53 | pub winding_start: u32, 54 | pub winding_count: u32, 55 | } 56 | -------------------------------------------------------------------------------- /rust/libdif/src/trigger.rs: -------------------------------------------------------------------------------- 1 | use crate::io::*; 2 | use crate::io::{Readable, Writable}; 3 | use crate::types::*; 4 | use bytes::{Buf, BufMut}; 5 | use dif_derive::{Readable, Writable}; 6 | 7 | #[derive(Debug)] 8 | pub struct Trigger { 9 | pub name: String, 10 | pub datablock: String, 11 | pub properties: Dictionary, 12 | pub polyhedron: Polyhedron, 13 | pub offset: Point3F, 14 | } 15 | 16 | #[derive(Debug, Readable, Writable)] 17 | pub struct Polyhedron { 18 | pub point_list: Vec, 19 | pub plane_list: Vec, 20 | pub edge_list: Vec, 21 | } 22 | 23 | #[derive(Debug, Readable, Writable)] 24 | pub struct PolyhedronEdge { 25 | pub face0: u32, 26 | pub face1: u32, 27 | pub vertex0: u32, 28 | pub vertex1: u32, 29 | } 30 | 31 | impl Readable for Trigger { 32 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 33 | Ok(Trigger { 34 | name: String::read(from, version)?, 35 | datablock: String::read(from, version)?, 36 | properties: Dictionary::read(from, version)?, 37 | polyhedron: Polyhedron::read(from, version)?, 38 | offset: Point3F::read(from, version)?, 39 | }) 40 | } 41 | } 42 | 43 | impl Writable for Trigger { 44 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 45 | self.name.write(to, version)?; 46 | self.datablock.write(to, version)?; 47 | if version.engine == EngineVersion::MBG { 48 | self.properties.write(to, version)?; 49 | } 50 | self.polyhedron.write(to, version)?; 51 | self.offset.write(to, version)?; 52 | Ok(()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /blender_plugin/io_dif/util.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from colorsys import hsv_to_rgb 4 | from itertools import count 5 | from fractions import Fraction 6 | 7 | texture_extensions = ("png", "jpg") 8 | 9 | default_materials = { 10 | "black": (0, 0, 0), 11 | "black25": (191, 191, 191), 12 | "black50": (128, 128, 128), 13 | "black75": (64, 64, 64), 14 | "blank": (255, 255, 255), 15 | "blue": (0, 0, 255), 16 | "darkRed": (128, 0, 0), 17 | "gray25": (64, 64, 64), 18 | "gray50": (128, 128, 128), 19 | "gray75": (191, 191, 191), 20 | "green": (26, 128, 64), 21 | "lightBlue": (10, 186, 245), 22 | "lightYellow": (249, 249, 99), 23 | "palegreen": (125, 136, 104), 24 | "red": (213, 0, 0), 25 | "white": (255, 255, 255), 26 | "yellow": (255, 255, 0) 27 | } 28 | 29 | for name, color in default_materials.items(): 30 | default_materials[name] = (color[0] / 255, color[1] / 255, color[2] / 255) 31 | 32 | for key, value in tuple(default_materials.items()): 33 | default_materials[key.lower()] = value 34 | 35 | def resolve_texture(filepath, name): 36 | dirname = os.path.dirname(filepath) 37 | 38 | while True: 39 | texbase = os.path.join(dirname, name) 40 | 41 | for extension in texture_extensions: 42 | texname = texbase + "." + extension 43 | 44 | if os.path.isfile(texname): 45 | return texname 46 | 47 | if os.path.ismount(dirname): 48 | break 49 | 50 | prevdir, dirname = dirname, os.path.dirname(dirname) 51 | 52 | if prevdir == dirname: 53 | break 54 | 55 | def fractions(): 56 | yield 0 57 | 58 | for k in count(): 59 | i = 2 ** k 60 | 61 | for j in range(1, i, 2): 62 | yield j / i 63 | 64 | def get_hsv_colors(): 65 | for h in fractions(): 66 | yield (h, 0.75, 0.75) 67 | 68 | def get_rgb_colors(): 69 | return map(lambda hsv: hsv_to_rgb(*hsv), get_hsv_colors()) 70 | -------------------------------------------------------------------------------- /rust/libdif/src/dif-derive/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "dif-derive" 5 | version = "0.1.0" 6 | dependencies = [ 7 | "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "quote 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "syn 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "proc-macro2" 14 | version = "1.0.1" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | dependencies = [ 17 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 18 | ] 19 | 20 | [[package]] 21 | name = "quote" 22 | version = "1.0.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | dependencies = [ 25 | "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 26 | ] 27 | 28 | [[package]] 29 | name = "syn" 30 | version = "1.0.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | dependencies = [ 33 | "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "quote 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "unicode-xid" 40 | version = "0.2.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [metadata] 44 | "checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802" 45 | "checksum quote 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49d77c41ca8767f2f41394c11a4eebccab83da25e7cc035387a3125f02be90a3" 46 | "checksum syn 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2ae5cd13590144ea968ba5d5520da7a4c08415861014399b5b349f74591c375f" 47 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 48 | -------------------------------------------------------------------------------- /rust/libdifbuilder/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bsp; 2 | pub mod builder; 3 | use std::io::Cursor; 4 | 5 | use bsp::BSP_CONFIG; 6 | use builder::{BSPReport, ProgressEventListener}; 7 | use builder::{PLANE_EPSILON, POINT_EPSILON}; 8 | use dif::io::EngineVersion; 9 | use dif::io::Version; 10 | use quick_xml::de::Deserializer; 11 | use serde::Deserialize; 12 | 13 | use crate::bsp::SplitMethod; 14 | 15 | static mut MB_ONLY: bool = true; 16 | 17 | pub unsafe fn set_convert_configuration( 18 | mb_only: bool, 19 | point_epsilon: f32, 20 | plane_epsilon: f32, 21 | split_epsilon: f32, 22 | split_method: SplitMethod, 23 | ) { 24 | unsafe { 25 | BSP_CONFIG.epsilon = split_epsilon; 26 | BSP_CONFIG.split_method = split_method; 27 | POINT_EPSILON = point_epsilon; 28 | PLANE_EPSILON = plane_epsilon; 29 | MB_ONLY = mb_only; 30 | } 31 | } 32 | 33 | // pub fn convert_to_dif( 34 | // engine_ver: EngineVersion, 35 | // interior_version: u32, 36 | // progress_fn: &mut dyn ProgressEventListener, 37 | // ) -> (Vec>, Vec) { 38 | // let version = Version { 39 | // engine: engine_ver, 40 | // dif: 44, 41 | // interior: interior_version, 42 | // material_list: 1, 43 | // vehicle_collision: 0, 44 | // force_field: 0, 45 | // }; 46 | // let b = builder::DIFBuilder::new(true); 47 | // } 48 | 49 | // pub fn convert_csx_to_dif( 50 | // csxbuf: String, 51 | // engine_ver: EngineVersion, 52 | // interior_version: u32, 53 | // progress_fn: &mut dyn ProgressEventListener, 54 | // ) -> (Vec>, Vec) { 55 | // let cur = Cursor::new(csxbuf); 56 | // let reader = std::io::BufReader::new(cur); 57 | // let mut des = Deserializer::from_reader(reader); 58 | // let mut cscene = csx::ConstructorScene::deserialize(&mut des).unwrap(); 59 | 60 | // // Transform the vertices and planes to absolute coords, also assign unique ids to face 61 | // preprocess_csx(&mut cscene); 62 | // let version = Version { 63 | // engine: engine_ver, 64 | // dif: 44, 65 | // interior: interior_version, 66 | // material_list: 1, 67 | // vehicle_collision: 0, 68 | // force_field: 0, 69 | // }; 70 | // let buf = convert_csx(&cscene, version, unsafe { MB_ONLY }, progress_fn); 71 | // buf 72 | // } 73 | -------------------------------------------------------------------------------- /rust/libdif/src/static_mesh.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Writable; 2 | use crate::io::*; 3 | use crate::types::*; 4 | use bytes::{Buf, BufMut}; 5 | use dif_derive::{Readable, Writable}; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct StaticMesh { 9 | pub primitives: Vec, 10 | pub indices: Vec, 11 | pub vertexes: Vec, 12 | pub normals: Vec, 13 | pub diffuse_uvs: Vec, 14 | pub lightmap_uvs: Vec, 15 | 16 | pub base_material_list: Option, 17 | 18 | pub has_solid: u8, 19 | pub has_translucency: u8, 20 | pub bounds: BoxF, 21 | pub transform: MatrixF, 22 | pub scale: Point3F, 23 | } 24 | 25 | #[derive(Debug, Readable, Writable, Clone)] 26 | pub struct Primitive { 27 | pub alpha: u8, 28 | pub tex_s: u32, 29 | pub tex_t: u32, 30 | pub diffuse_index: i32, 31 | pub light_map_index: i32, 32 | pub start: u32, 33 | pub count: u32, 34 | pub light_map_equation_x: PlaneF, 35 | pub light_map_equation_y: PlaneF, 36 | pub light_map_offset: Point2I, 37 | pub light_map_size: Point2I, 38 | } 39 | 40 | #[derive(Debug, Clone)] 41 | pub struct Material { 42 | pub flags: u32, 43 | pub reflectance_map: u32, 44 | pub bump_map: u32, 45 | pub detail_map: u32, 46 | pub light_map: u32, 47 | pub detail_scale: u32, 48 | pub reflection_amount: u32, 49 | pub diffuse_bitmap: Option, 50 | } 51 | 52 | #[derive(Debug, Clone)] 53 | pub struct MaterialList { 54 | pub materials: Vec, 55 | } 56 | 57 | impl Readable for StaticMesh { 58 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 59 | let primitives = Vec::::read(from, version)?; 60 | let indices = Vec::::read(from, version)?; 61 | let vertexes = Vec::::read(from, version)?; 62 | let normals = Vec::::read(from, version)?; 63 | let diffuse_uvs = Vec::::read(from, version)?; 64 | let lightmap_uvs = Vec::::read(from, version)?; 65 | 66 | let base_material_list = if u8::read(from, version)? == 0 { 67 | None 68 | } else { 69 | Some(MaterialList::read(from, version)?) 70 | }; 71 | 72 | let has_solid = u8::read(from, version)?; 73 | let has_translucency = u8::read(from, version)?; 74 | let bounds = BoxF::read(from, version)?; 75 | let transform = MatrixF::read(from, version)?; 76 | let scale = Point3F::read(from, version)?; 77 | 78 | Ok(StaticMesh { 79 | primitives, 80 | indices, 81 | vertexes, 82 | normals, 83 | diffuse_uvs, 84 | lightmap_uvs, 85 | base_material_list, 86 | has_solid, 87 | has_translucency, 88 | bounds, 89 | transform, 90 | scale, 91 | }) 92 | } 93 | } 94 | 95 | impl Writable for StaticMesh { 96 | fn write(&self, _to: &mut dyn BufMut, _version: &Version) -> DifResult<()> { 97 | unimplemented!() 98 | } 99 | } 100 | 101 | impl Readable for MaterialList { 102 | fn read(_from: &mut dyn Buf, _version: &mut Version) -> DifResult { 103 | // Yikes 104 | unimplemented!() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /rust/libdif/src/dif.rs: -------------------------------------------------------------------------------- 1 | use crate::ai_special_node::AISpecialNode; 2 | use crate::force_field::ForceField; 3 | use crate::game_entity::GameEntity; 4 | use crate::interior::Interior; 5 | use crate::interior_path_follower::InteriorPathFollower; 6 | use crate::io::*; 7 | use crate::trigger::Trigger; 8 | use crate::types::*; 9 | use crate::vehicle_collision::VehicleCollision; 10 | use bytes::{Buf, BufMut}; 11 | use std::io::Cursor; 12 | 13 | #[derive(Debug)] 14 | pub struct Dif { 15 | pub interiors: Vec, 16 | pub sub_objects: Vec, 17 | pub triggers: Vec, 18 | pub interior_path_followers: Vec, 19 | pub force_fields: Vec, 20 | pub ai_special_nodes: Vec, 21 | pub vehicle_collision: Option, 22 | pub game_entities: Vec, 23 | } 24 | 25 | impl Dif { 26 | pub fn from_bytes(from: T) -> DifResult<(Self, Version)> 27 | where 28 | T: AsRef<[u8]>, 29 | { 30 | let mut version = Version::new(); 31 | let mut cursor = Cursor::new(from); 32 | let dif = Dif::read(&mut cursor, &mut version)?; 33 | Ok((dif, version)) 34 | } 35 | } 36 | 37 | impl Readable for Dif { 38 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 39 | version.dif = u32::read(from, version)?; 40 | if version.dif != 44 { 41 | return Err(DifError::from("Bad version")); 42 | } 43 | 44 | if u8::read(from, version)? != 0 { 45 | let _ = PNG::read(from, version)?; 46 | } 47 | 48 | let interiors = Vec::::read(from, version)?; 49 | let sub_objects = Vec::::read(from, version)?; 50 | let triggers = Vec::::read(from, version)?; 51 | let interior_path_followers = Vec::::read(from, version)?; 52 | let force_fields = Vec::::read(from, version)?; 53 | let ai_special_nodes = Vec::::read(from, version)?; 54 | 55 | let vehicle_collision = if u32::read(from, version)? == 0 { 56 | None 57 | } else { 58 | Some(VehicleCollision::read(from, version)?) 59 | }; 60 | 61 | let game_entities = if u32::read(from, version)? == 2 { 62 | Vec::::read(from, version)? 63 | } else { 64 | vec![] 65 | }; 66 | 67 | Ok(Dif { 68 | interiors, 69 | sub_objects, 70 | triggers, 71 | interior_path_followers, 72 | force_fields, 73 | ai_special_nodes, 74 | vehicle_collision, 75 | game_entities, 76 | }) 77 | } 78 | } 79 | 80 | impl Writable for Dif { 81 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 82 | version.dif.write(to, version)?; 83 | 0u8.write(to, version)?; 84 | self.interiors.write(to, version)?; 85 | self.sub_objects.write(to, version)?; 86 | self.triggers.write(to, version)?; 87 | self.interior_path_followers.write(to, version)?; 88 | self.force_fields.write(to, version)?; 89 | self.ai_special_nodes.write(to, version)?; 90 | 91 | if let Some(vehicle_collision) = &self.vehicle_collision { 92 | 1u32.write(to, version)?; 93 | vehicle_collision.write(to, version)?; 94 | } else { 95 | 0u32.write(to, version)?; 96 | } 97 | 98 | if self.game_entities.len() > 0 { 99 | 2u32.write(to, version)?; 100 | self.game_entities.write(to, version)?; 101 | } else { 102 | 0u32.write(to, version)?; 103 | } 104 | 0u32.write(to, version)?; 105 | 106 | Ok(()) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /rust/typed_ints/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::ops::{Add, AddAssign, Sub, SubAssign}; 3 | 4 | #[derive(Clone, Debug)] 5 | pub struct TypedEnumerate> { 6 | iter: I, 7 | count: E, 8 | } 9 | 10 | impl> TypedEnumerate { 11 | pub fn new(iter: I, first: E) -> TypedEnumerate { 12 | TypedEnumerate { iter, count: first } 13 | } 14 | } 15 | 16 | impl> Iterator for TypedEnumerate 17 | where 18 | I: Iterator, 19 | { 20 | type Item = (E, ::Item); 21 | 22 | fn next(&mut self) -> Option { 23 | let a = self.iter.next()?; 24 | let i = *&self.count; 25 | self.count += 1; 26 | Some((i, a)) 27 | } 28 | 29 | fn size_hint(&self) -> (usize, Option) { 30 | self.iter.size_hint() 31 | } 32 | fn count(self) -> usize { 33 | self.iter.count() 34 | } 35 | fn nth(&mut self, n: usize) -> Option { 36 | let a = self.iter.nth(n)?; 37 | // Possible undefined overflow. 38 | self.count += n; 39 | let i = *&self.count; 40 | self.count += 1; 41 | Some((i, a)) 42 | } 43 | } 44 | 45 | pub trait TypedEnum> 46 | where 47 | Self: Sized, 48 | { 49 | fn typed_enumerate(self, first: E) -> TypedEnumerate { 50 | TypedEnumerate::::new(self, first) 51 | } 52 | } 53 | 54 | impl> TypedEnum for I where I: Sized {} 55 | 56 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] 57 | pub struct TypedInt(B, PhantomData) 58 | where 59 | B: Copy; 60 | 61 | impl Copy for TypedInt where B: Copy {} 62 | 63 | impl Clone for TypedInt 64 | where 65 | B: Copy, 66 | { 67 | fn clone(&self) -> Self { 68 | Self(self.0, PhantomData) 69 | } 70 | } 71 | 72 | impl From for TypedInt 73 | where 74 | B: Copy, 75 | { 76 | fn from(inner: B) -> Self { 77 | Self(inner, PhantomData) 78 | } 79 | } 80 | 81 | impl TypedInt 82 | where 83 | B: Copy, 84 | { 85 | pub fn new(inner: B) -> Self { 86 | Self::from(inner) 87 | } 88 | pub fn into_inner(self) -> B { 89 | self.0 90 | } 91 | pub fn inner(&self) -> &B { 92 | &self.0 93 | } 94 | pub fn inner_mut(&mut self) -> &mut B { 95 | &mut self.0 96 | } 97 | } 98 | 99 | impl Add for TypedInt 100 | where 101 | B: Add + Copy, 102 | { 103 | type Output = Self; 104 | fn add(self, rhs: A) -> Self::Output { 105 | Self(self.0.add(rhs), PhantomData) 106 | } 107 | } 108 | 109 | impl AddAssign for TypedInt 110 | where 111 | B: AddAssign + Copy, 112 | { 113 | fn add_assign(&mut self, rhs: A) { 114 | self.0.add_assign(rhs); 115 | } 116 | } 117 | 118 | impl Sub for TypedInt 119 | where 120 | B: Sub + Copy, 121 | { 122 | type Output = Self; 123 | fn sub(self, rhs: A) -> Self::Output { 124 | Self(self.0.sub(rhs), PhantomData) 125 | } 126 | } 127 | 128 | impl SubAssign for TypedInt 129 | where 130 | B: SubAssign + Copy, 131 | { 132 | fn sub_assign(&mut self, rhs: A) { 133 | self.0.sub_assign(rhs); 134 | } 135 | } 136 | 137 | //impl PartialOrd for TypedInt where B: PartialOrd+Copy { 138 | // fn partial_cmp(&self, other: &A) -> Option { 139 | // self.0.partial_cmp(other) 140 | // } 141 | //} 142 | // 143 | //impl PartialEq for TypedInt where B: PartialEq+Copy { 144 | // fn eq(&self, other: &A) -> bool { 145 | // self.0.eq(other) 146 | // } 147 | // fn ne(&self, other: &A) -> bool { 148 | // self.0.ne(other) 149 | // } 150 | //} 151 | // 152 | //impl Ord for TypedInt where B: PartialOrd>+Eq+Ord+Copy { 153 | // fn cmp(&self, other: &TypedInt) -> Ordering { 154 | // self.0.cmp(&other.0) 155 | // } 156 | //} 157 | // 158 | //impl Eq for TypedInt where B: PartialEq>+Copy { 159 | //} 160 | // 161 | 162 | #[macro_export] 163 | macro_rules! typed_int { 164 | ($name:ident, $tag:ident, $base:ty) => { 165 | #[derive(Debug, Eq, Ord, PartialOrd, PartialEq)] 166 | pub struct $tag(usize); 167 | pub type $name = TypedInt<$base, $tag>; 168 | }; 169 | } 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IO DIF 2 | 3 | Blender plugin to import and export MBG Torque DIF interiors and Torque Constructor CSX files. 4 | Supported Blender Versions: 2.8.0 to 4.5 5 | 6 | ## Note 7 | 8 | The difs generated by this plugin ONLY work in Marble Blast not because of the dif versions but because of the very way the difs are constructed. A lot of stuff is not generated because MB does not require it and some unconventional algorithms are used which specifically make use of how MB makes use of difs. The collision **WILL** not work anywhere outside MB. Even raycasts don't work. 9 | 10 | ## Features 11 | 12 | ### Import DIF 13 | 14 | - Powered by [hxDIF](https://github.com/RandomityGuy/hxDIF) 15 | - Supports PathedInteriors and its path 16 | - Supports embedded GameEntities with properties 17 | - Supports loading textures 18 | 19 | ### Import CSX 20 | - Import Torque Constructor CSX files 21 | - Supports Entities and textures 22 | 23 | ### Export DIF 24 | 25 | - Powered by [DifBuilder](https://github.com/RandomityGuy/DifBuilder) 26 | - Export support for PathedInteriors and Markers 27 | - Export support for GameEntities and its properties 28 | - Additional export parameters provided by [obj2difPlus](https://github.com/RandomityGuy/obj2difPlus) 29 | 30 | ## Installation 31 | 32 | Download the plugin from Releases and install it how you normally install blender plugins. 33 | 34 | ## How to 35 | 36 | ### Import 37 | 38 | File > Import > Torque (.dif). 39 | It can't be any more simpler than that. 40 | 41 | If you want to load a Torque Constructor (.csx) file and create a Torque DIF, use File > Import > Torque Constructor (.csx). 42 | 43 | ### Export 44 | 45 | File > Export > Torque (.dif) 46 | 47 | #### Additional export options - Important! 48 | 49 | - Flip Faces: Flip the normals of the dif, incase the resultant dif is inside out. 50 | - Double Faces: Make all the faces double sided, may increase lag during collision detection. 51 | - Max Polygons: Number of maximum triangles that a DIF can have before splitting into multiple DIFs. Reduce this number if export fails. 52 | - Apply Modifiers: Whether to apply modifiers to meshes before exporting. 53 | - Export Visible: Export only visible geometry. 54 | - Export Selected: Export only selected geometry. 55 | - Use Material Names: Use Material Names instead of file names of the material textures. 56 | - Optimize For Marble Blast: Make the resultant DIF optimized for Marble Blast. Uncheck this if you want to use this for other Torque games. 57 | - BSP Algorithm: 58 | - Fast (Default): Use a sampling algorithm to determine the best splitter. This method is inherently random and may not always yield optimal results. 59 | - Exhaustive: Use an exhaustive search algorithm to determine the best splitter. May take longer but builds more balanced trees. Deterministic algorithm. 60 | - None: Do not build a BSP Tree, utilize this for fast conversion times or if building the BSP Tree fails/hangs on export. 61 | - IMPORTANT: If your geometry is too complex, consider using None mode as there is no guarantee that a BSP Tree can be optimally built within set constraints. 62 | - IMPORTANT: BSP Trees are only used for Raycasts and Drop To Ground feature in Marble Blast. If you are not using these features, you can safely disable the BSP Tree. 63 | - Point Epsilon: The minimum distance between two points to be considered different. 64 | - Plane Epsilon: Minimum difference between values of two plane to be considered equal. 65 | - Split Epsilon: Minimum difference between values of two splitting planes to be considered equal. 66 | 67 | 68 | ### DIF Properties Panel 69 | 70 | Located in the object properties panel 71 | 72 | - Interior Entity Type: 73 | - InteriorResource: normal static interior type 74 | - PathedInterior: moving platform 75 | - Marker Path: a curve object that describes the path of the moving platform. Each point will become a Marker 76 | - Marker Type: the smoothing to use on each marker 77 | - Total Time: the amount of time to complete the path 78 | - Start Time: the time that the platform should begin 79 | - Constant Speed: if the marker durations should instead be calculated to maintain a consistent speed 80 | - Speed: max speed in units per second 81 | - Start Index: Calculates Start Time based on marker index 82 | - Pause Duration: The time that the platform should spend at zero-length segments 83 | - Game Entity: represents an entity in the dif such as items 84 | - Game Class: the class of the entity such as "Item", "StaticShape", etc 85 | - Datablock: the datablock of the item. 86 | - Properties: a list of additional key value pairs which will be set to the object on Create Subs 87 | - Path Trigger: represents a trigger that will be added to the MustChange group 88 | - Datablock: the trigger datablock, MBG's types are TriggerGotoTarget and TriggerGotoDelayTarget 89 | - Pathed Interior: the target object 90 | - Calculate Target Time: if targetTime property should be created from a target marker index 91 | - Target Index: the marker to target 92 | 93 | ## Limitations 94 | 95 | - Limited Game Entity rotation support: rotation field is not properly applied to Game Entities in vanilla Torque 96 | 97 | ## Previews 98 | 99 | ![Imgur](https://imgur.com/OkSM6lY.png) 100 | 101 | ![Imgur](https://imgur.com/3NC5JmH.png) 102 | 103 | ## Build 104 | 105 | Checkout the repository correctly 106 | 107 | ``` 108 | git checkout https://github.com/RandomityGuy/io_dif.git 109 | cd rust/difbuilder 110 | cargo build --release 111 | ``` 112 | 113 | Copy resultant difbuilder.dll to blender_plugin/io_dif folder and rename it to DifBuilderLib.dll 114 | Copy blender_plugin/io_dif to your blender plugins folder. 115 | 116 | ## Credits 117 | 118 | Thanks HiGuy for your incomplete blender dif import plugin as well as the Rust dif library. 119 | -------------------------------------------------------------------------------- /rust/libdif/src/dif-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | extern crate quote; 3 | extern crate syn; 4 | 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericArgument, PathArguments, Type}; 8 | 9 | // #[derive(Readable)] implements io::Readable for a struct T whose body 10 | // reads (in sequence) all the members of T and returns Ok(T {members}) 11 | #[proc_macro_derive(Readable)] 12 | pub fn trivial_read_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStream { 13 | let ast = parse_macro_input!(item as DeriveInput); 14 | 15 | let name = &ast.ident; 16 | let fields = read_generate_fields(&ast.data); 17 | 18 | let expanded = quote! { 19 | impl Readable<#name> for #name { 20 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult<#name> { 21 | Ok(#name { 22 | #fields 23 | }) 24 | } 25 | } 26 | }; 27 | 28 | proc_macro::TokenStream::from(expanded) 29 | } 30 | 31 | // #[derive(Writable)] implements io::Writable for a struct T whose body 32 | // writes (in sequence) all the members of T and returns Ok 33 | #[proc_macro_derive(Writable)] 34 | pub fn trivial_write_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStream { 35 | let ast = parse_macro_input!(item as DeriveInput); 36 | 37 | let name = &ast.ident; 38 | let fields = write_generate_fields(&ast.data); 39 | 40 | let expanded = quote! { 41 | impl Writable<#name> for #name { 42 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 43 | #fields 44 | Ok(()) 45 | } 46 | } 47 | }; 48 | 49 | proc_macro::TokenStream::from(expanded) 50 | } 51 | 52 | // Take all the fields in a struct and generate `field: FType::read(from, version)?` 53 | // for each field. 54 | fn read_generate_fields(data: &Data) -> TokenStream { 55 | match data { 56 | Data::Struct(ref data) => { 57 | match data.fields { 58 | Fields::Named(ref fields) => { 59 | let field_reads = fields.named.iter().map(|f| { 60 | let name = &f.ident; 61 | // Generics need an extra :: so split that off into its own function 62 | let ftype = type_turbofish(&f.ty); 63 | 64 | quote! { 65 | #name: #ftype::read(from, version)? 66 | } 67 | }); 68 | quote! { 69 | #(#field_reads, )* 70 | } 71 | } 72 | Fields::Unnamed(_) | Fields::Unit => unimplemented!(), 73 | } 74 | } 75 | Data::Enum(_) | Data::Union(_) => unimplemented!(), 76 | } 77 | } 78 | 79 | // Take all the fields in a struct and generate `self.field.write(to, version)?` 80 | // for each field. 81 | fn write_generate_fields(data: &Data) -> TokenStream { 82 | match data { 83 | Data::Struct(ref data) => { 84 | match data.fields { 85 | Fields::Named(ref fields) => { 86 | let field_writes = fields.named.iter().map(|f| { 87 | let name = &f.ident; 88 | quote! { 89 | self.#name.write(to, version)? 90 | } 91 | }); 92 | quote! { 93 | #(#field_writes;)* 94 | } 95 | } 96 | Fields::Unnamed(_) | Fields::Unit => unimplemented!(), 97 | } 98 | } 99 | Data::Enum(_) | Data::Union(_) => unimplemented!(), 100 | } 101 | } 102 | 103 | // We can't do Vec::read, so this function adds an extra :: before the <> to make 104 | // this syntactically valid 105 | fn type_turbofish(t: &Type) -> TokenStream { 106 | match t { 107 | // Path is basically all "normal" types 108 | Type::Path(typepath) => { 109 | // Take all the segmented parts of the path and turbofish them all 110 | // (I assume we don't have to, but might as well) 111 | let mut segments = typepath 112 | .path 113 | .segments 114 | .iter() 115 | .map(|segment| { 116 | let ident = &segment.ident; 117 | 118 | match &segment.arguments { 119 | PathArguments::None => { 120 | // Normal (non-generic) type names are just the name 121 | quote! { #ident } 122 | } 123 | PathArguments::AngleBracketed(args) => { 124 | // Eg Vec, we need to stringify all the type args so we 125 | // can put them back into tokens 126 | let mut types = args 127 | .args 128 | .iter() 129 | .map(|genarg| match genarg { 130 | GenericArgument::Type(ty) => type_turbofish(ty), 131 | _ => unimplemented!(), 132 | }) 133 | .collect::>(); 134 | 135 | // There's probably a better way to do this 136 | let first = types.remove(0); 137 | quote! { #ident :: < #first #(, #types)* >} 138 | } 139 | PathArguments::Parenthesized(_) => unimplemented!(), 140 | } 141 | }) 142 | .collect::>(); 143 | 144 | // There's probably a better way to do this 145 | let first = segments.remove(0); 146 | quote! { #first #( :: #segments)* } 147 | } 148 | _ => { 149 | quote! { #t } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /rust/libdif/src/io.rs: -------------------------------------------------------------------------------- 1 | use crate::types::*; 2 | use bytes::{Buf, BufMut}; 3 | use std::mem::size_of; 4 | use typed_ints::TypedInt; 5 | 6 | #[derive(PartialEq, Eq, Debug)] 7 | pub enum EngineVersion { 8 | Unknown, 9 | MBG, 10 | TGE, 11 | TGEA, 12 | T3D, 13 | } 14 | 15 | pub struct Version { 16 | pub engine: EngineVersion, 17 | pub dif: u32, 18 | pub interior: u32, 19 | pub material_list: u8, 20 | pub vehicle_collision: u32, 21 | pub force_field: u32, 22 | } 23 | 24 | impl Version { 25 | pub fn new() -> Version { 26 | Version { 27 | engine: EngineVersion::Unknown, 28 | dif: 0, 29 | interior: 0, 30 | material_list: 0, 31 | vehicle_collision: 0, 32 | force_field: 0, 33 | } 34 | } 35 | 36 | pub fn is_tge(&self) -> bool { 37 | match self.engine { 38 | EngineVersion::MBG | EngineVersion::TGE => true, 39 | _ => false, 40 | } 41 | } 42 | } 43 | 44 | pub trait Readable { 45 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult; 46 | } 47 | 48 | pub trait Writable { 49 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()>; 50 | } 51 | 52 | pub fn read_vec( 53 | from: &mut dyn Buf, 54 | version: &mut Version, 55 | compare_func: fn(bool, u8) -> bool, 56 | into_func: fn(T2) -> T, 57 | ) -> DifResult> 58 | where 59 | T: Readable, 60 | T2: Readable, 61 | { 62 | let mut length = u32::read(from, version)?; 63 | 64 | let mut signed = false; 65 | let mut param = 0u8; 66 | 67 | if (length & 0x80000000) != 0 { 68 | length ^= 0x80000000; 69 | signed = true; 70 | param = u8::read(from, version)?; 71 | } 72 | 73 | let mut result: Vec = Vec::with_capacity(length as usize); 74 | 75 | for _ in 0..length { 76 | if compare_func(signed, param) { 77 | result.push(into_func(T2::read(from, version)?)); 78 | } else { 79 | result.push(T::read(from, version)?); 80 | } 81 | } 82 | 83 | Ok(result) 84 | } 85 | 86 | pub fn write_vec<'a, T: 'a, T2: 'a>( 87 | vec: &'a Vec, 88 | to: &mut dyn BufMut, 89 | version: &Version, 90 | ) -> DifResult<()> 91 | where 92 | T2: Writable, 93 | &'a T: Into<&'a T2>, 94 | { 95 | (vec.len() as u32).write(to, version)?; 96 | for item in vec { 97 | let converted: &'a T2 = item.into(); 98 | converted.write(to, version)?; 99 | } 100 | 101 | Ok(()) 102 | } 103 | 104 | pub fn read_vec_fn( 105 | from: &mut dyn Buf, 106 | version: &mut Version, 107 | read_func: F, 108 | ) -> DifResult> 109 | where 110 | F: Fn(&mut dyn Buf, &mut Version) -> DifResult, 111 | { 112 | let mut length = u32::read(from, version)?; 113 | 114 | if (length & 0x80000000) != 0 { 115 | length ^= 0x80000000; 116 | u8::read(from, version)?; 117 | } 118 | 119 | let mut result: Vec = Vec::with_capacity(length as usize); 120 | 121 | for _ in 0..length { 122 | result.push(read_func(from, version)?); 123 | } 124 | 125 | Ok(result) 126 | } 127 | 128 | pub fn write_vec_fn<'a, T: 'a, T2: 'a>( 129 | vec: &'a Vec, 130 | to: &mut dyn BufMut, 131 | version: &Version, 132 | convert_fn: fn(&'a T) -> T2, 133 | ) -> DifResult<()> 134 | where 135 | T2: Writable, 136 | { 137 | (vec.len() as u32).write(to, version)?; 138 | for item in vec { 139 | let converted: T2 = convert_fn(item); 140 | converted.write(to, version)?; 141 | } 142 | 143 | Ok(()) 144 | } 145 | 146 | pub fn read_vec_extra( 147 | from: &mut dyn Buf, 148 | version: &mut Version, 149 | extra_func: fn(&mut dyn Buf, &mut Version) -> DifResult, 150 | ) -> DifResult<(Vec, T2)> 151 | where 152 | T: Readable, 153 | { 154 | let length = u32::read(from, version)?; 155 | let extra = extra_func(from, version)?; 156 | let mut result: Vec = Vec::with_capacity(length as usize); 157 | 158 | for _ in 0..length { 159 | result.push(T::read(from, version)?); 160 | } 161 | 162 | Ok((result, extra)) 163 | } 164 | 165 | pub fn write_vec_extra<'a, T: 'a>( 166 | vec: &'a Vec, 167 | to: &mut dyn BufMut, 168 | version: &Version, 169 | extra_func: impl Fn(&mut dyn BufMut, &Version) -> DifResult<()>, 170 | ) -> DifResult<()> 171 | where 172 | T: Writable, 173 | { 174 | (vec.len() as u32).write(to, version)?; 175 | extra_func(to, version)?; 176 | 177 | for item in vec { 178 | item.write(to, version)?; 179 | } 180 | 181 | Ok(()) 182 | } 183 | 184 | impl Readable> for Vec 185 | where 186 | T: Readable, 187 | { 188 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult> { 189 | read_vec::(from, version, |_, _| false, |x| x) 190 | } 191 | } 192 | 193 | impl Writable> for Vec 194 | where 195 | T: Writable, 196 | { 197 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 198 | write_vec::(&self, to, version) 199 | } 200 | } 201 | 202 | impl Readable for String { 203 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 204 | let length = u8::read(from, version)?; 205 | let bytes = from.take(length as usize).collect::>(); 206 | Ok(String::from_utf8(bytes).map_err(|e| DifError::from(e))?) 207 | } 208 | } 209 | 210 | impl Writable for String { 211 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 212 | (self.len() as u8).write(to, version)?; 213 | for byte in self.bytes() { 214 | byte.write(to, version)?; 215 | } 216 | Ok(()) 217 | } 218 | } 219 | 220 | macro_rules! primitive_readable { 221 | ($ty: ty, $read_fn: ident) => { 222 | impl Readable<$ty> for $ty { 223 | fn read(from: &mut dyn Buf, _version: &mut Version) -> DifResult { 224 | if from.remaining() < size_of::() { 225 | return Err(DifError::from("EOF")); 226 | } 227 | Ok(from.$read_fn()) 228 | } 229 | } 230 | }; 231 | } 232 | 233 | macro_rules! primitive_writable { 234 | ($ty: ty, $write_fn: ident) => { 235 | impl Writable<$ty> for $ty { 236 | fn write(&self, to: &mut dyn BufMut, _version: &Version) -> DifResult<()> { 237 | Ok(to.$write_fn(*self)) 238 | } 239 | } 240 | }; 241 | } 242 | 243 | primitive_readable!(u8, get_u8); 244 | primitive_readable!(u16, get_u16_le); 245 | primitive_readable!(u32, get_u32_le); 246 | primitive_readable!(u64, get_u64_le); 247 | 248 | primitive_readable!(i8, get_i8); 249 | primitive_readable!(i16, get_i16_le); 250 | primitive_readable!(i32, get_i32_le); 251 | primitive_readable!(i64, get_i64_le); 252 | 253 | primitive_readable!(f32, get_f32_le); 254 | primitive_readable!(f64, get_f64_le); 255 | 256 | primitive_writable!(u8, put_u8); 257 | primitive_writable!(u16, put_u16_le); 258 | primitive_writable!(u32, put_u32_le); 259 | primitive_writable!(u64, put_u64_le); 260 | 261 | primitive_writable!(i8, put_i8); 262 | primitive_writable!(i16, put_i16_le); 263 | primitive_writable!(i32, put_i32_le); 264 | primitive_writable!(i64, put_i64_le); 265 | 266 | primitive_writable!(f32, put_f32_le); 267 | primitive_writable!(f64, put_f64_le); 268 | 269 | impl Readable> for TypedInt where T: Readable+Copy { 270 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult> { 271 | T::read(from, version).map(|b| Self::from(b)) 272 | } 273 | } 274 | 275 | impl Writable> for TypedInt where T: Writable+Copy { 276 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 277 | self.inner().write(to, version) 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /rust/libdif/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "approx" 7 | version = "0.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" 10 | dependencies = [ 11 | "num-traits", 12 | ] 13 | 14 | [[package]] 15 | name = "autocfg" 16 | version = "0.1.5" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" 19 | 20 | [[package]] 21 | name = "bitflags" 22 | version = "1.2.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 25 | 26 | [[package]] 27 | name = "byteorder" 28 | version = "1.3.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 31 | 32 | [[package]] 33 | name = "bytes" 34 | version = "0.4.12" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 37 | dependencies = [ 38 | "byteorder", 39 | "iovec", 40 | ] 41 | 42 | [[package]] 43 | name = "cgmath" 44 | version = "0.17.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "283944cdecc44bf0b8dd010ec9af888d3b4f142844fdbe026c20ef68148d6fe7" 47 | dependencies = [ 48 | "approx", 49 | "num-traits", 50 | "rand", 51 | ] 52 | 53 | [[package]] 54 | name = "cloudabi" 55 | version = "0.0.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 58 | dependencies = [ 59 | "bitflags", 60 | ] 61 | 62 | [[package]] 63 | name = "dif-derive" 64 | version = "0.1.0" 65 | dependencies = [ 66 | "proc-macro2", 67 | "quote", 68 | "syn", 69 | ] 70 | 71 | [[package]] 72 | name = "fuchsia-cprng" 73 | version = "0.1.1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 76 | 77 | [[package]] 78 | name = "iovec" 79 | version = "0.1.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" 82 | dependencies = [ 83 | "libc", 84 | "winapi 0.2.8", 85 | ] 86 | 87 | [[package]] 88 | name = "libc" 89 | version = "0.2.62" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" 92 | 93 | [[package]] 94 | name = "libdif" 95 | version = "0.1.0" 96 | dependencies = [ 97 | "bitflags", 98 | "bytes", 99 | "cgmath", 100 | "dif-derive", 101 | "typed_ints", 102 | "typenum", 103 | ] 104 | 105 | [[package]] 106 | name = "num-traits" 107 | version = "0.2.8" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 110 | dependencies = [ 111 | "autocfg", 112 | ] 113 | 114 | [[package]] 115 | name = "proc-macro2" 116 | version = "1.0.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802" 119 | dependencies = [ 120 | "unicode-xid", 121 | ] 122 | 123 | [[package]] 124 | name = "quote" 125 | version = "1.0.2" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 128 | dependencies = [ 129 | "proc-macro2", 130 | ] 131 | 132 | [[package]] 133 | name = "rand" 134 | version = "0.6.5" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 137 | dependencies = [ 138 | "autocfg", 139 | "libc", 140 | "rand_chacha", 141 | "rand_core 0.4.2", 142 | "rand_hc", 143 | "rand_isaac", 144 | "rand_jitter", 145 | "rand_os", 146 | "rand_pcg", 147 | "rand_xorshift", 148 | "winapi 0.3.7", 149 | ] 150 | 151 | [[package]] 152 | name = "rand_chacha" 153 | version = "0.1.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 156 | dependencies = [ 157 | "autocfg", 158 | "rand_core 0.3.1", 159 | ] 160 | 161 | [[package]] 162 | name = "rand_core" 163 | version = "0.3.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 166 | dependencies = [ 167 | "rand_core 0.4.2", 168 | ] 169 | 170 | [[package]] 171 | name = "rand_core" 172 | version = "0.4.2" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 175 | 176 | [[package]] 177 | name = "rand_hc" 178 | version = "0.1.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 181 | dependencies = [ 182 | "rand_core 0.3.1", 183 | ] 184 | 185 | [[package]] 186 | name = "rand_isaac" 187 | version = "0.1.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 190 | dependencies = [ 191 | "rand_core 0.3.1", 192 | ] 193 | 194 | [[package]] 195 | name = "rand_jitter" 196 | version = "0.1.4" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 199 | dependencies = [ 200 | "libc", 201 | "rand_core 0.4.2", 202 | "winapi 0.3.7", 203 | ] 204 | 205 | [[package]] 206 | name = "rand_os" 207 | version = "0.1.3" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 210 | dependencies = [ 211 | "cloudabi", 212 | "fuchsia-cprng", 213 | "libc", 214 | "rand_core 0.4.2", 215 | "rdrand", 216 | "winapi 0.3.7", 217 | ] 218 | 219 | [[package]] 220 | name = "rand_pcg" 221 | version = "0.1.2" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 224 | dependencies = [ 225 | "autocfg", 226 | "rand_core 0.4.2", 227 | ] 228 | 229 | [[package]] 230 | name = "rand_xorshift" 231 | version = "0.1.1" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 234 | dependencies = [ 235 | "rand_core 0.3.1", 236 | ] 237 | 238 | [[package]] 239 | name = "rdrand" 240 | version = "0.4.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 243 | dependencies = [ 244 | "rand_core 0.3.1", 245 | ] 246 | 247 | [[package]] 248 | name = "syn" 249 | version = "1.0.3" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "158521e6f544e7e3dcfc370ac180794aa38cb34a1b1e07609376d4adcf429b93" 252 | dependencies = [ 253 | "proc-macro2", 254 | "quote", 255 | "unicode-xid", 256 | ] 257 | 258 | [[package]] 259 | name = "typed_ints" 260 | version = "0.1.0" 261 | 262 | [[package]] 263 | name = "typenum" 264 | version = "1.17.0" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 267 | 268 | [[package]] 269 | name = "unicode-xid" 270 | version = "0.2.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 273 | 274 | [[package]] 275 | name = "winapi" 276 | version = "0.2.8" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 279 | 280 | [[package]] 281 | name = "winapi" 282 | version = "0.3.7" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" 285 | dependencies = [ 286 | "winapi-i686-pc-windows-gnu", 287 | "winapi-x86_64-pc-windows-gnu", 288 | ] 289 | 290 | [[package]] 291 | name = "winapi-i686-pc-windows-gnu" 292 | version = "0.4.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 295 | 296 | [[package]] 297 | name = "winapi-x86_64-pc-windows-gnu" 298 | version = "0.4.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 301 | -------------------------------------------------------------------------------- /rust/libdif/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::io::*; 2 | use crate::io::{Readable, Writable}; 3 | use bytes::{Buf, BufMut}; 4 | use cgmath::{InnerSpace, Matrix, Matrix4, Quaternion, Vector2, Vector3}; 5 | use dif_derive::{Readable, Writable}; 6 | use std::collections::HashMap; 7 | use std::error::Error; 8 | use std::fmt::{Display, Formatter}; 9 | use std::string::FromUtf8Error; 10 | 11 | pub type Point2F = Vector2; 12 | 13 | pub type Point2I = Vector2; 14 | 15 | pub type Point3F = Vector3; 16 | 17 | #[derive(Debug, Readable, Writable, Clone)] 18 | pub struct BoxF { 19 | pub min: Point3F, 20 | pub max: Point3F, 21 | } 22 | 23 | #[derive(Debug, Readable, Writable, Clone)] 24 | pub struct SphereF { 25 | pub origin: Point3F, 26 | pub radius: f32, 27 | } 28 | 29 | #[derive(Debug, Readable, Writable, Clone)] 30 | pub struct PlaneF { 31 | pub normal: Point3F, 32 | pub distance: f32, 33 | } 34 | 35 | pub type QuatF = Quaternion; 36 | 37 | #[derive(Clone, Copy, Debug, Readable, Writable)] 38 | pub struct ColorI { 39 | pub r: u8, 40 | pub g: u8, 41 | pub b: u8, 42 | pub a: u8, 43 | } 44 | 45 | pub type MatrixF = Matrix4; 46 | 47 | pub type Dictionary = HashMap; 48 | 49 | #[derive(Debug, Clone)] 50 | pub struct PNG { 51 | pub data: Vec, 52 | } 53 | 54 | pub type DifResult = Result; 55 | 56 | #[derive(Debug)] 57 | pub struct DifError { 58 | pub message: String, 59 | } 60 | 61 | impl BoxF { 62 | pub fn center(&self) -> Point3F { 63 | (self.min + self.max) / 2.0 64 | } 65 | pub fn extent(&self) -> Point3F { 66 | self.max - self.min 67 | } 68 | pub fn union(&self, other: &BoxF) -> BoxF { 69 | BoxF { 70 | min: Vector3 { 71 | x: self.min.x.min(other.min.x), 72 | y: self.min.y.min(other.min.y), 73 | z: self.min.z.min(other.min.z), 74 | }, 75 | max: Vector3 { 76 | x: self.max.x.max(other.max.x), 77 | y: self.max.y.max(other.max.y), 78 | z: self.max.z.max(other.max.z), 79 | }, 80 | } 81 | } 82 | pub fn union_point(&self, other: &Point3F) -> BoxF { 83 | BoxF { 84 | min: Vector3 { 85 | x: self.min.x.min(other.x), 86 | y: self.min.y.min(other.y), 87 | z: self.min.z.min(other.z), 88 | }, 89 | max: Vector3 { 90 | x: self.max.x.max(other.x), 91 | y: self.max.y.max(other.y), 92 | z: self.max.z.max(other.z), 93 | }, 94 | } 95 | } 96 | pub fn contains(&self, point: &Point3F) -> bool { 97 | return point.x >= self.min.x 98 | && point.y >= self.min.y 99 | && point.z >= self.min.z 100 | && point.x <= self.max.x 101 | && point.y <= self.max.y 102 | && point.z <= self.max.z; 103 | } 104 | 105 | pub fn from_vertices(vertices: &[&Point3F]) -> Self { 106 | use std::f32::{INFINITY, NEG_INFINITY}; 107 | let mut b = BoxF { 108 | min: Vector3 { 109 | x: INFINITY, 110 | y: INFINITY, 111 | z: INFINITY, 112 | }, 113 | max: Vector3 { 114 | x: NEG_INFINITY, 115 | y: NEG_INFINITY, 116 | z: NEG_INFINITY, 117 | }, 118 | }; 119 | 120 | for vertex in vertices { 121 | b.min.x = b.min.x.min(vertex.x); 122 | b.min.y = b.min.y.min(vertex.y); 123 | b.min.z = b.min.z.min(vertex.z); 124 | b.max.x = b.max.x.max(vertex.x); 125 | b.max.y = b.max.y.max(vertex.y); 126 | b.max.z = b.max.z.max(vertex.z); 127 | } 128 | 129 | b 130 | } 131 | } 132 | 133 | impl PlaneF { 134 | pub fn from_triangle(v0: Point3F, v1: Point3F, v2: Point3F) -> Self { 135 | let normal = (v2 - v0).cross(v1 - v0).normalize(); 136 | 137 | //Use the center of the plane, probably correct 138 | let average_point = (v0 + v1 + v2) / 3.0; 139 | 140 | let distance = (-average_point).dot(normal); 141 | PlaneF { normal, distance } 142 | } 143 | } 144 | 145 | impl From<&'static str> for DifError { 146 | fn from(message: &'static str) -> Self { 147 | DifError { 148 | message: message.into(), 149 | } 150 | } 151 | } 152 | 153 | impl From for DifError { 154 | fn from(err: FromUtf8Error) -> Self { 155 | DifError { 156 | message: format!("UTF-8 Error: {}", err), 157 | } 158 | } 159 | } 160 | 161 | impl Display for DifError { 162 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { 163 | write!(f, "DIF error: {}", self.message) 164 | } 165 | } 166 | 167 | impl Error for DifError { 168 | fn description(&self) -> &str { 169 | "DIF Error" 170 | } 171 | } 172 | 173 | impl Readable for Point2F { 174 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 175 | Ok(Point2F { 176 | x: f32::read(from, version)?, 177 | y: f32::read(from, version)?, 178 | }) 179 | } 180 | } 181 | 182 | impl Writable for Point2F { 183 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 184 | self.x.write(to, version)?; 185 | self.y.write(to, version)?; 186 | Ok(()) 187 | } 188 | } 189 | 190 | impl Readable for Point2I { 191 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 192 | Ok(Point2I { 193 | x: i32::read(from, version)?, 194 | y: i32::read(from, version)?, 195 | }) 196 | } 197 | } 198 | 199 | impl Writable for Point2I { 200 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 201 | self.x.write(to, version)?; 202 | self.y.write(to, version)?; 203 | Ok(()) 204 | } 205 | } 206 | 207 | impl Readable for Point3F { 208 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 209 | Ok(Point3F { 210 | x: f32::read(from, version)?, 211 | y: f32::read(from, version)?, 212 | z: f32::read(from, version)?, 213 | }) 214 | } 215 | } 216 | 217 | impl Writable for Point3F { 218 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 219 | self.x.write(to, version)?; 220 | self.y.write(to, version)?; 221 | self.z.write(to, version)?; 222 | Ok(()) 223 | } 224 | } 225 | 226 | impl Readable for QuatF { 227 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 228 | Ok(QuatF { 229 | s: f32::read(from, version)?, 230 | v: Vector3 { 231 | x: f32::read(from, version)?, 232 | y: f32::read(from, version)?, 233 | z: f32::read(from, version)?, 234 | }, 235 | }) 236 | } 237 | } 238 | 239 | impl Writable for QuatF { 240 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 241 | self.s.write(to, version)?; 242 | self.v.write(to, version)?; 243 | Ok(()) 244 | } 245 | } 246 | 247 | impl Readable for MatrixF { 248 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 249 | let m = MatrixF::new( 250 | f32::read(from, version)?, 251 | f32::read(from, version)?, 252 | f32::read(from, version)?, 253 | f32::read(from, version)?, 254 | f32::read(from, version)?, 255 | f32::read(from, version)?, 256 | f32::read(from, version)?, 257 | f32::read(from, version)?, 258 | f32::read(from, version)?, 259 | f32::read(from, version)?, 260 | f32::read(from, version)?, 261 | f32::read(from, version)?, 262 | f32::read(from, version)?, 263 | f32::read(from, version)?, 264 | f32::read(from, version)?, 265 | f32::read(from, version)?, 266 | ); 267 | Ok(m.transpose()) 268 | } 269 | } 270 | 271 | impl Writable for MatrixF { 272 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 273 | self.x.x.write(to, version)?; 274 | self.y.x.write(to, version)?; 275 | self.z.x.write(to, version)?; 276 | self.w.x.write(to, version)?; 277 | self.x.y.write(to, version)?; 278 | self.y.y.write(to, version)?; 279 | self.z.y.write(to, version)?; 280 | self.w.y.write(to, version)?; 281 | self.x.z.write(to, version)?; 282 | self.y.z.write(to, version)?; 283 | self.z.z.write(to, version)?; 284 | self.w.z.write(to, version)?; 285 | self.x.w.write(to, version)?; 286 | self.y.w.write(to, version)?; 287 | self.z.w.write(to, version)?; 288 | self.w.w.write(to, version)?; 289 | Ok(()) 290 | } 291 | } 292 | 293 | impl Readable for Dictionary { 294 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 295 | let length = u32::read(from, version)?; 296 | 297 | let mut result = Dictionary::new(); 298 | 299 | for _ in 0..length { 300 | let name = String::read(from, version)?; 301 | let value = String::read(from, version)?; 302 | result.insert(name, value); 303 | } 304 | 305 | Ok(result) 306 | } 307 | } 308 | 309 | impl Writable for Dictionary { 310 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 311 | (self.len() as u32).write(to, version)?; 312 | 313 | for (name, value) in self { 314 | name.write(to, version)?; 315 | value.write(to, version)?; 316 | } 317 | 318 | Ok(()) 319 | } 320 | } 321 | 322 | impl Readable for PNG { 323 | fn read(from: &mut dyn Buf, version: &mut Version) -> DifResult { 324 | let footer = [0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]; 325 | let mut data = vec![]; 326 | while data.len() < 8 || !data.ends_with(&footer) { 327 | data.push(u8::read(from, version)?); 328 | } 329 | Ok(PNG { data }) 330 | } 331 | } 332 | 333 | impl Writable for PNG { 334 | fn write(&self, to: &mut dyn BufMut, version: &Version) -> DifResult<()> { 335 | for byte in &self.data { 336 | byte.write(to, version)?; 337 | } 338 | 339 | Ok(()) 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /blender_plugin/io_dif/import_dif.py: -------------------------------------------------------------------------------- 1 | import array 2 | import os 3 | import time 4 | import bpy 5 | from bpy.props import CollectionProperty 6 | from bpy.types import Curve, Object 7 | import mathutils 8 | from .hxDif import * 9 | from bpy_extras.io_utils import unpack_list 10 | from bpy_extras.image_utils import load_image 11 | from .util import default_materials, resolve_texture, get_rgb_colors 12 | 13 | from bpy_extras.wm_utils.progress_report import ProgressReport, ProgressReportSubstep 14 | 15 | 16 | def create_material(filepath, matname): 17 | if "/" in matname: 18 | matname = matname.split("/")[1] 19 | mat = bpy.data.materials.new(matname) 20 | mat.use_nodes = True 21 | 22 | texname = resolve_texture(filepath, matname) 23 | if texname is not None: 24 | try: 25 | teximg = bpy.data.images.load(texname) 26 | except: 27 | teximg = None 28 | print("Cannot load image", texname) 29 | 30 | texslot = mat.node_tree.nodes.new("ShaderNodeTexImage") 31 | texslot.name = matname 32 | texslot.image = teximg 33 | if bpy.app.version < (4, 0, 0): 34 | mat.node_tree.nodes["Principled BSDF"].inputs["Specular"].default_value = 0 35 | else: 36 | mat.node_tree.nodes["Principled BSDF"].inputs["Roughness"].default_value = 1.0 37 | mat.node_tree.links.new( 38 | mat.node_tree.nodes["Principled BSDF"].inputs["Base Color"], 39 | texslot.outputs["Color"], 40 | ) 41 | 42 | return mat 43 | 44 | 45 | def fix_indices(indices: list[int]): 46 | new_indices = [0] * len(indices) 47 | for i in range(len(indices)): 48 | if i >= 2: 49 | if i % 2 == 0: 50 | new_indices[len(indices) - 1 - (i - 2) // 2] = indices[i] 51 | else: 52 | new_indices[(i + 1) // 2] = indices[i] 53 | else: 54 | new_indices[i] = indices[i] 55 | return new_indices 56 | 57 | 58 | def create_mesh(filepath, interior: Interior): 59 | """ 60 | :param Interior interior: 61 | :return: 62 | """ 63 | me = bpy.data.meshes.new("Mesh") 64 | 65 | for mat in interior.materialList: 66 | me.materials.append(create_material(filepath, mat)) 67 | 68 | surfaces: list[Surface] = interior.surfaces 69 | 70 | if bpy.app.version < (4, 0, 0): 71 | me.vertices.add(len(interior.points)) 72 | for i in range(0, len(interior.points)): 73 | me.vertices[i].co = [ 74 | interior.points[i].x, 75 | interior.points[i].y, 76 | interior.points[i].z, 77 | ] 78 | 79 | me.polygons.add(len(surfaces)) 80 | 81 | loop_count = 0 82 | for surf in surfaces: 83 | loop_count += surf.windingCount 84 | 85 | me.loops.add(loop_count) 86 | 87 | surface_uvs = {} 88 | cur_loop_idx = 0 89 | 90 | for (i, surface) in enumerate(surfaces): 91 | surf_indices = interior.windings[ 92 | surface.windingStart : (surface.windingStart + surface.windingCount) 93 | ] 94 | 95 | surf_indices = fix_indices(surf_indices) 96 | 97 | plane_flipped = surface.planeFlipped 98 | normal_index = interior.planes[surface.planeIndex & ~0x8000].normalIndex 99 | tex_gen = interior.texGenEQs[surface.texGenIndex] 100 | 101 | normal = interior.normals[normal_index] 102 | if plane_flipped: 103 | normal.x *= -1 104 | normal.y *= -1 105 | normal.z *= -1 106 | 107 | polygon = me.polygons[i] 108 | polygon.loop_start = cur_loop_idx 109 | polygon.loop_total = len(surf_indices) 110 | cur_loop_idx += polygon.loop_total 111 | polygon.material_index = surface.textureIndex 112 | 113 | def plane_to_uv(pt, plane): 114 | return pt.x * plane.x + pt.y * plane.y + pt.z * plane.z + plane.d 115 | 116 | for j, index in enumerate(surf_indices): 117 | me.loops[j + polygon.loop_start].vertex_index = index 118 | me.loops[j + polygon.loop_start].normal = (normal.x, normal.y, normal.z) 119 | 120 | pt = interior.points[index] 121 | 122 | uv = ( 123 | plane_to_uv(pt, tex_gen.planeX), 124 | -plane_to_uv(pt, tex_gen.planeY), 125 | ) 126 | surface_uvs[j + polygon.loop_start] = uv 127 | 128 | me.uv_layers.new() 129 | uvs = me.uv_layers[0] 130 | 131 | for loop_idx in surface_uvs: 132 | uvs.data[loop_idx].uv = surface_uvs[loop_idx] 133 | else: 134 | mesh_verts = [] 135 | for i in range(0, len(interior.points)): 136 | mesh_verts.append((interior.points[i].x, interior.points[i].y, interior.points[i].z)) 137 | 138 | mesh_faces = [] 139 | face_texs = [] 140 | face_uvs = [] 141 | cur_loop_idx = 0 142 | 143 | for (i, surface) in enumerate(surfaces): 144 | surf_indices = interior.windings[ 145 | surface.windingStart : (surface.windingStart + surface.windingCount) 146 | ] 147 | 148 | surf_indices = fix_indices(surf_indices) 149 | 150 | plane_flipped = surface.planeFlipped 151 | normal_index = interior.planes[surface.planeIndex & ~0x8000].normalIndex 152 | tex_gen = interior.texGenEQs[surface.texGenIndex] 153 | 154 | normal = interior.normals[normal_index] 155 | if plane_flipped: 156 | normal.x *= -1 157 | normal.y *= -1 158 | normal.z *= -1 159 | 160 | polygon = surf_indices 161 | cur_loop_idx += len(surf_indices) 162 | mesh_faces.append(polygon) 163 | 164 | face_texs.append(surface.textureIndex) 165 | 166 | def plane_to_uv(pt, plane): 167 | return pt.x * plane.x + pt.y * plane.y + pt.z * plane.z + plane.d 168 | 169 | face_uv = [] 170 | for j, index in enumerate(surf_indices): 171 | pt = interior.points[index] 172 | 173 | uv = ( 174 | plane_to_uv(pt, tex_gen.planeX), 175 | -plane_to_uv(pt, tex_gen.planeY), 176 | ) 177 | face_uv.append(uv) 178 | face_uvs.append(face_uv) 179 | 180 | me.from_pydata(mesh_verts, [], mesh_faces) 181 | 182 | if not me.uv_layers: 183 | me.uv_layers.new() 184 | 185 | uv_layer = me.uv_layers.active.data 186 | 187 | for i, poly in enumerate(me.polygons): 188 | p: bpy.types.MeshPolygon = poly 189 | p.material_index = face_texs[i] 190 | 191 | for j, loop_index in enumerate(p.loop_indices): 192 | loop = me.loops[loop_index] 193 | uv_layer[loop.index].uv = face_uvs[i][j] 194 | 195 | me.validate(verbose=True) 196 | me.update() 197 | 198 | ob = bpy.data.objects.new("Object", me) 199 | ob.empty_display_type = "SINGLE_ARROW" 200 | ob.empty_display_size = 0.5 201 | 202 | return ob 203 | 204 | 205 | def load( 206 | context: bpy.types.Context, 207 | filepath, 208 | *, 209 | global_clamp_size=0.0, 210 | use_smooth_groups=True, 211 | use_edges=True, 212 | use_split_objects=True, 213 | use_split_groups=True, 214 | use_image_search=True, 215 | use_groups_as_vgroups=False, 216 | use_cycles=True, 217 | relpath=None, 218 | global_matrix=None 219 | ): 220 | """ 221 | Called by the user interface or another script. 222 | load_obj(path) - should give acceptable results. 223 | This function passes the file and sends the data off 224 | to be split into objects and then converted into mesh objects 225 | """ 226 | 227 | dif = Dif.Load(str(filepath)) 228 | 229 | if global_matrix is None: 230 | global_matrix = mathutils.Matrix() 231 | 232 | # deselect all 233 | if bpy.ops.object.select_all.poll(): 234 | bpy.ops.object.select_all(action="DESELECT") 235 | 236 | scene = context.scene 237 | new_objects = [] # put new objects here 238 | 239 | for interior in dif.interiors: 240 | new_objects.append(create_mesh(filepath, interior)) 241 | 242 | pathedInteriors: list[Object] = [] 243 | for pathedInterior in dif.subObjects: 244 | pathedInteriors.append(create_mesh(filepath, pathedInterior)) 245 | 246 | # Create new obj 247 | for obj in new_objects: 248 | base = scene.collection.objects.link(obj) 249 | 250 | # we could apply this anywhere before scaling. 251 | obj.matrix_world = global_matrix 252 | 253 | for mover in dif.interiorPathfollowers: 254 | pos = mover.offset 255 | itr = pathedInteriors[mover.interiorResIndex] 256 | itr: Object = itr.copy() 257 | base = scene.collection.objects.link(itr) 258 | itr.location = [-pos.x, -pos.y, -pos.z] 259 | itr.dif_props.interior_type = "pathed_interior" 260 | itr.dif_props.start_time = int(mover.properties.h.get("initialPosition", 0)) 261 | itr.dif_props.reverse = mover.properties.h.get("initialTargetPosition", 0) == "-2" 262 | itr.dif_props.constant_speed = False 263 | 264 | waypoints: list[WayPoint] = mover.wayPoint 265 | 266 | markerpts = [ 267 | (waypt.position.x, waypt.position.y, waypt.position.z) 268 | for waypt in waypoints 269 | ] 270 | 271 | curve = bpy.data.curves.new("markers", type="CURVE") 272 | curve.dimensions = "3D" 273 | spline = curve.splines.new(type="POLY") 274 | spline.points.add(len(markerpts) - 1) 275 | 276 | for p, new_co in zip(spline.points, markerpts): 277 | p.co = new_co + (1.0,) 278 | 279 | path = bpy.data.objects.new("path", curve) 280 | scene.collection.objects.link(path) 281 | 282 | itr.dif_props.marker_path = curve 283 | 284 | total_time = 0 285 | for pt in waypoints: 286 | total_time += pt.msToNext 287 | itr.dif_props.total_time = total_time 288 | 289 | first_type = waypoints[0].smoothingType 290 | if first_type == 0: 291 | itr.dif_props.marker_type = "linear" 292 | elif first_type == 1: 293 | itr.dif_props.marker_type = "spline" 294 | elif first_type == 2: 295 | itr.dif_props.marker_type = "accelerate" 296 | 297 | for trigger_id in mover.triggerId: 298 | trigger = dif.triggers[trigger_id] 299 | tobj = bpy.data.objects.new(trigger.datablock, None) 300 | tobj.dif_props.interior_type = "path_trigger" 301 | tobj.dif_props.pathed_interior_target = itr 302 | tobj.dif_props.game_entity_datablock = trigger.datablock 303 | tobj.dif_props.target_marker = False 304 | for key in trigger.properties.h: 305 | prop = tobj.dif_props.game_entity_properties.add() 306 | prop.key = key 307 | prop.value = trigger.properties.get(key) 308 | 309 | t_min = mathutils.Vector((float('inf'), float('inf'), float('inf'))) 310 | t_max = mathutils.Vector((-float('inf'), -float('inf'), -float('inf'))) 311 | for p in trigger.polyhedron.pointList: 312 | t_min.x = min(t_min.x, p.x) 313 | t_min.y = min(t_min.y, p.y) 314 | t_min.z = min(t_min.z, p.z) 315 | 316 | t_max.x = max(t_max.x, p.x) 317 | t_max.y = max(t_max.y, p.y) 318 | t_max.z = max(t_max.z, p.z) 319 | 320 | tobj.location = t_min 321 | tobj.scale = mathutils.Vector((t_max.x - t_min.x, t_max.y - t_min.y, t_max.z - t_min.z)) 322 | tobj.location.y += tobj.scale.y 323 | tobj.location += mathutils.Vector((trigger.offset.x, trigger.offset.y, trigger.offset.z)) 324 | scene.collection.objects.link(tobj) 325 | 326 | if dif.gameEntities != None: 327 | for ge in dif.gameEntities: 328 | g: GameEntity = ge 329 | gobj = bpy.data.objects.new(g.datablock, None) 330 | gobj.location = (g.position.x, g.position.y, g.position.z) 331 | gobj.dif_props.interior_type = "game_entity" 332 | gobj.dif_props.game_entity_datablock = g.datablock 333 | gobj.dif_props.game_entity_gameclass = g.gameClass 334 | for key in g.properties.h: 335 | prop = gobj.dif_props.game_entity_properties.add() 336 | prop.key = key 337 | prop.value = g.properties.get(key) 338 | scene.collection.objects.link(gobj) 339 | 340 | context.view_layer.update() 341 | 342 | axis_min = [1000000000] * 3 343 | axis_max = [-1000000000] * 3 344 | 345 | if global_clamp_size: 346 | # Get all object bounds 347 | for ob in new_objects: 348 | for v in ob.bound_box: 349 | for axis, value in enumerate(v): 350 | if axis_min[axis] > value: 351 | axis_min[axis] = value 352 | if axis_max[axis] < value: 353 | axis_max[axis] = value 354 | 355 | # Scale objects 356 | max_axis = max( 357 | axis_max[0] - axis_min[0], 358 | axis_max[1] - axis_min[1], 359 | axis_max[2] - axis_min[2], 360 | ) 361 | scale = 1.0 362 | 363 | while global_clamp_size < max_axis * scale: 364 | scale = scale / 10.0 365 | 366 | for obj in new_objects: 367 | obj.scale = scale, scale, scale 368 | 369 | # progress.leave_substeps("Done.") 370 | 371 | return {"FINISHED"} 372 | -------------------------------------------------------------------------------- /blender_plugin/io_dif/import_csx.py: -------------------------------------------------------------------------------- 1 | import math 2 | from typing import Tuple 3 | import bpy 4 | from bpy.props import CollectionProperty 5 | from bpy.types import Curve, Object 6 | import mathutils 7 | from bpy_extras.io_utils import unpack_list 8 | from bpy_extras.image_utils import load_image 9 | 10 | from .util import default_materials, resolve_texture, get_rgb_colors 11 | 12 | from bpy_extras.wm_utils.progress_report import ProgressReport, ProgressReportSubstep 13 | 14 | import xml.etree.ElementTree as ET 15 | 16 | 17 | class CSXEntity: 18 | def __init__( 19 | self, 20 | id: str, 21 | className: str, 22 | origin: list[float], 23 | properties: dict, 24 | ): 25 | self.id = id 26 | self.className = className 27 | self.origin = origin 28 | self.properties = properties 29 | 30 | 31 | class CSXTexGen: 32 | def __init__( 33 | self, 34 | planeX: list[float], 35 | planeY: list[float], 36 | texRot: float, 37 | texScale: list[float], 38 | ): 39 | self.texRot = texRot 40 | self.texScale = texScale 41 | self.texPlaneX = planeX 42 | self.texPlaneY = planeY 43 | 44 | def compute_uv(self, vertex: list[float], texsizes: list[float]): 45 | if self.texScale[0] * self.texScale[1] == 0: 46 | return [0, 0] 47 | axisU, axisV = self.transform_axes() 48 | target = self.project_raw(vertex, axisU, axisV) 49 | target[0] *= (1 / self.texScale[0]) * (32 / texsizes[0]) 50 | target[1] *= (1 / -self.texScale[1]) * (32 / texsizes[1]) 51 | 52 | shift = [self.texPlaneX[3] / texsizes[0], -self.texPlaneY[3] / texsizes[1]] 53 | 54 | # rotate shift 55 | # if self.texRot % 360 != 0: 56 | # shift[0], shift[1] = ( 57 | # shift[0] * math.cos(math.radians(self.texRot)) 58 | # - shift[1] * math.sin(math.radians(self.texRot)), 59 | # shift[0] * math.sin(math.radians(self.texRot)) 60 | # + shift[1] * math.cos(math.radians(self.texRot)), 61 | # ) 62 | 63 | target[0] += shift[0] 64 | target[1] += shift[1] 65 | return target 66 | 67 | def project_raw(self, vertex: list[float], axisU: list[float], axisV: list[float]): 68 | return [ 69 | vertex[0] * axisU[0] + vertex[1] * axisU[1] + vertex[2] * axisU[2], 70 | vertex[0] * axisV[0] + vertex[1] * axisV[1] + vertex[2] * axisV[2], 71 | ] 72 | 73 | def transform_axes(self): 74 | axisU = mathutils.Vector( 75 | (self.texPlaneX[0], self.texPlaneX[1], self.texPlaneX[2]) 76 | ) 77 | axisV = mathutils.Vector( 78 | (self.texPlaneY[0], self.texPlaneY[1], self.texPlaneY[2]) 79 | ) 80 | if self.texRot % 360 == 0: 81 | return axisU, axisV 82 | upDir = axisU.cross(axisV) 83 | rotMat = mathutils.Matrix.Rotation(math.radians(self.texRot), 3, upDir) 84 | axisU.rotate(rotMat) 85 | axisV.rotate(rotMat) 86 | return axisU, axisV 87 | 88 | 89 | class CSXBrushFace: 90 | def __init__( 91 | self, 92 | id: str, 93 | plane: list[float], 94 | material: str, 95 | texgen: CSXTexGen, 96 | indices: list[int], 97 | texSize: list[int], 98 | ): 99 | self.id = id 100 | self.plane = plane 101 | self.material = material 102 | self.texgen = texgen 103 | self.indices = indices 104 | self.texSize = texSize 105 | 106 | 107 | class CSXBrush: 108 | def __init__( 109 | self, 110 | id: str, 111 | owner: str, 112 | type: int, 113 | pos: list[float], 114 | rot: list[float], 115 | transform: list[float], 116 | vertices: list[list[float]], 117 | faces: list[CSXBrushFace], 118 | ): 119 | self.id = id 120 | self.owner = owner 121 | self.type = type 122 | self.pos = pos 123 | self.rot = rot 124 | self.transform = transform 125 | self.vertices = vertices 126 | self.faces = faces 127 | 128 | 129 | class CSXDetail: 130 | def __init__(self, brushes: list[CSXBrush], entities: list[CSXEntity]): 131 | self.brushes = brushes 132 | self.entities = entities 133 | 134 | 135 | class CSX: 136 | def __init__(self, details: list[CSXDetail]): 137 | self.details = details 138 | 139 | 140 | def parse_texgen(texgen: str): 141 | texgen = texgen.split(" ") 142 | planeX = [float(x) for x in texgen[0:4]] 143 | planeY = [float(x) for x in texgen[4:8]] 144 | texRot = float(texgen[8]) 145 | texScale = [float(x) for x in texgen[9:]] 146 | return CSXTexGen(planeX, planeY, texRot, texScale) 147 | 148 | 149 | def parse_csx(path): 150 | csx: ET.ElementTree = ET.parse(path) 151 | csscene = csx.getroot() 152 | detailsxml = csscene.find("DetailLevels") 153 | 154 | details = [] 155 | for detail in detailsxml.iter("DetailLevel"): 156 | detailbrushes = [] 157 | for brush in detail.find("InteriorMap").find("Brushes").iter("Brush"): 158 | brushverts = [] 159 | for vert in brush.find("Vertices").iter("Vertex"): 160 | vertdata = [float(x) for x in vert.get("pos").split(" ")] 161 | brushverts.append(vertdata) 162 | brushfaces = [] 163 | for face in brush.iter("Face"): 164 | brushfaces.append( 165 | CSXBrushFace( 166 | face.get("id"), 167 | [float(x) for x in face.get("plane").split(" ")], 168 | face.get("material"), 169 | parse_texgen(face.get("texgens")), 170 | [ 171 | int(x) 172 | for x in face.find("Indices") 173 | .get("indices") 174 | .strip() 175 | .split(" ") 176 | ], 177 | [int(x) for x in face.get("texDiv").split(" ")], 178 | ) 179 | ) 180 | detailbrushes.append( 181 | CSXBrush( 182 | brush.get("id"), 183 | brush.get("owner"), 184 | int(brush.get("type")), 185 | [float(x) for x in brush.get("pos").split(" ")], 186 | [float(x) for x in brush.get("rot").split(" ")], 187 | [float(x) for x in brush.get("transform").split(" ")], 188 | brushverts, 189 | brushfaces, 190 | ) 191 | ) 192 | 193 | detailentities = [] 194 | for entity in detail.find("InteriorMap").find("Entities").iter("Entity"): 195 | if entity.get("isPointEntity") == "0": 196 | continue 197 | entityprops = entity.find("Properties").attrib 198 | detailentities.append( 199 | CSXEntity( 200 | entity.get("id"), 201 | entity.get("classname"), 202 | [float(x) for x in entity.get("origin").split(" ")], 203 | entityprops, 204 | ) 205 | ) 206 | 207 | details.append(CSXDetail(detailbrushes, detailentities)) 208 | 209 | cscene = CSX(details) 210 | return cscene 211 | 212 | 213 | def create_material(filepath, matname): 214 | if "/" in matname: 215 | matname = matname.split("/")[1] 216 | prevmat = bpy.data.materials.find(matname) 217 | if prevmat != -1: 218 | return bpy.data.materials.get(matname) 219 | mat = bpy.data.materials.new(matname) 220 | mat.use_nodes = True 221 | 222 | texname = resolve_texture(filepath, matname) 223 | if texname is not None: 224 | try: 225 | teximg = bpy.data.images.load(texname) 226 | except: 227 | teximg = None 228 | print("Cannot load image", texname) 229 | 230 | texslot = mat.node_tree.nodes.new("ShaderNodeTexImage") 231 | texslot.name = matname 232 | texslot.image = teximg 233 | if bpy.app.version < (4, 0, 0): 234 | mat.node_tree.nodes["Principled BSDF"].inputs["Specular"].default_value = 0 235 | else: 236 | mat.node_tree.nodes["Principled BSDF"].inputs["Roughness"].default_value = 1.0 237 | mat.node_tree.links.new( 238 | mat.node_tree.nodes["Principled BSDF"].inputs["Base Color"], 239 | texslot.outputs["Color"], 240 | ) 241 | 242 | return mat 243 | 244 | 245 | # texsize: scale: factor 246 | # 16 : 4 : 1/4 247 | # 16 : 3 : 1 248 | # 16 : 2 : 4 249 | # 16 : 1 : 16 250 | 251 | 252 | def create_mesh(filepath, brush: CSXBrush): 253 | """ 254 | :param Interior interior: 255 | :return: 256 | """ 257 | me = bpy.data.meshes.new("Mesh") 258 | 259 | materials = list(set(x.material for x in brush.faces)) 260 | 261 | for mat in materials: 262 | me.materials.append(create_material(filepath, mat)) 263 | 264 | if bpy.app.version < (4, 0, 0): 265 | me.vertices.add(len(brush.vertices)) 266 | for i in range(0, len(brush.vertices)): 267 | me.vertices[i].co = brush.vertices[i] 268 | 269 | me.polygons.add(len(brush.faces)) 270 | tot_loops = 0 271 | for face in brush.faces: 272 | tot_loops += len(face.indices) 273 | 274 | me.loops.add(tot_loops) 275 | 276 | surface_uvs = {} 277 | cur_loop_idx = 0 278 | 279 | for (i, face) in enumerate(brush.faces): 280 | tex_gen = face.texgen 281 | 282 | normal = face.plane[:3] 283 | 284 | polygon = me.polygons[i] 285 | polygon.loop_start = cur_loop_idx 286 | polygon.loop_total = len(face.indices) 287 | cur_loop_idx += polygon.loop_total 288 | polygon.material_index = materials.index(face.material) 289 | 290 | for j, index in enumerate(face.indices): 291 | me.loops[j + polygon.loop_start].vertex_index = index 292 | me.loops[j + polygon.loop_start].normal = normal 293 | 294 | pt = brush.vertices[index] 295 | 296 | uv = tex_gen.compute_uv(pt, face.texSize) 297 | surface_uvs[j + polygon.loop_start] = uv 298 | 299 | me.uv_layers.new() 300 | uvs = me.uv_layers[0] 301 | 302 | for loop_idx in surface_uvs: 303 | uvs.data[loop_idx].uv = surface_uvs[loop_idx] 304 | else: 305 | verts = [] 306 | faces = [] 307 | face_texs = [] 308 | face_uvs = [] 309 | for vert in brush.vertices: 310 | verts.append(vert) 311 | 312 | for face in brush.faces: 313 | face_verts = [] 314 | for index in face.indices: 315 | face_verts.append(index) 316 | faces.append(face_verts) 317 | face_texs.append(face.material) 318 | uvs = [] 319 | for i in range(0, len(face.indices)): 320 | uvs.append(face.texgen.compute_uv(verts[face.indices[i]], face.texSize)) 321 | face_uvs.append(uvs) 322 | 323 | me.from_pydata(verts, [], faces) 324 | 325 | if not me.uv_layers: 326 | me.uv_layers.new() 327 | 328 | uv_layer = me.uv_layers.active.data 329 | 330 | for i, poly in enumerate(me.polygons): 331 | p: bpy.types.MeshPolygon = poly 332 | p.material_index = materials.index(face_texs[i]) 333 | 334 | for j, loop_index in enumerate(p.loop_indices): 335 | loop = me.loops[loop_index] 336 | uv_layer[loop.index].uv = face_uvs[i][j] 337 | 338 | me.validate() 339 | me.update() 340 | 341 | transformmat = mathutils.Matrix( 342 | [[brush.transform[4 * i + j] for j in range(0, 4)] for i in range(0, 4)] 343 | ) 344 | 345 | _, rotq, scale = transformmat.decompose() 346 | rot: mathutils.Quaternion = rotq 347 | 348 | newmat = mathutils.Matrix( 349 | ((scale.x, 0, 0, 0), (0, scale.y, 0, 0), (0, 0, scale.z, 0), (0, 0, 0, 1)) 350 | ) 351 | tmp = mathutils.Matrix.Rotation( 352 | rot.angle, 4, mathutils.Vector((rot.axis.x, rot.axis.y, rot.axis.z)) 353 | ) 354 | 355 | newmat = newmat @ tmp 356 | 357 | newmat[0][3] = transformmat[0][3] 358 | newmat[1][3] = transformmat[1][3] 359 | newmat[2][3] = transformmat[2][3] 360 | 361 | ob = bpy.data.objects.new("Object", me) 362 | ob.empty_display_type = "SINGLE_ARROW" 363 | ob.empty_display_size = 0.5 364 | # ob.matrix_world = [ 365 | # [brush.transform[4 * i + j] for j in range(0, 4)] for i in range(0, 4) 366 | # ] 367 | ob.matrix_world = newmat 368 | # ob.rotation_axis_angle = (rot.axis.x, rot.axis.y, rot.axis.z, rot.angle) 369 | # ob.rotation_mode = "AXIS_ANGLE" 370 | # ob.scale = (scale.x, scale.y, scale.z) 371 | # ob.location = [brush.transform[3], brush.transform[7], brush.transform[11]] 372 | 373 | return ob 374 | 375 | 376 | def load( 377 | context: bpy.types.Context, 378 | filepath, 379 | *, 380 | global_clamp_size=0.0, 381 | use_smooth_groups=True, 382 | use_edges=True, 383 | use_split_objects=True, 384 | use_split_groups=True, 385 | use_image_search=True, 386 | use_groups_as_vgroups=False, 387 | use_cycles=True, 388 | relpath=None, 389 | global_matrix=None 390 | ): 391 | """ 392 | Called by the user interface or another script. 393 | load_obj(path) - should give acceptable results. 394 | This function passes the file and sends the data off 395 | to be split into objects and then converted into mesh objects 396 | """ 397 | 398 | csscene = parse_csx(str(filepath)) 399 | 400 | if global_matrix is None: 401 | global_matrix = mathutils.Matrix() 402 | 403 | # deselect all 404 | if bpy.ops.object.select_all.poll(): 405 | bpy.ops.object.select_all(action="DESELECT") 406 | 407 | scene = context.scene 408 | new_objects: list[Object] = [] # put new objects here 409 | 410 | for detail in csscene.details: 411 | for brush in detail.brushes: 412 | new_objects.append(create_mesh(filepath, brush)) 413 | 414 | for ge in detail.entities: 415 | g: CSXEntity = ge 416 | gobj = bpy.data.objects.new(g.className, None) 417 | gobj.location = g.origin 418 | gobj.dif_props.interior_type = "game_entity" 419 | gobj.dif_props.game_entity_datablock = g.className 420 | gobj.dif_props.game_entity_gameclass = g.properties["game_class"] 421 | for key in g.properties: 422 | prop = gobj.dif_props.game_entity_properties.add() 423 | prop.key = key 424 | prop.value = g.properties.get(key) 425 | scene.collection.objects.link(gobj) 426 | 427 | # Create new obj 428 | for obj in new_objects: 429 | base = scene.collection.objects.link(obj) 430 | 431 | context.view_layer.update() 432 | 433 | axis_min = [1000000000] * 3 434 | axis_max = [-1000000000] * 3 435 | 436 | if global_clamp_size: 437 | # Get all object bounds 438 | for ob in new_objects: 439 | for v in ob.bound_box: 440 | for axis, value in enumerate(v): 441 | if axis_min[axis] > value: 442 | axis_min[axis] = value 443 | if axis_max[axis] < value: 444 | axis_max[axis] = value 445 | 446 | # Scale objects 447 | max_axis = max( 448 | axis_max[0] - axis_min[0], 449 | axis_max[1] - axis_min[1], 450 | axis_max[2] - axis_min[2], 451 | ) 452 | scale = 1.0 453 | 454 | while global_clamp_size < max_axis * scale: 455 | scale = scale / 10.0 456 | 457 | for obj in new_objects: 458 | obj.scale = scale, scale, scale 459 | 460 | # progress.leave_substeps("Done.") 461 | 462 | return {"FINISHED"} 463 | -------------------------------------------------------------------------------- /rust/difbuilder/src/lib.rs: -------------------------------------------------------------------------------- 1 | // C library 2 | 3 | use std::{ 4 | collections::HashMap, 5 | ffi::{c_char, CStr, CString}, 6 | sync::Arc, 7 | thread, 8 | time::Instant, 9 | }; 10 | 11 | use cgmath::Quaternion; 12 | use dif::{ 13 | dif::Dif, 14 | game_entity::GameEntity, 15 | interior::Interior, 16 | interior_path_follower::{InteriorPathFollower, WayPoint}, 17 | io::{Version, Writable}, 18 | trigger::{Polyhedron, PolyhedronEdge, Trigger}, 19 | types::{Dictionary, PlaneF, Point2F, Point3F}, 20 | }; 21 | use difbuilder::{ 22 | builder::{self, ProgressEventListener}, 23 | set_convert_configuration, 24 | }; 25 | use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; 26 | 27 | struct ConsoleProgressListener { 28 | thread_tx: Option>, 29 | listener_cb: unsafe extern "C" fn(bool, u32, u32, *const c_char, *const c_char), 30 | } 31 | 32 | impl ConsoleProgressListener { 33 | fn new( 34 | listener_cb: unsafe extern "C" fn(bool, u32, u32, *const c_char, *const c_char), 35 | ) -> Self { 36 | ConsoleProgressListener { 37 | thread_tx: None, 38 | listener_cb, 39 | } 40 | } 41 | fn init(&mut self) -> thread::JoinHandle<()> { 42 | let (sender, receiver) = std::sync::mpsc::channel(); 43 | self.thread_tx = Some(sender); 44 | let handler: thread::JoinHandle<_> = thread::spawn(move || { 45 | let progress_bar: MultiProgress = MultiProgress::new(); 46 | let mut progress_types: HashMap = HashMap::new(); 47 | loop { 48 | let (stop, current, total, status, finish_status): ( 49 | bool, 50 | u32, 51 | u32, 52 | String, 53 | String, 54 | ) = receiver.recv().unwrap(); 55 | if stop { 56 | break; 57 | } 58 | if total == 0 { 59 | progress_bar.println(status).unwrap(); 60 | progress_bar.clear().unwrap(); 61 | } else if let Some((bar, ref mut last_updated)) = progress_types.get_mut(&status) { 62 | let recvtime = std::time::Instant::now(); 63 | if recvtime.duration_since(*last_updated).as_millis() < 100 && total != current 64 | { 65 | continue; 66 | } 67 | *last_updated = recvtime; 68 | 69 | bar.set_length(total as u64); 70 | bar.set_position(current as u64); 71 | bar.set_message(status.clone()); 72 | if current == total { 73 | bar.finish_with_message(finish_status); 74 | // self.progress_types.remove(&status); 75 | } 76 | } else { 77 | let sty = 78 | ProgressStyle::with_template("{msg} {bar:40.cyan/blue} {pos:>7}/{len:7}") 79 | .unwrap(); 80 | let pbar = progress_bar.add(ProgressBar::new(total as u64)); 81 | pbar.set_style(sty); 82 | pbar.set_position(current as u64); 83 | pbar.set_message(status.clone()); 84 | progress_types.insert(status.clone(), (pbar, std::time::Instant::now())); 85 | } 86 | } 87 | }); 88 | handler 89 | } 90 | 91 | fn stop(&self) { 92 | unsafe { 93 | let stat = CString::new("").unwrap(); 94 | let fin = CString::new("").unwrap(); 95 | (self.listener_cb)(false, 0, 0, stat.as_ptr(), fin.as_ptr()); 96 | } 97 | self.thread_tx 98 | .as_ref() 99 | .unwrap() 100 | .send((true, 0, 0, "".to_owned(), "".to_owned())) 101 | .unwrap(); 102 | } 103 | } 104 | 105 | impl ProgressEventListener for ConsoleProgressListener { 106 | fn progress(&mut self, current: u32, total: u32, status: String, finish_status: String) { 107 | unsafe { 108 | let stat = CString::new(status.clone()).unwrap(); 109 | let fin = CString::new(finish_status.clone()).unwrap(); 110 | (self.listener_cb)(false, current, total, stat.as_ptr(), fin.as_ptr()); 111 | } 112 | self.thread_tx 113 | .as_ref() 114 | .unwrap() 115 | .send((false, current, total, status, finish_status)) 116 | .unwrap(); 117 | } 118 | } 119 | 120 | pub struct TriangleRaw { 121 | verts: [Point3F; 3], 122 | uv: [Point2F; 3], 123 | norm: Point3F, 124 | material: String, 125 | } 126 | 127 | pub struct PathedInteriorImpl { 128 | pub interior: Interior, 129 | pub waypoints: Vec, 130 | pub trigger_ids: Vec, 131 | pub properties: Dictionary, 132 | pub offset: Point3F, 133 | } 134 | 135 | pub struct DifBuilderImpl { 136 | pub triangles: Vec, 137 | pub pathed_interiors: Vec, 138 | } 139 | 140 | pub struct MarkerListImpl { 141 | pub markers: Vec, 142 | } 143 | 144 | pub struct TriggerIdListImpl { 145 | pub trigger_ids: Vec, 146 | } 147 | 148 | #[no_mangle] 149 | pub extern "C" fn new_difbuilder() -> *const DifBuilderImpl { 150 | Arc::into_raw(Arc::new(DifBuilderImpl { 151 | triangles: Vec::new(), 152 | pathed_interiors: Vec::new(), 153 | })) 154 | } 155 | 156 | #[no_mangle] 157 | pub unsafe extern "C" fn dispose_difbuilder(ptr: *const DifBuilderImpl) { 158 | Arc::decrement_strong_count(ptr); 159 | } 160 | 161 | #[no_mangle] 162 | pub unsafe extern "C" fn dispose_dif(ptr: *const Dif) { 163 | Arc::decrement_strong_count(ptr); 164 | } 165 | 166 | #[no_mangle] 167 | pub unsafe extern "C" fn new_dict() -> *const Dictionary { 168 | Arc::into_raw(Arc::new(Dictionary::new())) 169 | } 170 | 171 | #[no_mangle] 172 | pub unsafe extern "C" fn dispose_dict(ptr: *const Dictionary) { 173 | Arc::decrement_strong_count(ptr); 174 | } 175 | 176 | #[no_mangle] 177 | pub unsafe extern "C" fn add_dict_kvp( 178 | ptr: *mut Dictionary, 179 | key: *const c_char, 180 | value: *const c_char, 181 | ) { 182 | ptr.as_mut().unwrap().insert( 183 | CStr::from_ptr(key).to_str().unwrap().to_owned(), 184 | CStr::from_ptr(value).to_str().unwrap().to_owned(), 185 | ); 186 | } 187 | 188 | #[no_mangle] 189 | pub unsafe extern "C" fn new_marker_list() -> *const MarkerListImpl { 190 | Arc::into_raw(Arc::new(MarkerListImpl { 191 | markers: Vec::new(), 192 | })) 193 | } 194 | 195 | #[no_mangle] 196 | pub unsafe extern "C" fn dispose_marker_list(ptr: *const MarkerListImpl) { 197 | Arc::decrement_strong_count(ptr); 198 | } 199 | 200 | #[no_mangle] 201 | pub unsafe extern "C" fn push_marker( 202 | ptr: *mut MarkerListImpl, 203 | pos: *const f32, 204 | ms_to_next: i32, 205 | smoothing_type: u32, 206 | ) { 207 | ptr.as_mut().unwrap().markers.push(WayPoint { 208 | ms_to_next: ms_to_next as u32, 209 | position: Point3F::new(*pos, *pos.offset(1), *pos.offset(2)), 210 | smoothing_type: smoothing_type, 211 | rotation: Quaternion::new(1.0, 0.0, 0.0, 0.0), 212 | }); 213 | } 214 | 215 | #[no_mangle] 216 | pub extern "C" fn new_trigger_id_list() -> *const TriggerIdListImpl { 217 | Arc::into_raw(Arc::new(TriggerIdListImpl { 218 | trigger_ids: Vec::new(), 219 | })) 220 | } 221 | 222 | #[no_mangle] 223 | pub unsafe extern "C" fn dispose_trigger_id_list(ptr: *const TriggerIdListImpl) { 224 | Arc::decrement_strong_count(ptr); 225 | } 226 | 227 | #[no_mangle] 228 | pub unsafe extern "C" fn push_trigger_id(ptr: *mut TriggerIdListImpl, trigger_id: u32) { 229 | ptr.as_mut().unwrap().trigger_ids.push(trigger_id); 230 | } 231 | 232 | #[no_mangle] 233 | pub unsafe extern "C" fn add_game_entity( 234 | ptr: *mut Dif, 235 | game_class: *const c_char, 236 | datablock: *const c_char, 237 | pos: *const f32, 238 | dict: *const Dictionary, 239 | ) { 240 | ptr.as_mut().unwrap().game_entities.push(GameEntity { 241 | datablock: CStr::from_ptr(datablock).to_str().unwrap().to_owned(), 242 | game_class: CStr::from_ptr(game_class).to_str().unwrap().to_owned(), 243 | position: Point3F::new(*pos, *pos.offset(1), *pos.offset(2)), 244 | properties: dict.as_ref().unwrap().clone(), 245 | }); 246 | } 247 | 248 | #[no_mangle] 249 | pub unsafe extern "C" fn add_triangle( 250 | ptr: *mut DifBuilderImpl, 251 | p1: *const f32, 252 | p2: *const f32, 253 | p3: *const f32, 254 | uv1: *const f32, 255 | uv2: *const f32, 256 | uv3: *const f32, 257 | normal: *const f32, 258 | material: *const c_char, 259 | ) { 260 | ptr.as_mut().unwrap().triangles.push(TriangleRaw { 261 | verts: [ 262 | Point3F::new(*p1, *p1.offset(1), *p1.offset(2)), 263 | Point3F::new(*p2, *p2.offset(1), *p2.offset(2)), 264 | Point3F::new(*p3, *p3.offset(1), *p3.offset(2)), 265 | ], 266 | uv: [ 267 | Point2F::new(*uv1, *uv1.offset(1)), 268 | Point2F::new(*uv2, *uv2.offset(1)), 269 | Point2F::new(*uv3, *uv3.offset(1)), 270 | ], 271 | material: CStr::from_ptr(material).to_str().unwrap().to_owned(), 272 | norm: Point3F::new(*normal, *normal.offset(1), *normal.offset(2)), 273 | }); 274 | } 275 | 276 | #[no_mangle] 277 | pub unsafe extern "C" fn add_trigger( 278 | ptr: *mut Dif, 279 | pos_vec: *const f32, 280 | size_vec: *const f32, 281 | name: *const c_char, 282 | datablock: *const c_char, 283 | props: *const Dictionary, 284 | ) { 285 | let pos = Point3F::new(*pos_vec, *pos_vec.offset(1), *pos_vec.offset(2)); 286 | let size = Point3F::new(*size_vec, *size_vec.offset(1), *size_vec.offset(2)); 287 | ptr.as_mut().unwrap().triggers.push(Trigger { 288 | name: CStr::from_ptr(name).to_str().unwrap().to_owned(), 289 | datablock: CStr::from_ptr(datablock).to_str().unwrap().to_owned(), 290 | offset: Point3F::new(0.0, 0.0, 0.0), 291 | properties: props.as_ref().unwrap().clone(), 292 | polyhedron: Polyhedron { 293 | point_list: vec![ 294 | Point3F::new(pos.x, pos.y, pos.z + size.z), 295 | Point3F::new(pos.x, pos.y + size.y, pos.z + size.z), 296 | Point3F::new(pos.x + size.x, pos.y + size.y, pos.z + size.z), 297 | Point3F::new(pos.x + size.x, pos.y, pos.z + size.z), 298 | Point3F::new(pos.x, pos.y, pos.z), 299 | Point3F::new(pos.x, pos.y + size.y, pos.z), 300 | Point3F::new(pos.x + size.x, pos.y + size.y, pos.z), 301 | Point3F::new(pos.x + size.x, pos.y, pos.z), 302 | ], 303 | plane_list: vec![ 304 | PlaneF { 305 | normal: Point3F::new(-1.0, 0.0, 0.0), 306 | distance: pos.x, 307 | }, 308 | PlaneF { 309 | normal: Point3F::new(0.0, 1.0, 0.0), 310 | distance: pos.y + size.y, 311 | }, 312 | PlaneF { 313 | normal: Point3F::new(1.0, 0.0, 0.0), 314 | distance: pos.x + size.x, 315 | }, 316 | PlaneF { 317 | normal: Point3F::new(0.0, -1.0, 0.0), 318 | distance: pos.y, 319 | }, 320 | PlaneF { 321 | normal: Point3F::new(0.0, 0.0, 1.0), 322 | distance: pos.z + size.z, 323 | }, 324 | PlaneF { 325 | normal: Point3F::new(0.0, 0.0, -1.0), 326 | distance: pos.z, 327 | }, 328 | ], 329 | edge_list: vec![ 330 | PolyhedronEdge { 331 | face0: 0, 332 | face1: 4, 333 | vertex0: 0, 334 | vertex1: 1, 335 | }, 336 | PolyhedronEdge { 337 | face0: 5, 338 | face1: 0, 339 | vertex0: 4, 340 | vertex1: 5, 341 | }, 342 | PolyhedronEdge { 343 | face0: 3, 344 | face1: 0, 345 | vertex0: 0, 346 | vertex1: 4, 347 | }, 348 | PolyhedronEdge { 349 | face0: 1, 350 | face1: 4, 351 | vertex0: 1, 352 | vertex1: 2, 353 | }, 354 | PolyhedronEdge { 355 | face0: 5, 356 | face1: 6, 357 | vertex0: 5, 358 | vertex1: 1, 359 | }, 360 | PolyhedronEdge { 361 | face0: 0, 362 | face1: 1, 363 | vertex0: 1, 364 | vertex1: 5, 365 | }, 366 | PolyhedronEdge { 367 | face0: 2, 368 | face1: 4, 369 | vertex0: 2, 370 | vertex1: 3, 371 | }, 372 | PolyhedronEdge { 373 | face0: 5, 374 | face1: 2, 375 | vertex0: 6, 376 | vertex1: 7, 377 | }, 378 | PolyhedronEdge { 379 | face0: 1, 380 | face1: 2, 381 | vertex0: 2, 382 | vertex1: 6, 383 | }, 384 | PolyhedronEdge { 385 | face0: 3, 386 | face1: 4, 387 | vertex0: 3, 388 | vertex1: 0, 389 | }, 390 | PolyhedronEdge { 391 | face0: 5, 392 | face1: 3, 393 | vertex0: 7, 394 | vertex1: 4, 395 | }, 396 | PolyhedronEdge { 397 | face0: 2, 398 | face1: 3, 399 | vertex0: 3, 400 | vertex1: 7, 401 | }, 402 | ], 403 | }, 404 | }); 405 | } 406 | 407 | #[no_mangle] 408 | pub unsafe extern "C" fn add_pathed_interior( 409 | ptr: *mut DifBuilderImpl, 410 | dif: *mut Dif, 411 | marker_list: *const MarkerListImpl, 412 | trigger_id_list: *const TriggerIdListImpl, 413 | props: *const Dictionary, 414 | offset: *const f32, 415 | ) { 416 | let pathed_interior = PathedInteriorImpl { 417 | interior: dif.as_mut().unwrap().interiors.swap_remove(0), 418 | waypoints: marker_list.as_ref().unwrap().markers.clone(), 419 | trigger_ids: trigger_id_list.as_ref().unwrap().trigger_ids.clone(), 420 | properties: props.as_ref().unwrap().clone(), 421 | offset: Point3F::new(*offset, *offset.offset(1), *offset.offset(2)), 422 | }; 423 | ptr.as_mut().unwrap().pathed_interiors.push(pathed_interior); 424 | } 425 | 426 | #[no_mangle] 427 | pub unsafe extern "C" fn build( 428 | ptr: *mut DifBuilderImpl, 429 | mb_only: bool, 430 | bsp_mode: i32, 431 | point_epsilon: f32, 432 | plane_epsilon: f32, 433 | split_epsilon: f32, 434 | listener_cb: unsafe extern "C" fn(bool, u32, u32, *const c_char, *const c_char), 435 | ) -> *const Dif { 436 | let mut listener = ConsoleProgressListener::new(listener_cb); 437 | let join_handler = listener.init(); 438 | 439 | set_convert_configuration( 440 | mb_only, 441 | point_epsilon, 442 | plane_epsilon, 443 | split_epsilon, 444 | match bsp_mode { 445 | 0 => difbuilder::bsp::SplitMethod::Fast, 446 | 1 => difbuilder::bsp::SplitMethod::Exhaustive, 447 | _ => difbuilder::bsp::SplitMethod::None, 448 | }, 449 | ); 450 | 451 | let mut actual_builder = builder::DIFBuilder::new(true); 452 | for tri in ptr.as_ref().unwrap().triangles.iter() { 453 | actual_builder.add_triangle( 454 | tri.verts[0], 455 | tri.verts[1], 456 | tri.verts[2], 457 | tri.uv[0], 458 | tri.uv[1], 459 | tri.uv[2], 460 | tri.norm, 461 | tri.material.clone(), 462 | ); 463 | } 464 | 465 | let (itr, r) = actual_builder.build(&mut listener); 466 | 467 | listener.stop(); 468 | join_handler.join().unwrap(); 469 | // Write the report 470 | println!("BSP Report"); 471 | println!( 472 | "Raycast Coverage: {}/{} ({}% of surface area), RMSE: {}, MAE: {}, Maximum Error: {}", 473 | r.hit, r.total, r.hit_area_percentage, r.rmse, r.mae, r.max_err 474 | ); 475 | println!("Balance Factor: {}", r.balance_factor); 476 | 477 | let mut dif = dif_with_interiors(vec![itr]); 478 | // Add the pathed interiors 479 | for pathed_interior in ptr.as_ref().unwrap().pathed_interiors.iter() { 480 | let sub_index = dif.sub_objects.len(); 481 | dif.sub_objects.push(pathed_interior.interior.clone()); 482 | dif.interior_path_followers.push(InteriorPathFollower { 483 | datablock: "PathedDefault".to_owned(), 484 | interior_res_index: sub_index as u32, 485 | name: "MustChange".to_owned(), 486 | offset: pathed_interior.offset, 487 | total_ms: pathed_interior 488 | .waypoints 489 | .iter() 490 | .map(|wp| wp.ms_to_next) 491 | .sum(), 492 | way_points: pathed_interior.waypoints.clone(), 493 | trigger_ids: pathed_interior.trigger_ids.clone(), 494 | properties: pathed_interior.properties.clone(), 495 | }); 496 | } 497 | 498 | Arc::into_raw(Arc::new(dif)) 499 | } 500 | 501 | #[no_mangle] 502 | pub unsafe extern "C" fn write_dif(dif: *const Dif, path: *const c_char) { 503 | let version = Version { 504 | engine: dif::io::EngineVersion::MBG, 505 | dif: 44, 506 | interior: 0, 507 | material_list: 1, 508 | vehicle_collision: 0, 509 | force_field: 0, 510 | }; 511 | let mut buf = vec![]; 512 | dif.as_ref().unwrap().write(&mut buf, &version).unwrap(); 513 | let path = CStr::from_ptr(path).to_str().unwrap(); 514 | std::fs::write(path, buf).unwrap(); 515 | } 516 | 517 | pub fn dif_with_interiors(interiors: Vec) -> Dif { 518 | Dif { 519 | interiors, 520 | sub_objects: vec![], 521 | triggers: vec![], 522 | interior_path_followers: vec![], 523 | force_fields: vec![], 524 | ai_special_nodes: vec![], 525 | vehicle_collision: None, 526 | game_entities: vec![], 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /blender_plugin/io_dif/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | if "bpy" in locals(): 15 | import importlib 16 | 17 | if "export_dif" in locals(): 18 | importlib.reload(export_dif) 19 | if "import_dif" in locals(): 20 | importlib.reload(import_dif) 21 | 22 | if "import_csx" in locals(): 23 | importlib.reload(import_csx) 24 | 25 | import os 26 | import platform 27 | import bpy 28 | import threading 29 | from bpy.props import ( 30 | BoolProperty, 31 | CollectionProperty, 32 | FloatProperty, 33 | IntProperty, 34 | StringProperty, 35 | EnumProperty, 36 | PointerProperty, 37 | ) 38 | from bpy_extras.io_utils import ( 39 | ImportHelper, 40 | ExportHelper, 41 | ) 42 | 43 | bl_info = { 44 | "name": "Torque DIF", 45 | "author": "RandomityGuy", 46 | "description": "Dif import and export plugin for blender", 47 | "blender": (2, 80, 0), 48 | "version": (1, 3, 4), 49 | "location": "File > Import-Export", 50 | "warning": "", 51 | "category": "Import-Export", 52 | } 53 | 54 | 55 | class InteriorKVP(bpy.types.PropertyGroup): 56 | key: StringProperty(name="") 57 | value: StringProperty(name="") 58 | 59 | 60 | class AddCustomProperty(bpy.types.Operator): 61 | bl_idname = "dif.add_prop" 62 | bl_label = "Add Property" 63 | 64 | def execute(self, context): 65 | dif_props: InteriorSettings = context.object.dif_props 66 | prop = dif_props.game_entity_properties.add() 67 | prop.key = "Key" 68 | prop.value = "Value" 69 | return {"FINISHED"} 70 | 71 | 72 | class DeleteCustomProperty(bpy.types.Operator): 73 | bl_idname = "dif.delete_prop" 74 | bl_label = "Delete Property" 75 | 76 | delete_id: IntProperty() 77 | 78 | def execute(self, context): 79 | dif_props: InteriorSettings = context.object.dif_props 80 | prop = dif_props.game_entity_properties.remove(self.delete_id) 81 | return {"FINISHED"} 82 | 83 | 84 | def set_marker_path(self, context): 85 | curve = self.marker_path 86 | if curve: 87 | for spline in curve.splines: 88 | spline.type = 'POLY' 89 | 90 | class InteriorSettings(bpy.types.PropertyGroup): 91 | # Interiors 92 | interior_type: EnumProperty( 93 | name="Interior Entity Type", 94 | items=( 95 | ("static_interior", "Interior Resource", "Normal static interior"), 96 | ("pathed_interior", "Pathed Interior", "Moving interior"), 97 | ("game_entity", "Game Entity", "A game object"), 98 | ("path_trigger", "Path Trigger", "A trigger for a pathed interior"), 99 | ), 100 | default="static_interior", 101 | description="How this object should be interpreted for the exporter.", 102 | ) 103 | marker_path: PointerProperty(type=bpy.types.Curve, name="Marker Path", description="The path to create markers from.", update=set_marker_path) 104 | constant_speed: BoolProperty(name = "Constant Speed", description = "If the marker durations should be based on speed instead of total time.", default=True) 105 | speed: FloatProperty(name="Speed", description="The speed that the platform should be moving at. If using Accelerate smoothing, this is max speed.", default=1, min=0.01, max=100) 106 | total_time: IntProperty(name="Total Time", description="The total time (in ms) from path start to end. Equally divided across each marker on export.", default=3000, min=1) 107 | start_time: IntProperty(name="Start Time", description="The time in the path (in ms) that the platform should be at level restart.", default=0, min=0) 108 | start_index: IntProperty(name="Start Index", description="The marker that the platform should be at level restart (0 is 1st marker).", default=0, min=0, soft_max=10) 109 | pause_duration: IntProperty(name = "Pause Duration", description="At a path segment of length 0, the platform will wait this long (in ms).", default=0, min=0, soft_max=10000) 110 | reverse: BoolProperty(name = "Reverse", description = "If the platform should loop backwards (if not using a trigger).") 111 | 112 | marker_type: EnumProperty( 113 | name="Marker Type", 114 | items=( 115 | ("linear", "Linear", "Linear interpolation"), 116 | ("spline", "Spline", "Centripetal Catmull–Rom path"), 117 | ("accelerate", "Accelerate", "Sinusoidal easing"), 118 | ), 119 | description="The type of smoothing that should be applied to all markers exported from the path.", 120 | ) 121 | 122 | # Triggers 123 | pathed_interior_target: PointerProperty(type=bpy.types.Object, name="Pathed Interior Target", description="The platform to trigger.") 124 | target_marker: BoolProperty(name = "Calculate Target Time", description="If enabled, the targetTime will be calculated to be at a specific marker.", default=True) 125 | target_index: IntProperty(name = "Target Index", description="The marker to target (0 is 1st marker).", default=0, min=0, soft_max=10) 126 | 127 | # Entities 128 | game_entity_datablock: StringProperty(name="Datablock") 129 | game_entity_gameclass: StringProperty(name="Game Class") 130 | game_entity_properties: CollectionProperty( 131 | type=InteriorKVP, name="Custom Properties" 132 | ) 133 | 134 | 135 | class InteriorPanel(bpy.types.Panel): 136 | bl_label = "DIF properties" 137 | bl_idname = "dif_properties" 138 | bl_space_type = "PROPERTIES" 139 | bl_region_type = "WINDOW" 140 | bl_context = "object" 141 | 142 | def draw(self, context): 143 | layout = self.layout 144 | sublayout = layout.row() 145 | sublayout.prop(context.object.dif_props, "interior_type") #TODO only show this on relevant objects? 146 | 147 | if(isinstance(context.object, bpy.types.Curve)): 148 | sublayout = layout.row() 149 | sublayout.prop(context.object.dif_props, "marker_type") 150 | 151 | if context.object.dif_props.interior_type == "pathed_interior": 152 | sublayout = layout.row() 153 | sublayout.prop(context.object.dif_props, "marker_path") 154 | sublayout = layout.row() 155 | sublayout.prop(context.object.dif_props, "marker_type") 156 | sublayout = layout.row() 157 | sublayout.prop(context.object.dif_props, "constant_speed") 158 | sublayout = layout.row() 159 | if context.object.dif_props.constant_speed: 160 | sublayout.prop(context.object.dif_props, "speed") 161 | sublayout = layout.row() 162 | sublayout.prop(context.object.dif_props, "start_index") 163 | sublayout = layout.row() 164 | sublayout.prop(context.object.dif_props, "pause_duration") 165 | sublayout = layout.row() 166 | else: 167 | sublayout.prop(context.object.dif_props, "total_time") 168 | sublayout = layout.row() 169 | sublayout.prop(context.object.dif_props, "start_time") 170 | sublayout = layout.row() 171 | 172 | sublayout.prop(context.object.dif_props, "reverse") 173 | 174 | if context.object.dif_props.interior_type in ["game_entity", "path_trigger"]: 175 | sublayout = layout.row() 176 | sublayout.prop(context.object.dif_props, "game_entity_datablock") 177 | sublayout = layout.row() 178 | if context.object.dif_props.interior_type == "path_trigger": 179 | sublayout.prop(context.object.dif_props, "pathed_interior_target") 180 | sublayout = layout.row() 181 | sublayout.prop(context.object.dif_props, "target_marker") 182 | sublayout = layout.row() 183 | if context.object.dif_props.target_marker: 184 | sublayout.prop(context.object.dif_props, "target_index") 185 | sublayout = layout.row() 186 | else: 187 | sublayout.prop(context.object.dif_props, "game_entity_gameclass") 188 | sublayout = layout.row() 189 | sublayout.label(text="Properties:") 190 | sublayout = layout.row() 191 | sublayout.operator(AddCustomProperty.bl_idname, text="Add Property") 192 | for i, custom_property in enumerate( 193 | context.object.dif_props.game_entity_properties 194 | ): 195 | sublayout = layout.row() 196 | sublayout.prop( 197 | context.object.dif_props.game_entity_properties[i], "key" 198 | ) 199 | sublayout.prop( 200 | context.object.dif_props.game_entity_properties[i], "value" 201 | ) 202 | sublayout.operator( 203 | DeleteCustomProperty.bl_idname, icon="X", text="" 204 | ).delete_id = i 205 | 206 | 207 | class ImportCSX(bpy.types.Operator, ImportHelper): 208 | """Load a Torque Constructor CSX File""" 209 | 210 | bl_idname = "import_scene.csx" 211 | bl_label = "Import Constructor CSX" 212 | bl_options = {"PRESET"} 213 | 214 | filename_ext = ".csx" 215 | filter_glob: StringProperty( 216 | default="*.csx", 217 | options={"HIDDEN"}, 218 | ) 219 | 220 | check_extension = True 221 | 222 | def execute(self, context): 223 | # print("Selected: " + context.active_object.name) 224 | from . import import_csx 225 | 226 | keywords = self.as_keywords( 227 | ignore=( 228 | "axis_forward", 229 | "axis_up", 230 | "filter_glob", 231 | ) 232 | ) 233 | 234 | if bpy.data.is_saved and context.preferences.filepaths.use_relative_paths: 235 | import os 236 | 237 | keywords["relpath"] = os.path.dirname(bpy.data.filepath) 238 | 239 | return import_csx.load(context, **keywords) 240 | 241 | def draw(self, context): 242 | pass 243 | 244 | 245 | class ImportDIF(bpy.types.Operator, ImportHelper): 246 | """Load a Torque DIF File""" 247 | 248 | bl_idname = "import_scene.dif" 249 | bl_label = "Import DIF" 250 | bl_options = {"PRESET"} 251 | 252 | filename_ext = ".dif" 253 | filter_glob: StringProperty( 254 | default="*.dif", 255 | options={"HIDDEN"}, 256 | ) 257 | 258 | check_extension = True 259 | 260 | def execute(self, context): 261 | # print("Selected: " + context.active_object.name) 262 | from . import import_dif 263 | 264 | keywords = self.as_keywords( 265 | ignore=( 266 | "axis_forward", 267 | "axis_up", 268 | "filter_glob", 269 | ) 270 | ) 271 | 272 | if bpy.data.is_saved and context.preferences.filepaths.use_relative_paths: 273 | import os 274 | 275 | keywords["relpath"] = os.path.dirname(bpy.data.filepath) 276 | 277 | return import_dif.load(context, **keywords) 278 | 279 | def draw(self, context): 280 | pass 281 | 282 | 283 | class ExportDIF(bpy.types.Operator, ExportHelper): 284 | """Save a Torque DIF File""" 285 | 286 | bl_idname = "export_scene.dif" 287 | bl_label = "Export DIF" 288 | bl_options = {"PRESET"} 289 | 290 | filename_ext = ".dif" 291 | filter_glob: StringProperty( 292 | default="*.dif", 293 | options={"HIDDEN"}, 294 | ) 295 | 296 | flip: BoolProperty( 297 | name="Flip faces", 298 | description="Flip normals of the faces, in case the resultant dif is inside out.", 299 | default=False, 300 | ) 301 | 302 | double: BoolProperty( 303 | name="Double faces", 304 | description="Make all the faces double sided, may cause lag during collision detection.", 305 | default=False, 306 | ) 307 | 308 | maxpolys: IntProperty( 309 | name="Polygons per DIF", 310 | description="Maximum number of polygons till a dif split is done", 311 | default=12000, 312 | min=1, 313 | max=12000, 314 | ) 315 | 316 | applymodifiers: BoolProperty( 317 | name="Apply Modifiers", 318 | description="Apply modifiers during export", 319 | default=True, 320 | ) 321 | 322 | exportvisible: BoolProperty( 323 | name="Export Visible", 324 | description="Export only visible geometry", 325 | default=True, 326 | ) 327 | 328 | exportselected: BoolProperty( 329 | name="Export Selected", 330 | description="Export only selected geometry", 331 | default=False, 332 | ) 333 | 334 | usematnames: BoolProperty( 335 | name="Use Material Names", 336 | description="Use material names instead of material texture file names", 337 | default=True, 338 | ) 339 | 340 | mbonly: BoolProperty( 341 | name="Optimize for Marble Blast", 342 | description="Make the resultant DIF optimized for Marble Blast. Uncheck this if you want to use this for other Torque games.", 343 | default=True 344 | ) 345 | 346 | bspmode: EnumProperty( 347 | items=[ 348 | ("Fast", "Fast", "Use a sampling algorithm to determine the best splitter."), 349 | ("Exhaustive", "Exhaustive", "Use an exhaustive search algorithm to determine the best splitter. May take longer but builds more balanced trees."), 350 | ("None", "None", "Do not build a BSP Tree, utilize this for fast conversion times or if building the BSP Tree fails.") 351 | ], 352 | name="BSP Algorithm", 353 | description="The algorithm used for building the BSP Tree of the DIF.", 354 | default="Fast" 355 | ) 356 | 357 | pointepsilon: FloatProperty( 358 | name="Point Epsilon", 359 | description="Minimum distance between two points to be considered equal.", 360 | default=1e-6 361 | ) 362 | 363 | planeepsilon: FloatProperty( 364 | name="Plane Epsilon", 365 | description="Minimum difference between values of two plane to be considered equal.", 366 | default=1e-5 367 | ) 368 | 369 | splitepsilon: FloatProperty( 370 | name="Split Epsilon", 371 | description="Minimum difference between values of two splitting planes to be considered equal.", 372 | default=1e-4 373 | ) 374 | 375 | check_extension = True 376 | 377 | # def draw(self, context): 378 | # layout = self.layout 379 | # layout.prop(self, "flip") 380 | # layout.prop(self, "double") 381 | # layout.prop(self, "maxpolys") 382 | # layout.prop(self, "applymodifiers") 383 | # layout.prop(self, "exportvisible") 384 | # layout.prop(self, "exportselected") 385 | # layout.prop(self, "usematnames") 386 | # layout.prop(self, "mbonly") 387 | # layout.prop(self, "bspmode") 388 | # layout.prop(self, "pointepsilon") 389 | # layout.prop(self, "planeepsilon") 390 | # layout.prop(self, "splitepsilon") 391 | # layout.label(text="BSP Algorithms:") 392 | # layout.label(text="Fast: Default mode, uses a sampling algorithm to determine the best splitter.") 393 | # layout.label(text="Exhaustive: Tries to find the most optimal splits for BSP Tree. May take longer but it is deterministic.") 394 | # layout.label(text="None: Do not generate BSP Tree") 395 | # layout.label(text="If your geometry is too complex, consider using None mode as there is no guarantee that a BSP Tree can be optimally built within set constraints.") 396 | # layout.label(text="BSP Trees are only used for Raycasts and Drop To Ground feature in Marble Blast. If you are not using these features, you can safely disable the BSP Tree.") 397 | 398 | def execute(self, context): 399 | from . import export_dif 400 | if bpy.app.version >= (4, 0, 0): 401 | bpy.types.VIEW3D_HT_header.append(progress_bar) 402 | keywords = self.as_keywords(ignore=("check_existing", "filter_glob")) 403 | export_dif.save( 404 | context, 405 | keywords["filepath"], 406 | keywords.get("flip", False), 407 | keywords.get("double", False), 408 | keywords.get("maxpolys", 12000), 409 | keywords.get("applymodifiers", True), 410 | keywords.get("exportvisible", True), 411 | keywords.get("exportselected", False), 412 | keywords.get("usematnames", False), 413 | keywords.get("mbonly", True), 414 | keywords.get("bspmode", "Fast"), 415 | keywords.get("pointepsilon", 1e-6), 416 | keywords.get("planeepsilon", 1e-5), 417 | keywords.get("splitepsilon", 1e-4), 418 | ) 419 | stop_progress() 420 | 421 | return {"FINISHED"} 422 | 423 | classes = (ExportDIF, ImportDIF, ImportCSX) 424 | 425 | 426 | def menu_func_export_dif(self, context): 427 | self.layout.operator(ExportDIF.bl_idname, text="Torque (.dif)") 428 | 429 | 430 | def menu_func_import_dif(self, context): 431 | self.layout.operator(ImportDIF.bl_idname, text="Torque (.dif)") 432 | 433 | 434 | def menu_func_import_csx(self, context): 435 | self.layout.operator(ImportCSX.bl_idname, text="Torque Constructor (.csx)") 436 | 437 | 438 | def progress_bar(self, context): 439 | row = self.layout.row() 440 | if bpy.app.version >= (4, 0, 0): 441 | row.progress( 442 | factor=progress_bar.progress, 443 | type="BAR", 444 | text=progress_bar.progress_text 445 | ) 446 | row.scale_x = 2 447 | 448 | def set_progress(progress, progress_text): 449 | delta = progress - progress_bar.progress 450 | if abs(delta) >= 0.1: 451 | progress_bar.progress = progress 452 | progress_bar.progress_text = progress_text 453 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 454 | 455 | def stop_progress(): 456 | if bpy.app.version >= (4, 0, 0): 457 | bpy.types.VIEW3D_HT_header.remove(progress_bar) 458 | 459 | progress_bar.progress = 0 460 | progress_bar.progress_text = "" 461 | 462 | def register(): 463 | for cls in classes: 464 | bpy.utils.register_class(cls) 465 | bpy.types.TOPBAR_MT_file_export.append(menu_func_export_dif) 466 | bpy.types.TOPBAR_MT_file_import.append(menu_func_import_dif) 467 | bpy.types.TOPBAR_MT_file_import.append(menu_func_import_csx) 468 | # bpy.types.STATUSBAR_HT_header.append(progress_bar) 469 | bpy.utils.register_class(InteriorPanel) 470 | bpy.utils.register_class(AddCustomProperty) 471 | bpy.utils.register_class(DeleteCustomProperty) 472 | bpy.utils.register_class(InteriorKVP) 473 | bpy.utils.register_class(InteriorSettings) 474 | 475 | if platform.system() == "Windows": 476 | dllpath = os.path.join( 477 | os.path.dirname(os.path.realpath(__file__)), "DifBuilderLib.dll" 478 | ) 479 | elif platform.system() == "Darwin": 480 | dllpath = os.path.join( 481 | os.path.dirname(os.path.realpath(__file__)), "DifBuilderLib.dylib" 482 | ) 483 | elif platform.system() == "Linux": 484 | dllpath = os.path.join( 485 | os.path.dirname(os.path.realpath(__file__)), "DifBuilderLib.so" 486 | ) 487 | if not os.path.isfile(dllpath): 488 | raise Exception( 489 | "There was an error loading the necessary dll required for dif export. Please download the plugin from the proper location: https://github.com/RandomityGuy/io_dif/releases" 490 | ) 491 | 492 | bpy.types.Object.dif_props = PointerProperty(type=InteriorSettings) 493 | 494 | 495 | def unregister(): 496 | for cls in classes: 497 | bpy.utils.unregister_class(cls) 498 | bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_dif) 499 | bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_dif) 500 | bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_csx) 501 | bpy.utils.unregister_class(InteriorPanel) 502 | bpy.utils.unregister_class(AddCustomProperty) 503 | bpy.utils.unregister_class(DeleteCustomProperty) 504 | bpy.utils.unregister_class(InteriorKVP) 505 | bpy.utils.unregister_class(InteriorSettings) 506 | 507 | del bpy.types.Object.dif_props 508 | 509 | 510 | if __name__ == "__main__": 511 | register() 512 | -------------------------------------------------------------------------------- /blender_plugin/io_dif/export_dif.py: -------------------------------------------------------------------------------- 1 | from os.path import join 2 | import re 3 | from typing import Dict 4 | from . import set_progress, stop_progress 5 | import bpy 6 | import ctypes 7 | import os 8 | import platform 9 | from pathlib import Path 10 | 11 | from bpy.types import Curve, Image, Material, Mesh, Object, ShaderNodeTexImage 12 | from bpy_extras.wm_utils.progress_report import ProgressReport, ProgressReportSubstep 13 | from mathutils import Quaternion, Vector, Matrix 14 | 15 | if platform.system() == "Windows": 16 | dllpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "DifBuilderLib.dll") 17 | elif platform.system() == "Darwin": 18 | dllpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "DifBuilderLib.dylib") 19 | elif platform.system() == "Linux": 20 | dllpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "DifBuilderLib.so") 21 | difbuilderlib = None 22 | try: 23 | difbuilderlib = ctypes.CDLL(dllpath) 24 | except: 25 | raise Exception( 26 | "There was an error loading the necessary dll required for dif export. Please download the plugin from the proper location: https://github.com/RandomityGuy/io_dif/releases" 27 | ) 28 | 29 | STATUSFN = ctypes.CFUNCTYPE(None, ctypes.c_bool, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_char_p, ctypes.c_char_p) 30 | 31 | difbuilderlib.new_difbuilder.restype = ctypes.c_void_p 32 | difbuilderlib.dispose_difbuilder.argtypes = [ctypes.c_void_p] 33 | difbuilderlib.add_triangle.argtypes = [ 34 | ctypes.c_void_p, 35 | ctypes.POINTER(ctypes.c_float), 36 | ctypes.POINTER(ctypes.c_float), 37 | ctypes.POINTER(ctypes.c_float), 38 | ctypes.POINTER(ctypes.c_float), 39 | ctypes.POINTER(ctypes.c_float), 40 | ctypes.POINTER(ctypes.c_float), 41 | ctypes.POINTER(ctypes.c_float), 42 | ctypes.c_char_p, 43 | ] 44 | difbuilderlib.build.argtypes = [ctypes.c_void_p, ctypes.c_bool, ctypes.c_int32, ctypes.c_float, ctypes.c_float, ctypes.c_float, STATUSFN] 45 | difbuilderlib.build.restype = ctypes.c_void_p 46 | 47 | difbuilderlib.dispose_dif.argtypes = [ctypes.c_void_p] 48 | difbuilderlib.write_dif.argtypes = [ctypes.c_void_p, ctypes.c_char_p] 49 | 50 | difbuilderlib.add_pathed_interior.argtypes = [ 51 | ctypes.c_void_p, 52 | ctypes.c_void_p, 53 | ctypes.c_void_p, 54 | ctypes.c_void_p, 55 | ctypes.c_void_p, 56 | ctypes.POINTER(ctypes.c_float), 57 | ] 58 | 59 | difbuilderlib.new_marker_list.restype = ctypes.c_void_p 60 | difbuilderlib.dispose_marker_list.argtypes = [ctypes.c_void_p] 61 | difbuilderlib.push_marker.argtypes = [ 62 | ctypes.c_void_p, 63 | ctypes.POINTER(ctypes.c_float), 64 | ctypes.c_int, 65 | ctypes.c_int, 66 | ] 67 | difbuilderlib.new_trigger_id_list.restype = ctypes.c_void_p 68 | difbuilderlib.dispose_trigger_id_list.argtypes = [ctypes.c_void_p] 69 | difbuilderlib.push_trigger_id.argtypes = [ 70 | ctypes.c_void_p, 71 | ctypes.c_int, 72 | ] 73 | 74 | difbuilderlib.add_game_entity.argtypes = [ 75 | ctypes.c_void_p, 76 | ctypes.c_char_p, 77 | ctypes.c_char_p, 78 | ctypes.POINTER(ctypes.c_float), 79 | ctypes.c_void_p, 80 | ] 81 | difbuilderlib.new_dict.restype = ctypes.c_void_p 82 | difbuilderlib.dispose_dict.argtypes = [ctypes.c_void_p] 83 | difbuilderlib.add_dict_kvp.argtypes = [ 84 | ctypes.c_void_p, 85 | ctypes.c_char_p, 86 | ctypes.c_char_p, 87 | ] 88 | difbuilderlib.add_trigger.argtypes = [ 89 | ctypes.c_void_p, 90 | ctypes.POINTER(ctypes.c_float), 91 | ctypes.POINTER(ctypes.c_float), 92 | ctypes.c_char_p, 93 | ctypes.c_char_p, 94 | ctypes.c_void_p, 95 | ] 96 | 97 | current_status = (False, 0, 0, "", "") 98 | 99 | def update_status(stop, current, total, status, finish_status): 100 | global current_status 101 | current_status = (stop, current, total, status.decode('utf-8'), finish_status.decode('utf-8')) 102 | set_progress(current / total if total != 0 else 1, status.decode('utf-8')) 103 | if stop: 104 | stop_progress() 105 | 106 | update_status_c = STATUSFN(update_status) 107 | 108 | class MarkerList: 109 | def __init__(self): 110 | self.__ptr__ = difbuilderlib.new_marker_list() 111 | 112 | def __del__(self): 113 | difbuilderlib.dispose_marker_list(self.__ptr__) 114 | 115 | def push_marker(self, vec, msToNext, smoothing_type): 116 | vecarr = (ctypes.c_float * len(vec))(*vec) 117 | difbuilderlib.push_marker(self.__ptr__, vecarr, msToNext, smoothing_type) 118 | 119 | 120 | class TriggerIDList: 121 | def __init__(self): 122 | self.__ptr__ = difbuilderlib.new_trigger_id_list() 123 | 124 | def __del__(self): 125 | difbuilderlib.dispose_trigger_id_list(self.__ptr__) 126 | 127 | def push_trigger_id(self, num): 128 | difbuilderlib.push_trigger_id(self.__ptr__, num) 129 | 130 | 131 | class DIFDict: 132 | def __init__(self): 133 | self.__ptr__ = difbuilderlib.new_dict() 134 | 135 | def __del__(self): 136 | difbuilderlib.dispose_dict(self.__ptr__) 137 | 138 | def add_kvp(self, key, value): 139 | difbuilderlib.add_dict_kvp( 140 | self.__ptr__, 141 | ctypes.create_string_buffer(key.encode("ascii")), 142 | ctypes.create_string_buffer(value.encode("ascii")), 143 | ) 144 | 145 | 146 | class Dif: 147 | def __init__(self, ptr): 148 | self.__ptr__ = ptr 149 | 150 | def __del__(self): 151 | difbuilderlib.dispose_dif(self.__ptr__) 152 | 153 | def write_dif(self, path): 154 | difbuilderlib.write_dif( 155 | self.__ptr__, ctypes.create_string_buffer(path.encode("ascii")) 156 | ) 157 | 158 | def add_game_entity(self, entity): 159 | vecarr = (ctypes.c_float * len(entity.position))(*entity.position) 160 | difbuilderlib.add_game_entity( 161 | self.__ptr__, 162 | ctypes.create_string_buffer(entity.gameclass.encode("ascii")), 163 | ctypes.create_string_buffer(entity.datablock.encode("ascii")), 164 | vecarr, 165 | entity.properties.__ptr__, 166 | ) 167 | 168 | def add_trigger(self, trigger): 169 | pos_vecarr = (ctypes.c_float * len(trigger.position))(*trigger.position) 170 | size_vecarr = (ctypes.c_float * len(trigger.size))(*trigger.size) 171 | difbuilderlib.add_trigger( 172 | self.__ptr__, 173 | pos_vecarr, 174 | size_vecarr, 175 | ctypes.create_string_buffer(trigger.name.encode("ascii")), 176 | ctypes.create_string_buffer(trigger.datablock.encode("ascii")), 177 | trigger.properties.__ptr__, 178 | ) 179 | 180 | 181 | class DifBuilder: 182 | def __init__(self): 183 | self.__ptr__ = difbuilderlib.new_difbuilder() 184 | 185 | def __del__(self): 186 | difbuilderlib.dispose_difbuilder(self.__ptr__) 187 | 188 | def add_triangle(self, p1, p2, p3, uv1, uv2, uv3, n, material): 189 | p1arr = (ctypes.c_float * len(p1))(*p1) 190 | p2arr = (ctypes.c_float * len(p2))(*p2) 191 | p3arr = (ctypes.c_float * len(p3))(*p3) 192 | 193 | uv1 = (uv1[0], -uv1[1]) 194 | uv2 = (uv2[0], -uv2[1]) 195 | uv3 = (uv3[0], -uv3[1]) 196 | 197 | uv1arr = (ctypes.c_float * len(uv1))(*uv1) 198 | uv2arr = (ctypes.c_float * len(uv2))(*uv2) 199 | uv3arr = (ctypes.c_float * len(uv3))(*uv3) 200 | 201 | narr = (ctypes.c_float * len(n))(*n) 202 | 203 | mat = ctypes.c_char_p(material.encode("ascii")) 204 | 205 | difbuilderlib.add_triangle( 206 | self.__ptr__, p3arr, p2arr, p1arr, uv3arr, uv2arr, uv1arr, narr, mat 207 | ) 208 | 209 | def add_pathed_interior(self, mp): 210 | vecarr = (ctypes.c_float * len(mp.offset))(*mp.offset) 211 | difbuilderlib.add_pathed_interior(self.__ptr__, mp.dif.__ptr__, mp.marker_list.__ptr__, mp.trigger_id_list.__ptr__, mp.properties.__ptr__, vecarr) 212 | 213 | def build(self, mbonly, bspmode, pointepsilon, planeepsilon, splitepsilon): 214 | return Dif(difbuilderlib.build(self.__ptr__, mbonly, bspmode, pointepsilon, planeepsilon, splitepsilon, update_status_c)) 215 | 216 | 217 | def mesh_triangulate(me): 218 | import bmesh 219 | 220 | bm = bmesh.new() 221 | bm.from_mesh(me) 222 | bmesh.ops.triangulate(bm, faces=bm.faces) 223 | bm.to_mesh(me) 224 | bm.free() 225 | 226 | 227 | def resolve_texture(mat: Material, usematnames: bool): 228 | if usematnames: 229 | matname = mat.name 230 | # Strip off the .\d+ extension 231 | matname = re.sub(r"\.\d+$", "", matname) 232 | return matname 233 | img: ShaderNodeTexImage = None 234 | for n in mat.node_tree.nodes: 235 | if n.type == "TEX_IMAGE": 236 | img = n 237 | break 238 | 239 | if img == None: 240 | matname = mat.name 241 | # Strip off the .\d+ extension 242 | matname = re.sub(r"\.\d+$", "", matname) 243 | return matname 244 | 245 | return Path(img.image.filepath).stem 246 | 247 | 248 | def get_offset(depsgraph, applymodifiers=True): 249 | obs = bpy.context.scene.objects 250 | minv = [1e9, 1e9, 1e9] 251 | maxv = [-1e9, -1e9, -1e9] 252 | 253 | for obj in obs: 254 | ob_eval = obj.evaluated_get(depsgraph) if applymodifiers else obj 255 | try: 256 | mesh = ob_eval.to_mesh() 257 | except RuntimeError: 258 | continue 259 | 260 | mesh.transform(ob_eval.matrix_world) 261 | 262 | for vert in mesh.vertices: 263 | for i in range(0, 3): 264 | if minv[i] > vert.co[i]: 265 | minv[i] = vert.co[i] 266 | if maxv[i] < vert.co[i]: 267 | maxv[i] = vert.co[i] 268 | 269 | ob_eval.to_mesh_clear() 270 | 271 | off = [((maxv[i] - minv[i]) / 2) + 50 for i in range(0, 3)] 272 | return off 273 | 274 | 275 | def formatScale(scale): 276 | return "%.5f %.5f %.5f" % (scale[0], scale[1], scale[2]) 277 | 278 | 279 | def formatRotation(axis_ang): 280 | from math import degrees 281 | return "%.5f %.5f %.5f %.5f" % ( 282 | axis_ang[0][0], 283 | axis_ang[0][1], 284 | axis_ang[0][2], 285 | degrees(-axis_ang[1])) 286 | 287 | def is_degenerate_triangle(p1: Vector, p2: Vector, p3: Vector): 288 | return (p1 - p2).cross(p1 - p3).length < 1e-6 289 | 290 | class GamePathedInterior: 291 | def __init__(self, ob: Object, triggers: list[Object], offset, flip, double, usematnames, mbonly=True, bspmode="Fast", pointepsilon=1e-6, planeepsilon=1e-5, splitepsilon=1e-4): 292 | difbuilder = DifBuilder() 293 | 294 | mesh = ob.to_mesh() 295 | 296 | mesh.calc_loop_triangles() 297 | if bpy.app.version < (4, 0, 0): 298 | mesh.calc_normals_split() 299 | 300 | mesh_verts = mesh.vertices 301 | 302 | if mesh.uv_layers != None and mesh.uv_layers.active != None: 303 | active_uv_layer = mesh.uv_layers.active.data 304 | else: 305 | active_uv_layer = mesh.attributes.get('UVMap') 306 | 307 | for tri_idx in mesh.loop_triangles: 308 | tri: bpy.types.MeshLoopTriangle = tri_idx 309 | 310 | rawp1 = mesh_verts[tri.vertices[0]].co 311 | rawp2 = mesh_verts[tri.vertices[1]].co 312 | rawp3 = mesh_verts[tri.vertices[2]].co 313 | 314 | if is_degenerate_triangle(Vector(rawp1), Vector(rawp2), Vector(rawp3)): 315 | continue 316 | 317 | p1 = [rawp1[i] + offset[i] for i in range(0, 3)] 318 | p2 = [rawp2[i] + offset[i] for i in range(0, 3)] 319 | p3 = [rawp3[i] + offset[i] for i in range(0, 3)] 320 | 321 | uv1 = active_uv_layer[tri.loops[0]].uv[:] 322 | uv2 = active_uv_layer[tri.loops[1]].uv[:] 323 | uv3 = active_uv_layer[tri.loops[2]].uv[:] 324 | 325 | n = tri.normal 326 | 327 | material = ( 328 | resolve_texture(mesh.materials[tri.material_index], usematnames) 329 | if tri.material_index != None 330 | else "NULL" 331 | ) 332 | 333 | if not flip: 334 | difbuilder.add_triangle(p1, p2, p3, uv1, uv2, uv3, n, material) 335 | if double: 336 | difbuilder.add_triangle(p3, p2, p1, uv3, uv2, uv1, n, material) 337 | else: 338 | difbuilder.add_triangle(p3, p2, p1, uv3, uv2, uv1, n, material) 339 | if double: 340 | difbuilder.add_triangle(p1, p2, p3, uv1, uv2, uv3, n, material) 341 | 342 | bspvalue = None 343 | if bspmode == "Fast": 344 | bspvalue = 0 345 | elif bspmode == "Exhaustive": 346 | bspvalue = 1 347 | else: 348 | bspvalue = 2 349 | 350 | dif = difbuilder.build(mbonly, bspvalue, pointepsilon, planeepsilon, splitepsilon) 351 | 352 | marker_ob = ob.dif_props.marker_path 353 | 354 | marker_list = MarkerList() 355 | 356 | if(marker_ob): 357 | marker_pts = ( 358 | marker_ob.splines[0].bezier_points 359 | if (len(marker_ob.splines[0].bezier_points) != 0) 360 | else marker_ob.splines[0].points 361 | ) 362 | 363 | path_type = ob.dif_props.marker_type 364 | if path_type == "linear": 365 | smoothing_type = 0 366 | elif path_type == "spline": 367 | smoothing_type = 1 368 | elif path_type == "accelerate": 369 | smoothing_type = 2 370 | 371 | curve_transform = None 372 | curve_obj = next((obj for obj in bpy.data.objects if obj.data == marker_ob.original), None) 373 | if curve_obj: 374 | curve_transform = curve_obj.matrix_world 375 | 376 | cum_times = [0] # Used for "target marker" triggers and "start index" 377 | 378 | for index, pt in enumerate(marker_pts): 379 | if index == len(marker_pts)-1: 380 | msToNext = 0 381 | else: 382 | if(ob.dif_props.constant_speed): 383 | p0 = Vector(marker_pts[index].co[:3]) 384 | p1 = Vector(marker_pts[index+1].co[:3]) 385 | marker_dist = (p1 - p0).length 386 | 387 | if(ob.dif_props.marker_type == "spline"): 388 | p0 = marker_pts[index-1].co[:3] 389 | p1 = marker_pts[index].co[:3] 390 | p2 = marker_pts[index+1].co[:3] 391 | p3 = marker_pts[(index+2) % len(marker_pts)].co[:3] 392 | length = GamePathedInterior.catmull_rom_length(p0, p1, p2, p3) 393 | else: 394 | length = marker_dist 395 | 396 | if(marker_dist < 0.01): 397 | msToNext = ob.dif_props.pause_duration 398 | else: 399 | msToNext = length / (ob.dif_props.speed / 1000) 400 | 401 | else: 402 | msToNext = ob.dif_props.total_time / (len(marker_pts)-1) 403 | 404 | msToNext = int(max(msToNext, 1)) 405 | 406 | co = pt.co 407 | if len(co) == 4: 408 | co = Vector((co.x, co.y, co.z)) 409 | 410 | if(curve_transform): 411 | co = curve_transform @ co 412 | 413 | marker_list.push_marker(co, msToNext, smoothing_type) 414 | 415 | cum_times.append(cum_times[-1]+msToNext) 416 | 417 | else: 418 | marker_list.push_marker(ob.location, ob.dif_props.total_time, 0) 419 | marker_list.push_marker(ob.location, 0, 0) 420 | 421 | trigger_id_list = TriggerIDList() 422 | 423 | if(ob.dif_props.constant_speed): 424 | marker_idx = min(ob.dif_props.start_index, len(cum_times)-1) 425 | starting_time = cum_times[marker_idx] 426 | else: 427 | starting_time = ob.dif_props.start_time 428 | 429 | if(ob.dif_props.reverse): 430 | initial_target_position = -2 431 | else: 432 | initial_target_position = -1 433 | 434 | for index, trigger in enumerate(triggers): 435 | if trigger.target_object is ob: 436 | trigger_id_list.push_trigger_id(index) 437 | initial_target_position = starting_time 438 | 439 | # Update the trigger target time if using "target marker" 440 | if(trigger.target_marker): 441 | marker_idx = min(trigger.target_index, len(cum_times)-1) 442 | trigger.properties.add_kvp("targetTime", str(cum_times[marker_idx])) 443 | trigger.name = "MustChange_m" + str(marker_idx) 444 | 445 | ob.to_mesh_clear() 446 | 447 | self.dif = dif 448 | self.marker_list = marker_list 449 | self.trigger_id_list = trigger_id_list 450 | 451 | propertydict = DIFDict() 452 | propertydict.add_kvp("initialTargetPosition", str(initial_target_position)) 453 | propertydict.add_kvp("initialPosition", str(starting_time)) 454 | 455 | if(ob.matrix_world != Matrix.Identity(4)): 456 | propertydict.add_kvp("baseScale", formatScale(ob.scale)) 457 | axis_ang_raw: Vector = ob.matrix_world.to_quaternion().to_axis_angle() 458 | propertydict.add_kvp("baseRotation", formatRotation(axis_ang_raw)) 459 | 460 | self.properties = propertydict 461 | self.offset = [-(ob.location[i] + offset[i]) for i in range(0, 3)] 462 | 463 | @staticmethod 464 | def catmull_rom(t, p0, p1, p2, p3): 465 | return 0.5 * ((3*p1 - 3*p2 + p3 - p0)*t*t*t 466 | + (2*p0 - 5*p1 + 4*p2 - p3)*t*t 467 | + (p2 - p0)*t 468 | + 2*p1) 469 | 470 | @staticmethod 471 | def catmull_rom_length(p0, p1, p2, p3, samples=20): 472 | total_length = 0 473 | last_vec = None 474 | 475 | for i in range(0, samples+1): 476 | t = i / samples 477 | x = GamePathedInterior.catmull_rom(t, p0[0], p1[0], p2[0], p3[0]) 478 | y = GamePathedInterior.catmull_rom(t, p0[1], p1[1], p2[1], p3[1]) 479 | z = GamePathedInterior.catmull_rom(t, p0[2], p1[2], p2[2], p3[2]) 480 | new_vec = Vector((x, y, z)) 481 | if last_vec: 482 | total_length += (new_vec - last_vec).length 483 | last_vec = new_vec 484 | 485 | return total_length 486 | 487 | 488 | class GameEntity: 489 | def __init__(self, ob, offset): 490 | props = ob.dif_props 491 | 492 | propertydict = DIFDict() 493 | for prop in props.game_entity_properties: 494 | propertydict.add_kvp(prop.key, prop.value) 495 | 496 | propertydict.add_kvp("scale", formatScale(ob.scale)) 497 | 498 | axis_ang_raw: Vector = ob.matrix_world.to_quaternion().to_axis_angle() 499 | propertydict.add_kvp("rotation", formatRotation(axis_ang_raw)) 500 | 501 | if props.game_entity_gameclass == "Trigger": 502 | propertydict.add_kvp("polyhedron", "0 0 0 1 0 0 0 -1 0 0 0 1") 503 | 504 | self.position = [ob.location[i] + offset[i] for i in range(0, 3)] 505 | self.datablock = props.game_entity_datablock 506 | self.gameclass = props.game_entity_gameclass 507 | self.properties = propertydict 508 | 509 | 510 | class GameTrigger: 511 | def __init__(self, ob, offset): 512 | props = ob.dif_props 513 | 514 | propertydict = DIFDict() 515 | for prop in props.game_entity_properties: 516 | propertydict.add_kvp(prop.key, prop.value) 517 | 518 | #axis_ang_raw: Vector = ob.matrix_world.to_quaternion().to_axis_angle() 519 | #propertydict.add_kvp("rotation", formatRotation(axis_ang_raw)) 520 | 521 | self.position = [ob.location[i] + offset[i] for i in range(0, 3)] 522 | self.size = [ob.scale[0], -ob.scale[1], ob.scale[2]] 523 | self.datablock = props.game_entity_datablock 524 | self.properties = propertydict 525 | self.name = "MustChange" 526 | 527 | self.target_object = ob.dif_props.pathed_interior_target 528 | self.target_marker = ob.dif_props.target_marker 529 | self.target_index = ob.dif_props.target_index 530 | 531 | 532 | def save( 533 | context: bpy.types.Context, 534 | filepath: str = "", 535 | flip=False, 536 | double=False, 537 | maxtricount=12000, 538 | applymodifiers=True, 539 | exportvisible=True, 540 | exportselected=False, 541 | usematnames=False, 542 | mbonly=True, 543 | bspmode="Fast", 544 | pointepsilon=1e-6, 545 | planeepsilon=1e-5, 546 | splitepsilon=1e-4 547 | ): 548 | import bpy 549 | import bmesh 550 | 551 | builders = [DifBuilder()] 552 | 553 | difbuilder = builders[0] 554 | 555 | depsgraph = context.evaluated_depsgraph_get() 556 | 557 | off = [0, 0, 0] # get_offset(depsgraph, applymodifiers) 558 | 559 | tris = 0 560 | 561 | def save_mesh(obj: Object, mesh: Mesh, offset, flip=False, double=False): 562 | import bpy 563 | 564 | nonlocal tris, difbuilder 565 | 566 | mesh.calc_loop_triangles() 567 | if bpy.app.version < (4, 0, 0): 568 | mesh.calc_normals_split() 569 | 570 | mesh_verts = mesh.vertices 571 | 572 | if mesh.uv_layers != None and mesh.uv_layers.active != None: 573 | active_uv_layer = mesh.uv_layers.active.data 574 | else: 575 | active_uv_layer = mesh.attributes.get('UVMap') 576 | 577 | for tri_idx in mesh.loop_triangles: 578 | 579 | tri: bpy.types.MeshLoopTriangle = tri_idx 580 | 581 | if tris > maxtricount: 582 | tris = 0 583 | builders.append(DifBuilder()) 584 | difbuilder = builders[-1] 585 | 586 | rawp1 = mesh_verts[tri.vertices[0]].co 587 | rawp2 = mesh_verts[tri.vertices[1]].co 588 | rawp3 = mesh_verts[tri.vertices[2]].co 589 | 590 | if is_degenerate_triangle(Vector(rawp1), Vector(rawp2), Vector(rawp3)): 591 | continue 592 | 593 | p1 = [rawp1[i] + offset[i] for i in range(0, 3)] 594 | p2 = [rawp2[i] + offset[i] for i in range(0, 3)] 595 | p3 = [rawp3[i] + offset[i] for i in range(0, 3)] 596 | 597 | # uv = [ 598 | # active_uv_layer[l].uv[:] 599 | # for l in range(poly.loop_start, poly.loop_start + poly.loop_total) 600 | # ] 601 | 602 | uv1 = active_uv_layer[tri.loops[0]].uv[:] 603 | uv2 = active_uv_layer[tri.loops[1]].uv[:] 604 | uv3 = active_uv_layer[tri.loops[2]].uv[:] 605 | 606 | n = tri.normal 607 | 608 | material = ( 609 | resolve_texture(mesh.materials[tri.material_index], usematnames) 610 | if tri.material_index != None 611 | else "NULL" 612 | ) 613 | 614 | if not flip: 615 | difbuilder.add_triangle(p1, p2, p3, uv1, uv2, uv3, n, material) 616 | tris += 1 617 | if double: 618 | difbuilder.add_triangle(p3, p2, p1, uv3, uv2, uv1, n, material) 619 | tris += 1 620 | else: 621 | difbuilder.add_triangle(p3, p2, p1, uv3, uv2, uv1, n, material) 622 | tris += 1 623 | if double: 624 | difbuilder.add_triangle(p1, p2, p3, uv1, uv2, uv3, n, material) 625 | tris += 1 626 | 627 | mp_list = [] 628 | game_entities: list[GameEntity] = [] 629 | triggers: list[GameTrigger] = [] 630 | 631 | def is_object_instance_selected(object_instance): 632 | # For instanced objects we check selection of their instancer (more accurately: check 633 | # selection status of the original object corresponding to the instancer). 634 | if object_instance.parent: 635 | return object_instance.parent.original.select_get() 636 | # For non-instanced objects we check selection state of the original object. 637 | return object_instance.object.original.select_get() 638 | 639 | def is_object_instance_visible(object_instance): 640 | # For instanced objects we check visibility of their instancer (more accurately: check 641 | # visibility status of the original object corresponding to the instancer). 642 | if object_instance.parent: 643 | return object_instance.parent.original.visible_get() 644 | # For non-instanced objects we check visibility state of the original object. 645 | return object_instance.object.original.visible_get() 646 | 647 | # handle normal export for lower versions 648 | if bpy.app.version < (3, 1, 0) or not applymodifiers: 649 | obs = ( 650 | bpy.context.selected_objects 651 | if exportselected 652 | else bpy.context.scene.objects 653 | ) 654 | for ob in obs: 655 | ob: Object = ob 656 | if exportvisible: 657 | if not ob.visible_get(): 658 | continue 659 | 660 | ob_eval = ob.evaluated_get(depsgraph) if applymodifiers else ob 661 | 662 | dif_props = ob_eval.dif_props 663 | 664 | if dif_props.interior_type == "game_entity": 665 | game_entities.append(GameEntity(ob_eval, off)) 666 | 667 | if dif_props.interior_type == "path_trigger": 668 | triggers.append(GameTrigger(ob_eval, off)) 669 | 670 | try: 671 | me = ob_eval.to_mesh() 672 | except RuntimeError: 673 | continue 674 | 675 | if dif_props.interior_type == "static_interior": 676 | me.transform(ob_eval.matrix_world) 677 | try: 678 | save_mesh(ob_eval, me, off, flip, double) 679 | except: 680 | print("Skipping mesh due to issue while saving") 681 | 682 | ob_eval.to_mesh_clear() 683 | 684 | if dif_props.interior_type == "pathed_interior": 685 | mp_list.append(ob_eval) 686 | 687 | # handle object instances for these versions, ew code duplication 688 | if bpy.app.version >= (3, 1, 0) and applymodifiers: 689 | for object_instance in depsgraph.object_instances: 690 | if exportselected: 691 | if not is_object_instance_selected(object_instance): 692 | continue 693 | 694 | if exportvisible: 695 | if not is_object_instance_visible(object_instance): 696 | continue 697 | 698 | ob_eval = ( 699 | object_instance.object 700 | if applymodifiers 701 | else object_instance.object.original 702 | ) 703 | 704 | dif_props = ob_eval.dif_props 705 | 706 | if dif_props.interior_type == "game_entity": 707 | game_entities.append(GameEntity(ob_eval, off)) 708 | 709 | if dif_props.interior_type == "path_trigger": 710 | triggers.append(GameTrigger(ob_eval, off)) 711 | 712 | try: 713 | me = ob_eval.to_mesh() 714 | except RuntimeError: 715 | print("Skipping mesh due to bad eval") 716 | continue 717 | 718 | if dif_props.interior_type == "static_interior": 719 | me.transform(ob_eval.matrix_world) 720 | try: 721 | save_mesh(ob_eval, me, off, flip, double) 722 | except: 723 | print("Skipping mesh due to issue while saving") 724 | 725 | ob_eval.to_mesh_clear() 726 | 727 | if dif_props.interior_type == "pathed_interior": 728 | mp_list.append(ob_eval) 729 | 730 | mp_difs = [] 731 | 732 | for mp in mp_list: 733 | mp_difs.append(GamePathedInterior(mp, triggers, off, flip, double, usematnames, mbonly, bspmode, pointepsilon, planeepsilon, splitepsilon)) 734 | 735 | bspvalue = None 736 | if bspmode == "Fast": 737 | bspvalue = 0 738 | elif bspmode == "Exhaustive": 739 | bspvalue = 1 740 | else: 741 | bspvalue = 2 742 | 743 | if tris != 0: 744 | for i in range(0, len(builders)): 745 | if i == 0: 746 | for mp in mp_difs: 747 | builders[i].add_pathed_interior(mp) 748 | 749 | dif = builders[i].build(mbonly, bspvalue, pointepsilon, planeepsilon, splitepsilon) 750 | 751 | if i == 0: 752 | for ge in game_entities: 753 | dif.add_game_entity(ge) 754 | 755 | for trigger in triggers: 756 | dif.add_trigger(trigger) 757 | 758 | dif.write_dif(str(Path(filepath).with_suffix("")) + str(i) + ".dif") 759 | -------------------------------------------------------------------------------- /rust/libdifbuilder/src/bsp.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::{HashMap, HashSet}, 4 | sync::Mutex, 5 | vec, 6 | }; 7 | 8 | use cgmath::{InnerSpace, Vector3}; 9 | use dif::types::{PlaneF, Point3F}; 10 | use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng}; 11 | 12 | use crate::builder::{OrdPlaneF, ProgressEventListener, Triangle}; 13 | use rayon::prelude::*; 14 | 15 | #[derive(PartialEq, PartialOrd, Eq, Ord, Copy, Clone)] 16 | pub enum SplitMethod { 17 | Fast, 18 | Exhaustive, 19 | None, 20 | } 21 | 22 | pub struct BSPConfig { 23 | pub split_method: SplitMethod, 24 | pub epsilon: f32, 25 | } 26 | 27 | pub static mut BSP_CONFIG: BSPConfig = BSPConfig { 28 | split_method: SplitMethod::Fast, 29 | epsilon: 1e-4, 30 | }; 31 | 32 | #[derive(Clone)] 33 | pub struct BSPPolygon { 34 | pub vertices: Vec, 35 | pub indices: Vec, 36 | pub plane: PlaneF, 37 | pub plane_id: usize, 38 | pub id: usize, 39 | pub used_plane: bool, 40 | pub inverted_plane: bool, 41 | pub area_calc: f32, 42 | } 43 | 44 | // (front, back, splits, coplanar, tiny_windings) 45 | impl BSPPolygon { 46 | fn calculate_split_rating( 47 | &self, 48 | plane_id: usize, 49 | plane_list: &[PlaneF], 50 | considered_planes: &Mutex>>, 51 | ) -> (i32, i32, i32, i32, i32) { 52 | if !considered_planes 53 | .lock() 54 | .unwrap() 55 | .borrow() 56 | .contains(&plane_id) 57 | { 58 | if self.plane_id == plane_id { 59 | considered_planes 60 | .lock() 61 | .unwrap() 62 | .borrow_mut() 63 | .insert(plane_id); 64 | if self.inverted_plane { 65 | return (1, 0, 0, 1, 0); 66 | } else { 67 | return (0, 1, 0, 1, 0); 68 | } 69 | } 70 | } 71 | let epsilon = unsafe { BSP_CONFIG.epsilon }; 72 | let test_plane = &plane_list[plane_id as usize]; 73 | let mut max_front = 0.0; 74 | let mut min_back = 0.0; 75 | for &idx in &self.indices { 76 | let pt = self.vertices[idx]; 77 | let d = test_plane.normal.dot(pt) + test_plane.distance; 78 | if d > max_front { 79 | max_front = d; 80 | } 81 | if d < min_back { 82 | min_back = d; 83 | } 84 | } 85 | 86 | let mut front = 0; 87 | let mut back = 0; 88 | let mut splits = 0; 89 | let mut tiny_windings = 0; 90 | if max_front > epsilon { 91 | front = 1; 92 | } 93 | if min_back < -epsilon { 94 | back = 1; 95 | } 96 | if max_front > epsilon && min_back < -epsilon { 97 | splits = 1; 98 | } 99 | if (max_front > 0.0 && max_front < 1.0) || (min_back < 0.0 && min_back > -1.0) { 100 | tiny_windings = 1; 101 | } 102 | (front, back, splits, 0, tiny_windings) 103 | } 104 | 105 | fn split(&self, plane: usize, plane_list: &[PlaneF]) -> [BSPPolygon; 2] { 106 | let mut front_brush = self.clone(); 107 | let mut back_brush = self.clone(); 108 | 109 | let plane_in_brush = self.plane_id == plane; 110 | 111 | back_brush.clip_plane(plane, plane_list, false); 112 | front_brush.clip_plane(plane, plane_list, true); 113 | 114 | let plane_in_front = front_brush.plane_id == plane; 115 | let plane_in_back = back_brush.plane_id == plane; 116 | 117 | if plane_in_brush { 118 | if !plane_in_back && !plane_in_front { 119 | assert!(false, "Wtf"); 120 | } 121 | front_brush.used_plane = true; 122 | back_brush.used_plane = true; 123 | } 124 | 125 | return [front_brush, back_brush]; 126 | } 127 | 128 | fn clip_plane(&mut self, plane: usize, plane_list: &[PlaneF], flip_face: bool) { 129 | let mut vertices = std::mem::take(&mut self.vertices); 130 | let original_indices = std::mem::take(&mut self.indices); 131 | let mut plane_value = plane_list[plane].clone(); 132 | if flip_face { 133 | plane_value.normal *= -1.0; 134 | plane_value.distance *= -1.0; 135 | } 136 | 137 | let mut new_indices: Vec = Vec::with_capacity(original_indices.len()); 138 | let mut _points_on_plane = 0; 139 | let epsilon = unsafe { BSP_CONFIG.epsilon }; 140 | for i in 0..original_indices.len() { 141 | let v1 = &vertices[original_indices[i] as usize]; 142 | let v2 = &vertices[original_indices[(i + 1) % original_indices.len()] as usize]; 143 | let d1 = v1.dot(plane_value.normal) + plane_value.distance; 144 | let d2 = v2.dot(plane_value.normal) + plane_value.distance; 145 | if d1 > epsilon { 146 | // Ignore 147 | } 148 | if d1 <= epsilon { 149 | // Keep 150 | new_indices.push(original_indices[i]); 151 | } 152 | if d1.abs() < epsilon { 153 | _points_on_plane += 1; 154 | } 155 | if (d1 > epsilon && d2 < -epsilon) || (d1 < -epsilon && d2 > epsilon) { 156 | let t = (-plane_value.distance - plane_value.normal.dot(*v1)) 157 | / plane_value.normal.dot(v2 - v1); 158 | let v3 = v1 + (v2 - v1) * t; 159 | new_indices.push(vertices.len()); 160 | vertices.push(v3); 161 | } 162 | } 163 | // if clip_face && points_on_plane == face.indices.len() { 164 | // new_indices.clear(); 165 | // } 166 | // Sanity check 167 | let test_epsilon = unsafe { BSP_CONFIG.epsilon * 10.0 }; 168 | for idx in new_indices.iter() { 169 | let pt = vertices[*idx as usize]; 170 | let d = plane_value.normal.dot(pt) + plane_value.distance; 171 | if d > test_epsilon { 172 | assert!(false, "Invalid CLIP of {} (epsilon: {})", d, test_epsilon); 173 | } 174 | } 175 | 176 | self.vertices = vertices; 177 | self.indices = new_indices; 178 | self.area_calc = self.area(); 179 | } 180 | 181 | fn _classify_score(&self, plane: &PlaneF) -> i32 { 182 | let mut front_count = 0; 183 | let mut back_count = 0; 184 | let mut on_count = 0; 185 | self.indices.iter().for_each(|i| { 186 | let pt = self.vertices[*i as usize]; 187 | let face_dot = pt.dot(plane.normal) + plane.distance; 188 | if face_dot > unsafe { BSP_CONFIG.epsilon } { 189 | front_count += 1; 190 | } else if face_dot < unsafe { -BSP_CONFIG.epsilon } { 191 | back_count += 1; 192 | } else { 193 | on_count += 1; 194 | } 195 | }); 196 | if front_count > 0 && back_count == 0 { 197 | front_count 198 | } else if front_count == 0 && back_count > 0 { 199 | -back_count 200 | } else if front_count == 0 && back_count == 0 && on_count > 0 { 201 | 0 202 | } else { 203 | front_count - back_count 204 | } 205 | } 206 | 207 | fn classify_poly(&self, plane: &PlaneF) -> i32 { 208 | let mut front_count = 0; 209 | let mut back_count = 0; 210 | let mut on_count = 0; 211 | self.indices.iter().for_each(|i| { 212 | let pt = self.vertices[*i as usize]; 213 | let face_dot = pt.dot(plane.normal) + plane.distance; 214 | if face_dot > unsafe { BSP_CONFIG.epsilon } { 215 | front_count += 1; 216 | } else if face_dot < unsafe { -BSP_CONFIG.epsilon } { 217 | back_count += 1; 218 | } else { 219 | on_count += 1; 220 | } 221 | }); 222 | if front_count > 0 && back_count == 0 { 223 | 1 // Is in front 224 | } else if front_count == 0 && back_count > 0 { 225 | -1 // Is in back 226 | } else if front_count == 0 && back_count == 0 && on_count > 0 { 227 | 0 // Is on the plane 228 | } else { 229 | 2 // Is spanning the plane 230 | } 231 | } 232 | 233 | fn area(&self) -> f32 { 234 | if self.indices.len() < 2 { 235 | 0.0 236 | } else { 237 | let v0 = self.vertices[self.indices[0]]; 238 | let mut a = 0.0; 239 | for i in 1..self.indices.len() { 240 | let v1 = self.vertices[self.indices[i]]; 241 | let v2 = self.vertices[self.indices[(i + 1) % self.indices.len()]]; 242 | let tri_a = (v1 - v0).cross(v2 - v0).magnitude() / 2.0; 243 | a += tri_a; 244 | } 245 | a 246 | } 247 | } 248 | } 249 | 250 | pub struct DIFBSPNode { 251 | pub brush_list: Vec, 252 | pub front: Option>, 253 | pub back: Option>, 254 | pub plane_index: Option, 255 | pub solid: bool, 256 | pub avail_planes: Vec, 257 | } 258 | 259 | impl DIFBSPNode { 260 | fn from_brushes(brush_list: Vec) -> DIFBSPNode { 261 | DIFBSPNode { 262 | front: None, 263 | back: None, 264 | plane_index: None, 265 | avail_planes: brush_list 266 | .iter() 267 | .map(|b| b.plane_id) 268 | .collect::>() 269 | .into_iter() 270 | .collect::>(), 271 | brush_list: brush_list, 272 | solid: false, 273 | } 274 | } 275 | 276 | fn height(&self) -> i32 { 277 | let mut value = 0; 278 | if let Some(ref front) = self.front { 279 | value = std::cmp::max(value, front.height()); 280 | } 281 | if let Some(ref back) = self.back { 282 | value = std::cmp::max(value, back.height()); 283 | } 284 | value + 1 285 | } 286 | 287 | pub fn balance_factor(&self) -> i32 { 288 | let mut value = 0; 289 | if let Some(ref front) = self.front { 290 | value += front.height(); 291 | } 292 | if let Some(ref back) = self.back { 293 | value -= back.height(); 294 | } 295 | value 296 | } 297 | 298 | fn split( 299 | &mut self, 300 | plane_list: &[PlaneF], 301 | used_planes: &mut HashSet, 302 | depth: usize, 303 | progress_report_callback: &mut dyn ProgressEventListener, 304 | ) { 305 | let mut unused_planes = false; 306 | for brush in self.brush_list.iter() { 307 | if !brush.used_plane { 308 | unused_planes = true; 309 | break; 310 | } 311 | } 312 | let mut total_faces = 0; 313 | let mut remaining_faces = 0; 314 | for brush in self.brush_list.iter() { 315 | if !brush.used_plane { 316 | remaining_faces += 1; 317 | } 318 | total_faces += 1; 319 | } 320 | 321 | if unused_planes && self.plane_index == None { 322 | let split_plane = match unsafe { &BSP_CONFIG.split_method } { 323 | SplitMethod::Fast => self.select_best_splitter(plane_list), 324 | SplitMethod::Exhaustive => self.select_best_splitter_new(plane_list), 325 | _ => { 326 | panic!("Should never reach here!") 327 | } 328 | }; 329 | if let Some(split_plane) = split_plane { 330 | // Do split 331 | self.split_brush_list(split_plane, plane_list); 332 | self.plane_index = Some(split_plane); 333 | 334 | // if depth > 200 { 335 | // println!( 336 | // "Warning: depth over 200 {}, id {}, len {}", 337 | // depth, 338 | // self.plane_index.unwrap(), 339 | // self.brush_list.len() 340 | // ); 341 | // } 342 | 343 | if !used_planes.contains(&split_plane) { 344 | used_planes.insert(split_plane); 345 | progress_report_callback.progress( 346 | used_planes.len() as u32, 347 | plane_list.len() as u32, 348 | "Building BSP".to_string(), 349 | "Built BSP".to_string(), 350 | ); 351 | } 352 | 353 | match self.front { 354 | Some(ref mut n) => { 355 | n.brush_list.iter_mut().for_each(|b| { 356 | if b.plane_id == split_plane { 357 | b.used_plane = true; 358 | } 359 | }); 360 | n.split(plane_list, used_planes, depth + 1, progress_report_callback); 361 | } 362 | None => {} 363 | }; 364 | match self.back { 365 | Some(ref mut n) => { 366 | n.brush_list.iter_mut().for_each(|b| { 367 | if b.plane_id == split_plane { 368 | b.used_plane = true; 369 | } 370 | }); 371 | n.split(plane_list, used_planes, depth + 1, progress_report_callback); 372 | } 373 | None => {} 374 | }; 375 | } 376 | } 377 | } 378 | 379 | fn split_brush_list(&mut self, plane_id: usize, plane_list: &[PlaneF]) { 380 | let mut front_brushes: Vec = vec![]; 381 | let mut back_brushes: Vec = vec![]; 382 | let mut front_solid = self.solid; 383 | let mut back_solid = self.solid; 384 | let mut plane_in_brush = false; 385 | for brush in self.brush_list.iter() { 386 | if brush.plane_id == plane_id { 387 | plane_in_brush = true; 388 | break; 389 | } 390 | } 391 | assert!(plane_in_brush, "Not in brush??"); 392 | 393 | self.brush_list.iter().for_each(|b| { 394 | if b.plane_id == plane_id { 395 | let mut cl = b.clone(); 396 | cl.used_plane = true; 397 | back_brushes.push(cl); 398 | back_solid = true; 399 | } else { 400 | let [front_brush, back_brush] = b.split(plane_id, plane_list); 401 | if front_brush.indices.len() > 2 { 402 | front_solid = front_brush.used_plane; 403 | front_brushes.push(front_brush); 404 | } 405 | if back_brush.indices.len() > 2 { 406 | back_solid = back_brush.used_plane; 407 | back_brushes.push(back_brush); 408 | } 409 | } 410 | }); 411 | if front_brushes.len() != 0 { 412 | let front_node = DIFBSPNode { 413 | front: None, 414 | back: None, 415 | avail_planes: front_brushes 416 | .iter() 417 | .filter(|b| b.plane_id != plane_id && !b.used_plane) 418 | .map(|b| b.plane_id) 419 | .collect::>() 420 | .into_iter() 421 | .collect::>(), 422 | brush_list: front_brushes, 423 | solid: front_solid, 424 | plane_index: None, 425 | }; 426 | self.front = Some(Box::new(front_node)); 427 | } 428 | if back_brushes.len() != 0 { 429 | let back_node = DIFBSPNode { 430 | front: None, 431 | back: None, 432 | solid: back_solid, 433 | avail_planes: back_brushes 434 | .iter() 435 | .filter(|b| b.plane_id != plane_id && !b.used_plane) 436 | .map(|b| b.plane_id) 437 | .collect::>() 438 | .into_iter() 439 | .collect::>(), 440 | brush_list: back_brushes, 441 | plane_index: None, 442 | }; 443 | self.back = Some(Box::new(back_node)); 444 | } 445 | self.brush_list.clear(); 446 | self.avail_planes.clear(); 447 | } 448 | 449 | fn select_best_splitter_new(&self, plane_list: &[PlaneF]) -> Option { 450 | use std::f32::consts::PI; 451 | let mut vector_planes: Vec<(Vector3, Vec)> = vec![]; 452 | // Create semi sphere unit vectors 453 | for i in 0..8 { 454 | for j in 0..8 { 455 | let p = -PI + PI * i as f32 / 8.0; 456 | let t = (PI / 2.0) * j as f32 / 8.0; 457 | let vecval = Vector3::new(t.cos() * p.sin(), t.sin() * p.sin(), p.cos()); 458 | vector_planes.push((vecval, vec![])); 459 | } 460 | } 461 | // Quantize all the polygons to vectors according to max dot product 462 | let mut used_faces: HashSet = HashSet::new(); 463 | self.brush_list.iter().for_each(|f| { 464 | if !f.used_plane && !used_faces.contains(&f.plane_id) { 465 | used_faces.insert(f.plane_id); 466 | let mut max_dot = -1.0; 467 | let mut max_index = None; 468 | let face_plane = &plane_list[f.plane_id]; 469 | vector_planes.iter().enumerate().for_each(|(i, (v, _))| { 470 | let dot = v.dot(face_plane.normal); 471 | if dot > max_dot { 472 | max_dot = dot; 473 | max_index = Some(i); 474 | } 475 | }); 476 | if let Some(max_index) = max_index { 477 | vector_planes[max_index].1.push(f.plane_id); 478 | } 479 | } 480 | }); 481 | // Sort all the polygons from each list in vectorPlanes according to d 482 | for (_, p_list) in vector_planes.iter_mut() { 483 | p_list.sort_by(|a, b| plane_list[*a].distance.total_cmp(&plane_list[*b].distance)); 484 | } 485 | 486 | // Get the least depth polygons from centre of each vectorPlanes 487 | let least_depth_planes = vector_planes 488 | .iter() 489 | .filter(|(_, p)| p.len() > 0) 490 | .map(|(_, pl)| pl[pl.len() / 2]) 491 | .collect::>(); 492 | 493 | let val = least_depth_planes.par_iter().max_by_key(|&&p_idx| { 494 | self.calc_plane_rating(p_idx, plane_list) 495 | // self.brush_list 496 | // .par_iter() 497 | // .map(|b| b.classify_score(&plane_list[**p_idx])) 498 | // .sum::() 499 | }); 500 | 501 | // if let Some(&inner) = val { 502 | // let entropy = self.calc_plane_rating(inner, plane_list); 503 | // if entropy < 700 { 504 | // println!("Warning: chose a plane {} with suboptimal entropy", inner); 505 | // } 506 | // } 507 | 508 | match val { 509 | Some(i) => Some(*i), 510 | None => None, 511 | } 512 | } 513 | 514 | fn select_best_splitter(&self, plane_list: &[PlaneF]) -> Option { 515 | let mut rng = StdRng::seed_from_u64(42); 516 | 517 | let chosen_planes = self 518 | .brush_list 519 | .iter() 520 | .filter(|f| !f.used_plane) 521 | .map(|f| f.plane_id) 522 | .collect::>() // Get distinct 523 | .into_iter() 524 | .collect::>(); 525 | // let chosen_planes = &self.avail_planes; 526 | // Intersect this_planes and unused_planes 527 | let max_plane = chosen_planes 528 | .choose_multiple(&mut rng, 32) 529 | .collect::>() 530 | .into_par_iter() 531 | .max_by_key(|&&p| self.calc_plane_rating(p, plane_list)); 532 | 533 | match max_plane { 534 | Some(&x) => Some(x), 535 | None => None, 536 | } 537 | } 538 | 539 | fn calc_plane_rating(&self, plane_id: usize, plane_list: &[PlaneF]) -> i32 { 540 | let plane = &plane_list[plane_id as usize]; 541 | let mut zero_count = 0; 542 | if plane.normal.x.abs() < unsafe { BSP_CONFIG.epsilon } { 543 | zero_count += 1; 544 | } 545 | if plane.normal.y.abs() < unsafe { BSP_CONFIG.epsilon } { 546 | zero_count += 1; 547 | } 548 | if plane.normal.z.abs() < unsafe { BSP_CONFIG.epsilon } { 549 | zero_count += 1; 550 | } 551 | let axial = zero_count == 2; 552 | let considered_planes = Mutex::from(RefCell::from(HashSet::new())); 553 | let (front, back, splits, coplanar, tiny_windings) = self 554 | .brush_list 555 | .par_iter() 556 | .map(|b| b.calculate_split_rating(plane_id, plane_list, &considered_planes)) 557 | .reduce( 558 | || (0, 0, 0, 0, 0), 559 | |a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2, a.3 + b.3, a.4 + b.4), 560 | ); 561 | 562 | // the rating adds to both when splits happens 563 | let front_only = front - splits; 564 | let back_only = back - splits; 565 | 566 | let real_front_and_back = front_only + back_only; // A symmetric_diff B 567 | 568 | let front_and_back = front + back; // A u B 569 | 570 | let jaccard = real_front_and_back as f32 / front_and_back as f32; 571 | 572 | let entropy = if front_and_back > 0 { 573 | (front as f32 / front_and_back as f32) * (front as f32 / front_and_back as f32).log2() 574 | + (back as f32 / front_and_back as f32) 575 | * (back as f32 / front_and_back as f32).log2() 576 | } else { 577 | 0.0 578 | }; 579 | 580 | // let gini = 1.0 581 | // - (front as f32 / front_and_back as f32).powi(2) 582 | // - (back as f32 / front_and_back as f32).powi(2); 583 | 584 | // return (jaccard * 1000.0).round() as i32; 585 | 586 | return (-entropy * jaccard * 1000.0).round() as i32; 587 | 588 | // let mut final_score = 5 * coplanar - 5 * splits - (front - back).abs(); 589 | // final_score -= 1000 * tiny_windings; 590 | // if axial { 591 | // final_score += 5; 592 | // } 593 | // return final_score; 594 | } 595 | 596 | pub fn ray_cast( 597 | &self, 598 | start: Point3F, 599 | end: Point3F, 600 | plane_index: usize, 601 | plane_list: &[PlaneF], 602 | ) -> bool { 603 | if self.plane_index.is_none() { 604 | if self.solid { 605 | let mut found = false; 606 | for brush in self.brush_list.iter() { 607 | if brush.plane_id == plane_index { 608 | found = true; 609 | break; 610 | } 611 | if found { 612 | break; 613 | } 614 | } 615 | return found; 616 | } else { 617 | false 618 | } 619 | } else { 620 | use std::cmp::Ordering; 621 | let plane_f = &plane_list[self.plane_index.unwrap()]; 622 | let plane_norm = &plane_f.normal; 623 | let plane_d = &plane_f.distance; 624 | let s_side_value = plane_norm.dot(start) + plane_d; 625 | let e_side_value = plane_norm.dot(end) + plane_d; 626 | let s_side = s_side_value.total_cmp(&0.0); 627 | let e_side = e_side_value.total_cmp(&0.0); 628 | 629 | match (s_side, e_side) { 630 | (Ordering::Greater, Ordering::Greater) 631 | | (Ordering::Greater, Ordering::Equal) 632 | | (Ordering::Equal, Ordering::Greater) => { 633 | if let Some(node_value) = &self.front { 634 | node_value.ray_cast(start, end, plane_index, plane_list) 635 | } else { 636 | false 637 | } 638 | } 639 | (Ordering::Greater, Ordering::Less) => { 640 | let intersect_t = 641 | (-plane_d - start.dot(*plane_norm)) / (end - start).dot(*plane_norm); 642 | let ip = start + (end - start) * intersect_t; 643 | if let Some(node_value) = &self.front { 644 | if node_value.ray_cast(start, ip, plane_index, plane_list) { 645 | return true; 646 | } 647 | } 648 | if let Some(node_value) = &self.back { 649 | node_value.ray_cast(ip, end, self.plane_index.unwrap(), plane_list) 650 | } else { 651 | false 652 | } 653 | } 654 | (Ordering::Less, Ordering::Greater) => { 655 | let intersect_t = 656 | (-plane_d - start.dot(*plane_norm)) / (end - start).dot(*plane_norm); 657 | let ip = start + (end - start) * intersect_t; 658 | if let Some(node_value) = &self.back { 659 | if node_value.ray_cast(start, ip, plane_index, plane_list) { 660 | return true; 661 | } 662 | } 663 | if let Some(node_value) = &self.front { 664 | node_value.ray_cast(ip, end, self.plane_index.unwrap(), plane_list) 665 | } else { 666 | false 667 | } 668 | } 669 | (Ordering::Less, Ordering::Less) 670 | | (Ordering::Less, Ordering::Equal) 671 | | (Ordering::Equal, Ordering::Less) => { 672 | if let Some(node_value) = &self.back { 673 | node_value.ray_cast(start, end, plane_index, plane_list) 674 | } else { 675 | false 676 | } 677 | } 678 | _ => false, 679 | } 680 | } 681 | } 682 | 683 | fn split_new_impl( 684 | &mut self, 685 | plane_list: &[PlaneF], 686 | used_planes: &mut HashSet, 687 | depth: usize, 688 | progress_report_callback: &mut dyn ProgressEventListener, 689 | ) { 690 | let mut unused_planes = false; 691 | for brush in self.brush_list.iter() { 692 | if !brush.used_plane { 693 | unused_planes = true; 694 | break; 695 | } 696 | } 697 | let mut total_faces = 0; 698 | let mut remaining_faces = 0; 699 | for brush in self.brush_list.iter() { 700 | if !brush.used_plane { 701 | remaining_faces += 1; 702 | } 703 | total_faces += 1; 704 | } 705 | 706 | if unused_planes && self.plane_index == None { 707 | let split_plane = match unsafe { &BSP_CONFIG.split_method } { 708 | SplitMethod::Fast => self.select_best_splitter(plane_list), 709 | SplitMethod::Exhaustive => self.select_best_splitter_new(plane_list), 710 | _ => { 711 | panic!("Should never reach here!") 712 | } 713 | }; 714 | 715 | if let Some(split_plane) = split_plane { 716 | self.plane_index = Some(split_plane); 717 | 718 | // Classify each brush as front, back, or coinciding 719 | let mut front_brushes: Vec = vec![]; 720 | let mut back_brushes: Vec = vec![]; 721 | 722 | for mut b in std::mem::take(&mut self.brush_list) { 723 | if b.plane_id == split_plane { 724 | // Coinciding, put in back for now 725 | b.used_plane = true; 726 | back_brushes.push(b); 727 | } else { 728 | let classify_score = b.classify_poly(&plane_list[split_plane]); 729 | 730 | if classify_score == 1 { 731 | front_brushes.push(b); 732 | } else if classify_score == -1 { 733 | back_brushes.push(b); 734 | } else if classify_score == 0 { 735 | // Coinciding, put in back for now 736 | b.used_plane = true; 737 | // back_brushes.push(cl); 738 | } else if classify_score == 2 { 739 | // Spanning, split it 740 | let [front_brush, back_brush] = b.split(split_plane, plane_list); 741 | if front_brush.indices.len() > 2 { 742 | front_brushes.push(front_brush); 743 | } 744 | if back_brush.indices.len() > 2 { 745 | back_brushes.push(back_brush); 746 | } 747 | } 748 | } 749 | } 750 | 751 | if !used_planes.contains(&split_plane) { 752 | used_planes.insert(split_plane); 753 | progress_report_callback.progress( 754 | used_planes.len() as u32, 755 | plane_list.len() as u32, 756 | "Building BSP".to_string(), 757 | "Built BSP".to_string(), 758 | ); 759 | } 760 | 761 | if front_brushes.len() != 0 { 762 | let front_node = DIFBSPNode { 763 | front: None, 764 | back: None, 765 | avail_planes: front_brushes 766 | .iter() 767 | .filter(|b| b.plane_id != split_plane && !b.used_plane) 768 | .map(|b| b.plane_id) 769 | .collect::>() 770 | .into_iter() 771 | .collect::>(), 772 | brush_list: front_brushes, 773 | solid: false, 774 | plane_index: None, 775 | }; 776 | self.front = Some(Box::new(front_node)); 777 | } 778 | if back_brushes.len() != 0 { 779 | let back_node = DIFBSPNode { 780 | front: None, 781 | back: None, 782 | solid: false, 783 | avail_planes: back_brushes 784 | .iter() 785 | .filter(|b| b.plane_id != split_plane && !b.used_plane) 786 | .map(|b| b.plane_id) 787 | .collect::>() 788 | .into_iter() 789 | .collect::>(), 790 | brush_list: back_brushes, 791 | plane_index: None, 792 | }; 793 | self.back = Some(Box::new(back_node)); 794 | } 795 | 796 | self.brush_list.clear(); 797 | self.avail_planes.clear(); 798 | 799 | if let Some(ref mut n) = self.front { 800 | n.brush_list.iter_mut().for_each(|b| { 801 | if b.plane_id == split_plane { 802 | b.used_plane = true; 803 | } 804 | }); 805 | n.split_new_impl(plane_list, used_planes, depth + 1, progress_report_callback); 806 | }; 807 | if let Some(ref mut n) = self.back { 808 | n.brush_list.iter_mut().for_each(|b| { 809 | if b.plane_id == split_plane { 810 | b.used_plane = true; 811 | } 812 | }); 813 | n.split_new_impl(plane_list, used_planes, depth + 1, progress_report_callback); 814 | }; 815 | } 816 | } 817 | } 818 | } 819 | 820 | pub fn build_bsp( 821 | brush_list: &[Triangle], 822 | progress_report_callback: &mut dyn ProgressEventListener, 823 | ) -> (DIFBSPNode, Vec) { 824 | let mut plane_map: HashMap = HashMap::new(); 825 | let mut plane_list: Vec = vec![]; 826 | 827 | let bsp_polygons = brush_list 828 | .iter() 829 | .map(|b| { 830 | let mut plane_id = plane_list.len(); 831 | let ord_plane = OrdPlaneF::from(&b.plane); 832 | let mut plane_inverted = false; 833 | if plane_map.contains_key(&ord_plane) { 834 | plane_id = plane_map[&ord_plane]; 835 | } else { 836 | // Try inverted 837 | // let mut pinvplane = b.plane.clone(); 838 | // pinvplane.normal *= -1.0; 839 | // pinvplane.distance *= -1.0; 840 | // let ord_plane = OrdPlaneF::from(&pinvplane); 841 | // if plane_map.contains_key(&ord_plane) { 842 | // plane_id = plane_map[&ord_plane]; 843 | // plane_inverted = true; 844 | // } else { 845 | plane_list.push(b.plane.clone()); 846 | plane_map.insert(OrdPlaneF::from(&b.plane), plane_id); 847 | // } 848 | } 849 | 850 | let mut poly = BSPPolygon { 851 | id: b.id as usize, 852 | plane: b.plane.clone(), 853 | plane_id: plane_id, 854 | indices: vec![0, 1, 2], 855 | vertices: b.verts.to_vec(), 856 | used_plane: false, 857 | inverted_plane: plane_inverted, 858 | area_calc: 0.0, 859 | }; 860 | poly.area_calc = poly.area(); 861 | poly 862 | }) 863 | .collect::>(); 864 | 865 | let mut root = DIFBSPNode::from_brushes(bsp_polygons); 866 | if unsafe { BSP_CONFIG.split_method } == SplitMethod::None { 867 | root.front = Some(Box::new(DIFBSPNode { 868 | back: None, 869 | brush_list: Vec::new(), 870 | front: None, 871 | plane_index: None, 872 | solid: false, 873 | avail_planes: Vec::new(), 874 | })); 875 | root.back = Some(Box::new(DIFBSPNode { 876 | back: None, 877 | brush_list: Vec::new(), 878 | front: None, 879 | plane_index: None, 880 | solid: false, 881 | avail_planes: Vec::new(), 882 | })); 883 | root.plane_index = Some(0); 884 | } else { 885 | let mut used_planes: HashSet = HashSet::new(); 886 | root.split_new_impl(&plane_list, &mut used_planes, 0, progress_report_callback); 887 | } 888 | (root, plane_list) 889 | } 890 | --------------------------------------------------------------------------------