mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 06:09:57 +00:00
jak3: custom level support (#3522)
This commit is contained in:
parent
c12a5d777c
commit
a61f24a168
90
custom_levels/jak3/test-zone/test-zone.jsonc
Normal file
90
custom_levels/jak3/test-zone/test-zone.jsonc
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
// The "in-game" name of the level. Should be lower case, with dashes (GOAL symbol name)
|
||||
// the name of this file, and the folder this file is in must have the same name.
|
||||
"long_name": "test-zone",
|
||||
// The file name, should be upper case and 8 characters or less.
|
||||
"iso_name": "TESTZONE",
|
||||
// The nickname, should be exactly 3 characters
|
||||
"nickname": "tsz", // 3 char name, all lowercase
|
||||
// Background mesh file.
|
||||
// Must have vertex colors. Use the blender cycles renderer, bake, diffuse, uncheck color,
|
||||
// and bake to vertex colors. For now, only the first vertex color group is used, so make sure you
|
||||
// only have 1.
|
||||
"gltf_file": "custom_levels/jak3/test-zone/test-zone2.glb",
|
||||
|
||||
// automatically set wall vs. ground based on angle. Useful if you don't want to assign this yourself
|
||||
"automatic_wall_detection": true,
|
||||
"automatic_wall_angle": 45.0,
|
||||
|
||||
// if your mesh has triangles with incorrect orientation, set this to make all collision mesh triangles double sided
|
||||
// this makes collision 2x slower and bigger, so only use if really needed
|
||||
"double_sided_collide": false,
|
||||
|
||||
// available res-lump tag types:
|
||||
// integer types: int32, uint32, enum-int32, enum-uint32
|
||||
// float types: float, meters (1 meter = 4096.0 units), degrees (65536.0 = 360°)
|
||||
// vector types: vector (normal floats), vector4m (meters), vector3m (meters with w set to 1.0), vector-vol (normal floats with w in meters), movie-pos (meters with w in degrees)
|
||||
// special types: symbol, type, string, eco-info, cell-info, buzzer-info, water-height
|
||||
//
|
||||
// examples:
|
||||
//
|
||||
// adds a float tag 'spring-height' with value of 200 meters (1 meter = 4096.0 units):
|
||||
// "spring-height": ["meters", 200.0]
|
||||
//
|
||||
// adds a degrees tag 'rotoffset':
|
||||
// "rotoffset": ["degrees", -45.0]
|
||||
//
|
||||
// adds a movie-pos tag:
|
||||
// "movie-pos": ["movie-pos", [100.22, -25.3, 99.5, 180.0]]
|
||||
//
|
||||
// adds an enum tag 'options':
|
||||
// "options": ["enum-int32", "(fact-options large)"]
|
||||
//
|
||||
// adds a water-height tag:
|
||||
// "water-height": ["water-height", 25.0, 0.5, 2.0, "(water-flags can-swim can-wade)"]
|
||||
//
|
||||
// adds an eco-info tag:
|
||||
// "eco-info": ["eco-info", "(pickup-type health)", 2]
|
||||
//
|
||||
// adds a 'type' tag (using the "symbol" and "string" lump types works the same way):
|
||||
// "spawn-types": ["type", "spyder", "juicer"]
|
||||
|
||||
// The base actor id for your custom level. If you have multiple levels, this should be unique!
|
||||
"base_id": 100,
|
||||
|
||||
// All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file.
|
||||
// "art_groups": [],
|
||||
|
||||
// Any textures you want to include in your custom level.
|
||||
// This is mainly useful for textures which are not in the common level files and have no art group associated with them.
|
||||
// To get a list of all the textures, you can extract all of the game's textures
|
||||
// by setting "save_texture_pngs" to true in the decompiler config.
|
||||
// "textures": [],
|
||||
|
||||
"actors" : [
|
||||
{
|
||||
"trans": [-15.2818, 15.2461, 17.1360], // translation
|
||||
"etype": "crate", // actor type
|
||||
"game_task": "(game-task none)", // associated game task (for powercells, etc)
|
||||
"kill_mask": 0,
|
||||
"quat": [0, 0, 0, 1], // quaternion
|
||||
"bsphere": [-15.2818, 15.2461, 17.1360, 10], // bounding sphere
|
||||
"lump": {
|
||||
"name": "test-crate",
|
||||
"eco-info": ["eco-info", "(pickup-type gem)", 1]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"trans": [-5.4630, 17.4553, 1.6169], // translation
|
||||
"etype": "eco-yellow", // actor type
|
||||
"game_task": "(game-task none)", // associated game task (for powercells, etc)
|
||||
"kill_mask": 0,
|
||||
"quat": [0, 0, 0, 1], // quaternion
|
||||
"bsphere": [-5.4630, 17.4553, 1.6169, 10], // bounding sphere
|
||||
"lump": {
|
||||
"name": "test-eco"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
BIN
custom_levels/jak3/test-zone/test-zone2.glb
Normal file
BIN
custom_levels/jak3/test-zone/test-zone2.glb
Normal file
Binary file not shown.
8
custom_levels/jak3/test-zone/testzone.gd
Normal file
8
custom_levels/jak3/test-zone/testzone.gd
Normal file
@ -0,0 +1,8 @@
|
||||
;; DGO definition file for Awful Village level
|
||||
;; We use the convention of having a longer DGO name for levels without precomputed visibility.
|
||||
|
||||
;; the actual file name still needs to be 8.3
|
||||
("TSZ.DGO"
|
||||
("test-zone.go"
|
||||
)
|
||||
)
|
@ -2306,10 +2306,32 @@
|
||||
(dotimes (s1-1 s2-3)
|
||||
(set! sv-48 (-> s3-4 data s1-1))
|
||||
(cond
|
||||
((and (is-object-visible? s4-1 (-> sv-48 vis-id))
|
||||
;; og:preserve-this entity blacklist
|
||||
((and (#if PC_PORT (or (with-pc (and (not (-> *pc-settings* ps2-actor-vis?))
|
||||
;; ban specific entities
|
||||
; (not (let ((name (res-lump-struct (-> sv-48 entity) 'name string)))
|
||||
; (nmember name '("fort-entry-gate-11"
|
||||
; "fort-elec-gate-11"
|
||||
; "fort-elec-gate-12"
|
||||
; "com-airlock-outer-13"
|
||||
; "com-airlock-inner-41"
|
||||
; "under-lift-4"
|
||||
; "under-locking-1"
|
||||
; "under-locking-2"))))
|
||||
))
|
||||
(is-object-visible? s4-1 (-> sv-48 vis-id)))
|
||||
(is-object-visible? s4-1 (-> sv-48 vis-id)))
|
||||
(not (logtest? (-> sv-48 perm status) (entity-perm-status bit-9 bit-10)))
|
||||
(not (logtest? (-> sv-48 kill-mask) sv-32))
|
||||
(or (-> s4-1 vis-info 0) (< (vector-vector-distance (-> sv-48 trans) sv-16) (-> sv-48 vis-dist)))
|
||||
;; og:preserve-this PC port note: added this extra check to fix level types being used after deload because of bad entity placement
|
||||
(#if PC_PORT
|
||||
(or (not (type-type? (-> sv-48 entity type) entity-actor)) (and (valid? (-> (the-as entity-actor (-> sv-48 entity)) etype) type "entity-type-check etype" #f *stdcon*)
|
||||
(valid? (-> (the-as entity-actor (-> sv-48 entity)) etype symbol) symbol "entity-type-check symbol" #f *stdcon*)
|
||||
(valid? (-> (the-as entity-actor (-> sv-48 entity)) etype symbol value) type "entity-type-check value" #f *stdcon*)
|
||||
(= (-> (the-as entity-actor (-> sv-48 entity)) etype) (-> (the-as entity-actor (-> sv-48 entity)) etype symbol value))
|
||||
))
|
||||
#f)
|
||||
)
|
||||
(when (not (or (-> sv-48 process) (logtest? (-> sv-48 perm status) (entity-perm-status bit-0 dead)) s0-0))
|
||||
(birth! (-> sv-48 entity))
|
||||
|
@ -20658,3 +20658,60 @@
|
||||
4amy
|
||||
)
|
||||
)
|
||||
|
||||
;; og:preserve-this added test-zone level
|
||||
(define test-zone
|
||||
(new 'static 'level-load-info
|
||||
:name 'test-zone
|
||||
:visname 'test-zone-vis
|
||||
:nickname 'tsz
|
||||
:dbname 'test-zone
|
||||
:taskname 'default
|
||||
:index #x128
|
||||
:master-level #f
|
||||
:level-flags (level-flags lf8 lf9 lf11 lf12)
|
||||
:packages '()
|
||||
:run-packages '("common")
|
||||
:memory-mode (level-memory-mode large)
|
||||
:music-bank #f
|
||||
:extra-sound-bank #f
|
||||
:mood-func 'update-mood-default
|
||||
:special-mood #f
|
||||
:ocean #f
|
||||
:ocean-alpha 1.0
|
||||
:priority 100
|
||||
:draw-priority 10.0
|
||||
:base-task-mask (task-mask task0)
|
||||
:bigmap-id (bigmap-id no-map)
|
||||
:continues '((new 'static 'continue-point
|
||||
:name "test-zone-start"
|
||||
:level 'test-zone
|
||||
:trans (static-vectorm 0 10 10)
|
||||
:camera-trans (static-vectorm 0 1 0)
|
||||
:quat (new 'static 'vector4h :data (new 'static 'array int16 4 0 #x7f9f 0 -2460))
|
||||
:camera-rot (new 'static 'array int16 9 -32717 0 -1749 #xd1 #x7f0c -3954 #x6c8 -3958 -32478)
|
||||
:on-goto #f
|
||||
:vis-nick 'tsz
|
||||
:vehicle-type #x1b
|
||||
:want-count 3
|
||||
:want (new 'static 'inline-array level-buffer-state-small 3
|
||||
(new 'static 'level-buffer-state-small :name 'wasall :display? 'display)
|
||||
(new 'static 'level-buffer-state-small :name 'waswide :display? 'display)
|
||||
(new 'static 'level-buffer-state-small :name 'test-zone :display? 'display)
|
||||
)
|
||||
:want-sound (new 'static 'array symbol 3 #f #f #f)
|
||||
)
|
||||
)
|
||||
:callback-list '()
|
||||
:borrow #f
|
||||
:bottom-height (meters -150)
|
||||
:fog-height (meters 80)
|
||||
:max-rain 1.0
|
||||
:fog-mult 1.0
|
||||
:mood-range (new 'static 'mood-range :max-cloud 1.0 :min-fog 0.5 :max-fog 1.0)
|
||||
)
|
||||
)
|
||||
|
||||
(#when PC_PORT
|
||||
(cons! *level-load-list* 'test-zone)
|
||||
)
|
@ -760,7 +760,8 @@
|
||||
)
|
||||
)
|
||||
(let ((s5-5 (level-get *level* (-> arg0 level))))
|
||||
(when s5-5
|
||||
;; og:preserve-this don't wait for vis if level doesn't have it
|
||||
(when (and s5-5 (-> s5-5 vis-info 0))
|
||||
(while (and (-> *level* vis?) (-> s5-5 vis-info 0) (= (-> s5-5 all-visible?) 'loading))
|
||||
(suspend)
|
||||
)
|
||||
|
@ -394,6 +394,17 @@
|
||||
(cgo-file "lwlandm.gd" common-dep)
|
||||
(cgo-file "lwstdpck.gd" common-dep)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Example Custom Level
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Set up the build system to build the level geometry
|
||||
;; this path is relative to the custom_levels/jak3 folder
|
||||
;; it should point to the .jsonc file that specifies the level.
|
||||
(build-custom-level "test-zone")
|
||||
;; the DGO file
|
||||
(custom-level-cgo "TSZ.DGO" "test-zone/testzone.gd")
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ANIMATIONS
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -235,3 +235,20 @@
|
||||
(copy-objs ,@objs)))
|
||||
)
|
||||
))
|
||||
|
||||
(defun custom-level-cgo (output-name desc-file-name)
|
||||
"Add a CGO with the given output name (in $OUT/iso) and input name (in custom_levels/jak3/)"
|
||||
(let ((out-name (string-append "$OUT/iso/" output-name)))
|
||||
(defstep :in (string-append "custom_levels/jak3/" desc-file-name)
|
||||
:tool 'dgo
|
||||
:out `(,out-name)
|
||||
)
|
||||
(set! *all-cgos* (cons out-name *all-cgos*))
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro build-custom-level (name)
|
||||
(let* ((path (string-append "custom_levels/jak3/" name "/" name ".jsonc")))
|
||||
`(defstep :in ,path
|
||||
:tool 'build-level3
|
||||
:out '(,(string-append "$OUT/obj/" name ".go")))))
|
@ -8,20 +8,25 @@ add_library(compiler
|
||||
build_level/common/build_level.cpp
|
||||
build_level/jak1/build_level.cpp
|
||||
build_level/jak2/build_level.cpp
|
||||
build_level/jak3/build_level.cpp
|
||||
build_level/collide/jak1/collide_bvh.cpp
|
||||
build_level/collide/jak1/collide_drawable.cpp
|
||||
build_level/collide/jak1/collide_pack.cpp
|
||||
build_level/collide/jak2/collide.cpp
|
||||
build_level/collide/jak3/collide.cpp
|
||||
build_level/common/color_quantization.cpp
|
||||
build_level/common/Entity.cpp
|
||||
build_level/jak1/Entity.cpp
|
||||
build_level/jak2/Entity.cpp
|
||||
build_level/jak3/Entity.cpp
|
||||
build_level/common/FileInfo.cpp
|
||||
build_level/jak1/FileInfo.cpp
|
||||
build_level/jak2/FileInfo.cpp
|
||||
build_level/jak3/FileInfo.cpp
|
||||
build_level/common/gltf_mesh_extract.cpp
|
||||
build_level/jak1/LevelFile.cpp
|
||||
build_level/jak2/LevelFile.cpp
|
||||
build_level/jak3/LevelFile.cpp
|
||||
build_level/common/ResLump.cpp
|
||||
build_level/common/Tfrag.cpp
|
||||
build_level/jak1/ambient.cpp
|
||||
|
@ -312,3 +312,222 @@ struct CollideFace {
|
||||
PatSurface pat;
|
||||
};
|
||||
} // namespace jak2
|
||||
|
||||
namespace jak3 {
|
||||
struct PatSurface {
|
||||
enum class Mode { GROUND = 0, WALL = 1, OBSTACLE = 2, HALFPIPE = 3, MAX_MODE = 4 };
|
||||
enum class Material {
|
||||
NONE = 0,
|
||||
ICE = 1,
|
||||
QUICKSAND = 2,
|
||||
WATERBOTTOM = 3,
|
||||
TAR = 4,
|
||||
SAND = 5,
|
||||
WOOD = 6,
|
||||
GRASS = 7,
|
||||
PCMETAL = 8,
|
||||
SNOW = 9,
|
||||
DEEPSNOW = 10,
|
||||
HOTCOALS = 11,
|
||||
LAVA = 12,
|
||||
CRWOOD = 13,
|
||||
GRAVEL = 14,
|
||||
DIRT = 15,
|
||||
METAL = 16,
|
||||
STRAW = 17,
|
||||
TUBE = 18,
|
||||
SWAMP = 19,
|
||||
STOPPROJ = 20,
|
||||
ROTATE = 21,
|
||||
NEUTRAL = 22,
|
||||
STONE = 23,
|
||||
CRMETAL = 24,
|
||||
CARPET = 25,
|
||||
GRMETAL = 26,
|
||||
SHMETAL = 27,
|
||||
HDWOOD = 28,
|
||||
SQUISH = 29,
|
||||
MHSHROOM = 30,
|
||||
FOREST = 31,
|
||||
MHSWAMP = 32,
|
||||
DMAKER = 33,
|
||||
MAX_MATERIAL = 34
|
||||
};
|
||||
|
||||
enum class Event {
|
||||
NONE = 0,
|
||||
DEADLY = 1,
|
||||
ENDLESSFALL = 2,
|
||||
BURN = 3,
|
||||
DEADLYUP = 4,
|
||||
BURNUP = 5,
|
||||
MELT = 6,
|
||||
SLIDE = 7,
|
||||
LIP = 8,
|
||||
LIPRAMP = 9,
|
||||
SHOCK = 10,
|
||||
SHOCKUP = 11,
|
||||
HIDE = 12,
|
||||
RAIL = 13,
|
||||
SLIPPERY = 14,
|
||||
DRAG = 15,
|
||||
WATERFLOOR = 16,
|
||||
HANG = 17,
|
||||
FRY = 18,
|
||||
SLIME = 19,
|
||||
MAX_EVENT = 20
|
||||
};
|
||||
|
||||
void set_noentity(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 0);
|
||||
} else {
|
||||
val &= ~(1 << 0);
|
||||
}
|
||||
}
|
||||
bool get_noentity() const { return val & (1 << 0); }
|
||||
|
||||
void set_nocamera(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 1);
|
||||
} else {
|
||||
val &= ~(1 << 1);
|
||||
}
|
||||
}
|
||||
bool get_nocamera() const { return val & (1 << 1); }
|
||||
|
||||
void set_noedge(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 2);
|
||||
} else {
|
||||
val &= ~(1 << 2);
|
||||
}
|
||||
}
|
||||
bool get_noedge() const { return val & (1 << 2); }
|
||||
|
||||
void set_nogrind(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 3);
|
||||
} else {
|
||||
val &= ~(1 << 3);
|
||||
}
|
||||
}
|
||||
bool get_nogrind() const { return val & (1 << 3); }
|
||||
|
||||
void set_nojak(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 4);
|
||||
} else {
|
||||
val &= ~(1 << 4);
|
||||
}
|
||||
}
|
||||
bool get_nojak() const { return val & (1 << 4); }
|
||||
|
||||
void set_noboard(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 5);
|
||||
} else {
|
||||
val &= ~(1 << 5);
|
||||
}
|
||||
}
|
||||
bool get_noboard() const { return val & (1 << 5); }
|
||||
|
||||
void set_nopilot(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 6);
|
||||
} else {
|
||||
val &= ~(1 << 6);
|
||||
}
|
||||
}
|
||||
bool get_nopilot() const { return val & (1 << 6); }
|
||||
|
||||
void set_mode(Mode mode) {
|
||||
val &= ~(0b111 << 7);
|
||||
val |= ((u32)mode << 7);
|
||||
}
|
||||
Mode get_mode() const { return (Mode)(0b111 & (val >> 7)); }
|
||||
|
||||
void set_material(Material mat) {
|
||||
val &= ~(0b111111 << 10);
|
||||
val |= ((u32)mat << 10);
|
||||
}
|
||||
Material get_material() const { return (Material)(0b111111 & (val >> 10)); }
|
||||
|
||||
void set_nolineofsight(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 16);
|
||||
} else {
|
||||
val &= ~(1 << 16);
|
||||
}
|
||||
}
|
||||
bool get_nolineofsight() const { return val & (1 << 16); }
|
||||
|
||||
void set_event(Event ev) {
|
||||
val &= ~(0b111111 << 18);
|
||||
val |= ((u32)ev << 18);
|
||||
}
|
||||
Event get_event() const { return (Event)(0b111111 & (val >> 18)); }
|
||||
|
||||
void set_probe(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 24);
|
||||
} else {
|
||||
val &= ~(1 << 24);
|
||||
}
|
||||
}
|
||||
bool get_probe() const { return val & (1 << 24); }
|
||||
|
||||
void set_nomech(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 25);
|
||||
} else {
|
||||
val &= ~(1 << 25);
|
||||
}
|
||||
}
|
||||
bool get_nomech() const { return val & (1 << 25); }
|
||||
|
||||
void set_noproj(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 26);
|
||||
} else {
|
||||
val &= ~(1 << 26);
|
||||
}
|
||||
}
|
||||
bool get_noproj() const { return val & (1 << 26); }
|
||||
|
||||
void set_noendlessfall(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 27);
|
||||
} else {
|
||||
val &= ~(1 << 27);
|
||||
}
|
||||
}
|
||||
bool get_noendlessfall() const { return val & (1 << 27); }
|
||||
|
||||
void set_noprobe(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 28);
|
||||
} else {
|
||||
val &= ~(1 << 28);
|
||||
}
|
||||
}
|
||||
bool get_noprobe() const { return val & (1 << 28); }
|
||||
|
||||
void set_board(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 4);
|
||||
} else {
|
||||
val &= ~(1 << 4);
|
||||
}
|
||||
}
|
||||
bool get_board() const { return val & (1 << 4); }
|
||||
|
||||
bool operator==(const PatSurface& other) const { return val == other.val; }
|
||||
u32 val = 0;
|
||||
};
|
||||
|
||||
struct CollideFace {
|
||||
math::Vector3f v[3];
|
||||
PatSurface pat;
|
||||
};
|
||||
} // namespace jak3
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
|
||||
namespace jak2 {
|
||||
/*!
|
||||
* An axis-aligned bounding box
|
||||
*/
|
||||
@ -1156,3 +1157,4 @@ size_t add_to_object_file(const CollideHash& hash, DataObjectGenerator& gen) {
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace jak2
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include "goalc/build_level/collide/common/collide_common.h"
|
||||
|
||||
class DataObjectGenerator;
|
||||
namespace jak2 {
|
||||
// High-level collision system idea:
|
||||
// Each level has a single collide-hash object storing all collision data.
|
||||
// The mesh is divided into "fragments". Each fragment is made up of triangles.
|
||||
@ -44,7 +46,7 @@ struct CollideBucket {
|
||||
};
|
||||
|
||||
struct CollideFragment {
|
||||
std::vector<jak2::PatSurface> pat_array;
|
||||
std::vector<PatSurface> pat_array;
|
||||
|
||||
// per-cell references to the index list
|
||||
std::vector<CollideBucket> buckets;
|
||||
@ -115,8 +117,7 @@ struct CollideHash {
|
||||
};
|
||||
|
||||
CollideHash construct_collide_hash(const std::vector<jak1::CollideFace>& tris);
|
||||
CollideHash construct_collide_hash(const std::vector<jak2::CollideFace>& tris);
|
||||
|
||||
class DataObjectGenerator;
|
||||
CollideHash construct_collide_hash(const std::vector<CollideFace>& tris);
|
||||
|
||||
size_t add_to_object_file(const CollideHash& hash, DataObjectGenerator& gen);
|
||||
} // namespace jak2
|
1162
goalc/build_level/collide/jak3/collide.cpp
Normal file
1162
goalc/build_level/collide/jak3/collide.cpp
Normal file
File diff suppressed because it is too large
Load Diff
123
goalc/build_level/collide/jak3/collide.h
Normal file
123
goalc/build_level/collide/jak3/collide.h
Normal file
@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
#include "goalc/build_level/collide/common/collide_common.h"
|
||||
|
||||
class DataObjectGenerator;
|
||||
namespace jak3 {
|
||||
// High-level collision system idea:
|
||||
// Each level has a single collide-hash object storing all collision data.
|
||||
// The mesh is divided into "fragments". Each fragment is made up of triangles.
|
||||
// There's a two-level lookup: if you want to find all triangles in a box, you must first find all
|
||||
// the fragments that intersect the box, then find all the triangles in those fragments that
|
||||
// intersect the box.
|
||||
// Each fragment has a bounding box. All triangles inside that fragment fit inside the bounding box.
|
||||
|
||||
/*!
|
||||
* Vertex in the collide mesh. This is stored as an offset from the bottom corner of the bounding
|
||||
* box. This is scaled by 16. (a "1" stored here means a distance of 16.f, or 16/4096 of in-game
|
||||
* meter.)
|
||||
*/
|
||||
struct CollideFragmentVertex {
|
||||
u16 position[3];
|
||||
};
|
||||
|
||||
/*!
|
||||
* Polygon in the collide mesh. This is a reference to three vertices in the vertex array, and a
|
||||
* "pat" (polygon attributes?) in the pat array.
|
||||
*/
|
||||
struct CollideFragmentPoly {
|
||||
u8 vertex_index[3];
|
||||
u8 pat_index;
|
||||
};
|
||||
|
||||
/*!
|
||||
* The Collide Fragment is divided into a 3D grid. Each cell in the grid has a "bucket" which
|
||||
* collects a list of all polygons that intersect the cell. The bucket stores a reference to values
|
||||
* in the index list, which are polygon indices.
|
||||
*/
|
||||
struct CollideBucket {
|
||||
s16 index;
|
||||
s16 count;
|
||||
};
|
||||
|
||||
struct CollideFragment {
|
||||
std::vector<jak3::PatSurface> pat_array;
|
||||
|
||||
// per-cell references to the index list
|
||||
std::vector<CollideBucket> buckets;
|
||||
|
||||
// references to polygons
|
||||
std::vector<u8> index_array;
|
||||
|
||||
// references to vertices/pats
|
||||
std::vector<CollideFragmentPoly> poly_array;
|
||||
|
||||
std::vector<CollideFragmentVertex> vert_array;
|
||||
|
||||
// others
|
||||
|
||||
// the x/y/z sizes of a grid cell
|
||||
math::Vector3f grid_step;
|
||||
|
||||
// inverse of grid step
|
||||
math::Vector3f axis_scale;
|
||||
|
||||
// the corners of our bounding box
|
||||
math::Vector3f bbox_min_corner;
|
||||
math::Vector3f bbox_max_corner;
|
||||
math::Vector4f bsphere;
|
||||
math::Vector<s32, 3> bbox_min_corner_i;
|
||||
math::Vector<s32, 3> bbox_max_corner_i;
|
||||
|
||||
// the number of cells in the grid along the x/y/z axis
|
||||
u32 dimension_array[3] = {0, 0, 0};
|
||||
};
|
||||
|
||||
/*
|
||||
((num-ids uint16 :offset 4)
|
||||
(id-count uint16 :offset 6)
|
||||
(num-buckets uint32 :offset 8)
|
||||
(qwc-id-bits uint32 :offset 12)
|
||||
(grid-step vector :inline :offset 16)
|
||||
(bbox bounding-box :inline :offset-assert 32)
|
||||
(bbox4w bounding-box4w :inline :offset-assert 64)
|
||||
(axis-scale vector :inline :offset 48)
|
||||
(avg-extents vector :inline :offset 64)
|
||||
(bucket-array uint32 :offset 44)
|
||||
(item-array (inline-array collide-hash-item) :offset 60 :score 1)
|
||||
(dimension-array uint32 3 :offset 76) ;; ?
|
||||
(num-items uint32 :offset 92)
|
||||
*/
|
||||
|
||||
struct CollideHash {
|
||||
// if you have a bit for each ID in the item list, how many quadwords (128-byte word) is it?
|
||||
u32 qwc_id_bits = 0;
|
||||
|
||||
// this is similar to the use in CollideHashFragment, but this points to entries in the .
|
||||
std::vector<CollideBucket> buckets;
|
||||
|
||||
// buckets point to this array, which points to the fragments below
|
||||
std::vector<u32> index_array;
|
||||
|
||||
// the actual fragments
|
||||
std::vector<CollideFragment> fragments;
|
||||
|
||||
// all these have the same meaning as in CollideFragment and define the grid.
|
||||
math::Vector3f grid_step;
|
||||
math::Vector3f axis_scale;
|
||||
math::Vector3f bbox_min_corner;
|
||||
math::Vector<s32, 3> bbox_min_corner_i;
|
||||
math::Vector<s32, 3> bbox_max_corner_i;
|
||||
u32 dimension_array[3] = {0, 0, 0};
|
||||
};
|
||||
|
||||
CollideHash construct_collide_hash(const std::vector<jak1::CollideFace>& tris);
|
||||
CollideHash construct_collide_hash(const std::vector<jak3::CollideFace>& tris);
|
||||
|
||||
size_t add_to_object_file(const CollideHash& hash, DataObjectGenerator& gen);
|
||||
} // namespace jak3
|
118
goalc/build_level/jak3/Entity.cpp
Normal file
118
goalc/build_level/jak3/Entity.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include "Entity.h"
|
||||
|
||||
namespace jak3 {
|
||||
size_t EntityActor::generate(DataObjectGenerator& gen) const {
|
||||
size_t result = res_lump.generate_header(gen, "entity-actor");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
gen.add_word_float(trans[i]);
|
||||
}
|
||||
gen.add_word(aid);
|
||||
gen.add_word(kill_mask);
|
||||
gen.add_type_tag(etype);
|
||||
|
||||
ASSERT(game_task < UINT16_MAX);
|
||||
ASSERT(vis_id < UINT16_MAX);
|
||||
u32 packed = (game_task) | (vis_id << 16);
|
||||
gen.add_word(packed);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
gen.add_word_float(quat[i]);
|
||||
}
|
||||
|
||||
res_lump.generate_tag_list_and_data(gen, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_drawable_actor(DataObjectGenerator& gen,
|
||||
const EntityActor& actor,
|
||||
size_t actor_loc) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-actor"); // 0
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(actor.vis_id); // 4
|
||||
gen.link_word_to_byte(gen.add_word(0), actor_loc); // 8
|
||||
gen.add_word(0); // 12
|
||||
for (int i = 0; i < 4; i++) {
|
||||
gen.add_word_float(actor.bsphere[i]); // 16, 20, 24, 28
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen,
|
||||
const std::vector<EntityActor>& actors) {
|
||||
std::vector<size_t> actor_locs;
|
||||
for (auto& actor : actors) {
|
||||
actor_locs.push_back(actor.generate(gen));
|
||||
}
|
||||
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-inline-array-actor"); // 0
|
||||
size_t result = gen.current_offset_bytes();
|
||||
ASSERT(actors.size() < UINT16_MAX);
|
||||
gen.add_word(actors.size() << 16); // 4
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
|
||||
ASSERT((gen.current_offset_bytes() % 16) == 0);
|
||||
|
||||
for (size_t i = 0; i < actors.size(); i++) {
|
||||
generate_drawable_actor(gen, actors[i], actor_locs[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_actors_from_json(const nlohmann::json& json,
|
||||
std::vector<EntityActor>& actor_list,
|
||||
u32 base_aid,
|
||||
decompiler::DecompilerTypeSystem& dts) {
|
||||
for (const auto& actor_json : json) {
|
||||
auto& actor = actor_list.emplace_back();
|
||||
actor.aid = actor_json.value("aid", base_aid + actor_list.size());
|
||||
actor.trans = vectorm3_from_json(actor_json.at("trans"));
|
||||
actor.etype = actor_json.at("etype").get<std::string>();
|
||||
if (actor_json.contains("kill_mask") && actor_json.at("kill_mask").is_string()) {
|
||||
actor.kill_mask = get_enum_val(actor_json.at("kill_mask").get<std::string>(), dts);
|
||||
} else {
|
||||
actor.kill_mask = actor_json.value("kill_mask", 0);
|
||||
}
|
||||
if (actor_json.contains("game_task") && actor_json.at("game_task").is_string()) {
|
||||
actor.game_task = get_enum_val(actor_json.at("game_task").get<std::string>(), dts);
|
||||
} else {
|
||||
actor.game_task = actor_json.value("game_task", 0);
|
||||
}
|
||||
actor.vis_id = actor_json.value("vis_id", 0);
|
||||
actor.quat = math::Vector4f(0, 0, 0, 1);
|
||||
if (actor_json.find("quat") != actor_json.end()) {
|
||||
actor.quat = vector_from_json(actor_json.at("quat"));
|
||||
}
|
||||
actor.bsphere = vectorm4_from_json(actor_json.at("bsphere"));
|
||||
|
||||
if (actor_json.find("lump") != actor_json.end()) {
|
||||
for (auto [key, value] : actor_json.at("lump").items()) {
|
||||
if (value.is_string()) {
|
||||
std::string value_string = value.get<std::string>();
|
||||
if (value_string.size() > 0 && value_string[0] == '\'') {
|
||||
actor.res_lump.add_res(
|
||||
std::make_unique<ResSymbol>(key, value_string.substr(1), -1000000000.0000));
|
||||
} else {
|
||||
actor.res_lump.add_res(
|
||||
std::make_unique<ResString>(key, value_string, -1000000000.0000));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value.is_array()) {
|
||||
actor.res_lump.add_res(res_from_json_array(key, value, dts));
|
||||
}
|
||||
}
|
||||
}
|
||||
actor.res_lump.sort_res();
|
||||
}
|
||||
}
|
||||
} // namespace jak3
|
37
goalc/build_level/jak3/Entity.h
Normal file
37
goalc/build_level/jak3/Entity.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "goalc/build_level/common/Entity.h"
|
||||
|
||||
namespace jak3 {
|
||||
/*
|
||||
* (trans vector :inline :offset-assert 32)
|
||||
* (aid uint32 :offset-assert 48)
|
||||
* (kill-mask task-mask :offset-assert 52)
|
||||
* (etype type :offset-assert 56) ;; probably type
|
||||
* (task game-task :offset-assert 60)
|
||||
* (vis-id uint16 :offset-assert 62)
|
||||
* (quat quaternion :inline :offset-assert 64)
|
||||
*/
|
||||
struct EntityActor {
|
||||
ResLump res_lump;
|
||||
math::Vector4f trans; // w = 1 here
|
||||
u32 aid = 0;
|
||||
u32 kill_mask = 0;
|
||||
std::string etype;
|
||||
u32 game_task = 0;
|
||||
u32 vis_id = 0;
|
||||
math::Vector4f quat;
|
||||
|
||||
math::Vector4f bsphere;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen,
|
||||
const std::vector<EntityActor>& actors);
|
||||
|
||||
void add_actors_from_json(const nlohmann::json& json,
|
||||
std::vector<EntityActor>& actor_list,
|
||||
u32 base_aid,
|
||||
decompiler::DecompilerTypeSystem& dts);
|
||||
} // namespace jak3
|
19
goalc/build_level/jak3/FileInfo.cpp
Normal file
19
goalc/build_level/jak3/FileInfo.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "FileInfo.h"
|
||||
|
||||
#include "common/versions/versions.h"
|
||||
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
|
||||
namespace jak3 {
|
||||
FileInfo make_file_info_for_level(const std::string& file_name) {
|
||||
FileInfo info;
|
||||
info.file_type = "bsp-header";
|
||||
info.file_name = file_name;
|
||||
info.major_version = versions::jak3::LEVEL_FILE_VERSION;
|
||||
info.minor_version = 0;
|
||||
info.maya_file_name = "Unknown";
|
||||
info.tool_debug = "Created by OpenGOAL buildlevel";
|
||||
info.mdb_file_name = "Unknown";
|
||||
return info;
|
||||
}
|
||||
} // namespace jak3
|
7
goalc/build_level/jak3/FileInfo.h
Normal file
7
goalc/build_level/jak3/FileInfo.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "goalc/build_level/common/FileInfo.h"
|
||||
|
||||
namespace jak3 {
|
||||
FileInfo make_file_info_for_level(const std::string& file_name);
|
||||
} // namespace jak3
|
141
goalc/build_level/jak3/LevelFile.cpp
Normal file
141
goalc/build_level/jak3/LevelFile.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#include "LevelFile.h"
|
||||
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
|
||||
namespace jak3 {
|
||||
size_t DrawableTreeArray::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
/*
|
||||
(deftype drawable-tree-array (drawable-group)
|
||||
((trees drawable-tree 1 :offset 32 :score 100))
|
||||
:flag-assert #x1200000024
|
||||
)
|
||||
(deftype drawable-group (drawable)
|
||||
((length int16 :offset 6)
|
||||
(data drawable 1 :offset-assert 32)
|
||||
)
|
||||
(:methods
|
||||
(new (symbol type int) _type_)
|
||||
)
|
||||
:flag-assert #x1200000024
|
||||
)
|
||||
*/
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-tree-array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
int num_trees = 0;
|
||||
num_trees += tfrags.size();
|
||||
gen.add_word(num_trees << 16);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
|
||||
// todo add trees...
|
||||
|
||||
if (num_trees == 0) {
|
||||
gen.add_word(0); // the one at the end.
|
||||
} else {
|
||||
int tree_word = (int)gen.current_offset_bytes() / 4;
|
||||
for (int i = 0; i < num_trees; i++) {
|
||||
gen.add_word(0);
|
||||
}
|
||||
|
||||
for (auto& tfrag : tfrags) {
|
||||
// gen.set_word(tree_word++, tfrag.add_to_object_file(gen));
|
||||
gen.link_word_to_byte(tree_word++, tfrag.add_to_object_file(gen));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_u32_array(const std::vector<u32>& array, DataObjectGenerator& gen) {
|
||||
gen.align(4);
|
||||
size_t result = gen.current_offset_bytes();
|
||||
for (auto& entry : array) {
|
||||
gen.add_word(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> LevelFile::save_object_file() const {
|
||||
DataObjectGenerator gen;
|
||||
gen.add_type_tag("bsp-header");
|
||||
|
||||
// add blank space for the bsp-header
|
||||
while (gen.words() < 100) {
|
||||
gen.add_word(0);
|
||||
}
|
||||
|
||||
//(info file-info :offset 4)
|
||||
auto file_info_slot = info.add_to_object_file(gen);
|
||||
gen.link_word_to_byte(1, file_info_slot);
|
||||
|
||||
//(bsphere vector :inline :offset-assert 16)
|
||||
//(all-visible-list (pointer uint8) :offset-assert 32)
|
||||
//(visible-list-length int32 :offset-assert 36)
|
||||
//(drawable-trees drawable-tree-array :offset-assert 40)
|
||||
gen.link_word_to_byte(40 / 4, drawable_trees.add_to_object_file(gen));
|
||||
//(pat pointer :offset-assert 44)
|
||||
//(pat-length int32 :offset-assert 48)
|
||||
//(texture-remap-table (pointer uint64) :offset-assert 52)
|
||||
//(texture-remap-table-len int32 :offset-assert 56)
|
||||
//(texture-ids (pointer texture-id) :offset-assert 60)
|
||||
//(texture-page-count int32 :offset-assert 64)
|
||||
//(unknown-basic basic :offset-assert 68)
|
||||
//(name symbol :offset-assert 72)
|
||||
gen.link_word_to_symbol(name, 72 / 4);
|
||||
//(nickname symbol :offset-assert 76)
|
||||
gen.link_word_to_symbol(nickname, 76 / 4);
|
||||
//(vis-info level-vis-info 8 :offset-assert 80)
|
||||
//(actors drawable-inline-array-actor :offset-assert 112)
|
||||
gen.link_word_to_byte(112 / 4, generate_inline_array_actors(gen, actors));
|
||||
//(cameras (array entity-camera) :offset-assert 116)
|
||||
//(nodes (inline-array bsp-node) :offset-assert 120)
|
||||
//(level level :offset-assert 124)
|
||||
//(current-leaf-idx uint16 :offset-assert 128)
|
||||
//(cam-outside-bsp uint8 :offset 152)
|
||||
//(cam-using-back uint8 :offset-assert 153)
|
||||
//(cam-box-idx uint16 :offset-assert 154)
|
||||
//(ambients symbol :offset-assert 156)
|
||||
//(subdivide-close float :offset-assert 160)
|
||||
//(subdivide-far float :offset-assert 160)
|
||||
//(race-meshes (array entity-race-mesh) :offset-assert 168)
|
||||
//(actor-birth-order (pointer uint32) :offset-assert 172)
|
||||
gen.link_word_to_byte(172 / 4, generate_u32_array(actor_birth_order, gen));
|
||||
//(light-hash light-hash :offset-assert 176)
|
||||
//(nav-meshes (array entity-nav-mesh) :offset-assert 180)
|
||||
//(actor-groups (array actor-group) :offset-assert 184)
|
||||
//(region-trees (array drawable-tree-region-prim) :offset-assert 188)
|
||||
//(region-array region-array :offset-assert 192)
|
||||
//(collide-hash collide-hash :offset-assert 196)
|
||||
gen.link_word_to_byte(196 / 4, add_to_object_file(collide_hash, gen));
|
||||
//(wind-array uint32 :offset 200)
|
||||
//(wind-array-length int32 :offset 204)
|
||||
//(city-level-info city-level-info :offset 208)
|
||||
//(vis-spheres vector-array :offset 216)
|
||||
//(vis-spheres-length uint32 :offset 248)
|
||||
//(region-tree drawable-tree-region-prim :offset 252)
|
||||
//(tfrag-masks texture-masks-array :offset-assert 256)
|
||||
//(tfrag-closest (pointer float) :offset-assert 260)
|
||||
//(tfrag-mask-count uint32 :offset 260)
|
||||
//(shrub-masks texture-masks-array :offset-assert 264)
|
||||
//(shrub-closest (pointer float) :offset-assert 268)
|
||||
//(shrub-mask-count uint32 :offset 268)
|
||||
//(alpha-masks texture-masks-array :offset-assert 272)
|
||||
//(alpha-closest (pointer float) :offset-assert 276)
|
||||
//(alpha-mask-count uint32 :offset 276)
|
||||
//(water-masks texture-masks-array :offset-assert 280)
|
||||
//(water-closest (pointer float) :offset-assert 284)
|
||||
//(water-mask-count uint32 :offset 284)
|
||||
//(bsp-scale vector :inline :offset-assert 288)
|
||||
//(bsp-offset vector :inline :offset-assert 304)
|
||||
//(hfrag-drawable drawable :offset 320)
|
||||
//(end uint8 :offset 399)
|
||||
|
||||
return gen.generate_v2();
|
||||
}
|
||||
} // namespace jak3
|
182
goalc/build_level/jak3/LevelFile.h
Normal file
182
goalc/build_level/jak3/LevelFile.h
Normal file
@ -0,0 +1,182 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "goalc/build_level/collide/common/collide_common.h"
|
||||
#include "goalc/build_level/collide/jak3/collide.h"
|
||||
#include "goalc/build_level/common/Tfrag.h"
|
||||
#include "goalc/build_level/jak3/Entity.h"
|
||||
#include "goalc/build_level/jak3/FileInfo.h"
|
||||
|
||||
namespace jak3 {
|
||||
struct VisibilityString {
|
||||
std::vector<u8> bytes;
|
||||
};
|
||||
|
||||
struct DrawableTreeInstanceTie {};
|
||||
|
||||
struct DrawableTreeActor {};
|
||||
|
||||
struct DrawableTreeInstanceShrub {};
|
||||
|
||||
struct DrawableTreeRegionPrim {};
|
||||
|
||||
struct DrawableTreeArray {
|
||||
std::vector<DrawableTreeTfrag> tfrags;
|
||||
std::vector<DrawableTreeInstanceTie> ties;
|
||||
std::vector<DrawableTreeActor> actors; // unused?
|
||||
std::vector<DrawableTreeRegionPrim> regions;
|
||||
std::vector<DrawableTreeInstanceShrub> shrubs;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct TextureRemap {};
|
||||
|
||||
struct TextureId {};
|
||||
|
||||
struct VisInfo {};
|
||||
|
||||
struct EntityCamera {};
|
||||
|
||||
struct BspNode {};
|
||||
|
||||
struct RaceMesh {};
|
||||
|
||||
struct LightHash {};
|
||||
|
||||
struct EntityNavMesh {};
|
||||
|
||||
struct ActorGroup {};
|
||||
|
||||
struct RegionTree {};
|
||||
|
||||
struct RegionArray {};
|
||||
|
||||
struct CityLevelInfo {};
|
||||
|
||||
struct TextureMasksArray {};
|
||||
|
||||
// This is a place to collect all the data that should go into the bsp-header file.
|
||||
struct LevelFile {
|
||||
// (info file-info :offset 4)
|
||||
FileInfo info;
|
||||
|
||||
// (all-visible-list (pointer uint16) :offset-assert 32)
|
||||
// (visible-list-length int16 :offset-assert 36)
|
||||
// (extra-vis-list-length int16 :offset-assert 38)
|
||||
VisibilityString all_visibile_list;
|
||||
|
||||
// (drawable-trees drawable-tree-array :offset-assert 40)
|
||||
DrawableTreeArray drawable_trees;
|
||||
|
||||
// (pat pointer :offset-assert 44)
|
||||
// (pat-length int32 :offset-assert 48)
|
||||
std::vector<PatSurface> pat;
|
||||
|
||||
// (texture-remap-table (pointer uint64) :offset-assert 52)
|
||||
// (texture-remap-table-len int32 :offset-assert 56)
|
||||
std::vector<TextureRemap> texture_remap_table;
|
||||
|
||||
// (texture-ids (pointer texture-id) :offset-assert 60)
|
||||
// (texture-page-count int32 :offset-assert 64)
|
||||
std::vector<TextureId> texture_ids;
|
||||
|
||||
// (unknown-basic basic :offset-assert 68)
|
||||
// "misc", seems like it can be zero and is unused.
|
||||
|
||||
// (name symbol :offset-assert 72)
|
||||
std::string name; // full name
|
||||
|
||||
// (nickname symbol :offset-assert 76)
|
||||
std::string nickname; // 3 char name
|
||||
|
||||
// (vis-info level-vis-info 8 :offset-assert 80) ;; note: 0 when
|
||||
std::array<VisInfo, 8> vis_infos;
|
||||
|
||||
// (actors drawable-inline-array-actor :offset-assert 112)
|
||||
std::vector<EntityActor> actors;
|
||||
|
||||
// (cameras (array entity-camera) :offset-assert 116)
|
||||
std::vector<EntityCamera> cameras;
|
||||
|
||||
// (nodes (inline-array bsp-node) :offset-assert 120)
|
||||
std::vector<BspNode> nodes;
|
||||
|
||||
// (level level :offset-assert 124)
|
||||
// zero
|
||||
|
||||
// (current-leaf-idx uint16 :offset-assert 128)
|
||||
// zero
|
||||
|
||||
// (texture-flags texture-page-flag 10 :offset-assert 130)
|
||||
std::vector<u16> texture_flags;
|
||||
|
||||
// (cam-outside-bsp uint8 :offset 152)
|
||||
// (cam-using-back uint8 :offset-assert 153)
|
||||
// (cam-box-idx uint16 :offset-assert 154)
|
||||
// zero
|
||||
|
||||
// (ambients symbol :offset-assert 156)
|
||||
// #t
|
||||
|
||||
// (subdivide-close float :offset-assert 160)
|
||||
float close_subdiv = 0;
|
||||
|
||||
// (subdivide-far float :offset-assert 164)
|
||||
float far_subdiv = 0;
|
||||
|
||||
// (race-meshes (array entity-race-mesh) :offset-assert 168)
|
||||
std::vector<RaceMesh> race_meshes;
|
||||
|
||||
// (actor-birth-order (pointer uint32) :offset-assert 172)
|
||||
std::vector<u32> actor_birth_order;
|
||||
|
||||
// (light-hash light-hash :offset-assert 176)
|
||||
LightHash light_hash;
|
||||
// (nav-meshes (array entity-nav-mesh) :offset-assert 180)
|
||||
std::vector<EntityNavMesh> entity_nav_meshes;
|
||||
// (actor-groups (array actor-group) :offset-assert 184)
|
||||
std::vector<ActorGroup> actor_groups;
|
||||
// (region-trees (array drawable-tree-region-prim) :offset-assert 188)
|
||||
std::vector<RegionTree> region_trees;
|
||||
// (region-array region-array :offset-assert 192)
|
||||
RegionArray region_array;
|
||||
// (collide-hash collide-hash :offset-assert 196)
|
||||
CollideHash collide_hash;
|
||||
// (wind-array uint32 :offset 200)
|
||||
std::vector<u32> wind_array;
|
||||
// (wind-array-length int32 :offset 204)
|
||||
s32 wind_array_length = 0;
|
||||
// (city-level-info city-level-info :offset 208)
|
||||
CityLevelInfo city_level_info;
|
||||
// (vis-spheres vector-array :offset 216)
|
||||
// (vis-spheres-length uint32 :offset 248)
|
||||
|
||||
// (region-tree drawable-tree-region-prim :offset 252)
|
||||
RegionTree region_tree;
|
||||
// (tfrag-masks texture-masks-array :offset-assert 256)
|
||||
// (tfrag-closest (pointer float) :offset-assert 260)
|
||||
// (tfrag-mask-count uint32 :offset 260)
|
||||
TextureMasksArray tfrag_masks;
|
||||
// (shrub-masks texture-masks-array :offset-assert 264)
|
||||
// (shrub-closest (pointer float) :offset-assert 268)
|
||||
// (shrub-mask-count uint32 :offset 268)
|
||||
TextureMasksArray shrub_masks;
|
||||
// (alpha-masks texture-masks-array :offset-assert 272)
|
||||
// (alpha-closest (pointer float) :offset-assert 276)
|
||||
// (alpha-mask-count uint32 :offset 276)
|
||||
TextureMasksArray alpha_masks;
|
||||
// (water-masks texture-masks-array :offset-assert 280)
|
||||
// (water-closest (pointer float) :offset-assert 284)
|
||||
// (water-mask-count uint32 :offset 284)
|
||||
TextureMasksArray water_masks;
|
||||
// (bsp-scale vector :inline :offset-assert 288)
|
||||
// (bsp-offset vector :inline :offset-assert 304)
|
||||
|
||||
std::vector<u8> save_object_file() const;
|
||||
};
|
||||
} // namespace jak3
|
197
goalc/build_level/jak3/build_level.cpp
Normal file
197
goalc/build_level/jak3/build_level.cpp
Normal file
@ -0,0 +1,197 @@
|
||||
#include "build_level.h"
|
||||
|
||||
#include "decompiler/extractor/extractor_util.h"
|
||||
#include "decompiler/level_extractor/extract_merc.h"
|
||||
#include "goalc/build_level/collide/jak3/collide.h"
|
||||
#include "goalc/build_level/common/Tfrag.h"
|
||||
#include "goalc/build_level/jak3/Entity.h"
|
||||
#include "goalc/build_level/jak3/FileInfo.h"
|
||||
#include "goalc/build_level/jak3/LevelFile.h"
|
||||
|
||||
namespace jak3 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
const std::string& bsp_output_file,
|
||||
const std::string& output_prefix) {
|
||||
auto level_json = parse_commented_json(
|
||||
file_util::read_text_file(file_util::get_file_path({input_file})), input_file);
|
||||
LevelFile file; // GOAL level file
|
||||
tfrag3::Level pc_level; // PC level file
|
||||
TexturePool tex_pool; // pc level texture pool
|
||||
|
||||
// process input mesh from blender
|
||||
gltf_mesh_extract::Input mesh_extract_in;
|
||||
mesh_extract_in.filename =
|
||||
file_util::get_file_path({level_json.at("gltf_file").get<std::string>()});
|
||||
mesh_extract_in.auto_wall_enable = level_json.value("automatic_wall_detection", true);
|
||||
mesh_extract_in.double_sided_collide = level_json.at("double_sided_collide").get<bool>();
|
||||
mesh_extract_in.auto_wall_angle = level_json.value("automatic_wall_angle", 30.0);
|
||||
mesh_extract_in.tex_pool = &tex_pool;
|
||||
gltf_mesh_extract::Output mesh_extract_out;
|
||||
gltf_mesh_extract::extract(mesh_extract_in, mesh_extract_out);
|
||||
|
||||
// add stuff to the GOAL level structure
|
||||
file.info = make_file_info_for_level(fs::path(input_file).filename().string());
|
||||
// all vis
|
||||
// drawable trees
|
||||
// pat
|
||||
// texture remap
|
||||
// texture ids
|
||||
// unk zero
|
||||
// name
|
||||
file.name = level_json.at("long_name").get<std::string>();
|
||||
// nick
|
||||
file.nickname = level_json.at("nickname").get<std::string>();
|
||||
// vis infos
|
||||
// actors
|
||||
auto dts = decompiler::DecompilerTypeSystem(GameVersion::Jak3);
|
||||
dts.parse_enum_defs({"decompiler", "config", "jak3", "all-types.gc"});
|
||||
std::vector<EntityActor> actors;
|
||||
add_actors_from_json(level_json.at("actors"), actors, level_json.value("base_id", 1234), dts);
|
||||
std::sort(actors.begin(), actors.end(), [](auto& a, auto& b) { return a.aid < b.aid; });
|
||||
auto duplicates = std::adjacent_find(actors.begin(), actors.end(),
|
||||
[](auto& a, auto& b) { return a.aid == b.aid; });
|
||||
ASSERT_MSG(duplicates == actors.end(),
|
||||
fmt::format("Actor IDs must be unique. Found at least two actors with ID {}",
|
||||
duplicates->aid));
|
||||
file.actors = std::move(actors);
|
||||
// cameras
|
||||
// nodes
|
||||
// regions
|
||||
// subdivs
|
||||
// actor birth
|
||||
for (size_t i = 0; i < file.actors.size(); i++) {
|
||||
file.actor_birth_order.push_back(i);
|
||||
}
|
||||
|
||||
// add stuff to the PC level structure
|
||||
pc_level.level_name = file.name;
|
||||
|
||||
// TFRAG
|
||||
auto& tfrag_drawable_tree = file.drawable_trees.tfrags.emplace_back();
|
||||
tfrag_from_gltf(mesh_extract_out.tfrag, tfrag_drawable_tree,
|
||||
pc_level.tfrag_trees[0].emplace_back());
|
||||
pc_level.textures = std::move(tex_pool.textures_by_idx);
|
||||
|
||||
// COLLIDE
|
||||
if (mesh_extract_out.collide.faces.empty()) {
|
||||
lg::error("No collision geometry was found");
|
||||
} else {
|
||||
file.collide_hash = construct_collide_hash(mesh_extract_out.collide.faces);
|
||||
}
|
||||
|
||||
// Save the GOAL level
|
||||
auto result = file.save_object_file();
|
||||
lg::print("Level bsp file size {} bytes\n", result.size());
|
||||
auto save_path = file_util::get_jak_project_dir() / bsp_output_file;
|
||||
file_util::create_dir_if_needed_for_file(save_path);
|
||||
lg::print("Saving to {}\n", save_path.string());
|
||||
file_util::write_binary_file(save_path, result.data(), result.size());
|
||||
|
||||
// Add textures and models
|
||||
// TODO remove hardcoded config settings
|
||||
if ((level_json.contains("art_groups") && !level_json.at("art_groups").empty()) ||
|
||||
(level_json.contains("textures") && !level_json.at("textures").empty())) {
|
||||
lg::info("Looking for ISO path...");
|
||||
const auto iso_folder = file_util::get_iso_dir_for_game(GameVersion::Jak3);
|
||||
lg::info("Found ISO path: {}", iso_folder.string());
|
||||
|
||||
if (iso_folder.empty() || !fs::exists(iso_folder)) {
|
||||
lg::warn("Could not locate ISO path!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for iso build info if it's available, otherwise default to ntsc_v1
|
||||
const auto version_info = get_version_info_or_default(iso_folder);
|
||||
|
||||
decompiler::Config config;
|
||||
try {
|
||||
config = decompiler::read_config_file(
|
||||
file_util::get_jak_project_dir() / "decompiler/config/jak3/jak3_config.jsonc",
|
||||
version_info.decomp_config_version,
|
||||
R"({"decompile_code": false, "find_functions": false, "levels_extract": true, "allowed_objects": [], "save_texture_pngs": false})");
|
||||
} catch (const std::exception& e) {
|
||||
lg::error("Failed to parse config: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<fs::path> dgos, objs;
|
||||
for (const auto& dgo_name : config.dgo_names) {
|
||||
dgos.push_back(iso_folder / dgo_name);
|
||||
}
|
||||
|
||||
for (const auto& obj_name : config.object_file_names) {
|
||||
objs.push_back(iso_folder / obj_name);
|
||||
}
|
||||
|
||||
decompiler::ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, {}, {}, {},
|
||||
config);
|
||||
|
||||
// need to process link data for tpages
|
||||
db.process_link_data(config);
|
||||
|
||||
decompiler::TextureDB tex_db;
|
||||
auto textures_out = file_util::get_jak_project_dir() / "decompiler_out/jak3/textures";
|
||||
file_util::create_dir_if_needed(textures_out);
|
||||
db.process_tpages(tex_db, textures_out, config, "");
|
||||
|
||||
// find all art groups used by the custom level in other dgos
|
||||
if (level_json.contains("art_groups") && !level_json.at("art_groups").empty()) {
|
||||
for (auto& dgo : config.dgo_names) {
|
||||
std::vector<std::string> processed_art_groups;
|
||||
// remove "DGO/" prefix
|
||||
const auto& dgo_name = dgo.substr(4);
|
||||
const auto& files = db.obj_files_by_dgo.at(dgo_name);
|
||||
auto art_groups =
|
||||
find_art_groups(processed_art_groups,
|
||||
level_json.at("art_groups").get<std::vector<std::string>>(), files);
|
||||
auto tex_remap = decompiler::extract_tex_remap(db, dgo_name);
|
||||
for (const auto& ag : art_groups) {
|
||||
if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) {
|
||||
const auto& ag_file = db.lookup_record(ag);
|
||||
lg::print("custom level: extracting art group {}\n", ag_file.name_in_dgo);
|
||||
decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false,
|
||||
db.version());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add textures
|
||||
if (level_json.contains("textures") && !level_json.at("textures").empty()) {
|
||||
std::vector<std::string> processed_textures;
|
||||
std::vector<std::string> wanted_texs =
|
||||
level_json.at("textures").get<std::vector<std::string>>();
|
||||
// first check the texture is not already in the level
|
||||
for (auto& level_tex : pc_level.textures) {
|
||||
if (std::find(wanted_texs.begin(), wanted_texs.end(), level_tex.debug_name) !=
|
||||
wanted_texs.end()) {
|
||||
processed_textures.push_back(level_tex.debug_name);
|
||||
}
|
||||
}
|
||||
|
||||
// then add
|
||||
for (auto& [id, tex] : tex_db.textures) {
|
||||
for (auto& tex0 : wanted_texs) {
|
||||
if (std::find(processed_textures.begin(), processed_textures.end(), tex.name) !=
|
||||
processed_textures.end()) {
|
||||
continue;
|
||||
}
|
||||
if (tex.name == tex0) {
|
||||
lg::info("custom level: adding texture {} from tpage {} ({})", tex.name, tex.page,
|
||||
tex_db.tpage_names.at(tex.page));
|
||||
pc_level.textures.push_back(
|
||||
make_texture(id, tex, tex_db.tpage_names.at(tex.page), true));
|
||||
processed_textures.push_back(tex.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the PC level
|
||||
save_pc_data(file.name, pc_level,
|
||||
file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace jak3
|
9
goalc/build_level/jak3/build_level.h
Normal file
9
goalc/build_level/jak3/build_level.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "goalc/build_level/common/build_level.h"
|
||||
|
||||
namespace jak3 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
const std::string& bsp_output_file,
|
||||
const std::string& output_prefix);
|
||||
} // namespace jak3
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "goalc/build_level/jak1/build_level.h"
|
||||
#include "goalc/build_level/jak2/build_level.h"
|
||||
#include "goalc/build_level/jak3/build_level.h"
|
||||
|
||||
#include "third-party/CLI11.hpp"
|
||||
|
||||
@ -33,7 +34,7 @@ int main(int argc, char** argv) {
|
||||
app.add_option("output-file", output_file,
|
||||
"Output .go file, (for example out/jak2/obj/test-zone.go)")
|
||||
->required();
|
||||
app.add_option("-g,--game", game, "Game version (jak1 or jak2)")->required();
|
||||
app.add_option("-g,--game", game, "Game version (jak1, jak2 or jak3)")->required();
|
||||
app.add_option("--proj-path", project_path_override,
|
||||
"Specify the location of the 'data/' folder");
|
||||
app.validate_positionals();
|
||||
@ -61,6 +62,9 @@ int main(int argc, char** argv) {
|
||||
case GameVersion::Jak2:
|
||||
jak2::run_build_level(input_json, output_file, "jak2/");
|
||||
break;
|
||||
case GameVersion::Jak3:
|
||||
jak3::run_build_level(input_json, output_file, "jak3/");
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED_MSG("unsupported game version");
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ MakeSystem::MakeSystem(const std::optional<REPL::Config> repl_config, const std:
|
||||
add_tool<SubtitleV2Tool>();
|
||||
add_tool<BuildLevelTool>();
|
||||
add_tool<BuildLevel2Tool>();
|
||||
add_tool<BuildLevel3Tool>();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "goalc/build_level/jak1/build_level.h"
|
||||
#include "goalc/build_level/jak2/build_level.h"
|
||||
#include "goalc/build_level/jak3/build_level.h"
|
||||
#include "goalc/compiler/Compiler.h"
|
||||
#include "goalc/data_compiler/dir_tpages.h"
|
||||
#include "goalc/data_compiler/game_count.h"
|
||||
@ -278,3 +279,20 @@ bool BuildLevel2Tool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
}
|
||||
return jak2::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix);
|
||||
}
|
||||
|
||||
BuildLevel3Tool::BuildLevel3Tool() : Tool("build-level3") {}
|
||||
|
||||
bool BuildLevel3Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
auto deps = get_build_level_deps(task.input.at(0));
|
||||
return Tool::needs_run({task.input, deps, task.output, task.arg}, path_map);
|
||||
}
|
||||
|
||||
bool BuildLevel3Tool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
return jak3::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix);
|
||||
}
|
@ -85,3 +85,10 @@ class BuildLevel2Tool : public Tool {
|
||||
bool run(const ToolInput& task, const PathMap& path_map) override;
|
||||
bool needs_run(const ToolInput& task, const PathMap& path_map) override;
|
||||
};
|
||||
|
||||
class BuildLevel3Tool : public Tool {
|
||||
public:
|
||||
BuildLevel3Tool();
|
||||
bool run(const ToolInput& task, const PathMap& path_map) override;
|
||||
bool needs_run(const ToolInput& task, const PathMap& path_map) override;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user