Git Product home page Git Product logo

t3d2map's Introduction

t3d2map

Convert WhelOfTime / Unreal1 maps in t3d to Valve .map files

Work in progress... The issue with this conversion is that map files are only composed of convex brushes while Unreak/t3d starts with a filled world and use CSG operations to excavate from it resulting in mostly concave geometry. This converter makes use of CGAL algorithms to achieve this.

map file created with Unreal 1 engine and opened in TrenchBroom

This is however a very heavy work even for very simple maps because CSG and triangulation create many many vertices and there is no optimization. Perhaps a better solution would be to parse Unreal compiled maps and extract the BSP tree which already contains a stream of convex polygons ready to be imported in any other engine without running costly CSG.

  • CSG : constructive solid geometry : boolean operations resulting in a very concave mesh
  • BSP : Binary Space Partitionning : a way to represent a concave mesh with a set of convex meshes by partitionning the concave mesh in various places until this makes a convex mesh.

This repository makes use of CGAL to perform the CSG to get the concave mesh. This in itself is error prone because the concavity of Unreal Engine maps is very concave. Also planes that splits zones cannot be represented. Then there is an improvized BSP cutting algorithm (cut at random places until there are no more concave meshes) that is very inefficient.

T3D file format stores the CSG tree but the compiled Unreal Engine maps have the pre-computed BSP tree that might be exported. This would be a better strategy. See https://www.acordero.org/projects/unreal-tournament-package-tool/ which can open Unreal Engine Packages and view polys.

C++ Build

Conan build does not work because of conan v2, until it works, ensure CGAL is installed system-wide. Once it works, perhaps this will be how to install the CGAL dependency:

conan profile detect --force
conan install . --output-folder=build --build=missing

Build with CMake:

cmake .
make

Run

Use:

./t3d2map ./examples/test.t3d

Or:

make && rm -f dbg_*.obj && ./t3d2map --debug-mesh --convert ../WoT-conversion/textures/CONVERSION --game WoT -o examples/test.map examples/test.t3d

Or:

make && for t3d in ~/Games/wineprefix/WoT/drive_c/*.T3D; do ./t3d2map --obj "${t3d%.*}.obj" "$t3d"; done

Usage

The --game flag is only used to populate the // Game: xxx at the top of the file to ensure TrenchBroom pick up the correct game settings when opening the file.

The CONVERSION file via the --convert flag is a file containing a texture for each line. For each texture it has 3 fields separated by spaces:

  • the texture name as it appears in t3d file
  • the texture package name for the worldspawn _tb_textures
  • the texture name for the .map file

example:

BannWall1918 textures/AesSedaiT.utx AesSedaiT.utx/BannWall1918
BannWall1919 textures/AesSedaiT.utx AesSedaiT.utx/BannWall1919
BookBook1752 textures/AesSedaiT.utx AesSedaiT.utx/BookBook1752
BookBook1755_m textures/AesSedaiT.utx AesSedaiT.utx/BookBook1755_m

Roadmap:

  • Parse a list of T3D files representing (convex?) polygons
  • Generate .obj file for each .t3d file
  • Check that this is really the BSP tree for a map... not sure anymore
  • If the polygons are all convex, generate a map file
  • Add code to parse Unreal Engine packages instead of t3d files

  • Parse t3d files
  • Parse brushes
  • Implement actor translation
  • Implement actor rotation
  • Implement actor scaling
  • Generate CSG concave mesh
    • Handle plane meshes (exclude from CSG tree). Currently it voids the entire scene.
  • Decompose in convex meshes
  • Split disjoined meshes while keeping the uvmap
  • Generate .map file
    • Handle rounding errors producing vectors with all 3 coordinates at 0
  • Parse texture UV mapping
  • Generate a list of texture mappings and associate each face with a mapping
    • texture name
    • U vector
    • V vector
    • Texture origin (t3d.origin + uvector * (upan or 0) + vvector * (vpan or 0))
  • Handle two coplanar faces in the same resulting mesh having different texture
    • coplanar polygons on a mesh must all share the same texture mapping, else those polygons must be split in separate meshes.
  • Generate UV mapping to .map files
    • transform the U and V vectors to unit vectors and extract the scale factor for "X scale" and "Y scale"
    • set rotation to 0 (embedded within the U, V vectors)
    • transform the texture origin to U and V offsets discarding the component orthogonal to U and V as this is not necessary
    • try multiple texture and look to see if visually it matches

CGAL algorithm

Note: I believe there is no way to have face properties in a Nef_Polyhedra. Given this limitation there are two options:

  • find a way to reconstruct the mapping of output face properties from the original input mesh face without keeping a track of it during boolean operation or decomposition. Pro: use the Nef_Polyhedra boolean algorithms that seems more powerful than mesh algorithms
    • store face properties (texture and all vertices) indexed by each of their vertices
    • in the output mesh, for each triangular face
      • get all the face properties for all their 3 vertices. Do not keep duplicate face properties
      • for each face property, check if the current face is a subset of the original face
        • apply rotation and translation to both to eliminate z coordinate and work in 2D
        • use 2D boolean algorithm to check inclusion / intersection. Possibly full inclusion might not be met because of floating point errors.
      • if multiple faces matches, keep the face for which the (intersection surface - excluded surface) is the greatest compared to the original surface
      • assign the face property to the face
      • assign the face properties to the face vertices that did not reference it
    • keep doing the previous iteration step until no more faces are being assigned
    • assign the rest of faces with null properties
    • if a face is split in a non contiguous manner, the non contiguous parts will not receive face properties. Solution: face properties should be indexed by their normal and their distance from the origin. Because of floating point errors, it is possible that generated faces are not exactly coplanar. Comparaison must be performed with some epsilon, and probably hash based stucture is not going to fit.
    • this is possibly expansive
  • Use custom algorithm for convex decomposition and perform everything with the Surface_mesh objects. Use clip https://doc.cgal.org/latest/Polygon_mesh_processing/index.html#title21

General algorithm:

  • if the corefine boolean algorithms allows to work on a Nef_Polyhedra, use this data structure for boolean operation, else use Surface_mesh or a data structure that is compatible
  • start with a world object that spans the world bounding box plus some padding, no texture (or transparent texture)
  • for each brush in the t3d file
  • copy the world to a polyhedron: CGAL/cgal#5431 (if needed). Copy face properties.
  • create a convex decomposition of the world polyhedra, make sure that the face properties are conserved
  • alternative: use hand written algorithm to decompose a Surface_mesh into convex meshes
  • new faces should be assigned the empty texture property (same as original world mesh)
  • export to .map

resources

.map file format

T3D format:

Convex decomposition:

Boolean operations:

tools:


UV Mapping

T3D format defines UV mapping for each face with 2 vectors (U, V) and 2 numbers (UPan, VPan). Both U and V must be orthogonal to the face normal vector (hence be "coplanar" with the face). The 2D point defined by U and V represents the origin on the 2D texture coordinates (pixels). U=2 means that the texture starts at the 3rd pixel on the left.

The texture is applied to the face such that on 3D space the U vector represents the horizontal axis of the texture image, and the V vector represents the vertical axis. For a texture not to be deformed the U and V vector must be orthogonal. The length of the vectors represents the length of the texture. A unit vector represents the full width or height of the texture.

The .map format represents convex meshes with a set of intersecting planes, each plane represented by 3 points (not necessarily on the surface mesh) organized so the normal vector points out. Original texture information was subject to incoherences so Valve updated the format to remove all ambiguity.

Quake standard format for UV mapping represents for each face after the texture name real numbers for : X offset, Y offset, rotation, X scale, Y scale. Texture are applied naturally on faces that are orthogonal to the X, Y or Z axis. If a face is not orthogonal to the X, Y or Z axis, the texture will be stretched in some way.

Valve texture format represents UV mapping after the texture name with:

  • U 3d vector and U offset (from world origin 0,0,0 ?)
  • V 3d vector and V offset (from world origin 0,0,0 ?)
  • rotation
  • X scale
  • Y scale

t3d2map's People

Contributors

mildred avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

qw-ctf

t3d2map's Issues

Compile issues

I'm getting issues due to either g++ version or cgal version. Some of the classes are deemed to not implement all functionality. Would you mind adding a GitHub action as that serves both as testing for you, as well as executable build env documentation for others.

The steps in the docs dont help.

Fwiw I'm on Ubuntu 23.10.

tried with:

  • g++ (Ubuntu 13.2.0-4ubuntu3) 13.2.0
  • clang 16.0.6 (15)
  • clang 17.0.2 (1~exp1ubuntu2.1)

Error:

/usr/bin/c++ -DBOOST_ALL_NO_LIB -DCGAL_USE_BASIC_VIEWER -DCGAL_USE_GMPXX=1  -std=c++20 -g -O0 -g -std=gnu++20 -fdiagnostics-color=always -frounding-math -MD -MT CMakeFiles/t3d2map.dir/src/main.cpp.o -MF CMakeFiles/t3d2map.dir/src/main.cpp.o.d -o CMakeFiles/t3d2map.dir/src/main.cpp.o -c /home/daniel/Development/home/quake/t3d2map/src/main.cpp
In file included from /usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polygon_mesh.h:19,
                 from /usr/include/CGAL/Polygon_mesh_processing/triangulate_hole.h:20,
                 from /home/daniel/Development/home/quake/t3d2map/src/main.cpp:17:
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h: In instantiation of ‘bool CGAL::internal::triangulate_hole_polyline_with_cdt(const PointRange&, Tracer&, Visitor&, const Validity_checker&, const Traits&, typename Traits::FT) [with PointRange = std::vector<CGAL::Point_3<CGAL::Epeck> >; Tracer = Tracer_polyline_incomplete<CGAL::Triple<int, int, int>, std::back_insert_iterator<std::vector<CGAL::Triple<int, int, int> > >, std::back_insert_iterator<std::vector<std::pair<int, int> > > >; Visitor = const FaceUVMapCopyVisitor; Validity_checker = CGAL::Polygon_mesh_processing::triangulate_hole_polyline<std::vector<CGAL::Point_3<CGAL::Epeck> >, std::vector<CGAL::Point_3<CGAL::Epeck> >, std::back_insert_iterator<std::vector<CGAL::Triple<int, int, int> > >, CGAL::Named_function_parameters<bool, CGAL::internal_np::use_2d_constrained_delaunay_triangulation_t, CGAL::Named_function_parameters<FaceUVMapCopyVisitor, CGAL::internal_np::visitor_t, CGAL::internal_np::No_property> > >(const std::vector<CGAL::Point_3<CGAL::Epeck> >&, const std::vector<CGAL::Point_3<CGAL::Epeck> >&, std::back_insert_iterator<std::vector<CGAL::Triple<int, int, int> > >, const CGAL::Named_function_parameters<bool, CGAL::internal_np::use_2d_constrained_delaunay_triangulation_t, CGAL::Named_function_parameters<FaceUVMapCopyVisitor, CGAL::internal_np::visitor_t, CGAL::internal_np::No_property> >&)::Always_valid; Traits = CGAL::Epeck; typename Traits::FT = CGAL::Lazy_exact_nt<__gmp_expr<__mpq_struct [1], __mpq_struct [1]> >]’:
/usr/include/CGAL/Polygon_mesh_processing/triangulate_hole.h:780:45:   required from ‘OutputIterator CGAL::Polygon_mesh_processing::triangulate_hole_polyline(const PointRange1&, const PointRange2&, OutputIterator, const NamedParameters&) [with PointRange1 = std::vector<CGAL::Point_3<CGAL::Epeck> >; PointRange2 = std::vector<CGAL::Point_3<CGAL::Epeck> >; OutputIterator = std::back_insert_iterator<std::vector<CGAL::Triple<int, int, int> > >; NamedParameters = CGAL::Named_function_parameters<bool, CGAL::internal_np::use_2d_constrained_delaunay_triangulation_t, CGAL::Named_function_parameters<FaceUVMapCopyVisitor, CGAL::internal_np::visitor_t, CGAL::internal_np::No_property> >]’
/usr/include/CGAL/Polygon_mesh_processing/triangulate_hole.h:819:37:   required from ‘OutputIterator CGAL::Polygon_mesh_processing::triangulate_hole_polyline(const PointRange&, OutputIterator, const CGAL::Named_function_parameters<NP_T, NP_Tag, NP_Base>&) [with PointRange = std::vector<CGAL::Point_3<CGAL::Epeck> >; OutputIterator = std::back_insert_iterator<std::vector<CGAL::Triple<int, int, int> > >; NP_T = bool; NP_Tag = CGAL::internal_np::use_2d_constrained_delaunay_triangulation_t; NP_Base = CGAL::Named_function_parameters<FaceUVMapCopyVisitor, CGAL::internal_np::visitor_t, CGAL::internal_np::No_property>]’
/usr/include/CGAL/Polygon_mesh_processing/triangulate_faces.h:96:35:   required from ‘bool CGAL::Polygon_mesh_processing::internal::Triangulate_polygon_mesh_modifier<PolygonMesh>::triangulate_face_with_hole_filling(face_descriptor, PolygonMesh&, VPM, Visitor, const NamedParameters&) [with VPM = CGAL::Surface_mesh<CGAL::Point_3<CGAL::Epeck> >::Property_map<CGAL::SM_Vertex_index, CGAL::Point_3<CGAL::Epeck> >; Visitor = FaceUVMapCopyVisitor; NamedParameters = CGAL::Named_function_parameters<FaceUVMapCopyVisitor, CGAL::internal_np::visitor_t, CGAL::internal_np::No_property>; PolygonMesh = CGAL::Surface_mesh<CGAL::Point_3<CGAL::Epeck> >; face_descriptor = CGAL::SM_Face_index]’
/usr/include/CGAL/Polygon_mesh_processing/triangulate_faces.h:233:46:   required from ‘bool CGAL::Polygon_mesh_processing::internal::Triangulate_polygon_mesh_modifier<PolygonMesh>::operator()(face_descriptor, PolygonMesh&, const NamedParameters&) [with NamedParameters = CGAL::Named_function_parameters<FaceUVMapCopyVisitor, CGAL::internal_np::visitor_t, CGAL::internal_np::No_property>; PolygonMesh = CGAL::Surface_mesh<CGAL::Point_3<CGAL::Epeck> >; face_descriptor = CGAL::SM_Face_index]’
/usr/include/CGAL/Polygon_mesh_processing/triangulate_faces.h:365:17:   required from ‘bool CGAL::Polygon_mesh_processing::triangulate_faces(FaceRange, PolygonMesh&, const NamedParameters&) [with FaceRange = CGAL::Iterator_range<CGAL::Surface_mesh<CGAL::Point_3<CGAL::Epeck> >::Index_iterator<CGAL::SM_Face_index> >; PolygonMesh = CGAL::Surface_mesh<CGAL::Point_3<CGAL::Epeck> >; NamedParameters = CGAL::Named_function_parameters<FaceUVMapCopyVisitor, CGAL::internal_np::visitor_t, CGAL::internal_np::No_property>]’
/usr/include/CGAL/Polygon_mesh_processing/triangulate_faces.h:423:27:   required from ‘bool CGAL::Polygon_mesh_processing::triangulate_faces(PolygonMesh&, const NamedParameters&) [with PolygonMesh = CGAL::Surface_mesh<CGAL::Point_3<CGAL::Epeck> >; NamedParameters = CGAL::Named_function_parameters<FaceUVMapCopyVisitor, CGAL::internal_np::visitor_t, CGAL::internal_np::No_property>]’
/home/daniel/Development/home/quake/t3d2map/src/main.cpp:1001:29:   required from here
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1349:11: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘start_planar_phase’
 1349 |   visitor.start_planar_phase();
      |   ~~~~~~~~^~~~~~~~~~~~~~~~~~
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1393:13: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘end_planar_phase’
 1393 |     visitor.end_planar_phase(false);
      |     ~~~~~~~~^~~~~~~~~~~~~~~~
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1406:13: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘end_planar_phase’
 1406 |     visitor.end_planar_phase(false);
      |     ~~~~~~~~^~~~~~~~~~~~~~~~
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1412:17: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘end_planar_phase’
 1412 |         visitor.end_planar_phase(false);
      |         ~~~~~~~~^~~~~~~~~~~~~~~~
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1421:13: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘end_planar_phase’
 1421 |     visitor.end_planar_phase(false);
      |     ~~~~~~~~^~~~~~~~~~~~~~~~
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1461:13: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘end_planar_phase’
 1461 |     visitor.end_planar_phase(false);
      |     ~~~~~~~~^~~~~~~~~~~~~~~~
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1491:13: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘end_planar_phase’
 1491 |     visitor.end_planar_phase(false);
      |     ~~~~~~~~^~~~~~~~~~~~~~~~
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1509:17: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘end_planar_phase’
 1509 |         visitor.end_planar_phase(false);
      |         ~~~~~~~~^~~~~~~~~~~~~~~~
/usr/include/CGAL/Polygon_mesh_processing/internal/Hole_filling/Triangulate_hole_polyline.h:1518:11: error: ‘const class FaceUVMapCopyVisitor’ has no member named ‘end_planar_phase’
 1518 |   visitor.end_planar_phase(true);
      |   ~~~~~~~~^~~~~~~~~~~~~~~~

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.