mirror of
https://github.com/open-goal/jak-project
synced 2026-08-21 23:00:45 -04:00
custom levels: etie and build actor support for jak2/3 (#3851)
Custom levels for Jak 2/3 now support envmapped TIE geometry. The TIE extract was also changed to ignore materials that have the specular flag set, but are missing a roughness texture. Jak 2/3 now also support the `build-actor` tool. The `build-custom-level` and `build-actor` macros now have a few new options: - Both now have a `force-run` option (`#f` by default) that, when set to `#t`, will always run level/art group generation even if the output files are up to date. - `build-custom-level` has a `gen-fr3` option (`#t` by default) that, when set to `#f`, will skip generating the FR3 file for the custom level and only generate the GOAL level file to skip the potentially slow process of finding and adding art groups and textures. Useful for when you want to temporarily edit only the GOAL side of the level (such as entity placement, etc.). - `build-actor` has a `texture-bucket` option (default 0) which will determine what DMA sink group the model will be placed in, which is useful to determine the draw order of the model. Previously, this was omitted, resulting in shadows not drawing over custom actors because the actors were put in a bucket that is drawn after shadows (this behavior can be restored with `:texture-bucket #f`).
This commit is contained in:
@@ -685,14 +685,15 @@ bool material_has_envmap(const tinygltf::Material& mat) {
|
||||
}
|
||||
|
||||
bool envmap_is_valid(const tinygltf::Material& mat) {
|
||||
if (material_has_envmap(mat) && mat.pbrMetallicRoughness.metallicRoughnessTexture.index < 0) {
|
||||
auto envmap = material_has_envmap(mat);
|
||||
if (envmap && mat.pbrMetallicRoughness.metallicRoughnessTexture.index < 0) {
|
||||
lg::warn(fmt::format(
|
||||
"Material \"{}\" has specular property set, but is missing a metallic roughness texture, "
|
||||
"ignoring envmap!",
|
||||
mat.name));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return envmap;
|
||||
}
|
||||
|
||||
std::optional<int> find_single_skin(const tinygltf::Model& model,
|
||||
|
||||
@@ -56,6 +56,10 @@
|
||||
// Removed so that the release builds don't have to double-decompile the game
|
||||
// "art_groups": ["prsn-torture-ag"],
|
||||
|
||||
// If you have any custom models in the "custom_assets/jak2/models/custom_levels" folder that you want to use in your level, add them to this list.
|
||||
// Note: Like with art groups, these should also be added to your level's .gd file.
|
||||
"custom_models": ["test-actor"],
|
||||
|
||||
// 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
|
||||
@@ -99,6 +103,17 @@
|
||||
"lump": {
|
||||
"name": "test-torture"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"trans": [5.41, 3.5, 28.42], // translation
|
||||
"etype": "test-actor", // actor type
|
||||
"game_task": 0, // associated game task (for powercells, etc)
|
||||
"quat": [0, 0, 0, 1], // quaternion
|
||||
"bsphere": [5.41, 3.5, 28.42, 10], // bounding sphere
|
||||
"lump": {
|
||||
"name": "test-actor"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,7 @@
|
||||
("TSZ.DGO"
|
||||
(
|
||||
"prison-obs.o"
|
||||
"test-zone-obs.o"
|
||||
"test-actor-ag.go"
|
||||
"test-zone.go"
|
||||
))
|
||||
Binary file not shown.
@@ -55,6 +55,10 @@
|
||||
// All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file.
|
||||
// "art_groups": [],
|
||||
|
||||
// If you have any custom models in the "custom_assets/jak3/models/custom_levels" folder that you want to use in your level, add them to this list.
|
||||
// Note: Like with art groups, these should also be added to your level's .gd file.
|
||||
"custom_models": ["test-actor"],
|
||||
|
||||
// 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
|
||||
@@ -87,6 +91,17 @@
|
||||
"lump": {
|
||||
"name": "test-eco"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"trans": [5.41, 3.5, 28.42], // translation
|
||||
"etype": "test-actor", // actor type
|
||||
"game_task": 0, // associated game task (for powercells, etc)
|
||||
"quat": [0, 0, 0, 1], // quaternion
|
||||
"bsphere": [5.41, 3.5, 28.42, 10], // bounding sphere
|
||||
"lump": {
|
||||
"name": "test-actor"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
;; the actual file name still needs to be 8.3
|
||||
("TSZ.DGO"
|
||||
("test-zone.go"
|
||||
)
|
||||
)
|
||||
(
|
||||
"test-zone-obs.o"
|
||||
"test-actor-ag.go"
|
||||
"test-zone.go"
|
||||
))
|
||||
Binary file not shown.
@@ -218,15 +218,15 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro build-custom-level (name)
|
||||
(defmacro build-custom-level (name &key (force-run #f) &key (gen-fr3 #t))
|
||||
(let* ((path (string-append "custom_assets/jak1/levels/" name "/" name ".jsonc")))
|
||||
`(defstep :in ,path
|
||||
`(defstep :in '(,path ,(symbol->string force-run) ,(symbol->string gen-fr3))
|
||||
:tool 'build-level
|
||||
:out '(,(string-append "$OUT/obj/" name ".go")))))
|
||||
|
||||
(defmacro build-actor (name &key (gen-mesh #f))
|
||||
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0))
|
||||
(let* ((path (string-append "custom_assets/jak1/models/custom_levels/" name ".glb")))
|
||||
`(defstep :in '(,path ,(symbol->string gen-mesh))
|
||||
`(defstep :in '(,path ,(symbol->string gen-mesh) ,(symbol->string force-run) ,(if (integer? texture-bucket) (int->string texture-bucket) (symbol->string texture-bucket)))
|
||||
:tool 'build-actor
|
||||
:out '(,(string-append "$OUT/obj/" name "-ag.go")))))
|
||||
|
||||
|
||||
@@ -420,5 +420,30 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro def-actor (name &key (idle #f) &key (lods #f) &key (art (idle-ja)) &key (joints ()) &key (shadow 0) &key bounds &key (longest-edge 0.0) &key (texture-level 0) &key (sort 0) &key (version 7) &key (origin-joint-index 0) &key (shadow-joint-index 0) &key (light-index 0))
|
||||
`(begin
|
||||
(def-art-elt ,(string->symbol-format "{}-ag" name) ,(string->symbol-format "{}-lod0-jg" name) 0)
|
||||
(def-art-elt ,(string->symbol-format "{}-ag" name) ,(string->symbol-format "{}-lod0-mg" name) 1)
|
||||
,@(apply-i (lambda (x i)
|
||||
`(def-art-elt ,(string->symbol-format "{}-ag" name) ,(string->symbol-format "{}-{}" name x) ,(+ i 2)))
|
||||
art)
|
||||
,@(apply-i (lambda (x i) `(def-joint-node ,(string->symbol-format "{}-lod0-jg" name) ,(symbol->string x) ,(1+ i))) joints)
|
||||
(defskelgroup ,(string->symbol-format "*{}-sg*" name)
|
||||
,name
|
||||
,(string->symbol-format "{}-lod0-jg" name)
|
||||
,(if idle (string->symbol-format "{}-{}" name idle) (string->symbol-format "{}-{}" name (car art)))
|
||||
,(if lods
|
||||
`(,@(apply (lambda (x) `(,(string->symbol-format "{}-{}-mg" name (car x)) (meters ,(cadr x)))) lods))
|
||||
`((,(string->symbol-format "{}-lod0-mg" name) (meters 999999))))
|
||||
:version ,version
|
||||
:shadow ,shadow
|
||||
:bounds (static-spherem ,@bounds)
|
||||
:longest-edge ,longest-edge
|
||||
:texture-level ,texture-level
|
||||
:sort ,sort
|
||||
:origin-joint-index ,origin-joint-index
|
||||
:shadow-joint-index ,shadow-joint-index
|
||||
:light-index ,light-index)))
|
||||
|
||||
(import "goal_src/jak2/engine/data/art-elts.gc")
|
||||
(import "goal_src/jak2/engine/data/joint-nodes.gc")
|
||||
@@ -300,8 +300,14 @@
|
||||
;; it should point to the .jsonc file that specifies the level.
|
||||
(build-custom-level "test-zone")
|
||||
;; the DGO file
|
||||
(goal-src "levels/test-zone/test-zone-obs.gc" "process-focusable")
|
||||
(custom-level-cgo "TSZ.DGO" "test-zone/testzone.gd")
|
||||
|
||||
;; generate the art group for a custom actor.
|
||||
;; requires a .glb model file in custom_assets/jak1/models/custom_levels
|
||||
;; to also generate a collide-mesh, add :gen-mesh #t
|
||||
(build-actor "test-actor" :force-run #t :gen-mesh #t)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ANIMATIONS
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
;;-*-Lisp-*-
|
||||
(in-package goal)
|
||||
(deftype test-actor (process-drawable)
|
||||
((root collide-shape-moving :override)
|
||||
(birth-time time-frame)
|
||||
(base vector :inline)
|
||||
(old-base vector :inline)
|
||||
(bob-offset int64)
|
||||
(bob-amount float))
|
||||
(:methods
|
||||
(init-collision! (_type_) object))
|
||||
(:state-methods
|
||||
idle))
|
||||
|
||||
(def-actor test-actor
|
||||
:bounds (0 0 0 5))
|
||||
|
||||
(defmethod init-collision! ((this test-actor))
|
||||
(let ((cshape (new 'process 'collide-shape-moving this (collide-list-enum hit-by-player))))
|
||||
(set! (-> cshape dynam) (copy *standard-dynamics* 'process))
|
||||
(set! (-> cshape reaction) cshape-reaction-default)
|
||||
(set! (-> cshape no-reaction) (the (function collide-shape-moving collide-query vector vector object) nothing))
|
||||
(let ((mesh (new 'process 'collide-shape-prim-mesh cshape (the uint 0) (the uint 0))))
|
||||
(set! (-> mesh prim-core collide-as) (collide-spec pusher))
|
||||
(set! (-> mesh collide-with) (collide-spec jak player-list))
|
||||
(set! (-> mesh prim-core action) (collide-action solid rideable))
|
||||
(set! (-> mesh transform-index) 0)
|
||||
(set! (-> cshape total-prims) (the uint 1))
|
||||
(set! (-> cshape root-prim) mesh)
|
||||
(set-vector! (-> mesh local-sphere) 0.0 (meters 0) 0.0 (meters 5)))
|
||||
(pusher-init cshape)
|
||||
(set! (-> cshape nav-radius) (* 0.75 (-> cshape root-prim local-sphere w)))
|
||||
(let ((prim (-> cshape root-prim)))
|
||||
(set! (-> cshape backup-collide-as) (-> prim prim-core collide-as))
|
||||
(set! (-> cshape backup-collide-with) (-> prim prim-core collide-with))
|
||||
)
|
||||
(set! (-> this root) cshape)))
|
||||
|
||||
(defmethod init-from-entity! ((this test-actor) (e entity-actor))
|
||||
(logior! (-> this mask) (process-mask enemy))
|
||||
(init-collision! this)
|
||||
(process-drawable-from-entity! this e)
|
||||
(set! (-> this bob-amount) 1024.0)
|
||||
(set! (-> this bob-offset)
|
||||
(+ (the int (-> this root trans x)) (the int (-> this root trans y)) (the int (-> this root trans z))))
|
||||
(set-time! (-> this birth-time))
|
||||
(vector-copy! (-> this base) (-> this root trans))
|
||||
(vector-copy! (-> this old-base) (-> this root trans))
|
||||
(initialize-skeleton this *test-actor-sg* '())
|
||||
(logclear! (-> this mask) (process-mask actor-pause))
|
||||
(transform-post)
|
||||
(go-virtual idle :proc this)
|
||||
(none))
|
||||
|
||||
(defbehavior test-actor-init-by-other test-actor ((pos vector))
|
||||
(logior! (-> self mask) (process-mask enemy))
|
||||
(init-collision! self)
|
||||
(initialize-skeleton self *test-actor-sg* '())
|
||||
(vector-copy! (-> self root trans) pos)
|
||||
(quaternion-identity! (-> self root quat))
|
||||
(vector-identity! (-> self root scale))
|
||||
(set! (-> self bob-amount) 1024.0)
|
||||
(set! (-> self bob-offset)
|
||||
(+ (the int (-> self root trans x)) (the int (-> self root trans y)) (the int (-> self root trans z))))
|
||||
(set-time! (-> self birth-time))
|
||||
(vector-copy! (-> self base) (-> self root trans))
|
||||
(vector-copy! (-> self old-base) (-> self root trans))
|
||||
(logclear! (-> self mask) (process-mask actor-pause))
|
||||
(transform-post)
|
||||
(go-virtual idle))
|
||||
|
||||
(defstate idle (test-actor)
|
||||
:virtual #t
|
||||
:event
|
||||
(behavior ((proc process) (argc int) (message symbol) (block event-message-block))
|
||||
(case message
|
||||
(('attack 'touch)
|
||||
; (if (= (-> proc type) target)
|
||||
; (send-event proc 'attack #f (static-attack-info ((shove-up (meters 2.5)) (shove-back (meters 7.5)))))
|
||||
; )
|
||||
#t)))
|
||||
:code
|
||||
(behavior ()
|
||||
(loop
|
||||
(quaternion-rotate-y! (-> self root quat) (-> self root quat) (* (degrees 45) (seconds-per-frame)))
|
||||
(let ((bob (-> self bob-amount)))
|
||||
(when (< 0.0 bob)
|
||||
(set! (-> self root trans y)
|
||||
(+ (-> self base y)
|
||||
(* bob
|
||||
(sin (* 109.22667 (the float (mod (+ (- (current-time) (-> self birth-time)) (-> self bob-offset)) (seconds 2))))))))
|
||||
(update-transforms (-> self root))))
|
||||
; (debug-draw-tris (-> (res-lump-struct (-> self draw art-group data 0 extra) 'collide-mesh-group (array collide-mesh)) 0) self 2)
|
||||
; (dotimes (i (-> self node-list length))
|
||||
; (let* ((joint (-> self node-list data i)) (jpos (vector<-cspace! (new-stack-vector0) joint)))
|
||||
; (add-debug-sphere #t (bucket-id debug) jpos (meters 0.1) (static-rgba 0 #xff 0 #x40))
|
||||
; (add-debug-text-sphere (!= (-> joint joint) #f) (bucket-id debug) jpos (meters 0.1) (-> joint joint name) (static-rgba 0 #xff 0 #x40))
|
||||
; )
|
||||
; )
|
||||
(suspend)))
|
||||
:post transform-post)
|
||||
@@ -135,12 +135,18 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro build-custom-level (name)
|
||||
(defmacro build-custom-level (name &key (force-run #f) &key (gen-fr3 #t))
|
||||
(let* ((path (string-append "custom_assets/jak2/levels/" name "/" name ".jsonc")))
|
||||
`(defstep :in ,path
|
||||
`(defstep :in '(,path ,(symbol->string force-run) ,(symbol->string gen-fr3))
|
||||
:tool 'build-level2
|
||||
:out '(,(string-append "$OUT/obj/" name ".go")))))
|
||||
|
||||
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0))
|
||||
(let* ((path (string-append "custom_assets/jak2/models/custom_levels/" name ".glb")))
|
||||
`(defstep :in '(,path ,(symbol->string gen-mesh) ,(symbol->string force-run) ,(if (integer? texture-bucket) (int->string texture-bucket) (symbol->string texture-bucket)))
|
||||
:tool 'build-actor2
|
||||
:out '(,(string-append "$OUT/obj/" name "-ag.go")))))
|
||||
|
||||
(defmacro group (name &rest stuff)
|
||||
`(defstep :in ""
|
||||
:tool 'group
|
||||
|
||||
@@ -486,5 +486,32 @@ Each process-drawable has a draw-control."
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro def-actor (name &key (idle #f) &key (lods #f) &key (art (idle-ja)) &key (joints ()) &key (shadow 0) &key bounds &key (longest-edge 0.0) &key (texture-level 0) &key (sort 0) &key (version 7) &key (origin-joint-index 0) &key (shadow-joint-index 0) &key (light-index 0) &key (global-effects 0) &key (clothing ()))
|
||||
`(begin
|
||||
(def-art-elt ,(string->symbol-format "{}-ag" name) ,(string->symbol-format "{}-lod0-jg" name) 0)
|
||||
(def-art-elt ,(string->symbol-format "{}-ag" name) ,(string->symbol-format "{}-lod0-mg" name) 1)
|
||||
,@(apply-i (lambda (x i)
|
||||
`(def-art-elt ,(string->symbol-format "{}-ag" name) ,(string->symbol-format "{}-{}" name x) ,(+ i 2)))
|
||||
art)
|
||||
,@(apply-i (lambda (x i) `(def-joint-node ,(string->symbol-format "{}-lod0-jg" name) ,(symbol->string x) ,(1+ i))) joints)
|
||||
(defskelgroup ,(string->symbol-format "*{}-sg*" name)
|
||||
,name
|
||||
,(string->symbol-format "{}-lod0-jg" name)
|
||||
,(if idle (string->symbol-format "{}-{}" name idle) (string->symbol-format "{}-{}" name (car art)))
|
||||
,(if lods
|
||||
`(,@(apply (lambda (x) `(,(string->symbol-format "{}-{}-mg" name (car x)) (meters ,(cadr x)))) lods))
|
||||
`((,(string->symbol-format "{}-lod0-mg" name) (meters 999999))))
|
||||
:version ,version
|
||||
:shadow ,shadow
|
||||
:bounds (static-spherem ,@bounds)
|
||||
:longest-edge ,longest-edge
|
||||
:texture-level ,texture-level
|
||||
:sort ,sort
|
||||
:origin-joint-index ,origin-joint-index
|
||||
:shadow-joint-index ,shadow-joint-index
|
||||
:light-index ,light-index
|
||||
:global-effects ,global-effects
|
||||
:clothing ,clothing)))
|
||||
|
||||
(import "goal_src/jak3/engine/data/art-elts.gc")
|
||||
(import "goal_src/jak3/engine/data/joint-nodes.gc")
|
||||
|
||||
@@ -403,8 +403,14 @@
|
||||
;; it should point to the .jsonc file that specifies the level.
|
||||
(build-custom-level "test-zone")
|
||||
;; the DGO file
|
||||
(goal-src "levels/test-zone/test-zone-obs.gc" "process-focusable")
|
||||
(custom-level-cgo "TSZ.DGO" "test-zone/testzone.gd")
|
||||
|
||||
;; generate the art group for a custom actor.
|
||||
;; requires a .glb model file in custom_assets/jak3/models/custom_levels
|
||||
;; to also generate a collide-mesh, add :gen-mesh #t
|
||||
(build-actor "test-actor" :gen-mesh #t)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ANIMATIONS
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
;;-*-Lisp-*-
|
||||
(in-package goal)
|
||||
(deftype test-actor (process-drawable)
|
||||
((root collide-shape-moving :override)
|
||||
(birth-time time-frame)
|
||||
(base vector :inline)
|
||||
(old-base vector :inline)
|
||||
(bob-offset int64)
|
||||
(bob-amount float))
|
||||
(:methods
|
||||
(init-collision! (_type_) object))
|
||||
(:state-methods
|
||||
idle))
|
||||
|
||||
(def-actor test-actor
|
||||
:bounds (0 0 0 5))
|
||||
|
||||
(defmethod init-collision! ((this test-actor))
|
||||
(let ((cshape (new 'process 'collide-shape-moving this (collide-list-enum hit-by-player))))
|
||||
(set! (-> cshape dynam) (copy *standard-dynamics* 'process))
|
||||
(set! (-> cshape reaction) cshape-reaction-default)
|
||||
(set! (-> cshape no-reaction) (the (function collide-shape-moving collide-query vector vector object) nothing))
|
||||
(let ((mesh (new 'process 'collide-shape-prim-mesh cshape (the uint 0) (the uint 0))))
|
||||
(set! (-> mesh prim-core collide-as) (collide-spec pusher))
|
||||
(set! (-> mesh collide-with) (collide-spec jak player-list))
|
||||
(set! (-> mesh prim-core action) (collide-action solid rideable))
|
||||
(set! (-> mesh transform-index) 0)
|
||||
(set! (-> cshape total-prims) (the uint 1))
|
||||
(set! (-> cshape root-prim) mesh)
|
||||
(set-vector! (-> mesh local-sphere) 0.0 (meters 0) 0.0 (meters 5)))
|
||||
(pusher-init cshape)
|
||||
(set! (-> cshape nav-radius) (* 0.75 (-> cshape root-prim local-sphere w)))
|
||||
(let ((prim (-> cshape root-prim)))
|
||||
(set! (-> cshape backup-collide-as) (-> prim prim-core collide-as))
|
||||
(set! (-> cshape backup-collide-with) (-> prim prim-core collide-with))
|
||||
)
|
||||
(set! (-> this root) cshape)))
|
||||
|
||||
(defmethod init-from-entity! ((this test-actor) (e entity-actor))
|
||||
(logior! (-> this mask) (process-mask enemy))
|
||||
(init-collision! this)
|
||||
(process-drawable-from-entity! this e)
|
||||
(set! (-> this bob-amount) 1024.0)
|
||||
(set! (-> this bob-offset)
|
||||
(+ (the int (-> this root trans x)) (the int (-> this root trans y)) (the int (-> this root trans z))))
|
||||
(set-time! (-> this birth-time))
|
||||
(vector-copy! (-> this base) (-> this root trans))
|
||||
(vector-copy! (-> this old-base) (-> this root trans))
|
||||
(initialize-skeleton this *test-actor-sg* '())
|
||||
(logclear! (-> this mask) (process-mask actor-pause))
|
||||
(transform-post)
|
||||
(go-virtual idle :proc this))
|
||||
|
||||
(defbehavior test-actor-init-by-other test-actor ((pos vector))
|
||||
(logior! (-> self mask) (process-mask enemy))
|
||||
(init-collision! self)
|
||||
(initialize-skeleton self *test-actor-sg* '())
|
||||
(vector-copy! (-> self root trans) pos)
|
||||
(quaternion-identity! (-> self root quat))
|
||||
(vector-identity! (-> self root scale))
|
||||
(set! (-> self bob-amount) 1024.0)
|
||||
(set! (-> self bob-offset)
|
||||
(+ (the int (-> self root trans x)) (the int (-> self root trans y)) (the int (-> self root trans z))))
|
||||
(set-time! (-> self birth-time))
|
||||
(vector-copy! (-> self base) (-> self root trans))
|
||||
(vector-copy! (-> self old-base) (-> self root trans))
|
||||
(logclear! (-> self mask) (process-mask actor-pause))
|
||||
(transform-post)
|
||||
(go-virtual idle))
|
||||
|
||||
(defstate idle (test-actor)
|
||||
:virtual #t
|
||||
:event
|
||||
(behavior ((proc process) (argc int) (message symbol) (block event-message-block))
|
||||
(case message
|
||||
(('attack 'touch)
|
||||
; (if (= (-> proc type) target)
|
||||
; (send-event proc 'attack #f (static-attack-info ((shove-up (meters 2.5)) (shove-back (meters 7.5)))))
|
||||
; )
|
||||
#t)))
|
||||
:code
|
||||
(behavior ()
|
||||
(loop
|
||||
(quaternion-rotate-y! (-> self root quat) (-> self root quat) (* (degrees 45) (seconds-per-frame)))
|
||||
(let ((bob (-> self bob-amount)))
|
||||
(when (< 0.0 bob)
|
||||
(set! (-> self root trans y)
|
||||
(+ (-> self base y)
|
||||
(* bob
|
||||
(sin (* 109.22667 (the float (mod (+ (- (current-time) (-> self birth-time)) (-> self bob-offset)) (seconds 2))))))))
|
||||
(update-transforms (-> self root))))
|
||||
; (debug-draw-tris (-> (res-lump-struct (-> self draw art-group data 0 extra) 'collide-mesh-group (array collide-mesh)) 0) self 2)
|
||||
; (dotimes (i (-> self node-list length))
|
||||
; (let* ((joint (-> self node-list data i)) (jpos (vector<-cspace! (new-stack-vector0) joint)))
|
||||
; (add-debug-sphere #t (bucket-id debug) jpos (meters 0.1) (static-rgba 0 #xff 0 #x40))
|
||||
; (add-debug-text-sphere (!= (-> joint joint) #f) (bucket-id debug) jpos (meters 0.1) (-> joint joint name) (static-rgba 0 #xff 0 #x40))
|
||||
; )
|
||||
; )
|
||||
(suspend)))
|
||||
:post transform-post)
|
||||
@@ -247,8 +247,14 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro build-custom-level (name)
|
||||
(defmacro build-custom-level (name &key (force-run #f) &key (gen-fr3 #t))
|
||||
(let* ((path (string-append "custom_assets/jak3/levels/" name "/" name ".jsonc")))
|
||||
`(defstep :in ,path
|
||||
`(defstep :in '(,path ,(symbol->string force-run) ,(symbol->string gen-fr3))
|
||||
:tool 'build-level3
|
||||
:out '(,(string-append "$OUT/obj/" name ".go")))))
|
||||
:out '(,(string-append "$OUT/obj/" name ".go")))))
|
||||
|
||||
(defmacro build-actor (name &key (gen-mesh #f) &key (force-run #f) &key (texture-bucket 0))
|
||||
(let* ((path (string-append "custom_assets/jak3/models/custom_levels/" name ".glb")))
|
||||
`(defstep :in '(,path ,(symbol->string gen-mesh) ,(symbol->string force-run) ,(if (integer? texture-bucket) (int->string texture-bucket) (symbol->string texture-bucket)))
|
||||
:tool 'build-actor3
|
||||
:out '(,(string-append "$OUT/obj/" name "-ag.go")))))
|
||||
@@ -60,7 +60,10 @@ add_library(compiler
|
||||
data_compiler/dir_tpages.cpp
|
||||
data_compiler/game_count.cpp
|
||||
data_compiler/DataObjectGenerator.cpp
|
||||
build_actor/common/build_actor.cpp
|
||||
build_actor/jak1/build_actor.cpp
|
||||
build_actor/jak2/build_actor.cpp
|
||||
build_actor/jak3/build_actor.cpp
|
||||
debugger/Debugger.cpp
|
||||
debugger/DebugInfo.cpp
|
||||
listener/Listener.cpp
|
||||
|
||||
@@ -180,7 +180,7 @@ gltf_util::MercSwapData load_merc_model(u32 current_idx_count,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<jak1::CollideMesh> gen_collide_mesh_from_model(
|
||||
std::vector<jak1::CollideMesh> gen_collide_mesh_from_model_jak1(
|
||||
const tinygltf::Model& model,
|
||||
const std::vector<gltf_util::NodeWithTransform>& all_nodes,
|
||||
int joint_idx) {
|
||||
@@ -278,3 +278,201 @@ std::vector<jak1::CollideMesh> gen_collide_mesh_from_model(
|
||||
}
|
||||
return cmeshes;
|
||||
}
|
||||
|
||||
std::vector<jak2::CollideMesh> gen_collide_mesh_from_model_jak2(
|
||||
const tinygltf::Model& model,
|
||||
const std::vector<gltf_util::NodeWithTransform>& all_nodes,
|
||||
int joint_idx) {
|
||||
// data for a single primitive
|
||||
struct PrimWork {
|
||||
std::string mesh_name;
|
||||
std::vector<math::Vector4f> verts;
|
||||
std::vector<u32> indices;
|
||||
gltf_mesh_extract::PatResult pat;
|
||||
};
|
||||
std::vector<std::vector<PrimWork>> mesh_data;
|
||||
int mesh_count = 0;
|
||||
// int prim_count = 0;
|
||||
for (const auto& n : all_nodes) {
|
||||
const auto& node = model.nodes[n.node_idx];
|
||||
gltf_mesh_extract::PatResult mesh_default_collide =
|
||||
gltf_mesh_extract::custom_props_to_pat(node.extras, node.name);
|
||||
if (node.mesh >= 0) {
|
||||
const auto& mesh = model.meshes[node.mesh];
|
||||
mesh_count++;
|
||||
std::vector<PrimWork> prims;
|
||||
for (const auto& prim : mesh.primitives) {
|
||||
// get material
|
||||
const auto& mat_idx = prim.material;
|
||||
gltf_mesh_extract::PatResult pat = mesh_default_collide;
|
||||
if (mat_idx != -1) {
|
||||
const auto& mat = model.materials[mat_idx];
|
||||
auto mat_pat = gltf_mesh_extract::custom_props_to_pat(mat.extras, mat.name);
|
||||
if (mat_pat.set) {
|
||||
pat = mat_pat;
|
||||
}
|
||||
}
|
||||
if (pat.set && pat.ignore) {
|
||||
continue; // skip, no collide here
|
||||
}
|
||||
auto& prim_data = prims.emplace_back();
|
||||
prim_data.mesh_name = mesh.name;
|
||||
prim_data.pat = pat;
|
||||
// prim_count++;
|
||||
// extract index buffer
|
||||
std::vector<u32> prim_indices = gltf_util::gltf_index_buffer(model, prim.indices, 0);
|
||||
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES,
|
||||
fmt::format("Mesh {}: Unsupported triangle mode {}", mesh.name, prim.mode));
|
||||
// extract vertices
|
||||
auto verts =
|
||||
gltf_util::gltf_vertices(model, prim.attributes, n.w_T_node, true, true, mesh.name);
|
||||
ASSERT_MSG(verts.vtx.size() <= 255,
|
||||
fmt::format("primitive of mesh {} has too many vertices (max 255, actual {})",
|
||||
mesh.name, verts.vtx.size()));
|
||||
prim_data.verts.reserve(verts.vtx.size());
|
||||
for (auto& vert : verts.vtx) {
|
||||
prim_data.verts.emplace_back(vert.x, vert.y, vert.z, 1.0);
|
||||
}
|
||||
prim_data.indices = prim_indices;
|
||||
mesh_data.push_back(prims);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<jak2::CollideMesh> cmeshes;
|
||||
cmeshes.reserve(mesh_count);
|
||||
// we extracted all of the prim data for each mesh, now combine
|
||||
for (size_t p = 0; p < mesh_data.size(); p++) {
|
||||
auto& prims = mesh_data.at(p);
|
||||
std::vector<math::Vector4f> verts;
|
||||
std::vector<jak2::CollideMeshTri> tris;
|
||||
int vert_count = 0;
|
||||
for (auto& prim : prims) {
|
||||
if (prim.pat.ignore) {
|
||||
continue;
|
||||
}
|
||||
vert_count += verts.size();
|
||||
verts.insert(verts.end(), prim.verts.begin(), prim.verts.end());
|
||||
for (size_t i = 0; i < prim.indices.size(); i += 3) {
|
||||
auto& tri = tris.emplace_back();
|
||||
tri.vert_idx[0] = prim.indices.at(i);
|
||||
tri.vert_idx[1] = prim.indices.at(i + 1);
|
||||
tri.vert_idx[2] = prim.indices.at(i + 2);
|
||||
tri.pat = jak2_pat(prim.pat.pat);
|
||||
}
|
||||
}
|
||||
ASSERT_MSG(vert_count <= 255, fmt::format("Mesh {} has too many vertices (max 255, actual {})",
|
||||
prims.at(p).mesh_name, vert_count));
|
||||
auto& cmesh = cmeshes.emplace_back();
|
||||
// TODO joint idx as a custom property in blender?
|
||||
cmesh.joint_id = joint_idx;
|
||||
cmesh.vertices.reserve(255);
|
||||
cmesh.vertices.insert(cmesh.vertices.begin(), verts.begin(), verts.end());
|
||||
cmesh.num_verts = cmesh.vertices.size();
|
||||
cmesh.num_tris = tris.size();
|
||||
cmesh.tris.reserve(cmesh.num_tris);
|
||||
for (size_t j = 0; j < cmesh.num_tris; j++) {
|
||||
cmesh.tris.push_back(tris.at(j));
|
||||
}
|
||||
}
|
||||
return cmeshes;
|
||||
}
|
||||
|
||||
std::vector<jak3::CollideMesh> gen_collide_mesh_from_model_jak3(
|
||||
const tinygltf::Model& model,
|
||||
const std::vector<gltf_util::NodeWithTransform>& all_nodes,
|
||||
int joint_idx) {
|
||||
// data for a single primitive
|
||||
struct PrimWork {
|
||||
std::string mesh_name;
|
||||
std::vector<math::Vector4f> verts;
|
||||
std::vector<u32> indices;
|
||||
gltf_mesh_extract::PatResult pat;
|
||||
};
|
||||
std::vector<std::vector<PrimWork>> mesh_data;
|
||||
int mesh_count = 0;
|
||||
// int prim_count = 0;
|
||||
for (const auto& n : all_nodes) {
|
||||
const auto& node = model.nodes[n.node_idx];
|
||||
gltf_mesh_extract::PatResult mesh_default_collide =
|
||||
gltf_mesh_extract::custom_props_to_pat(node.extras, node.name);
|
||||
if (node.mesh >= 0) {
|
||||
const auto& mesh = model.meshes[node.mesh];
|
||||
mesh_count++;
|
||||
std::vector<PrimWork> prims;
|
||||
for (const auto& prim : mesh.primitives) {
|
||||
// get material
|
||||
const auto& mat_idx = prim.material;
|
||||
gltf_mesh_extract::PatResult pat = mesh_default_collide;
|
||||
if (mat_idx != -1) {
|
||||
const auto& mat = model.materials[mat_idx];
|
||||
auto mat_pat = gltf_mesh_extract::custom_props_to_pat(mat.extras, mat.name);
|
||||
if (mat_pat.set) {
|
||||
pat = mat_pat;
|
||||
}
|
||||
}
|
||||
if (pat.set && pat.ignore) {
|
||||
continue; // skip, no collide here
|
||||
}
|
||||
auto& prim_data = prims.emplace_back();
|
||||
prim_data.mesh_name = mesh.name;
|
||||
prim_data.pat = pat;
|
||||
// prim_count++;
|
||||
// extract index buffer
|
||||
std::vector<u32> prim_indices = gltf_util::gltf_index_buffer(model, prim.indices, 0);
|
||||
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES,
|
||||
fmt::format("Mesh {}: Unsupported triangle mode {}", mesh.name, prim.mode));
|
||||
// extract vertices
|
||||
auto verts =
|
||||
gltf_util::gltf_vertices(model, prim.attributes, n.w_T_node, true, true, mesh.name);
|
||||
ASSERT_MSG(verts.vtx.size() <= 255,
|
||||
fmt::format("primitive of mesh {} has too many vertices (max 255, actual {})",
|
||||
mesh.name, verts.vtx.size()));
|
||||
prim_data.verts.reserve(verts.vtx.size());
|
||||
for (auto& vert : verts.vtx) {
|
||||
prim_data.verts.emplace_back(vert.x, vert.y, vert.z, 1.0);
|
||||
}
|
||||
prim_data.indices = prim_indices;
|
||||
mesh_data.push_back(prims);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<jak3::CollideMesh> cmeshes;
|
||||
cmeshes.reserve(mesh_count);
|
||||
// we extracted all of the prim data for each mesh, now combine
|
||||
for (size_t p = 0; p < mesh_data.size(); p++) {
|
||||
auto& prims = mesh_data.at(p);
|
||||
std::vector<math::Vector4f> verts;
|
||||
std::vector<jak3::CollideMeshTri> tris;
|
||||
int vert_count = 0;
|
||||
for (auto& prim : prims) {
|
||||
if (prim.pat.ignore) {
|
||||
continue;
|
||||
}
|
||||
vert_count += verts.size();
|
||||
verts.insert(verts.end(), prim.verts.begin(), prim.verts.end());
|
||||
for (size_t i = 0; i < prim.indices.size(); i += 3) {
|
||||
auto& tri = tris.emplace_back();
|
||||
tri.vert_idx[0] = prim.indices.at(i);
|
||||
tri.vert_idx[1] = prim.indices.at(i + 1);
|
||||
tri.vert_idx[2] = prim.indices.at(i + 2);
|
||||
tri.pat = jak3_pat(prim.pat.pat);
|
||||
}
|
||||
}
|
||||
ASSERT_MSG(vert_count <= 255, fmt::format("Mesh {} has too many vertices (max 255, actual {})",
|
||||
prims.at(p).mesh_name, vert_count));
|
||||
auto& cmesh = cmeshes.emplace_back();
|
||||
// TODO joint idx as a custom property in blender?
|
||||
cmesh.joint_id = joint_idx;
|
||||
cmesh.vertices.reserve(255);
|
||||
cmesh.vertices.insert(cmesh.vertices.begin(), verts.begin(), verts.end());
|
||||
cmesh.num_verts = cmesh.vertices.size();
|
||||
cmesh.num_tris = tris.size();
|
||||
cmesh.tris.reserve(cmesh.num_tris);
|
||||
for (size_t j = 0; j < cmesh.num_tris; j++) {
|
||||
cmesh.tris.push_back(tris.at(j));
|
||||
}
|
||||
}
|
||||
return cmeshes;
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "common/util/gltf_util.h"
|
||||
|
||||
#include "goalc/build_actor/jak1/build_actor.h"
|
||||
#include "goalc/build_actor/jak2/build_actor.h"
|
||||
#include "goalc/build_actor/jak3/build_actor.h"
|
||||
|
||||
void extract(const std::string& name,
|
||||
gltf_util::MercExtractData& out,
|
||||
@@ -17,7 +19,16 @@ gltf_util::MercSwapData load_merc_model(u32 current_idx_count,
|
||||
u32 current_tex_count,
|
||||
const std::string& path,
|
||||
const std::string& name);
|
||||
std::vector<jak1::CollideMesh> gen_collide_mesh_from_model(
|
||||
|
||||
std::vector<jak1::CollideMesh> gen_collide_mesh_from_model_jak1(
|
||||
const tinygltf::Model& model,
|
||||
const std::vector<gltf_util::NodeWithTransform>& all_nodes,
|
||||
int joint_idx);
|
||||
std::vector<jak2::CollideMesh> gen_collide_mesh_from_model_jak2(
|
||||
const tinygltf::Model& model,
|
||||
const std::vector<gltf_util::NodeWithTransform>& all_nodes,
|
||||
int joint_idx);
|
||||
std::vector<jak3::CollideMesh> gen_collide_mesh_from_model_jak3(
|
||||
const tinygltf::Model& model,
|
||||
const std::vector<gltf_util::NodeWithTransform>& all_nodes,
|
||||
int joint_idx);
|
||||
@@ -0,0 +1,171 @@
|
||||
#include "build_actor.h"
|
||||
|
||||
#include "common/log/log.h"
|
||||
#include "common/math/geometry.h"
|
||||
|
||||
#include "goalc/build_actor/common/MercExtract.h"
|
||||
#include "goalc/build_actor/common/animation_processing.h"
|
||||
|
||||
#include "third-party/tiny_gltf/tiny_gltf.h"
|
||||
|
||||
using namespace gltf_util;
|
||||
|
||||
std::map<int, size_t> g_joint_map;
|
||||
|
||||
size_t Joint::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("joint");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_ref_to_string_in_pool(name);
|
||||
gen.add_word(number);
|
||||
if (parent == -1) {
|
||||
gen.add_symbol_link("#f");
|
||||
} else {
|
||||
gen.link_word_to_byte(gen.add_word(0), g_joint_map[parent]);
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
gen.add_word_float(bind_pose(i, j));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Load tinygltf::Model from a .glb file (binary format), fatal error if it fails.
|
||||
*/
|
||||
tinygltf::Model load_gltf_model(const fs::path& path) {
|
||||
tinygltf::TinyGLTF loader;
|
||||
tinygltf::Model model;
|
||||
std::string err, warn;
|
||||
bool res = loader.LoadBinaryFromFile(&model, &err, &warn, path.string());
|
||||
ASSERT_MSG(warn.empty(), warn.c_str());
|
||||
ASSERT_MSG(err.empty(), err.c_str());
|
||||
ASSERT_MSG(res, "Failed to load GLTF file!");
|
||||
return model;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Extract the "skeleton" structure from a GLTF model's skin. This requires that the skin's joints
|
||||
* are topologically sorted (parents always have lower index than children).
|
||||
*/
|
||||
std::vector<GltfJoint> extract_skeleton(const tinygltf::Model& model, int skin_idx) {
|
||||
const auto& skin = model.skins.at(skin_idx);
|
||||
lg::info("skin name is {}", skin.name);
|
||||
lg::info("skeleton root is {}", skin.skeleton);
|
||||
auto inverse_bind_matrices = extract_mat4(model, skin.inverseBindMatrices);
|
||||
ASSERT(inverse_bind_matrices.size() == skin.joints.size());
|
||||
|
||||
std::map<int, int> node_to_joint;
|
||||
std::map<int, int> joint_to_node;
|
||||
std::vector<GltfJoint> joints;
|
||||
|
||||
for (size_t i = 0; i < skin.joints.size(); i++) {
|
||||
auto joint_node_idx = skin.joints[i];
|
||||
const auto& joint_node = model.nodes.at(joint_node_idx);
|
||||
// auto ibm = inverse_bind_matrices[i];
|
||||
// lg::info(" joint {}", joint_node_idx);
|
||||
// lg::info(" {}", joint_node.name);
|
||||
// lg::info("\n{}", ibm.to_string_aligned());
|
||||
node_to_joint[joint_node_idx] = i;
|
||||
joint_to_node[i] = joint_node_idx;
|
||||
|
||||
auto& gjoint = joints.emplace_back();
|
||||
gjoint.bind_pose_T_w = inverse_bind_matrices[i];
|
||||
gjoint.name = joint_node.name;
|
||||
gjoint.gltf_node_index = joint_node_idx;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < skin.joints.size(); i++) {
|
||||
auto joint_node_idx = skin.joints[i];
|
||||
const auto& joint_node = model.nodes.at(joint_node_idx);
|
||||
|
||||
// set up children
|
||||
for (int child_node_idx : joint_node.children) {
|
||||
int child_joint_idx = node_to_joint.at(child_node_idx);
|
||||
joints.at(i).children.push_back(child_joint_idx);
|
||||
auto& child = joints.at(child_joint_idx);
|
||||
ASSERT(child.parent == -1);
|
||||
child.parent = i;
|
||||
ASSERT(child_joint_idx > (int)i);
|
||||
}
|
||||
}
|
||||
ASSERT(joints.at(0).parent == -1);
|
||||
|
||||
// for (auto& joint : joints) {
|
||||
// if (joint.parent == -1) {
|
||||
// lg::warn("parentless {}", joint.name);
|
||||
// } else {
|
||||
// lg::info("joint {}, child of {}", joint.name, joints.at(joint.parent).name);
|
||||
// }
|
||||
// }
|
||||
lg::info("total of {} joints", joints.size());
|
||||
return joints;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert from GLTF joint format to game joint format.
|
||||
* @param joint_index the index of the joint, in the GLTF file.
|
||||
* @param prefix_joint_count number of joints to be inserted before GLTF joints in the game
|
||||
* @param parent_of_gltf the parent game joint of all GLTF joints.
|
||||
*/
|
||||
Joint convert_joint(const GltfJoint& joint,
|
||||
int joint_index,
|
||||
int prefix_joint_count,
|
||||
int parent_of_gltf) {
|
||||
// node matrix is p_T_myself
|
||||
// p_T_myself = parent_bind_pose_T_w * w_T_bind_pose
|
||||
int parent;
|
||||
if (joint.parent == -1) {
|
||||
parent = parent_of_gltf;
|
||||
} else {
|
||||
parent = joint.parent + prefix_joint_count;
|
||||
}
|
||||
math::Matrix4f fixed_matrix = joint.bind_pose_T_w;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
fixed_matrix(i, 3) *= 4096;
|
||||
}
|
||||
|
||||
return Joint(joint.name, joint_index + prefix_joint_count, parent, fixed_matrix.transposed());
|
||||
}
|
||||
|
||||
constexpr int kGltfToGameJointOffset = 1;
|
||||
/*!
|
||||
* Convert GTLF joint list to game joint list.
|
||||
* Currently, this inserts a single "align" joint and places the root joint of the GLTF as the
|
||||
* prejoint. However, we might want to change this, to allow GLTF files to specify "align" at some
|
||||
* point.
|
||||
*/
|
||||
std::vector<Joint> convert_joints(const std::vector<GltfJoint>& gjoints) {
|
||||
std::vector<Joint> joints;
|
||||
joints.emplace_back("align", 0, -1, math::Matrix4f::identity());
|
||||
ASSERT(kGltfToGameJointOffset == joints.size());
|
||||
for (int gjoint_idx = 0; gjoint_idx < int(gjoints.size()); gjoint_idx++) {
|
||||
// using -1 as the parent index since gltf's shouldn't be child of align.
|
||||
joints.push_back(convert_joint(gjoints[gjoint_idx], gjoint_idx, kGltfToGameJointOffset, -1));
|
||||
}
|
||||
|
||||
return joints;
|
||||
}
|
||||
|
||||
std::vector<anim::CompressedAnim> process_anim(const tinygltf::Model& model,
|
||||
const std::vector<GltfJoint>& gjoints) {
|
||||
if (model.animations.empty()) {
|
||||
lg::warn("no animations detected!"); // TODO: make up a dummy one
|
||||
return {};
|
||||
}
|
||||
|
||||
std::map<int, int> node_to_joint;
|
||||
for (size_t i = 0; i < gjoints.size(); i++) {
|
||||
node_to_joint[gjoints[i].gltf_node_index] = i + kGltfToGameJointOffset;
|
||||
}
|
||||
|
||||
std::vector<anim::CompressedAnim> ret;
|
||||
for (auto& anim : model.animations) {
|
||||
lg::info("Processing animation {}", anim.name);
|
||||
ret.push_back(
|
||||
anim::compress_animation(anim::extract_anim_from_gltf(model, anim, node_to_joint, 60)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/util/gltf_util.h"
|
||||
|
||||
#include "goalc/build_actor/common/animation_processing.h"
|
||||
#include "goalc/build_actor/common/art_types.h"
|
||||
#include "goalc/build_level/collide/common/collide_common.h"
|
||||
|
||||
struct Joint {
|
||||
std::string name;
|
||||
s32 number;
|
||||
int parent;
|
||||
math::Matrix4f bind_pose{};
|
||||
|
||||
Joint(const std::string& name, int number, int parent, math::Matrix4f bind_pose) {
|
||||
this->name = name;
|
||||
this->number = number;
|
||||
this->parent = parent;
|
||||
this->bind_pose = bind_pose;
|
||||
}
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
extern std::map<int, size_t> g_joint_map;
|
||||
|
||||
struct BuildActorParams {
|
||||
bool gen_collide_mesh = false;
|
||||
s8 texture_bucket = -1;
|
||||
s32 texture_level = -1;
|
||||
s32 joint_channel = -1;
|
||||
math::Vector4f trans_offset = {0.f, 0.f, 0.f, 1.f};
|
||||
std::vector<float> lod_dist{};
|
||||
};
|
||||
|
||||
struct GltfJoint {
|
||||
math::Matrix4f bind_pose_T_w; // inverse bind pose
|
||||
std::string name;
|
||||
int gltf_node_index = 0;
|
||||
int parent = -1;
|
||||
std::vector<int> children;
|
||||
};
|
||||
|
||||
tinygltf::Model load_gltf_model(const fs::path& path);
|
||||
std::vector<GltfJoint> extract_skeleton(const tinygltf::Model& model, int skin_idx);
|
||||
std::vector<Joint> convert_joints(const std::vector<GltfJoint>& gjoints);
|
||||
std::vector<anim::CompressedAnim> process_anim(const tinygltf::Model& model,
|
||||
const std::vector<GltfJoint>& gjoints);
|
||||
@@ -138,27 +138,6 @@ ArtJointAnim::ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<J
|
||||
}
|
||||
}
|
||||
|
||||
std::map<int, size_t> g_joint_map;
|
||||
|
||||
size_t Joint::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("joint");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_ref_to_string_in_pool(name);
|
||||
gen.add_word(number);
|
||||
if (parent == -1) {
|
||||
gen.add_symbol_link("#f");
|
||||
} else {
|
||||
gen.link_word_to_byte(gen.add_word(0), g_joint_map[parent]);
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
gen.add_word_float(bind_pose(i, j));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressed::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("joint-anim-compressed");
|
||||
@@ -281,16 +260,25 @@ void ArtJointGeo::add_res() {
|
||||
lump.add_res(
|
||||
std::make_unique<ResRef>("collide-mesh-group", "array", mesh_slot, DEFAULT_RES_TIME));
|
||||
}
|
||||
// jgeo.lump.add_res(
|
||||
// std::make_unique<ResInt32>("texture-level", std::vector<s32>{2}, DEFAULT_RES_TIME));
|
||||
// jgeo.lump.add_res(std::make_unique<ResVector>(
|
||||
// "trans-offset", std::vector<math::Vector4f>{{0.0f, 2048.0f, 0.0f, 1.0f}},
|
||||
// DEFAULT_RES_TIME));
|
||||
// jgeo.lump.add_res(
|
||||
// std::make_unique<ResInt32>("joint-channel", std::vector<s32>{0}, DEFAULT_RES_TIME));
|
||||
// jgeo.lump.add_res(std::make_unique<ResFloat>(
|
||||
// "lod-dist", std::vector<float>{5000.0f * METER_LENGTH, 6000.0f * METER_LENGTH},
|
||||
// DEFAULT_RES_TIME));
|
||||
if (texture_bucket != -1) {
|
||||
lump.add_res(std::make_unique<ResUint8>(
|
||||
"texture-bucket", std::vector<u8>{static_cast<u8>(texture_bucket)}, DEFAULT_RES_TIME));
|
||||
}
|
||||
if (texture_level != -1) {
|
||||
lump.add_res(std::make_unique<ResInt32>("texture-level", std::vector<s32>{texture_level},
|
||||
DEFAULT_RES_TIME));
|
||||
}
|
||||
if (trans_offset != math::Vector4f{0.f, 0.f, 0.f, 1.f}) {
|
||||
lump.add_res(std::make_unique<ResVector>(
|
||||
"trans-offset", std::vector<math::Vector4f>{trans_offset}, DEFAULT_RES_TIME));
|
||||
}
|
||||
if (joint_channel != -1) {
|
||||
lump.add_res(std::make_unique<ResInt32>("joint-channel", std::vector<s32>{joint_channel},
|
||||
DEFAULT_RES_TIME));
|
||||
}
|
||||
if (!lod_dist.empty()) {
|
||||
lump.add_res(std::make_unique<ResFloat>("lod-dist", lod_dist, DEFAULT_RES_TIME));
|
||||
}
|
||||
lump.sort_res();
|
||||
}
|
||||
|
||||
@@ -569,159 +557,12 @@ int ArtGroup::get_joint_idx(const std::string& name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Load tinygltf::Model from a .glb file (binary format), fatal error if it fails.
|
||||
*/
|
||||
tinygltf::Model load_gltf_model(const fs::path& path) {
|
||||
tinygltf::TinyGLTF loader;
|
||||
tinygltf::Model model;
|
||||
std::string err, warn;
|
||||
bool res = loader.LoadBinaryFromFile(&model, &err, &warn, path.string());
|
||||
ASSERT_MSG(warn.empty(), warn.c_str());
|
||||
ASSERT_MSG(err.empty(), err.c_str());
|
||||
ASSERT_MSG(res, "Failed to load GLTF file!");
|
||||
return model;
|
||||
}
|
||||
|
||||
struct GltfJoint {
|
||||
math::Matrix4f bind_pose_T_w; // inverse bind pose
|
||||
std::string name;
|
||||
int gltf_node_index = 0;
|
||||
int parent = -1;
|
||||
std::vector<int> children;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Extract the "skeleton" structure from a GLTF model's skin. This requires that the skin's joints
|
||||
* are topologically sorted (parents always have lower index than children).
|
||||
*/
|
||||
std::vector<GltfJoint> extract_skeleton(const tinygltf::Model& model, int skin_idx) {
|
||||
const auto& skin = model.skins.at(skin_idx);
|
||||
lg::info("skin name is {}", skin.name);
|
||||
lg::info("skeleton root is {}", skin.skeleton);
|
||||
auto inverse_bind_matrices = extract_mat4(model, skin.inverseBindMatrices);
|
||||
ASSERT(inverse_bind_matrices.size() == skin.joints.size());
|
||||
|
||||
std::map<int, int> node_to_joint;
|
||||
std::map<int, int> joint_to_node;
|
||||
std::vector<GltfJoint> joints;
|
||||
|
||||
for (size_t i = 0; i < skin.joints.size(); i++) {
|
||||
auto joint_node_idx = skin.joints[i];
|
||||
const auto& joint_node = model.nodes.at(joint_node_idx);
|
||||
// auto ibm = inverse_bind_matrices[i];
|
||||
// lg::info(" joint {}", joint_node_idx);
|
||||
// lg::info(" {}", joint_node.name);
|
||||
// lg::info("\n{}", ibm.to_string_aligned());
|
||||
node_to_joint[joint_node_idx] = i;
|
||||
joint_to_node[i] = joint_node_idx;
|
||||
|
||||
auto& gjoint = joints.emplace_back();
|
||||
gjoint.bind_pose_T_w = inverse_bind_matrices[i];
|
||||
gjoint.name = joint_node.name;
|
||||
gjoint.gltf_node_index = joint_node_idx;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < skin.joints.size(); i++) {
|
||||
auto joint_node_idx = skin.joints[i];
|
||||
const auto& joint_node = model.nodes.at(joint_node_idx);
|
||||
|
||||
// set up children
|
||||
for (int child_node_idx : joint_node.children) {
|
||||
int child_joint_idx = node_to_joint.at(child_node_idx);
|
||||
joints.at(i).children.push_back(child_joint_idx);
|
||||
auto& child = joints.at(child_joint_idx);
|
||||
ASSERT(child.parent == -1);
|
||||
child.parent = i;
|
||||
ASSERT(child_joint_idx > (int)i);
|
||||
}
|
||||
}
|
||||
ASSERT(joints.at(0).parent == -1);
|
||||
|
||||
// for (auto& joint : joints) {
|
||||
// if (joint.parent == -1) {
|
||||
// lg::warn("parentless {}", joint.name);
|
||||
// } else {
|
||||
// lg::info("joint {}, child of {}", joint.name, joints.at(joint.parent).name);
|
||||
// }
|
||||
// }
|
||||
lg::info("total of {} joints", joints.size());
|
||||
return joints;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert from GLTF joint format to game joint format.
|
||||
* @param joint_index the index of the joint, in the GLTF file.
|
||||
* @param prefix_joint_count number of joints to be inserted before GLTF joints in the game
|
||||
* @param parent_of_gltf the parent game joint of all GLTF joints.
|
||||
*/
|
||||
Joint convert_joint(const GltfJoint& joint,
|
||||
int joint_index,
|
||||
int prefix_joint_count,
|
||||
int parent_of_gltf) {
|
||||
// node matrix is p_T_myself
|
||||
// p_T_myself = parent_bind_pose_T_w * w_T_bind_pose
|
||||
int parent;
|
||||
if (joint.parent == -1) {
|
||||
parent = parent_of_gltf;
|
||||
} else {
|
||||
parent = joint.parent + prefix_joint_count;
|
||||
}
|
||||
math::Matrix4f fixed_matrix = joint.bind_pose_T_w;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
fixed_matrix(i, 3) *= 4096;
|
||||
}
|
||||
|
||||
return Joint(joint.name, joint_index + prefix_joint_count, parent, fixed_matrix.transposed());
|
||||
}
|
||||
|
||||
constexpr int kGltfToGameJointOffset = 1;
|
||||
/*!
|
||||
* Convert GTLF joint list to game joint list.
|
||||
* Currently, this inserts a single "align" joint and places the root joint of the GLTF as the
|
||||
* prejoint. However, we might want to change this, to allow GLTF files to specify "align" at some
|
||||
* point.
|
||||
*/
|
||||
std::vector<Joint> convert_joints(const std::vector<GltfJoint>& gjoints) {
|
||||
std::vector<Joint> joints;
|
||||
joints.emplace_back("align", 0, -1, math::Matrix4f::identity());
|
||||
ASSERT(kGltfToGameJointOffset == joints.size());
|
||||
for (int gjoint_idx = 0; gjoint_idx < int(gjoints.size()); gjoint_idx++) {
|
||||
// using -1 as the parent index since gltf's shouldn't be child of align.
|
||||
joints.push_back(convert_joint(gjoints[gjoint_idx], gjoint_idx, kGltfToGameJointOffset, -1));
|
||||
}
|
||||
|
||||
return joints;
|
||||
}
|
||||
|
||||
std::vector<anim::CompressedAnim> process_anim(const tinygltf::Model& model,
|
||||
const std::vector<GltfJoint>& gjoints) {
|
||||
if (model.animations.empty()) {
|
||||
lg::warn("no animations detected!"); // TODO: make up a dummy one
|
||||
return {};
|
||||
}
|
||||
|
||||
std::map<int, int> node_to_joint;
|
||||
for (size_t i = 0; i < gjoints.size(); i++) {
|
||||
node_to_joint[gjoints[i].gltf_node_index] = i + kGltfToGameJointOffset;
|
||||
}
|
||||
|
||||
std::vector<anim::CompressedAnim> ret;
|
||||
for (auto& anim : model.animations) {
|
||||
lg::info("Processing animation {}", anim.name);
|
||||
ret.push_back(
|
||||
anim::compress_animation(anim::extract_anim_from_gltf(model, anim, node_to_joint, 60)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Build GOAL format data for an actor. This doesn't generate the data for the .FR3.
|
||||
*/
|
||||
bool run_build_actor(const std::string& mdl_name,
|
||||
const std::string& ag_out,
|
||||
bool gen_collide_mesh) {
|
||||
const BuildActorParams& params) {
|
||||
std::string ag_name;
|
||||
if (fs::exists(file_util::get_jak_project_dir() / mdl_name)) {
|
||||
ag_name = fs::path(mdl_name).stem().string();
|
||||
@@ -766,11 +607,11 @@ bool run_build_actor(const std::string& mdl_name,
|
||||
}
|
||||
|
||||
std::vector<CollideMesh> mesh;
|
||||
if (gen_collide_mesh) {
|
||||
mesh = gen_collide_mesh_from_model(model, all_nodes, 3);
|
||||
if (params.gen_collide_mesh) {
|
||||
mesh = gen_collide_mesh_from_model_jak1(model, all_nodes, 3);
|
||||
}
|
||||
|
||||
std::shared_ptr<ArtJointGeo> jgeo = std::make_shared<ArtJointGeo>(ag.name, mesh, joints);
|
||||
std::shared_ptr<ArtJointGeo> jgeo = std::make_shared<ArtJointGeo>(ag.name, mesh, joints, params);
|
||||
|
||||
ag.elts.emplace_back(jgeo);
|
||||
// dummy merc-ctrl
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "goalc/build_actor/common/animation_processing.h"
|
||||
#include "goalc/build_actor/common/art_types.h"
|
||||
#include "goalc/build_actor/common/build_actor.h"
|
||||
#include "goalc/build_level/collide/common/collide_common.h"
|
||||
|
||||
namespace jak1 {
|
||||
@@ -13,22 +14,6 @@ namespace jak1 {
|
||||
// the "compressed" format, which combines data for all joints into a single structure.
|
||||
// By jak 2, they had cleaned this up, but for Jak 1, we have to deal with this weirdness.
|
||||
|
||||
struct Joint {
|
||||
std::string name;
|
||||
s32 number;
|
||||
int parent;
|
||||
math::Matrix4f bind_pose{};
|
||||
|
||||
Joint(const std::string& name, int number, int parent, math::Matrix4f bind_pose) {
|
||||
this->name = name;
|
||||
this->number = number;
|
||||
this->parent = parent;
|
||||
this->bind_pose = bind_pose;
|
||||
}
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
// basic
|
||||
// This is one of those weird leftover types.
|
||||
// There's one of these per-joint, per-animation, and all it's useful for is storing the
|
||||
@@ -153,21 +138,41 @@ struct CollideMesh {
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildActorParams {
|
||||
bool gen_collide_mesh = false;
|
||||
s8 texture_bucket = -1;
|
||||
s32 texture_level = -1;
|
||||
s32 joint_channel = -1;
|
||||
math::Vector4f trans_offset = {0.f, 0.f, 0.f, 1.f};
|
||||
std::vector<float> lod_dist{};
|
||||
};
|
||||
|
||||
struct ArtJointGeo : ArtElement {
|
||||
std::vector<Joint> data;
|
||||
std::vector<CollideMesh> cmeshes;
|
||||
ResLump lump;
|
||||
size_t mesh_slot;
|
||||
s8 texture_bucket;
|
||||
s32 texture_level;
|
||||
s32 joint_channel;
|
||||
math::Vector4f trans_offset;
|
||||
std::vector<float> lod_dist;
|
||||
|
||||
explicit ArtJointGeo(const std::string& name,
|
||||
std::vector<CollideMesh> cmeshes,
|
||||
std::vector<Joint>& joints) {
|
||||
std::vector<Joint>& joints,
|
||||
const BuildActorParams& params) {
|
||||
this->name = name + "-lod0";
|
||||
length = joints.size();
|
||||
for (auto& joint : joints) {
|
||||
data.push_back(joint);
|
||||
}
|
||||
this->cmeshes = std::move(cmeshes);
|
||||
this->texture_bucket = params.texture_bucket;
|
||||
this->texture_level = params.texture_level;
|
||||
this->joint_channel = params.joint_channel;
|
||||
this->trans_offset = params.trans_offset;
|
||||
this->lod_dist = params.lod_dist;
|
||||
}
|
||||
void add_res();
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
@@ -225,5 +230,5 @@ struct ArtGroup : Art {
|
||||
|
||||
bool run_build_actor(const std::string& input_model,
|
||||
const std::string& output_file,
|
||||
bool gen_collide_mesh);
|
||||
const BuildActorParams& params);
|
||||
} // namespace jak1
|
||||
|
||||
@@ -0,0 +1,632 @@
|
||||
#include "build_actor.h"
|
||||
|
||||
#include "common/log/log.h"
|
||||
#include "common/math/geometry.h"
|
||||
|
||||
#include "goalc/build_actor/common/MercExtract.h"
|
||||
#include "goalc/build_actor/common/animation_processing.h"
|
||||
|
||||
#include "third-party/tiny_gltf/tiny_gltf.h"
|
||||
|
||||
using namespace gltf_util;
|
||||
namespace jak2 {
|
||||
|
||||
JointAnimCompressedHDR::JointAnimCompressedHDR(const anim::CompressedAnim& anim) {
|
||||
matrix_bits = 0;
|
||||
if (anim.matrix_animated[0]) {
|
||||
matrix_bits |= 1;
|
||||
}
|
||||
if (anim.matrix_animated[1]) {
|
||||
matrix_bits |= 2;
|
||||
}
|
||||
|
||||
for (auto& word : control_bits) {
|
||||
word = 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < anim.joint_metadata.size(); i++) {
|
||||
const int word_idx = i / 8;
|
||||
if (word_idx >= 14) {
|
||||
lg::error("too many joints!!");
|
||||
break;
|
||||
}
|
||||
|
||||
u32 val = 0;
|
||||
const auto& metadata = anim.joint_metadata[i];
|
||||
if (metadata.animated_trans) {
|
||||
val |= (0b1);
|
||||
}
|
||||
if (metadata.animated_quat) {
|
||||
val |= (0b10);
|
||||
}
|
||||
if (metadata.animated_scale) {
|
||||
val |= (0b100);
|
||||
}
|
||||
if (metadata.big_trans_mode) {
|
||||
val |= (0b1000);
|
||||
}
|
||||
val = (val << (4 * (i % 8)));
|
||||
control_bits[word_idx] |= val;
|
||||
}
|
||||
|
||||
num_joints = 2 + anim.joint_metadata.size();
|
||||
}
|
||||
|
||||
JointAnimCompressedFixed::JointAnimCompressedFixed(const anim::CompressedAnim& anim) : hdr(anim) {
|
||||
u8* dest = (u8*)data;
|
||||
const u8* u64_src = (const u8*)anim.fixed.data64.data();
|
||||
const u8* u32_src = (const u8*)anim.fixed.data32.data();
|
||||
const u8* u16_src = (const u8*)anim.fixed.data16.data();
|
||||
|
||||
const int u64_size = anim.fixed.data64.size() * sizeof(u64);
|
||||
const int u32_size = anim.fixed.data32.size() * sizeof(u32);
|
||||
const int u16_size = anim.fixed.data16.size() * sizeof(u16);
|
||||
|
||||
if (u64_size + u32_size + u16_size > 133 * 4 * 4) {
|
||||
lg::die("fixed sizes are {} and {}", 133 * 4 * 4, u64_size + u32_size + u16_size);
|
||||
}
|
||||
ASSERT(u64_size + u32_size + u16_size <= 133 * 4 * 4);
|
||||
|
||||
offset_64 = 0;
|
||||
memcpy(dest, u64_src, u64_size);
|
||||
dest += u64_size;
|
||||
|
||||
offset_32 = offset_64 + u64_size;
|
||||
memcpy(dest, u32_src, u32_size);
|
||||
dest += u32_size;
|
||||
|
||||
offset_16 = offset_32 + u32_size;
|
||||
memcpy(dest, u16_src, u16_size);
|
||||
reserved = 0;
|
||||
|
||||
num_data_qw_used = (15 + u64_size + u32_size + u16_size) / 16;
|
||||
ASSERT(num_data_qw_used <= 133);
|
||||
}
|
||||
|
||||
JointAnimCompressedFrame::JointAnimCompressedFrame(const anim::CompressedFrame& frame) {
|
||||
reserved = 0;
|
||||
u8* dest = (u8*)data;
|
||||
const u8* u64_src = (const u8*)frame.data64.data();
|
||||
const u8* u32_src = (const u8*)frame.data32.data();
|
||||
const u8* u16_src = (const u8*)frame.data16.data();
|
||||
|
||||
const int u64_size = frame.data64.size() * sizeof(u64);
|
||||
const int u32_size = frame.data32.size() * sizeof(u32);
|
||||
const int u16_size = frame.data16.size() * sizeof(u16);
|
||||
|
||||
if (u64_size + u32_size + u16_size > 133 * 4 * 4) {
|
||||
lg::die("frame sizes are {} and {}", 133 * 4 * 4, u64_size + u32_size + u16_size);
|
||||
}
|
||||
|
||||
offset_64 = 0;
|
||||
memcpy(dest, u64_src, u64_size);
|
||||
dest += u64_size;
|
||||
|
||||
offset_32 = offset_64 + u64_size;
|
||||
memcpy(dest, u32_src, u32_size);
|
||||
dest += u32_size;
|
||||
|
||||
offset_16 = offset_32 + u32_size;
|
||||
memcpy(dest, u16_src, u16_size);
|
||||
reserved = 0;
|
||||
|
||||
num_data_qw_used = (15 + u64_size + u32_size + u16_size) / 16;
|
||||
ASSERT(num_data_qw_used <= 133);
|
||||
}
|
||||
|
||||
JointAnimCompressedControl::JointAnimCompressedControl(const anim::CompressedAnim& anim)
|
||||
: fixed(anim) {
|
||||
num_frames = anim.frames.size();
|
||||
for (auto& in_frame : anim.frames) {
|
||||
frame.emplace_back(in_frame);
|
||||
}
|
||||
fixed_qwc = fixed.num_data_qw_used;
|
||||
frame_qwc = frame.at(0).num_data_qw_used;
|
||||
}
|
||||
|
||||
ArtJointAnim::ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<Joint>& joints)
|
||||
: frames(anim) {
|
||||
this->name = anim.name;
|
||||
length = joints.size();
|
||||
speed = 1.0f;
|
||||
artist_base = 0.0f;
|
||||
artist_step = 1.0f;
|
||||
master_art_group_name = name;
|
||||
master_art_group_index = 2;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressedFrame::generate(DataObjectGenerator& gen) const {
|
||||
gen.align(16); // might have been 8, but 16 doesn't hurt.
|
||||
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(offset_64); // 0
|
||||
gen.add_word(offset_32); // 4
|
||||
gen.add_word(offset_16); // 8
|
||||
gen.add_word(reserved); // 12
|
||||
|
||||
for (u32 i = 0; i < num_data_qw_used; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
gen.add_word_float(data[i][j]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressedHDR::generate(DataObjectGenerator& gen) const {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
for (auto& bit : control_bits) {
|
||||
gen.add_word(bit);
|
||||
}
|
||||
gen.add_word(num_joints);
|
||||
gen.add_word(matrix_bits);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressedFixed::generate(DataObjectGenerator& gen) const {
|
||||
gen.align(16); // might have been 8, but 16 doesn't hurt.
|
||||
size_t result = gen.current_offset_bytes();
|
||||
hdr.generate(gen); // 0-64 (inline)
|
||||
gen.add_word(offset_64); // 64
|
||||
gen.add_word(offset_32); // 68
|
||||
gen.add_word(offset_16); // 72
|
||||
gen.add_word(reserved); // 76
|
||||
// default joint poses (taken from money-idle)
|
||||
for (int i = 0; i < num_data_qw_used; i++) {
|
||||
gen.add_word_float(data[i].x());
|
||||
gen.add_word_float(data[i].y());
|
||||
gen.add_word_float(data[i].z());
|
||||
gen.add_word_float(data[i].w());
|
||||
}
|
||||
// -- not sure what this is, part of the dummy data maybe?
|
||||
gen.add_word(0);
|
||||
gen.add_word(0x7fff0000);
|
||||
gen.add_word(0x2250000);
|
||||
gen.add_word(0x10001000);
|
||||
gen.add_word(0x10000000);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.align(4);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressedControl::generate(DataObjectGenerator& gen) const {
|
||||
gen.align(16);
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(num_frames); // 0
|
||||
gen.add_word(fixed_qwc); // 4
|
||||
gen.add_word(frame_qwc); // 8
|
||||
|
||||
auto ja_fixed_slot = gen.add_word(0);
|
||||
std::vector<int> ja_frame_slots;
|
||||
for (u32 i = 0; i < num_frames; i++) {
|
||||
ja_frame_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.link_word_to_byte(ja_fixed_slot, fixed.generate(gen));
|
||||
ASSERT(num_frames == frame.size());
|
||||
for (size_t i = 0; i < num_frames; i++) {
|
||||
gen.link_word_to_byte(ja_frame_slots[i], frame[i].generate(gen));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t CollideMesh::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("collide-mesh");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
// gen.add_word(0xffffffff - joint_id); // 4 (joint-id)
|
||||
gen.add_word(joint_id); // 4 (joint-id)
|
||||
gen.add_word(num_tris); // 8 (num-tris)
|
||||
gen.add_word(num_verts); // 12 (num-verts)
|
||||
auto vertices_slot = gen.add_word(0); // 16 (vertex-data)
|
||||
gen.add_word(0); // 20 (pad)
|
||||
gen.add_word(0); // 24 (pad)
|
||||
gen.add_word(0); // 28 (pad)
|
||||
for (auto& tri : tris) {
|
||||
u32 word = (tri.vert_idx[0] & 0xff) + ((tri.vert_idx[1] << 8) & 0xff00) +
|
||||
((tri.vert_idx[2] << 16) & 0xff0000);
|
||||
gen.add_word(word); // 0 (tris | vertex-index | unused)
|
||||
gen.add_word(tri.pat.val); // 4 (pat)
|
||||
}
|
||||
// vertex data start
|
||||
gen.link_word_to_byte(vertices_slot, gen.current_offset_bytes());
|
||||
for (auto& vert : vertices) {
|
||||
gen.add_word_float(vert.x());
|
||||
gen.add_word_float(vert.y());
|
||||
gen.add_word_float(vert.z());
|
||||
gen.add_word_float(vert.w());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
void ArtJointGeo::add_res() {
|
||||
if (!cmeshes.empty()) {
|
||||
lump.add_res(
|
||||
std::make_unique<ResRef>("collide-mesh-group", "array", mesh_slot, DEFAULT_RES_TIME));
|
||||
}
|
||||
if (texture_bucket != -1) {
|
||||
lump.add_res(std::make_unique<ResUint8>(
|
||||
"texture-bucket", std::vector<u8>{static_cast<u8>(texture_bucket)}, DEFAULT_RES_TIME));
|
||||
}
|
||||
if (texture_level != -1) {
|
||||
lump.add_res(std::make_unique<ResInt32>("texture-level", std::vector<s32>{texture_level},
|
||||
DEFAULT_RES_TIME));
|
||||
}
|
||||
if (trans_offset != math::Vector4f{0.f, 0.f, 0.f, 1.f}) {
|
||||
lump.add_res(std::make_unique<ResVector>(
|
||||
"trans-offset", std::vector<math::Vector4f>{trans_offset}, DEFAULT_RES_TIME));
|
||||
}
|
||||
if (joint_channel != -1) {
|
||||
lump.add_res(std::make_unique<ResInt32>("joint-channel", std::vector<s32>{joint_channel},
|
||||
DEFAULT_RES_TIME));
|
||||
}
|
||||
if (!lod_dist.empty()) {
|
||||
lump.add_res(std::make_unique<ResFloat>("lod-dist", lod_dist, DEFAULT_RES_TIME));
|
||||
}
|
||||
lump.sort_res();
|
||||
}
|
||||
|
||||
size_t ArtJointGeo::generate_mesh(DataObjectGenerator& gen) const {
|
||||
std::vector<size_t> data_slots;
|
||||
std::vector<size_t> content_slots;
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(cmeshes.size()); // 0 (length)
|
||||
gen.add_word(cmeshes.size()); // 4 (allocated-length)
|
||||
gen.add_type_tag("collide-mesh"); // 8 (content-type)
|
||||
content_slots.reserve(cmeshes.size());
|
||||
for (auto& data : cmeshes) {
|
||||
(void)data;
|
||||
content_slots.push_back(gen.add_word(0)); // 12 (data)
|
||||
}
|
||||
gen.align(4);
|
||||
for (size_t i = 0; i < content_slots.size(); i++) {
|
||||
data_slots.push_back(cmeshes.at(i).generate(gen));
|
||||
}
|
||||
for (size_t i = 0; i < content_slots.size(); i++) {
|
||||
gen.link_word_to_byte(content_slots.at(i), data_slots.at(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t ArtJointGeo::generate(DataObjectGenerator& gen) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("art-joint-geo");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(0); // 4
|
||||
gen.add_ref_to_string_in_pool(name); // 8
|
||||
gen.add_word(length); // 12
|
||||
auto res_slot = gen.add_word(0); // 16 (res-lump)
|
||||
gen.add_word(0); // 20
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
std::vector<size_t> joint_slots;
|
||||
for (int i = 0; i < length; i++) {
|
||||
joint_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.align(4);
|
||||
for (int i = 0; i < length; i++) {
|
||||
auto joint = data.at(i).generate(gen);
|
||||
gen.link_word_to_byte(joint_slots.at(i), joint);
|
||||
g_joint_map[data.at(i).number] = joint;
|
||||
}
|
||||
mesh_slot = generate_mesh(gen);
|
||||
add_res();
|
||||
auto res_header = lump.generate_header(gen, "res-lump");
|
||||
gen.link_word_to_byte(res_slot, res_header);
|
||||
lump.generate_tag_list_and_data(gen, res_header);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t ArtJointAnim::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("art-joint-anim");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_symbol_link("#f"); // 4 (eye block)
|
||||
gen.add_ref_to_string_in_pool(name); // 8
|
||||
gen.add_word(length); // 12
|
||||
gen.add_symbol_link("#f"); // 16 (res-lump)
|
||||
gen.add_word_float(speed); // 20
|
||||
gen.add_word_float(artist_base); // 24
|
||||
gen.add_word_float(artist_step); // 28
|
||||
gen.add_ref_to_string_in_pool(master_art_group_name); // 32
|
||||
gen.add_word(master_art_group_index); // 36
|
||||
gen.add_symbol_link("#f"); // 40 (blerc)
|
||||
auto ctrl_slot = gen.add_word(0);
|
||||
std::vector<size_t> frame_slots;
|
||||
for (int i = 0; i < length; i++) {
|
||||
frame_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.align(4);
|
||||
gen.link_word_to_byte(ctrl_slot, frames.generate(gen));
|
||||
return result;
|
||||
}
|
||||
|
||||
static size_t gen_dummy_frag_geo(DataObjectGenerator& gen) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
// frag geo stolen from money-lod2
|
||||
static std::vector<u32> words = {
|
||||
0x51180504, 0x1000013, 0x1b150000, 0x604, 0x5225, 0x80808080, 0x80808080,
|
||||
0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080,
|
||||
0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080,
|
||||
0x80808080, 0x80808080, 0x80808080, 0xb4e48086, 0x757d3a0a, 0x1e324000, 0x1e328186,
|
||||
0x757d3410, 0x1c4020, 0x432e8186, 0x251313, 0x71a28044, 0x1c8186, 0x757d2e16,
|
||||
0x96ce4040, 0x719b8186, 0x151919, 0x71c08064, 0x96ce8186, 0x757d281c, 0xb4e44060,
|
||||
0x71d28186, 0x251f1f, 0x435e8080, 0xb4e48186, 0x757d4322, 0x1e324080, 0x71cc7f86,
|
||||
0xffdb4025, 0x71ad0064, 0x435c8186, 0xffea582b, 0x71bc0044, 0x43348186, 0xffdb5b31,
|
||||
0x43530024, 0x71a48086, 0xffea3755, 0x43440000, 0x71a47f86, 0xffea3d3d, 0x43440080,
|
||||
0x432e7f86, 0x254646, 0x71a20044, 0x43657f86, 0x154949, 0x43400024, 0x719b8186,
|
||||
0x154c4c, 0x71c00064, 0x71d28186, 0x254f4f, 0x435e0000, 0x71d27f86, 0x250707,
|
||||
0x435e8000, 0x43658186, 0x150d0d, 0x43408024, 0x0, 0x0, 0x0,
|
||||
0xcb01005a, 0xcb00fffa, 0xcb01005a, 0x2101e01, 0x0, 0x0, 0x306,
|
||||
0x4030000, 0x120, 0x0, 0x1cf02c14, 0x66c801d, 0x0, 0x0,
|
||||
0x34, 0x0, 0x0, 0x0, 0x8, 0x0, 0x44,
|
||||
0x80, 0x42, 0x0,
|
||||
};
|
||||
for (auto& word : words) {
|
||||
gen.add_word(word);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t gen_dummy_frag_ctrl(DataObjectGenerator& gen) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(0x1063918);
|
||||
gen.add_word(0x603);
|
||||
gen.add_word(0x0);
|
||||
gen.add_word(0x0);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t gen_dummy_frag_ctrl_for_uploads(DataObjectGenerator& gen, int n) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
|
||||
std::vector<u8> packed_frag_ctrls;
|
||||
|
||||
// this is still a bit of a hack - the dummy_frag_ctrl above has 4 fragments, so we need to
|
||||
// provide fragment controls for each. The PC merc renderer will de-duplicate bone uploads over
|
||||
// all effects and fragments, so we just need to have a single fragment that asks for all bones,
|
||||
// and everything will work out.
|
||||
for (int k = 0; k < 4; k++) {
|
||||
packed_frag_ctrls.push_back(0);
|
||||
packed_frag_ctrls.push_back(0);
|
||||
packed_frag_ctrls.push_back(0);
|
||||
if (k == 0) { // for the first frag, do all matrix uploads.
|
||||
// note that these are bogus destination addresses, but nothing uses it on PC
|
||||
packed_frag_ctrls.push_back(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
packed_frag_ctrls.push_back(i);
|
||||
packed_frag_ctrls.push_back(i);
|
||||
}
|
||||
} else {
|
||||
// remaining frags can have empty matrix upload lists.
|
||||
packed_frag_ctrls.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> u32s((packed_frag_ctrls.size() + 3) / 4);
|
||||
memcpy(u32s.data(), packed_frag_ctrls.data(), packed_frag_ctrls.size());
|
||||
for (auto x : u32s) {
|
||||
gen.add_word(x);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t gen_dummy_extra_info(DataObjectGenerator& gen) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(0x1);
|
||||
gen.add_word(0x0);
|
||||
gen.add_word(0x0);
|
||||
gen.add_word(0x0);
|
||||
gen.add_word(0xb74ccccd);
|
||||
gen.add_word(0x40400000);
|
||||
gen.add_word(0x8066a1ff);
|
||||
gen.add_word(0x0);
|
||||
return result;
|
||||
}
|
||||
|
||||
void generate_merc_effects(DataObjectGenerator& gen, int effect_count, int joints) {
|
||||
struct EffectLocs {
|
||||
size_t frag_geo;
|
||||
size_t frag_ctrl;
|
||||
size_t extra_info;
|
||||
};
|
||||
std::vector<EffectLocs> locs;
|
||||
for (int i = 0; i < effect_count; i++) {
|
||||
EffectLocs loc{};
|
||||
loc.frag_geo = gen.add_word(0); // 112-140 (effect)
|
||||
loc.frag_ctrl = gen.add_word(0); // 116 (frag-ctrl)
|
||||
gen.add_word(0x0); // 120 (blend-data)
|
||||
gen.add_word(0x0); // 124 (blend-ctrl)
|
||||
gen.add_word(0x10000); // 128
|
||||
gen.add_word(0x140000); // 132
|
||||
gen.add_word(0x100001d); // 136
|
||||
loc.extra_info = gen.add_word(0); // 140 (extra-info)
|
||||
locs.push_back(loc);
|
||||
}
|
||||
for (auto& loc : locs) {
|
||||
gen.link_word_to_byte(loc.extra_info, gen_dummy_extra_info(gen));
|
||||
gen.link_word_to_byte(loc.frag_ctrl, gen_dummy_frag_ctrl_for_uploads(gen, joints + 3));
|
||||
gen.link_word_to_byte(loc.frag_geo, gen_dummy_frag_geo(gen));
|
||||
}
|
||||
}
|
||||
|
||||
size_t generate_dummy_merc_ctrl(DataObjectGenerator& gen, const ArtGroup& ag) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("merc-ctrl");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
// excluding align and prejoint
|
||||
auto joints = ((ArtJointGeo*)ag.elts.at(0).get())->length - 2;
|
||||
gen.add_word(0); // 4
|
||||
gen.add_ref_to_string_in_pool(ag.name + "-lod0"); // 8
|
||||
gen.add_word(0); // 12
|
||||
gen.add_symbol_link("#f"); // 16 (res-lump)
|
||||
gen.add_word(joints); // 20 (num-joints)
|
||||
gen.add_word(0x0); // 24 (seg-table)
|
||||
gen.add_word(0x0); // 28 (pad)
|
||||
gen.add_word(0x4185c8ac); // 32-160 (xyz-scale)
|
||||
gen.add_word(0xc780ff80); // 36 (st-magic)
|
||||
gen.add_word(0x40798000); // 40 (st-out-a)
|
||||
gen.add_word(0x40eb4000); // 44 (st-out-b)
|
||||
gen.add_word(0x4780ff80); // 48 (st-vif-add)
|
||||
gen.add_word(0x50000); // 52 ((st-int-off << 16) + st-int-scale)
|
||||
gen.add_word(ag.merc_effect_count); // 56 (effect-count)
|
||||
gen.add_word(0x0); // 60 (blend-target-count)
|
||||
gen.add_word((0x14 * ag.merc_effect_count << 16) +
|
||||
ag.merc_effect_count); // 64 ((fragment-count << 16) + tri-count)
|
||||
gen.add_word(0x130101); // 68
|
||||
gen.add_word(0x13001d); // 72
|
||||
gen.add_word(0x0); // 76
|
||||
gen.add_word(0x0); // 80
|
||||
gen.add_word(0x10101); // 84
|
||||
gen.add_word(0x130000); // 88
|
||||
gen.add_word(0x3f319ca9); // 92
|
||||
gen.add_word(0x0); // 96
|
||||
gen.add_word(0x0); // 100
|
||||
gen.add_word(0x0); // 104
|
||||
gen.add_word(0x0); // 108
|
||||
gen.add_word(0x0); // 112
|
||||
gen.add_word(0x0); // 116
|
||||
gen.add_word(0x0); // 120
|
||||
gen.add_word(0x0); // 124
|
||||
gen.add_word(0x0); // 128
|
||||
gen.add_word(0x0); // 132
|
||||
gen.add_word(0x0); // 136
|
||||
gen.add_word(0x0); // 140
|
||||
gen.add_word(0x0); // 144
|
||||
gen.add_word(0x0); // 148
|
||||
gen.add_word(0x0); // 152
|
||||
gen.add_word(0x0); // 156
|
||||
generate_merc_effects(gen, ag.merc_effect_count, joints);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> ArtGroup::save_object_file() const {
|
||||
DataObjectGenerator gen;
|
||||
gen.add_type_tag("art-group");
|
||||
auto ag_words = 8 + length;
|
||||
while (gen.words() < ag_words) {
|
||||
gen.add_word(0);
|
||||
}
|
||||
auto file_info_slot = info.add_to_object_file(gen);
|
||||
gen.link_word_to_byte(1, file_info_slot); // 4 (file-info)
|
||||
gen.link_word_to_string_in_pool(name, 8 / 4); // 8 (name)
|
||||
gen.set_word(12 / 4, length); // 12 (ag length)
|
||||
gen.link_word_to_symbol("#f", 16 / 4); // 16 (res-lump)
|
||||
gen.set_word(20 / 4, 0); // 20 (pad)
|
||||
gen.set_word(24 / 4, 0);
|
||||
gen.set_word(28 / 4, 0);
|
||||
if (!elts.empty()) {
|
||||
if (elts.at(0)) {
|
||||
auto jgeo = (ArtJointGeo*)elts.at(0).get();
|
||||
gen.link_word_to_byte(32 / 4, jgeo->generate(gen));
|
||||
}
|
||||
if (!elts.at(1)) {
|
||||
gen.link_word_to_byte(36 / 4, generate_dummy_merc_ctrl(gen, *this));
|
||||
}
|
||||
|
||||
for (size_t i = 2; i < elts.size(); i++) {
|
||||
auto ja = (ArtJointAnim*)elts.at(i).get();
|
||||
gen.link_word_to_byte((32 + i * 4) / 4, ja->generate(gen));
|
||||
}
|
||||
}
|
||||
|
||||
return gen.generate_v4();
|
||||
}
|
||||
|
||||
int ArtGroup::get_joint_idx(const std::string& name) {
|
||||
for (auto& elt : this->elts) {
|
||||
if (elt && typeid(*elt) == typeid(ArtJointGeo)) {
|
||||
auto jgeo = (ArtJointGeo*)elt.get();
|
||||
for (auto& joint : jgeo->data) {
|
||||
if (joint.name == name) {
|
||||
return joint.number;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Build GOAL format data for an actor. This doesn't generate the data for the .FR3.
|
||||
*/
|
||||
bool run_build_actor(const std::string& mdl_name,
|
||||
const std::string& ag_out,
|
||||
const BuildActorParams& params) {
|
||||
std::string ag_name;
|
||||
if (fs::exists(file_util::get_jak_project_dir() / mdl_name)) {
|
||||
ag_name = fs::path(mdl_name).stem().string();
|
||||
} else {
|
||||
ASSERT_MSG(false, "Model file not found: " + mdl_name);
|
||||
}
|
||||
|
||||
// Load GLTF file to check for a skeleton
|
||||
tinygltf::Model model = load_gltf_model(file_util::get_jak_project_dir() / mdl_name);
|
||||
auto all_nodes = flatten_nodes_from_all_scenes(model);
|
||||
auto skin_idx = find_single_skin(model, all_nodes);
|
||||
if (skin_idx) {
|
||||
lg::info("GLTF file contained a skin, this actor will have a real skeleton");
|
||||
}
|
||||
std::vector<anim::CompressedAnim> user_anims;
|
||||
|
||||
ArtGroup ag(ag_name);
|
||||
std::vector<Joint> joints;
|
||||
MercExtractData extract_data;
|
||||
extract("test", extract_data, model, all_nodes, 0, 0, 0);
|
||||
ag.merc_effect_count = extract_data.new_model.effects.size();
|
||||
// MercSwapData out;
|
||||
// merc_convert(out, extract_data);
|
||||
// Set up joints:
|
||||
if (skin_idx) {
|
||||
// read skeleton data out of GLTF
|
||||
auto skeleton_joints = extract_skeleton(model, *skin_idx);
|
||||
// convert to game format
|
||||
joints = convert_joints(skeleton_joints);
|
||||
// get animation from user.
|
||||
user_anims = process_anim(model, skeleton_joints);
|
||||
|
||||
} else {
|
||||
auto identity = math::Matrix4f::identity();
|
||||
joints.emplace_back("align", 0, -1, identity);
|
||||
joints.emplace_back("prejoint", 1, -1, identity);
|
||||
// matrix stolen from "egg" joint from "money" art group
|
||||
auto main_pose = math::Matrix4f::identity();
|
||||
main_pose(3, 1) = -2194.1628418f;
|
||||
Joint main("main", 2, 1, main_pose);
|
||||
joints.emplace_back(main);
|
||||
}
|
||||
|
||||
std::vector<CollideMesh> mesh;
|
||||
if (params.gen_collide_mesh) {
|
||||
mesh = gen_collide_mesh_from_model_jak2(model, all_nodes, 3);
|
||||
}
|
||||
|
||||
std::shared_ptr<ArtJointGeo> jgeo = std::make_shared<ArtJointGeo>(ag.name, mesh, joints, params);
|
||||
|
||||
ag.elts.emplace_back(jgeo);
|
||||
// dummy merc-ctrl
|
||||
ag.elts.emplace_back(nullptr);
|
||||
|
||||
if (!user_anims.empty()) {
|
||||
for (auto& anim : user_anims) {
|
||||
ag.elts.emplace_back(std::make_shared<ArtJointAnim>(anim, joints));
|
||||
}
|
||||
} else {
|
||||
ag.elts.emplace_back(std::make_shared<ArtJointAnim>(ag.name, joints));
|
||||
}
|
||||
|
||||
ag.length = ag.elts.size();
|
||||
|
||||
auto ag_file = ag.save_object_file();
|
||||
lg::info("ag file size {} bytes", ag_file.size());
|
||||
auto save_path = fs::path(ag_out);
|
||||
file_util::create_dir_if_needed_for_file(ag_out);
|
||||
lg::info("Saving to {}", save_path.string());
|
||||
file_util::write_binary_file(file_util::get_jak_project_dir() / save_path, ag_file.data(),
|
||||
ag_file.size());
|
||||
return true;
|
||||
}
|
||||
} // namespace jak2
|
||||
@@ -0,0 +1,215 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/util/gltf_util.h"
|
||||
|
||||
#include "goalc/build_actor/common/animation_processing.h"
|
||||
#include "goalc/build_actor/common/art_types.h"
|
||||
#include "goalc/build_actor/common/build_actor.h"
|
||||
#include "goalc/build_level/collide/common/collide_common.h"
|
||||
|
||||
namespace jak2 {
|
||||
|
||||
// basic
|
||||
|
||||
// Header for a compressed joint animation - this tells the decompressor how to read
|
||||
// the data in the animation.
|
||||
struct JointAnimCompressedHDR {
|
||||
u32 control_bits[14];
|
||||
u32 num_joints;
|
||||
u32 matrix_bits;
|
||||
|
||||
JointAnimCompressedHDR() {
|
||||
for (auto& bit : control_bits) {
|
||||
bit = 0;
|
||||
}
|
||||
num_joints = 1;
|
||||
matrix_bits = 0;
|
||||
}
|
||||
|
||||
explicit JointAnimCompressedHDR(const anim::CompressedAnim& anim);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct JointAnimCompressedFixed {
|
||||
JointAnimCompressedHDR hdr;
|
||||
u32 offset_64;
|
||||
u32 offset_32;
|
||||
u32 offset_16;
|
||||
u32 reserved;
|
||||
math::Vector4f data[133];
|
||||
int num_data_qw_used = 0;
|
||||
|
||||
JointAnimCompressedFixed() {
|
||||
offset_64 = 0;
|
||||
offset_32 = 0x88;
|
||||
offset_16 = 0x90;
|
||||
reserved = 0;
|
||||
data[0] = math::Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
data[1] = math::Vector4f(0.0f, 1.0f, 0.0f, 0.0f);
|
||||
data[2] = math::Vector4f(0.0f, 0.0f, 1.0f, 0.0f);
|
||||
data[3] = math::Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
data[4] = math::Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
data[5] = math::Vector4f(0.0f, 1.0f, 0.0f, 0.0f);
|
||||
data[6] = math::Vector4f(0.0f, 0.0f, 1.0f, 0.0f);
|
||||
data[7] = math::Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
num_data_qw_used = 8;
|
||||
}
|
||||
|
||||
JointAnimCompressedFixed(const anim::CompressedAnim& anim);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct JointAnimCompressedFrame {
|
||||
u32 offset_64;
|
||||
u32 offset_32;
|
||||
u32 offset_16;
|
||||
u32 reserved;
|
||||
math::Vector4f data[133];
|
||||
u32 num_data_qw_used = 0;
|
||||
|
||||
JointAnimCompressedFrame() {
|
||||
offset_64 = 0;
|
||||
offset_32 = 0;
|
||||
offset_16 = 0;
|
||||
reserved = 0;
|
||||
}
|
||||
|
||||
JointAnimCompressedFrame(const anim::CompressedFrame& frame);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct JointAnimCompressedControl {
|
||||
u32 num_frames;
|
||||
u32 fixed_qwc;
|
||||
u32 frame_qwc;
|
||||
JointAnimCompressedFixed fixed;
|
||||
std::vector<JointAnimCompressedFrame> frame;
|
||||
|
||||
JointAnimCompressedControl() {
|
||||
num_frames = 1;
|
||||
fixed_qwc = 0xf;
|
||||
frame_qwc = 1;
|
||||
frame.emplace_back();
|
||||
}
|
||||
|
||||
JointAnimCompressedControl(const anim::CompressedAnim& anim);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct CollideMeshTri {
|
||||
u8 vert_idx[3];
|
||||
u8 unused;
|
||||
PatSurface pat;
|
||||
};
|
||||
|
||||
struct CollideMesh {
|
||||
s32 joint_id;
|
||||
u32 num_tris;
|
||||
u32 num_verts;
|
||||
std::vector<math::Vector4f> vertices;
|
||||
std::vector<CollideMeshTri> tris;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
size_t calc_data_size() const {
|
||||
// (size-of collide-mesh) + type ptr = 36
|
||||
return 36 + 16 * vertices.size() + sizeof(CollideMeshTri) * tris.size();
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildActorParams {
|
||||
bool gen_collide_mesh = false;
|
||||
s8 texture_bucket = -1;
|
||||
s32 texture_level = -1;
|
||||
s32 joint_channel = -1;
|
||||
math::Vector4f trans_offset = {0.f, 0.f, 0.f, 1.f};
|
||||
std::vector<float> lod_dist{};
|
||||
};
|
||||
|
||||
struct ArtJointGeo : ArtElement {
|
||||
std::vector<Joint> data;
|
||||
std::vector<CollideMesh> cmeshes;
|
||||
ResLump lump;
|
||||
size_t mesh_slot;
|
||||
s8 texture_bucket;
|
||||
s32 texture_level;
|
||||
s32 joint_channel;
|
||||
math::Vector4f trans_offset;
|
||||
std::vector<float> lod_dist;
|
||||
|
||||
explicit ArtJointGeo(const std::string& name,
|
||||
std::vector<CollideMesh> cmeshes,
|
||||
std::vector<Joint>& joints,
|
||||
const BuildActorParams& params) {
|
||||
this->name = name + "-lod0";
|
||||
length = joints.size();
|
||||
for (auto& joint : joints) {
|
||||
data.push_back(joint);
|
||||
}
|
||||
this->cmeshes = std::move(cmeshes);
|
||||
this->texture_bucket = params.texture_bucket;
|
||||
this->texture_level = params.texture_level;
|
||||
this->joint_channel = params.joint_channel;
|
||||
this->trans_offset = params.trans_offset;
|
||||
this->lod_dist = params.lod_dist;
|
||||
}
|
||||
void add_res();
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
size_t generate_mesh(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct ArtJointAnim : ArtElement {
|
||||
MercEyeAnimBlock eye_anim_data;
|
||||
float speed;
|
||||
float artist_base;
|
||||
float artist_step;
|
||||
std::string master_art_group_name;
|
||||
s32 master_art_group_index;
|
||||
u8* blerc_data = nullptr;
|
||||
JointAnimCompressedControl frames;
|
||||
|
||||
ArtJointAnim(const std::string& name, const std::vector<Joint>& joints) {
|
||||
this->name = name + "-idle";
|
||||
length = joints.size();
|
||||
speed = 1.0f;
|
||||
artist_base = 0.0f;
|
||||
artist_step = 1.0f;
|
||||
master_art_group_name = name;
|
||||
master_art_group_index = 2;
|
||||
// for (auto& joint : joints) {
|
||||
// data.emplace_back(joint, 1);
|
||||
// }
|
||||
}
|
||||
|
||||
ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<Joint>& joints);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct ArtGroup : Art {
|
||||
FileInfo info;
|
||||
std::vector<std::shared_ptr<ArtElement>> elts;
|
||||
std::map<int, size_t> joint_map;
|
||||
int merc_effect_count;
|
||||
|
||||
explicit ArtGroup(const std::string& file_name) {
|
||||
info.file_type = "art-group";
|
||||
info.file_name = "/src/next/data/art-group7/" + file_name + "-ag.go";
|
||||
name = file_name;
|
||||
info.major_version = versions::jak2::ART_FILE_VERSION;
|
||||
info.minor_version = 0;
|
||||
info.tool_debug = "Created by OpenGOAL buildactor";
|
||||
info.mdb_file_name = "Unknown";
|
||||
info.maya_file_name = "Unknown";
|
||||
}
|
||||
std::vector<u8> save_object_file() const;
|
||||
int get_joint_idx(const std::string& name);
|
||||
};
|
||||
|
||||
bool run_build_actor(const std::string& input_model,
|
||||
const std::string& output_file,
|
||||
const BuildActorParams& params);
|
||||
} // namespace jak2
|
||||
@@ -0,0 +1,644 @@
|
||||
#include "build_actor.h"
|
||||
|
||||
#include "common/log/log.h"
|
||||
#include "common/math/geometry.h"
|
||||
|
||||
#include "goalc/build_actor/common/MercExtract.h"
|
||||
#include "goalc/build_actor/common/animation_processing.h"
|
||||
|
||||
#include "third-party/tiny_gltf/tiny_gltf.h"
|
||||
|
||||
using namespace gltf_util;
|
||||
namespace jak3 {
|
||||
|
||||
JointAnimCompressedHDR::JointAnimCompressedHDR(const anim::CompressedAnim& anim) {
|
||||
matrix_bits = 0;
|
||||
if (anim.matrix_animated[0]) {
|
||||
matrix_bits |= 1;
|
||||
}
|
||||
if (anim.matrix_animated[1]) {
|
||||
matrix_bits |= 2;
|
||||
}
|
||||
|
||||
for (auto& word : control_bits) {
|
||||
word = 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < anim.joint_metadata.size(); i++) {
|
||||
const int word_idx = i / 8;
|
||||
if (word_idx >= 14) {
|
||||
lg::error("too many joints!!");
|
||||
break;
|
||||
}
|
||||
|
||||
u32 val = 0;
|
||||
const auto& metadata = anim.joint_metadata[i];
|
||||
if (metadata.animated_trans) {
|
||||
val |= (0b1);
|
||||
}
|
||||
if (metadata.animated_quat) {
|
||||
val |= (0b10);
|
||||
}
|
||||
if (metadata.animated_scale) {
|
||||
val |= (0b100);
|
||||
}
|
||||
if (metadata.big_trans_mode) {
|
||||
val |= (0b1000);
|
||||
}
|
||||
val = (val << (4 * (i % 8)));
|
||||
control_bits[word_idx] |= val;
|
||||
}
|
||||
|
||||
num_joints = 2 + anim.joint_metadata.size();
|
||||
}
|
||||
|
||||
JointAnimCompressedFixed::JointAnimCompressedFixed(const anim::CompressedAnim& anim) : hdr(anim) {
|
||||
u8* dest = (u8*)data;
|
||||
const u8* u64_src = (const u8*)anim.fixed.data64.data();
|
||||
const u8* u32_src = (const u8*)anim.fixed.data32.data();
|
||||
const u8* u16_src = (const u8*)anim.fixed.data16.data();
|
||||
|
||||
const int u64_size = anim.fixed.data64.size() * sizeof(u64);
|
||||
const int u32_size = anim.fixed.data32.size() * sizeof(u32);
|
||||
const int u16_size = anim.fixed.data16.size() * sizeof(u16);
|
||||
|
||||
if (u64_size + u32_size + u16_size > 133 * 4 * 4) {
|
||||
lg::die("fixed sizes are {} and {}", 133 * 4 * 4, u64_size + u32_size + u16_size);
|
||||
}
|
||||
ASSERT(u64_size + u32_size + u16_size <= 133 * 4 * 4);
|
||||
|
||||
offset_64 = 0;
|
||||
memcpy(dest, u64_src, u64_size);
|
||||
dest += u64_size;
|
||||
|
||||
offset_32 = offset_64 + u64_size;
|
||||
memcpy(dest, u32_src, u32_size);
|
||||
dest += u32_size;
|
||||
|
||||
offset_16 = offset_32 + u32_size;
|
||||
memcpy(dest, u16_src, u16_size);
|
||||
reserved = 0;
|
||||
|
||||
num_data_qw_used = (15 + u64_size + u32_size + u16_size) / 16;
|
||||
ASSERT(num_data_qw_used <= 133);
|
||||
}
|
||||
|
||||
JointAnimCompressedFrame::JointAnimCompressedFrame(const anim::CompressedFrame& frame) {
|
||||
reserved = 0;
|
||||
u8* dest = (u8*)data;
|
||||
const u8* u64_src = (const u8*)frame.data64.data();
|
||||
const u8* u32_src = (const u8*)frame.data32.data();
|
||||
const u8* u16_src = (const u8*)frame.data16.data();
|
||||
|
||||
const int u64_size = frame.data64.size() * sizeof(u64);
|
||||
const int u32_size = frame.data32.size() * sizeof(u32);
|
||||
const int u16_size = frame.data16.size() * sizeof(u16);
|
||||
|
||||
if (u64_size + u32_size + u16_size > 133 * 4 * 4) {
|
||||
lg::die("frame sizes are {} and {}", 133 * 4 * 4, u64_size + u32_size + u16_size);
|
||||
}
|
||||
|
||||
offset_64 = 0;
|
||||
memcpy(dest, u64_src, u64_size);
|
||||
dest += u64_size;
|
||||
|
||||
offset_32 = offset_64 + u64_size;
|
||||
memcpy(dest, u32_src, u32_size);
|
||||
dest += u32_size;
|
||||
|
||||
offset_16 = offset_32 + u32_size;
|
||||
memcpy(dest, u16_src, u16_size);
|
||||
reserved = 0;
|
||||
|
||||
num_data_qw_used = (15 + u64_size + u32_size + u16_size) / 16;
|
||||
ASSERT(num_data_qw_used <= 133);
|
||||
}
|
||||
|
||||
JointAnimCompressedControl::JointAnimCompressedControl(const anim::CompressedAnim& anim)
|
||||
: fixed(anim) {
|
||||
num_frames = anim.frames.size();
|
||||
for (auto& in_frame : anim.frames) {
|
||||
frame.emplace_back(in_frame);
|
||||
}
|
||||
fixed_qwc = fixed.num_data_qw_used;
|
||||
frame_qwc = frame.at(0).num_data_qw_used;
|
||||
}
|
||||
|
||||
ArtJointAnim::ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<Joint>& joints)
|
||||
: frames(anim) {
|
||||
this->name = anim.name;
|
||||
length = joints.size();
|
||||
speed = 1.0f;
|
||||
artist_base = 0.0f;
|
||||
artist_step = 1.0f;
|
||||
master_art_group_name = name;
|
||||
master_art_group_index = 2;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressedFrame::generate(DataObjectGenerator& gen) const {
|
||||
gen.align(16); // might have been 8, but 16 doesn't hurt.
|
||||
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(offset_64); // 0
|
||||
gen.add_word(offset_32); // 4
|
||||
gen.add_word(offset_16); // 8
|
||||
gen.add_word(reserved); // 12
|
||||
|
||||
for (u32 i = 0; i < num_data_qw_used; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
gen.add_word_float(data[i][j]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressedHDR::generate(DataObjectGenerator& gen) const {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
for (auto& bit : control_bits) {
|
||||
gen.add_word(bit);
|
||||
}
|
||||
gen.add_word(num_joints);
|
||||
gen.add_word(matrix_bits);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressedFixed::generate(DataObjectGenerator& gen) const {
|
||||
gen.align(16); // might have been 8, but 16 doesn't hurt.
|
||||
size_t result = gen.current_offset_bytes();
|
||||
hdr.generate(gen); // 0-64 (inline)
|
||||
gen.add_word(offset_64); // 64
|
||||
gen.add_word(offset_32); // 68
|
||||
gen.add_word(offset_16); // 72
|
||||
gen.add_word(reserved); // 76
|
||||
// default joint poses (taken from money-idle)
|
||||
for (int i = 0; i < num_data_qw_used; i++) {
|
||||
gen.add_word_float(data[i].x());
|
||||
gen.add_word_float(data[i].y());
|
||||
gen.add_word_float(data[i].z());
|
||||
gen.add_word_float(data[i].w());
|
||||
}
|
||||
// -- not sure what this is, part of the dummy data maybe?
|
||||
gen.add_word(0);
|
||||
gen.add_word(0x7fff0000);
|
||||
gen.add_word(0x2250000);
|
||||
gen.add_word(0x10001000);
|
||||
gen.add_word(0x10000000);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.align(4);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t JointAnimCompressedControl::generate(DataObjectGenerator& gen) const {
|
||||
gen.align(16);
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(num_frames); // 0
|
||||
gen.add_word(fixed_qwc); // 4
|
||||
gen.add_word(frame_qwc); // 8
|
||||
|
||||
auto ja_fixed_slot = gen.add_word(0);
|
||||
std::vector<int> ja_frame_slots;
|
||||
for (u32 i = 0; i < num_frames; i++) {
|
||||
ja_frame_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.link_word_to_byte(ja_fixed_slot, fixed.generate(gen));
|
||||
ASSERT(num_frames == frame.size());
|
||||
for (size_t i = 0; i < num_frames; i++) {
|
||||
gen.link_word_to_byte(ja_frame_slots[i], frame[i].generate(gen));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t CollideMesh::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("collide-mesh");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
// gen.add_word(0xffffffff - joint_id); // 4 (joint-id)
|
||||
gen.add_word(joint_id); // 4 (joint-id)
|
||||
gen.add_word(num_tris); // 8 (num-tris)
|
||||
gen.add_word(num_verts); // 12 (num-verts)
|
||||
auto vertices_slot = gen.add_word(0); // 16 (vertex-data)
|
||||
gen.add_word(0); // 20 (pad)
|
||||
gen.add_word(0); // 24 (pad)
|
||||
gen.add_word(0); // 28 (pad)
|
||||
for (auto& tri : tris) {
|
||||
u32 word = (tri.vert_idx[0] & 0xff) + ((tri.vert_idx[1] << 8) & 0xff00) +
|
||||
((tri.vert_idx[2] << 16) & 0xff0000);
|
||||
gen.add_word(word); // 0 (tris | vertex-index | unused)
|
||||
gen.add_word(tri.pat.val); // 4 (pat)
|
||||
}
|
||||
// vertex data start
|
||||
gen.link_word_to_byte(vertices_slot, gen.current_offset_bytes());
|
||||
for (auto& vert : vertices) {
|
||||
gen.add_word_float(vert.x());
|
||||
gen.add_word_float(vert.y());
|
||||
gen.add_word_float(vert.z());
|
||||
gen.add_word_float(vert.w());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
void ArtJointGeo::add_res() {
|
||||
if (!cmeshes.empty()) {
|
||||
lump.add_res(
|
||||
std::make_unique<ResRef>("collide-mesh-group", "array", mesh_slot, DEFAULT_RES_TIME));
|
||||
}
|
||||
if (texture_bucket != -1) {
|
||||
lump.add_res(std::make_unique<ResUint8>(
|
||||
"texture-bucket", std::vector<u8>{static_cast<u8>(texture_bucket)}, DEFAULT_RES_TIME));
|
||||
}
|
||||
if (texture_level != -1) {
|
||||
lump.add_res(std::make_unique<ResInt32>("texture-level", std::vector<s32>{texture_level},
|
||||
DEFAULT_RES_TIME));
|
||||
}
|
||||
if (trans_offset != math::Vector4f{0.f, 0.f, 0.f, 1.f}) {
|
||||
lump.add_res(std::make_unique<ResVector>(
|
||||
"trans-offset", std::vector<math::Vector4f>{trans_offset}, DEFAULT_RES_TIME));
|
||||
}
|
||||
if (joint_channel != -1) {
|
||||
lump.add_res(std::make_unique<ResInt32>("joint-channel", std::vector<s32>{joint_channel},
|
||||
DEFAULT_RES_TIME));
|
||||
}
|
||||
if (!lod_dist.empty()) {
|
||||
lump.add_res(std::make_unique<ResFloat>("lod-dist", lod_dist, DEFAULT_RES_TIME));
|
||||
}
|
||||
lump.sort_res();
|
||||
}
|
||||
|
||||
size_t ArtJointGeo::generate_mesh(DataObjectGenerator& gen) const {
|
||||
std::vector<size_t> data_slots;
|
||||
std::vector<size_t> content_slots;
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(cmeshes.size()); // 0 (length)
|
||||
gen.add_word(cmeshes.size()); // 4 (allocated-length)
|
||||
gen.add_type_tag("collide-mesh"); // 8 (content-type)
|
||||
content_slots.reserve(cmeshes.size());
|
||||
for (auto& data : cmeshes) {
|
||||
(void)data;
|
||||
content_slots.push_back(gen.add_word(0)); // 12 (data)
|
||||
}
|
||||
gen.align(4);
|
||||
for (size_t i = 0; i < content_slots.size(); i++) {
|
||||
data_slots.push_back(cmeshes.at(i).generate(gen));
|
||||
}
|
||||
for (size_t i = 0; i < content_slots.size(); i++) {
|
||||
gen.link_word_to_byte(content_slots.at(i), data_slots.at(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t ArtJointGeo::generate(DataObjectGenerator& gen) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("art-joint-geo");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(0); // 4
|
||||
gen.add_ref_to_string_in_pool(name); // 8
|
||||
gen.add_word(length); // 12
|
||||
auto res_slot = gen.add_word(0); // 16 (res-lump)
|
||||
gen.add_ref_to_string_in_pool(master_art_group_name); // 20
|
||||
gen.add_word(master_art_group_index); // 24
|
||||
gen.add_word(0); // 28
|
||||
gen.add_word(0); // 32
|
||||
gen.add_word(0); // 36
|
||||
gen.add_word(0); // 40
|
||||
gen.add_word(0); // 44
|
||||
std::vector<size_t> joint_slots;
|
||||
for (int i = 0; i < length; i++) {
|
||||
joint_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.align(4);
|
||||
for (int i = 0; i < length; i++) {
|
||||
auto joint = data.at(i).generate(gen);
|
||||
gen.link_word_to_byte(joint_slots.at(i), joint);
|
||||
g_joint_map[data.at(i).number] = joint;
|
||||
}
|
||||
mesh_slot = generate_mesh(gen);
|
||||
add_res();
|
||||
auto res_header = lump.generate_header(gen, "res-lump");
|
||||
gen.link_word_to_byte(res_slot, res_header);
|
||||
lump.generate_tag_list_and_data(gen, res_header);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t ArtJointAnim::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("art-joint-anim");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(0); // 4
|
||||
gen.add_ref_to_string_in_pool(name); // 8
|
||||
gen.add_word(length); // 12
|
||||
gen.add_symbol_link("#f"); // 16 (res-lump)
|
||||
gen.add_ref_to_string_in_pool(master_art_group_name); // 20
|
||||
gen.add_word(master_art_group_index); // 24
|
||||
gen.add_symbol_link("#f"); // 28 (eye block)
|
||||
gen.add_symbol_link("#f"); // 32 (blerc)
|
||||
auto ctrl_slot = gen.add_word(0); // 36 (frames)
|
||||
gen.add_word(0); // 40
|
||||
gen.add_word(0); // 44
|
||||
gen.add_word_float(speed); // 48
|
||||
gen.add_word_float(artist_base); // 52
|
||||
gen.add_word_float(artist_step); // 56
|
||||
|
||||
std::vector<size_t> frame_slots;
|
||||
for (int i = 0; i < length; i++) {
|
||||
frame_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.align(4);
|
||||
gen.link_word_to_byte(ctrl_slot, frames.generate(gen));
|
||||
return result;
|
||||
}
|
||||
|
||||
static size_t gen_dummy_frag_geo(DataObjectGenerator& gen) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
// frag geo stolen from money-lod2
|
||||
static std::vector<u32> words = {
|
||||
0x51180504, 0x1000013, 0x1b150000, 0x604, 0x5225, 0x80808080, 0x80808080,
|
||||
0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080,
|
||||
0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080,
|
||||
0x80808080, 0x80808080, 0x80808080, 0xb4e48086, 0x757d3a0a, 0x1e324000, 0x1e328186,
|
||||
0x757d3410, 0x1c4020, 0x432e8186, 0x251313, 0x71a28044, 0x1c8186, 0x757d2e16,
|
||||
0x96ce4040, 0x719b8186, 0x151919, 0x71c08064, 0x96ce8186, 0x757d281c, 0xb4e44060,
|
||||
0x71d28186, 0x251f1f, 0x435e8080, 0xb4e48186, 0x757d4322, 0x1e324080, 0x71cc7f86,
|
||||
0xffdb4025, 0x71ad0064, 0x435c8186, 0xffea582b, 0x71bc0044, 0x43348186, 0xffdb5b31,
|
||||
0x43530024, 0x71a48086, 0xffea3755, 0x43440000, 0x71a47f86, 0xffea3d3d, 0x43440080,
|
||||
0x432e7f86, 0x254646, 0x71a20044, 0x43657f86, 0x154949, 0x43400024, 0x719b8186,
|
||||
0x154c4c, 0x71c00064, 0x71d28186, 0x254f4f, 0x435e0000, 0x71d27f86, 0x250707,
|
||||
0x435e8000, 0x43658186, 0x150d0d, 0x43408024, 0x0, 0x0, 0x0,
|
||||
0xcb01005a, 0xcb00fffa, 0xcb01005a, 0x2101e01, 0x0, 0x0, 0x306,
|
||||
0x4030000, 0x120, 0x0, 0x1cf02c14, 0x66c801d, 0x0, 0x0,
|
||||
0x34, 0x0, 0x0, 0x0, 0x8, 0x0, 0x44,
|
||||
0x80, 0x42, 0x0,
|
||||
};
|
||||
for (auto& word : words) {
|
||||
gen.add_word(word);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t gen_dummy_frag_ctrl(DataObjectGenerator& gen) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(0x1063918);
|
||||
gen.add_word(0x603);
|
||||
gen.add_word(0x0);
|
||||
gen.add_word(0x0);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t gen_dummy_frag_ctrl_for_uploads(DataObjectGenerator& gen, int n) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
|
||||
std::vector<u8> packed_frag_ctrls;
|
||||
|
||||
// this is still a bit of a hack - the dummy_frag_ctrl above has 4 fragments, so we need to
|
||||
// provide fragment controls for each. The PC merc renderer will de-duplicate bone uploads over
|
||||
// all effects and fragments, so we just need to have a single fragment that asks for all bones,
|
||||
// and everything will work out.
|
||||
for (int k = 0; k < 4; k++) {
|
||||
packed_frag_ctrls.push_back(0);
|
||||
packed_frag_ctrls.push_back(0);
|
||||
packed_frag_ctrls.push_back(0);
|
||||
if (k == 0) { // for the first frag, do all matrix uploads.
|
||||
// note that these are bogus destination addresses, but nothing uses it on PC
|
||||
packed_frag_ctrls.push_back(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
packed_frag_ctrls.push_back(i);
|
||||
packed_frag_ctrls.push_back(i);
|
||||
}
|
||||
} else {
|
||||
// remaining frags can have empty matrix upload lists.
|
||||
packed_frag_ctrls.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> u32s((packed_frag_ctrls.size() + 3) / 4);
|
||||
memcpy(u32s.data(), packed_frag_ctrls.data(), packed_frag_ctrls.size());
|
||||
for (auto x : u32s) {
|
||||
gen.add_word(x);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t gen_dummy_extra_info(DataObjectGenerator& gen) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(0x1);
|
||||
gen.add_word(0x0);
|
||||
gen.add_word(0x0);
|
||||
gen.add_word(0x0);
|
||||
gen.add_word(0xb74ccccd);
|
||||
gen.add_word(0x40400000);
|
||||
gen.add_word(0x8066a1ff);
|
||||
gen.add_word(0x0);
|
||||
return result;
|
||||
}
|
||||
|
||||
void generate_merc_effects(DataObjectGenerator& gen, int effect_count, int joints) {
|
||||
struct EffectLocs {
|
||||
size_t frag_geo;
|
||||
size_t frag_ctrl;
|
||||
size_t extra_info;
|
||||
};
|
||||
std::vector<EffectLocs> locs;
|
||||
for (int i = 0; i < effect_count; i++) {
|
||||
EffectLocs loc{};
|
||||
loc.frag_geo = gen.add_word(0); // 112-140 (effect)
|
||||
loc.frag_ctrl = gen.add_word(0); // 116 (frag-ctrl)
|
||||
gen.add_word(0x0); // 120 (blend-data)
|
||||
gen.add_word(0x0); // 124 (blend-ctrl)
|
||||
gen.add_word(0x10000); // 128
|
||||
gen.add_word(0x140000); // 132
|
||||
gen.add_word(0x100001d); // 136
|
||||
loc.extra_info = gen.add_word(0); // 140 (extra-info)
|
||||
locs.push_back(loc);
|
||||
}
|
||||
for (auto& loc : locs) {
|
||||
gen.link_word_to_byte(loc.extra_info, gen_dummy_extra_info(gen));
|
||||
gen.link_word_to_byte(loc.frag_ctrl, gen_dummy_frag_ctrl_for_uploads(gen, joints + 3));
|
||||
gen.link_word_to_byte(loc.frag_geo, gen_dummy_frag_geo(gen));
|
||||
}
|
||||
}
|
||||
|
||||
size_t generate_dummy_merc_ctrl(DataObjectGenerator& gen, const ArtGroup& ag) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("merc-ctrl");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
// excluding align and prejoint
|
||||
auto joints = ((ArtJointGeo*)ag.elts.at(0).get())->length - 2;
|
||||
gen.add_word(0); // 4
|
||||
gen.add_ref_to_string_in_pool(ag.name + "-lod0"); // 8
|
||||
gen.add_word(joints); // 12 (num-joints)
|
||||
gen.add_symbol_link("#f"); // 16 (res-lump)
|
||||
gen.add_ref_to_string_in_pool(ag.name); // 20 (master-art-group-name)
|
||||
gen.add_word(1); // 24 (master-art-group-index)
|
||||
gen.add_word(0x0); // 28 (seg-table)
|
||||
gen.add_word(0x0); // 32 (pad?)
|
||||
gen.add_word(0x0); // 36 (pad?)
|
||||
gen.add_word(0x0); // 40 (pad?)
|
||||
gen.add_word(0x0); // 44 (pad?)
|
||||
gen.add_word(0x4185c8ac); // 48-160 (xyz-scale)
|
||||
gen.add_word(0xc780ff80); // 52 (st-magic)
|
||||
gen.add_word(0x40798000); // 56 (st-out-a)
|
||||
gen.add_word(0x40eb4000); // 60 (st-out-b)
|
||||
gen.add_word(0x4780ff80); // 64 (st-vif-add)
|
||||
gen.add_word(0x50000); // 68 ((st-int-off << 16) + st-int-scale)
|
||||
gen.add_word(ag.merc_effect_count); // 72 (effect-count)
|
||||
gen.add_word(0x0); // 76 (blend-target-count)
|
||||
gen.add_word((0x14 * ag.merc_effect_count << 16) +
|
||||
ag.merc_effect_count); // 80 ((fragment-count << 16) + tri-count)
|
||||
gen.add_word(0x130101); // 84
|
||||
gen.add_word(0x13001d); // 88
|
||||
gen.add_word(0x0); // 92
|
||||
gen.add_word(0x0); // 96
|
||||
gen.add_word(0x10101); // 100
|
||||
gen.add_word(0x130000); // 104
|
||||
gen.add_word(0x3f319ca9); // 108
|
||||
gen.add_word(0x0); // 112
|
||||
gen.add_word(0x0); // 116
|
||||
gen.add_word(0x0); // 120
|
||||
gen.add_word(0x0); // 124
|
||||
gen.add_word(0x0); // 128
|
||||
gen.add_word(0x0); // 132
|
||||
gen.add_word(0x0); // 136
|
||||
gen.add_word(0x0); // 140
|
||||
gen.add_word(0x0); // 144
|
||||
gen.add_word(0x0); // 148
|
||||
gen.add_word(0x0); // 152
|
||||
gen.add_word(0x0); // 156
|
||||
gen.add_word(0x0); // 160
|
||||
gen.add_word(0x0); // 164
|
||||
gen.add_word(0x0); // 168
|
||||
gen.add_word(0x0); // 172
|
||||
generate_merc_effects(gen, ag.merc_effect_count, joints);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> ArtGroup::save_object_file() const {
|
||||
DataObjectGenerator gen;
|
||||
gen.add_type_tag("art-group");
|
||||
auto ag_words = 8 + length;
|
||||
while (gen.words() < ag_words) {
|
||||
gen.add_word(0);
|
||||
}
|
||||
auto file_info_slot = info.add_to_object_file(gen);
|
||||
gen.link_word_to_byte(1, file_info_slot); // 4 (file-info)
|
||||
gen.link_word_to_string_in_pool(name, 8 / 4); // 8 (name)
|
||||
gen.set_word(12 / 4, length); // 12 (ag length)
|
||||
gen.link_word_to_symbol("#f", 16 / 4); // 16 (res-lump)
|
||||
gen.set_word(20 / 4, 0); // 20 (pad)
|
||||
gen.set_word(24 / 4, 0);
|
||||
gen.set_word(28 / 4, 0);
|
||||
if (!elts.empty()) {
|
||||
if (elts.at(0)) {
|
||||
auto jgeo = (ArtJointGeo*)elts.at(0).get();
|
||||
gen.link_word_to_byte(32 / 4, jgeo->generate(gen));
|
||||
}
|
||||
if (!elts.at(1)) {
|
||||
gen.link_word_to_byte(36 / 4, generate_dummy_merc_ctrl(gen, *this));
|
||||
}
|
||||
|
||||
for (size_t i = 2; i < elts.size(); i++) {
|
||||
auto ja = (ArtJointAnim*)elts.at(i).get();
|
||||
gen.link_word_to_byte((32 + i * 4) / 4, ja->generate(gen));
|
||||
}
|
||||
}
|
||||
|
||||
return gen.generate_v4();
|
||||
}
|
||||
|
||||
int ArtGroup::get_joint_idx(const std::string& name) {
|
||||
for (auto& elt : this->elts) {
|
||||
if (elt && typeid(*elt) == typeid(ArtJointGeo)) {
|
||||
auto jgeo = (ArtJointGeo*)elt.get();
|
||||
for (auto& joint : jgeo->data) {
|
||||
if (joint.name == name) {
|
||||
return joint.number;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Build GOAL format data for an actor. This doesn't generate the data for the .FR3.
|
||||
*/
|
||||
bool run_build_actor(const std::string& mdl_name,
|
||||
const std::string& ag_out,
|
||||
const BuildActorParams& params) {
|
||||
std::string ag_name;
|
||||
if (fs::exists(file_util::get_jak_project_dir() / mdl_name)) {
|
||||
ag_name = fs::path(mdl_name).stem().string();
|
||||
} else {
|
||||
ASSERT_MSG(false, "Model file not found: " + mdl_name);
|
||||
}
|
||||
|
||||
// Load GLTF file to check for a skeleton
|
||||
tinygltf::Model model = load_gltf_model(file_util::get_jak_project_dir() / mdl_name);
|
||||
auto all_nodes = flatten_nodes_from_all_scenes(model);
|
||||
auto skin_idx = find_single_skin(model, all_nodes);
|
||||
if (skin_idx) {
|
||||
lg::info("GLTF file contained a skin, this actor will have a real skeleton");
|
||||
}
|
||||
std::vector<anim::CompressedAnim> user_anims;
|
||||
|
||||
ArtGroup ag(ag_name);
|
||||
std::vector<Joint> joints;
|
||||
MercExtractData extract_data;
|
||||
extract("test", extract_data, model, all_nodes, 0, 0, 0);
|
||||
ag.merc_effect_count = extract_data.new_model.effects.size();
|
||||
// MercSwapData out;
|
||||
// merc_convert(out, extract_data);
|
||||
// Set up joints:
|
||||
if (skin_idx) {
|
||||
// read skeleton data out of GLTF
|
||||
auto skeleton_joints = extract_skeleton(model, *skin_idx);
|
||||
// convert to game format
|
||||
joints = convert_joints(skeleton_joints);
|
||||
// get animation from user.
|
||||
user_anims = process_anim(model, skeleton_joints);
|
||||
|
||||
} else {
|
||||
auto identity = math::Matrix4f::identity();
|
||||
joints.emplace_back("align", 0, -1, identity);
|
||||
joints.emplace_back("prejoint", 1, -1, identity);
|
||||
// matrix stolen from "egg" joint from "money" art group
|
||||
auto main_pose = math::Matrix4f::identity();
|
||||
main_pose(3, 1) = -2194.1628418f;
|
||||
Joint main("main", 2, 1, main_pose);
|
||||
joints.emplace_back(main);
|
||||
}
|
||||
|
||||
std::vector<CollideMesh> mesh;
|
||||
if (params.gen_collide_mesh) {
|
||||
mesh = gen_collide_mesh_from_model_jak3(model, all_nodes, 3);
|
||||
}
|
||||
|
||||
std::shared_ptr<ArtJointGeo> jgeo = std::make_shared<ArtJointGeo>(ag.name, mesh, joints, params);
|
||||
|
||||
ag.elts.emplace_back(jgeo);
|
||||
// dummy merc-ctrl
|
||||
ag.elts.emplace_back(nullptr);
|
||||
|
||||
if (!user_anims.empty()) {
|
||||
for (auto& anim : user_anims) {
|
||||
ag.elts.emplace_back(std::make_shared<ArtJointAnim>(anim, joints));
|
||||
}
|
||||
} else {
|
||||
ag.elts.emplace_back(std::make_shared<ArtJointAnim>(ag.name, joints));
|
||||
}
|
||||
|
||||
ag.length = ag.elts.size();
|
||||
|
||||
auto ag_file = ag.save_object_file();
|
||||
lg::info("ag file size {} bytes", ag_file.size());
|
||||
auto save_path = fs::path(ag_out);
|
||||
file_util::create_dir_if_needed_for_file(ag_out);
|
||||
lg::info("Saving to {}", save_path.string());
|
||||
file_util::write_binary_file(file_util::get_jak_project_dir() / save_path, ag_file.data(),
|
||||
ag_file.size());
|
||||
return true;
|
||||
}
|
||||
} // namespace jak3
|
||||
@@ -0,0 +1,218 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/util/gltf_util.h"
|
||||
|
||||
#include "goalc/build_actor/common/animation_processing.h"
|
||||
#include "goalc/build_actor/common/art_types.h"
|
||||
#include "goalc/build_actor/common/build_actor.h"
|
||||
#include "goalc/build_level/collide/common/collide_common.h"
|
||||
|
||||
namespace jak3 {
|
||||
|
||||
struct ArtElement3 : Art {
|
||||
std::string master_art_group_name;
|
||||
s32 master_art_group_index;
|
||||
u8 pad[20];
|
||||
};
|
||||
|
||||
// basic
|
||||
|
||||
// Header for a compressed joint animation - this tells the decompressor how to read
|
||||
// the data in the animation.
|
||||
struct JointAnimCompressedHDR {
|
||||
u32 control_bits[14];
|
||||
u32 num_joints;
|
||||
u32 matrix_bits;
|
||||
|
||||
JointAnimCompressedHDR() {
|
||||
for (auto& bit : control_bits) {
|
||||
bit = 0;
|
||||
}
|
||||
num_joints = 1;
|
||||
matrix_bits = 0;
|
||||
}
|
||||
|
||||
explicit JointAnimCompressedHDR(const anim::CompressedAnim& anim);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct JointAnimCompressedFixed {
|
||||
JointAnimCompressedHDR hdr;
|
||||
u32 offset_64;
|
||||
u32 offset_32;
|
||||
u32 offset_16;
|
||||
u32 reserved;
|
||||
math::Vector4f data[133];
|
||||
int num_data_qw_used = 0;
|
||||
|
||||
JointAnimCompressedFixed() {
|
||||
offset_64 = 0;
|
||||
offset_32 = 0x88;
|
||||
offset_16 = 0x90;
|
||||
reserved = 0;
|
||||
data[0] = math::Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
data[1] = math::Vector4f(0.0f, 1.0f, 0.0f, 0.0f);
|
||||
data[2] = math::Vector4f(0.0f, 0.0f, 1.0f, 0.0f);
|
||||
data[3] = math::Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
data[4] = math::Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
data[5] = math::Vector4f(0.0f, 1.0f, 0.0f, 0.0f);
|
||||
data[6] = math::Vector4f(0.0f, 0.0f, 1.0f, 0.0f);
|
||||
data[7] = math::Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
num_data_qw_used = 8;
|
||||
}
|
||||
|
||||
JointAnimCompressedFixed(const anim::CompressedAnim& anim);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct JointAnimCompressedFrame {
|
||||
u32 offset_64;
|
||||
u32 offset_32;
|
||||
u32 offset_16;
|
||||
u32 reserved;
|
||||
math::Vector4f data[133];
|
||||
u32 num_data_qw_used = 0;
|
||||
|
||||
JointAnimCompressedFrame() {
|
||||
offset_64 = 0;
|
||||
offset_32 = 0;
|
||||
offset_16 = 0;
|
||||
reserved = 0;
|
||||
}
|
||||
|
||||
JointAnimCompressedFrame(const anim::CompressedFrame& frame);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct JointAnimCompressedControl {
|
||||
u32 num_frames;
|
||||
u32 fixed_qwc;
|
||||
u32 frame_qwc;
|
||||
JointAnimCompressedFixed fixed;
|
||||
std::vector<JointAnimCompressedFrame> frame;
|
||||
|
||||
JointAnimCompressedControl() {
|
||||
num_frames = 1;
|
||||
fixed_qwc = 0xf;
|
||||
frame_qwc = 1;
|
||||
frame.emplace_back();
|
||||
}
|
||||
|
||||
JointAnimCompressedControl(const anim::CompressedAnim& anim);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct CollideMeshTri {
|
||||
u8 vert_idx[3];
|
||||
u8 unused;
|
||||
PatSurface pat;
|
||||
};
|
||||
|
||||
struct CollideMesh {
|
||||
s32 joint_id;
|
||||
u32 num_tris;
|
||||
u32 num_verts;
|
||||
std::vector<math::Vector4f> vertices;
|
||||
std::vector<CollideMeshTri> tris;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
size_t calc_data_size() const {
|
||||
// (size-of collide-mesh) + type ptr = 36
|
||||
return 36 + 16 * vertices.size() + sizeof(CollideMeshTri) * tris.size();
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildActorParams {
|
||||
bool gen_collide_mesh = false;
|
||||
s8 texture_bucket = -1;
|
||||
s32 texture_level = -1;
|
||||
s32 joint_channel = -1;
|
||||
math::Vector4f trans_offset = {0.f, 0.f, 0.f, 1.f};
|
||||
std::vector<float> lod_dist{};
|
||||
};
|
||||
|
||||
struct ArtJointGeo : ArtElement3 {
|
||||
std::vector<Joint> data;
|
||||
std::vector<CollideMesh> cmeshes;
|
||||
ResLump lump;
|
||||
size_t mesh_slot;
|
||||
s8 texture_bucket;
|
||||
s32 texture_level;
|
||||
s32 joint_channel;
|
||||
math::Vector4f trans_offset;
|
||||
std::vector<float> lod_dist;
|
||||
|
||||
explicit ArtJointGeo(const std::string& name,
|
||||
std::vector<CollideMesh> cmeshes,
|
||||
std::vector<Joint>& joints,
|
||||
const BuildActorParams& params) {
|
||||
this->name = name + "-lod0";
|
||||
master_art_group_name = name;
|
||||
master_art_group_index = 0;
|
||||
length = joints.size();
|
||||
for (auto& joint : joints) {
|
||||
data.push_back(joint);
|
||||
}
|
||||
this->cmeshes = std::move(cmeshes);
|
||||
this->texture_bucket = params.texture_bucket;
|
||||
this->texture_level = params.texture_level;
|
||||
this->joint_channel = params.joint_channel;
|
||||
this->trans_offset = params.trans_offset;
|
||||
this->lod_dist = params.lod_dist;
|
||||
}
|
||||
void add_res();
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
size_t generate_mesh(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct ArtJointAnim : ArtElement3 {
|
||||
MercEyeAnimBlock eye_anim_data;
|
||||
float speed;
|
||||
float artist_base;
|
||||
float artist_step;
|
||||
u8* blerc_data = nullptr;
|
||||
JointAnimCompressedControl frames;
|
||||
|
||||
ArtJointAnim(const std::string& name, const std::vector<Joint>& joints) {
|
||||
this->name = name + "-idle";
|
||||
length = joints.size();
|
||||
speed = 1.0f;
|
||||
artist_base = 0.0f;
|
||||
artist_step = 1.0f;
|
||||
master_art_group_name = name;
|
||||
master_art_group_index = 2;
|
||||
}
|
||||
|
||||
ArtJointAnim(const anim::CompressedAnim& anim, const std::vector<Joint>& joints);
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct ArtGroup : Art {
|
||||
FileInfo info;
|
||||
std::vector<std::shared_ptr<ArtElement3>> elts;
|
||||
std::map<int, size_t> joint_map;
|
||||
int merc_effect_count;
|
||||
|
||||
explicit ArtGroup(const std::string& file_name) {
|
||||
info.file_type = "art-group";
|
||||
info.file_name = "/src/next/data/art-group8/" + file_name + "-ag.go";
|
||||
name = file_name;
|
||||
info.major_version = versions::jak3::ART_FILE_VERSION;
|
||||
info.minor_version = 0;
|
||||
info.tool_debug = "Created by OpenGOAL buildactor";
|
||||
info.mdb_file_name = "Unknown";
|
||||
info.maya_file_name = "Unknown";
|
||||
}
|
||||
std::vector<u8> save_object_file() const;
|
||||
int get_joint_idx(const std::string& name);
|
||||
};
|
||||
|
||||
bool run_build_actor(const std::string& input_model,
|
||||
const std::string& output_file,
|
||||
const BuildActorParams& params);
|
||||
} // namespace jak3
|
||||
@@ -2,7 +2,9 @@
|
||||
#include "common/util/Assert.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
|
||||
#include "goalc/build_actor/jak1/build_actor.h"
|
||||
#include "jak1/build_actor.h"
|
||||
#include "jak2/build_actor.h"
|
||||
#include "jak3/build_actor.h"
|
||||
|
||||
#include "third-party/CLI11.hpp"
|
||||
|
||||
@@ -14,8 +16,11 @@ int main(int argc, char** argv) {
|
||||
|
||||
// game version
|
||||
std::string game, mdl_name, output_file;
|
||||
bool gen_collide_mesh = false;
|
||||
fs::path project_path_override;
|
||||
bool gen_collide_mesh = false;
|
||||
s8 texture_bucket = -1;
|
||||
s32 texture_level = -1;
|
||||
s32 joint_channel = -1;
|
||||
|
||||
// path
|
||||
if (!file_util::setup_project_path(std::nullopt)) {
|
||||
@@ -31,10 +36,14 @@ int main(int argc, char** argv) {
|
||||
app.add_option("output-file", output_file,
|
||||
"Output *-ag.go file (for example: out/jak1/obj/test-ag.go)")
|
||||
->required();
|
||||
app.add_option("-g,--game", game, "Game version (only jak1 for now)")->required();
|
||||
app.add_option("-g,--game", game, "Game version (jak1, jak2, jak3)")->required();
|
||||
app.add_option("--proj-path", project_path_override,
|
||||
"Specify the location of the 'data/' folder");
|
||||
app.add_flag("-m,--mesh", gen_collide_mesh, "Whether to generate a collide-mesh for this model");
|
||||
app.add_option("--texture-bucket", texture_bucket, "Texture bucket to set for this model");
|
||||
app.add_option("--texture-level", texture_level, "Texture level to set for this model");
|
||||
app.add_option("--joint-channel", joint_channel,
|
||||
"Amount of joint channels to set for this model");
|
||||
app.validate_positionals();
|
||||
CLI11_PARSE(app, argc, argv)
|
||||
|
||||
@@ -54,9 +63,30 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
switch (game_version) {
|
||||
case GameVersion::Jak1:
|
||||
jak1::run_build_actor(mdl_name, output_file, gen_collide_mesh);
|
||||
break;
|
||||
case GameVersion::Jak1: {
|
||||
jak1::BuildActorParams params;
|
||||
params.gen_collide_mesh = gen_collide_mesh;
|
||||
params.texture_bucket = texture_bucket;
|
||||
params.texture_level = texture_level;
|
||||
params.joint_channel = joint_channel;
|
||||
jak1::run_build_actor(mdl_name, output_file, params);
|
||||
} break;
|
||||
case GameVersion::Jak2: {
|
||||
jak2::BuildActorParams params;
|
||||
params.gen_collide_mesh = gen_collide_mesh;
|
||||
params.texture_bucket = texture_bucket;
|
||||
params.texture_level = texture_level;
|
||||
params.joint_channel = joint_channel;
|
||||
jak2::run_build_actor(mdl_name, output_file, params);
|
||||
} break;
|
||||
case GameVersion::Jak3: {
|
||||
jak3::BuildActorParams params;
|
||||
params.gen_collide_mesh = gen_collide_mesh;
|
||||
params.texture_bucket = texture_bucket;
|
||||
params.texture_level = texture_level;
|
||||
params.joint_channel = joint_channel;
|
||||
jak3::run_build_actor(mdl_name, output_file, params);
|
||||
} break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED_MSG("unsupported game version");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "common/common_types.h"
|
||||
#include "common/math/Vector.h"
|
||||
#include "common/util/Assert.h"
|
||||
|
||||
struct CollideVertex {
|
||||
float x, y, z;
|
||||
@@ -530,4 +531,262 @@ struct CollideFace {
|
||||
math::Vector3f v[3];
|
||||
PatSurface pat;
|
||||
};
|
||||
} // namespace jak3
|
||||
} // namespace jak3
|
||||
|
||||
/*!
|
||||
* Convert jak1-format PatSurface to Jak 2.
|
||||
*/
|
||||
inline jak2::PatSurface jak2_pat(jak1::PatSurface jak1) {
|
||||
jak2::PatSurface result;
|
||||
|
||||
switch (jak1.get_mode()) {
|
||||
case jak1::PatSurface::Mode::GROUND:
|
||||
result.set_mode(jak2::PatSurface::Mode::GROUND);
|
||||
break;
|
||||
case jak1::PatSurface::Mode::WALL:
|
||||
result.set_mode(jak2::PatSurface::Mode::WALL);
|
||||
break;
|
||||
case jak1::PatSurface::Mode::OBSTACLE:
|
||||
result.set_mode(jak2::PatSurface::Mode::OBSTACLE);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (jak1.get_material()) {
|
||||
case jak1::PatSurface::Material::STONE:
|
||||
result.set_material(jak2::PatSurface::Material::STONE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::ICE:
|
||||
result.set_material(jak2::PatSurface::Material::ICE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::QUICKSAND:
|
||||
result.set_material(jak2::PatSurface::Material::QUICKSAND);
|
||||
break;
|
||||
case jak1::PatSurface::Material::WATERBOTTOM:
|
||||
result.set_material(jak2::PatSurface::Material::WATERBOTTOM);
|
||||
break;
|
||||
case jak1::PatSurface::Material::TAR:
|
||||
result.set_material(jak2::PatSurface::Material::TAR);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SAND:
|
||||
result.set_material(jak2::PatSurface::Material::SAND);
|
||||
break;
|
||||
case jak1::PatSurface::Material::WOOD:
|
||||
result.set_material(jak2::PatSurface::Material::WOOD);
|
||||
break;
|
||||
case jak1::PatSurface::Material::GRASS:
|
||||
result.set_material(jak2::PatSurface::Material::GRASS);
|
||||
break;
|
||||
case jak1::PatSurface::Material::PCMETAL:
|
||||
result.set_material(jak2::PatSurface::Material::PCMETAL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SNOW:
|
||||
result.set_material(jak2::PatSurface::Material::SNOW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::DEEPSNOW:
|
||||
result.set_material(jak2::PatSurface::Material::DEEPSNOW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::HOTCOALS:
|
||||
result.set_material(jak2::PatSurface::Material::HOTCOALS);
|
||||
break;
|
||||
case jak1::PatSurface::Material::LAVA:
|
||||
result.set_material(jak2::PatSurface::Material::LAVA);
|
||||
break;
|
||||
case jak1::PatSurface::Material::CRWOOD:
|
||||
result.set_material(jak2::PatSurface::Material::CRWOOD);
|
||||
break;
|
||||
case jak1::PatSurface::Material::GRAVEL:
|
||||
result.set_material(jak2::PatSurface::Material::GRAVEL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::DIRT:
|
||||
result.set_material(jak2::PatSurface::Material::DIRT);
|
||||
break;
|
||||
case jak1::PatSurface::Material::METAL:
|
||||
result.set_material(jak2::PatSurface::Material::METAL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::STRAW:
|
||||
result.set_material(jak2::PatSurface::Material::STRAW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::TUBE:
|
||||
result.set_material(jak2::PatSurface::Material::TUBE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SWAMP:
|
||||
result.set_material(jak2::PatSurface::Material::SWAMP);
|
||||
break;
|
||||
case jak1::PatSurface::Material::STOPPROJ:
|
||||
result.set_material(jak2::PatSurface::Material::STOPPROJ);
|
||||
break;
|
||||
case jak1::PatSurface::Material::ROTATE:
|
||||
result.set_material(jak2::PatSurface::Material::ROTATE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::NEUTRAL:
|
||||
result.set_material(jak2::PatSurface::Material::NEUTRAL);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (jak1.get_event()) {
|
||||
case jak1::PatSurface::Event::NONE:
|
||||
result.set_event(jak2::PatSurface::Event::NONE);
|
||||
break;
|
||||
case jak1::PatSurface::Event::DEADLY:
|
||||
result.set_event(jak2::PatSurface::Event::DEADLY);
|
||||
break;
|
||||
case jak1::PatSurface::Event::ENDLESSFALL:
|
||||
result.set_event(jak2::PatSurface::Event::ENDLESSFALL);
|
||||
break;
|
||||
case jak1::PatSurface::Event::BURN:
|
||||
result.set_event(jak2::PatSurface::Event::BURN);
|
||||
break;
|
||||
case jak1::PatSurface::Event::DEADLYUP:
|
||||
result.set_event(jak2::PatSurface::Event::DEADLYUP);
|
||||
break;
|
||||
case jak1::PatSurface::Event::BURNUP:
|
||||
result.set_event(jak2::PatSurface::Event::BURNUP);
|
||||
break;
|
||||
case jak1::PatSurface::Event::MELT:
|
||||
result.set_event(jak2::PatSurface::Event::MELT);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
result.set_noentity(jak1.get_noentity());
|
||||
result.set_nocamera(jak1.get_nocamera());
|
||||
|
||||
result.set_noedge(jak1.get_noedge());
|
||||
result.set_nolineofsight(jak1.get_nolineofsight());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert jak1-format PatSurface to Jak 3.
|
||||
*/
|
||||
inline jak3::PatSurface jak3_pat(jak1::PatSurface jak1) {
|
||||
jak3::PatSurface result;
|
||||
|
||||
switch (jak1.get_mode()) {
|
||||
case jak1::PatSurface::Mode::GROUND:
|
||||
result.set_mode(jak3::PatSurface::Mode::GROUND);
|
||||
break;
|
||||
case jak1::PatSurface::Mode::WALL:
|
||||
result.set_mode(jak3::PatSurface::Mode::WALL);
|
||||
break;
|
||||
case jak1::PatSurface::Mode::OBSTACLE:
|
||||
result.set_mode(jak3::PatSurface::Mode::OBSTACLE);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (jak1.get_material()) {
|
||||
case jak1::PatSurface::Material::STONE:
|
||||
result.set_material(jak3::PatSurface::Material::STONE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::ICE:
|
||||
result.set_material(jak3::PatSurface::Material::ICE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::QUICKSAND:
|
||||
result.set_material(jak3::PatSurface::Material::QUICKSAND);
|
||||
break;
|
||||
case jak1::PatSurface::Material::WATERBOTTOM:
|
||||
result.set_material(jak3::PatSurface::Material::WATERBOTTOM);
|
||||
break;
|
||||
case jak1::PatSurface::Material::TAR:
|
||||
result.set_material(jak3::PatSurface::Material::TAR);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SAND:
|
||||
result.set_material(jak3::PatSurface::Material::SAND);
|
||||
break;
|
||||
case jak1::PatSurface::Material::WOOD:
|
||||
result.set_material(jak3::PatSurface::Material::WOOD);
|
||||
break;
|
||||
case jak1::PatSurface::Material::GRASS:
|
||||
result.set_material(jak3::PatSurface::Material::GRASS);
|
||||
break;
|
||||
case jak1::PatSurface::Material::PCMETAL:
|
||||
result.set_material(jak3::PatSurface::Material::PCMETAL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SNOW:
|
||||
result.set_material(jak3::PatSurface::Material::SNOW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::DEEPSNOW:
|
||||
result.set_material(jak3::PatSurface::Material::DEEPSNOW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::HOTCOALS:
|
||||
result.set_material(jak3::PatSurface::Material::HOTCOALS);
|
||||
break;
|
||||
case jak1::PatSurface::Material::LAVA:
|
||||
result.set_material(jak3::PatSurface::Material::LAVA);
|
||||
break;
|
||||
case jak1::PatSurface::Material::CRWOOD:
|
||||
result.set_material(jak3::PatSurface::Material::CRWOOD);
|
||||
break;
|
||||
case jak1::PatSurface::Material::GRAVEL:
|
||||
result.set_material(jak3::PatSurface::Material::GRAVEL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::DIRT:
|
||||
result.set_material(jak3::PatSurface::Material::DIRT);
|
||||
break;
|
||||
case jak1::PatSurface::Material::METAL:
|
||||
result.set_material(jak3::PatSurface::Material::METAL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::STRAW:
|
||||
result.set_material(jak3::PatSurface::Material::STRAW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::TUBE:
|
||||
result.set_material(jak3::PatSurface::Material::TUBE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SWAMP:
|
||||
result.set_material(jak3::PatSurface::Material::SWAMP);
|
||||
break;
|
||||
case jak1::PatSurface::Material::STOPPROJ:
|
||||
result.set_material(jak3::PatSurface::Material::STOPPROJ);
|
||||
break;
|
||||
case jak1::PatSurface::Material::ROTATE:
|
||||
result.set_material(jak3::PatSurface::Material::ROTATE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::NEUTRAL:
|
||||
result.set_material(jak3::PatSurface::Material::NEUTRAL);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (jak1.get_event()) {
|
||||
case jak1::PatSurface::Event::NONE:
|
||||
result.set_event(jak3::PatSurface::Event::NONE);
|
||||
break;
|
||||
case jak1::PatSurface::Event::DEADLY:
|
||||
result.set_event(jak3::PatSurface::Event::DEADLY);
|
||||
break;
|
||||
case jak1::PatSurface::Event::ENDLESSFALL:
|
||||
result.set_event(jak3::PatSurface::Event::ENDLESSFALL);
|
||||
break;
|
||||
case jak1::PatSurface::Event::BURN:
|
||||
result.set_event(jak3::PatSurface::Event::BURN);
|
||||
break;
|
||||
case jak1::PatSurface::Event::DEADLYUP:
|
||||
result.set_event(jak3::PatSurface::Event::DEADLYUP);
|
||||
break;
|
||||
case jak1::PatSurface::Event::BURNUP:
|
||||
result.set_event(jak3::PatSurface::Event::BURNUP);
|
||||
break;
|
||||
case jak1::PatSurface::Event::MELT:
|
||||
result.set_event(jak3::PatSurface::Event::MELT);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
result.set_noentity(jak1.get_noentity());
|
||||
result.set_nocamera(jak1.get_nocamera());
|
||||
|
||||
result.set_noedge(jak1.get_noedge());
|
||||
result.set_nolineofsight(jak1.get_nolineofsight());
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -135,135 +135,6 @@ bool bounding_box_bounding_box(const BoundingBox& a, const BoundingBox& b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert jak1-format PatSurface to Jak 2.
|
||||
*/
|
||||
jak2::PatSurface jak2_pat(jak1::PatSurface jak1) {
|
||||
jak2::PatSurface result;
|
||||
|
||||
switch (jak1.get_mode()) {
|
||||
case jak1::PatSurface::Mode::GROUND:
|
||||
result.set_mode(jak2::PatSurface::Mode::GROUND);
|
||||
break;
|
||||
case jak1::PatSurface::Mode::WALL:
|
||||
result.set_mode(jak2::PatSurface::Mode::WALL);
|
||||
break;
|
||||
case jak1::PatSurface::Mode::OBSTACLE:
|
||||
result.set_mode(jak2::PatSurface::Mode::OBSTACLE);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (jak1.get_material()) {
|
||||
case jak1::PatSurface::Material::STONE:
|
||||
result.set_material(jak2::PatSurface::Material::STONE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::ICE:
|
||||
result.set_material(jak2::PatSurface::Material::ICE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::QUICKSAND:
|
||||
result.set_material(jak2::PatSurface::Material::QUICKSAND);
|
||||
break;
|
||||
case jak1::PatSurface::Material::WATERBOTTOM:
|
||||
result.set_material(jak2::PatSurface::Material::WATERBOTTOM);
|
||||
break;
|
||||
case jak1::PatSurface::Material::TAR:
|
||||
result.set_material(jak2::PatSurface::Material::TAR);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SAND:
|
||||
result.set_material(jak2::PatSurface::Material::SAND);
|
||||
break;
|
||||
case jak1::PatSurface::Material::WOOD:
|
||||
result.set_material(jak2::PatSurface::Material::WOOD);
|
||||
break;
|
||||
case jak1::PatSurface::Material::GRASS:
|
||||
result.set_material(jak2::PatSurface::Material::GRASS);
|
||||
break;
|
||||
case jak1::PatSurface::Material::PCMETAL:
|
||||
result.set_material(jak2::PatSurface::Material::PCMETAL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SNOW:
|
||||
result.set_material(jak2::PatSurface::Material::SNOW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::DEEPSNOW:
|
||||
result.set_material(jak2::PatSurface::Material::DEEPSNOW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::HOTCOALS:
|
||||
result.set_material(jak2::PatSurface::Material::HOTCOALS);
|
||||
break;
|
||||
case jak1::PatSurface::Material::LAVA:
|
||||
result.set_material(jak2::PatSurface::Material::LAVA);
|
||||
break;
|
||||
case jak1::PatSurface::Material::CRWOOD:
|
||||
result.set_material(jak2::PatSurface::Material::CRWOOD);
|
||||
break;
|
||||
case jak1::PatSurface::Material::GRAVEL:
|
||||
result.set_material(jak2::PatSurface::Material::GRAVEL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::DIRT:
|
||||
result.set_material(jak2::PatSurface::Material::DIRT);
|
||||
break;
|
||||
case jak1::PatSurface::Material::METAL:
|
||||
result.set_material(jak2::PatSurface::Material::METAL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::STRAW:
|
||||
result.set_material(jak2::PatSurface::Material::STRAW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::TUBE:
|
||||
result.set_material(jak2::PatSurface::Material::TUBE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SWAMP:
|
||||
result.set_material(jak2::PatSurface::Material::SWAMP);
|
||||
break;
|
||||
case jak1::PatSurface::Material::STOPPROJ:
|
||||
result.set_material(jak2::PatSurface::Material::STOPPROJ);
|
||||
break;
|
||||
case jak1::PatSurface::Material::ROTATE:
|
||||
result.set_material(jak2::PatSurface::Material::ROTATE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::NEUTRAL:
|
||||
result.set_material(jak2::PatSurface::Material::NEUTRAL);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (jak1.get_event()) {
|
||||
case jak1::PatSurface::Event::NONE:
|
||||
result.set_event(jak2::PatSurface::Event::NONE);
|
||||
break;
|
||||
case jak1::PatSurface::Event::DEADLY:
|
||||
result.set_event(jak2::PatSurface::Event::DEADLY);
|
||||
break;
|
||||
case jak1::PatSurface::Event::ENDLESSFALL:
|
||||
result.set_event(jak2::PatSurface::Event::ENDLESSFALL);
|
||||
break;
|
||||
case jak1::PatSurface::Event::BURN:
|
||||
result.set_event(jak2::PatSurface::Event::BURN);
|
||||
break;
|
||||
case jak1::PatSurface::Event::DEADLYUP:
|
||||
result.set_event(jak2::PatSurface::Event::DEADLYUP);
|
||||
break;
|
||||
case jak1::PatSurface::Event::BURNUP:
|
||||
result.set_event(jak2::PatSurface::Event::BURNUP);
|
||||
break;
|
||||
case jak1::PatSurface::Event::MELT:
|
||||
result.set_event(jak2::PatSurface::Event::MELT);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
result.set_noentity(jak1.get_noentity());
|
||||
result.set_nocamera(jak1.get_nocamera());
|
||||
|
||||
result.set_noedge(jak1.get_noedge());
|
||||
result.set_nolineofsight(jak1.get_nolineofsight());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a collide hash from a jak1 format mesh by converting to jak 2.
|
||||
*/
|
||||
|
||||
@@ -136,135 +136,6 @@ bool bounding_box_bounding_box(const BoundingBox& a, const BoundingBox& b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert jak1-format PatSurface to Jak 3.
|
||||
*/
|
||||
jak3::PatSurface jak3_pat(jak1::PatSurface jak1) {
|
||||
jak3::PatSurface result;
|
||||
|
||||
switch (jak1.get_mode()) {
|
||||
case jak1::PatSurface::Mode::GROUND:
|
||||
result.set_mode(jak3::PatSurface::Mode::GROUND);
|
||||
break;
|
||||
case jak1::PatSurface::Mode::WALL:
|
||||
result.set_mode(jak3::PatSurface::Mode::WALL);
|
||||
break;
|
||||
case jak1::PatSurface::Mode::OBSTACLE:
|
||||
result.set_mode(jak3::PatSurface::Mode::OBSTACLE);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (jak1.get_material()) {
|
||||
case jak1::PatSurface::Material::STONE:
|
||||
result.set_material(jak3::PatSurface::Material::STONE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::ICE:
|
||||
result.set_material(jak3::PatSurface::Material::ICE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::QUICKSAND:
|
||||
result.set_material(jak3::PatSurface::Material::QUICKSAND);
|
||||
break;
|
||||
case jak1::PatSurface::Material::WATERBOTTOM:
|
||||
result.set_material(jak3::PatSurface::Material::WATERBOTTOM);
|
||||
break;
|
||||
case jak1::PatSurface::Material::TAR:
|
||||
result.set_material(jak3::PatSurface::Material::TAR);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SAND:
|
||||
result.set_material(jak3::PatSurface::Material::SAND);
|
||||
break;
|
||||
case jak1::PatSurface::Material::WOOD:
|
||||
result.set_material(jak3::PatSurface::Material::WOOD);
|
||||
break;
|
||||
case jak1::PatSurface::Material::GRASS:
|
||||
result.set_material(jak3::PatSurface::Material::GRASS);
|
||||
break;
|
||||
case jak1::PatSurface::Material::PCMETAL:
|
||||
result.set_material(jak3::PatSurface::Material::PCMETAL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SNOW:
|
||||
result.set_material(jak3::PatSurface::Material::SNOW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::DEEPSNOW:
|
||||
result.set_material(jak3::PatSurface::Material::DEEPSNOW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::HOTCOALS:
|
||||
result.set_material(jak3::PatSurface::Material::HOTCOALS);
|
||||
break;
|
||||
case jak1::PatSurface::Material::LAVA:
|
||||
result.set_material(jak3::PatSurface::Material::LAVA);
|
||||
break;
|
||||
case jak1::PatSurface::Material::CRWOOD:
|
||||
result.set_material(jak3::PatSurface::Material::CRWOOD);
|
||||
break;
|
||||
case jak1::PatSurface::Material::GRAVEL:
|
||||
result.set_material(jak3::PatSurface::Material::GRAVEL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::DIRT:
|
||||
result.set_material(jak3::PatSurface::Material::DIRT);
|
||||
break;
|
||||
case jak1::PatSurface::Material::METAL:
|
||||
result.set_material(jak3::PatSurface::Material::METAL);
|
||||
break;
|
||||
case jak1::PatSurface::Material::STRAW:
|
||||
result.set_material(jak3::PatSurface::Material::STRAW);
|
||||
break;
|
||||
case jak1::PatSurface::Material::TUBE:
|
||||
result.set_material(jak3::PatSurface::Material::TUBE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::SWAMP:
|
||||
result.set_material(jak3::PatSurface::Material::SWAMP);
|
||||
break;
|
||||
case jak1::PatSurface::Material::STOPPROJ:
|
||||
result.set_material(jak3::PatSurface::Material::STOPPROJ);
|
||||
break;
|
||||
case jak1::PatSurface::Material::ROTATE:
|
||||
result.set_material(jak3::PatSurface::Material::ROTATE);
|
||||
break;
|
||||
case jak1::PatSurface::Material::NEUTRAL:
|
||||
result.set_material(jak3::PatSurface::Material::NEUTRAL);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (jak1.get_event()) {
|
||||
case jak1::PatSurface::Event::NONE:
|
||||
result.set_event(jak3::PatSurface::Event::NONE);
|
||||
break;
|
||||
case jak1::PatSurface::Event::DEADLY:
|
||||
result.set_event(jak3::PatSurface::Event::DEADLY);
|
||||
break;
|
||||
case jak1::PatSurface::Event::ENDLESSFALL:
|
||||
result.set_event(jak3::PatSurface::Event::ENDLESSFALL);
|
||||
break;
|
||||
case jak1::PatSurface::Event::BURN:
|
||||
result.set_event(jak3::PatSurface::Event::BURN);
|
||||
break;
|
||||
case jak1::PatSurface::Event::DEADLYUP:
|
||||
result.set_event(jak3::PatSurface::Event::DEADLYUP);
|
||||
break;
|
||||
case jak1::PatSurface::Event::BURNUP:
|
||||
result.set_event(jak3::PatSurface::Event::BURNUP);
|
||||
break;
|
||||
case jak1::PatSurface::Event::MELT:
|
||||
result.set_event(jak3::PatSurface::Event::MELT);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
result.set_noentity(jak1.get_noentity());
|
||||
result.set_nocamera(jak1.get_nocamera());
|
||||
|
||||
result.set_noedge(jak1.get_noedge());
|
||||
result.set_nolineofsight(jak1.get_nolineofsight());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a collide hash from a jak1 format mesh by converting to jak 3.
|
||||
*/
|
||||
|
||||
@@ -83,7 +83,7 @@ void dedup_tie_vertices(TieOutput& data) {
|
||||
bool prim_needs_tie(const tinygltf::Model& model, const tinygltf::Primitive& prim) {
|
||||
if (prim.material >= 0) {
|
||||
auto mat = model.materials.at(prim.material);
|
||||
return mat.extensions.contains("KHR_materials_specular");
|
||||
return envmap_is_valid(mat);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
namespace jak1 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
const std::string& bsp_output_file,
|
||||
const std::string& output_prefix) {
|
||||
const std::string& output_prefix,
|
||||
bool gen_fr3) {
|
||||
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
|
||||
@@ -129,8 +130,8 @@ bool run_build_level(const std::string& input_file,
|
||||
|
||||
// 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())) {
|
||||
if (gen_fr3 && ((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::Jak1);
|
||||
lg::info("Found ISO path: {}", iso_folder.string());
|
||||
@@ -173,6 +174,11 @@ bool run_build_level(const std::string& input_file,
|
||||
auto textures_out = file_util::get_jak_project_dir() / "decompiler_out/jak1/textures";
|
||||
file_util::create_dir_if_needed(textures_out);
|
||||
db.process_tpages(tex_db, textures_out, config, "");
|
||||
auto replacements_path = file_util::get_jak_project_dir() / "custom_assets" /
|
||||
game_version_names[config.game_version] / "texture_replacements";
|
||||
if (fs::exists(replacements_path)) {
|
||||
tex_db.replace_textures(replacements_path);
|
||||
}
|
||||
|
||||
std::vector<std::string> processed_art_groups;
|
||||
|
||||
@@ -313,7 +319,7 @@ bool run_build_level(const std::string& input_file,
|
||||
}
|
||||
|
||||
// add custom models to fr3
|
||||
if (level_json.contains("custom_models") && !level_json.at("custom_models").empty()) {
|
||||
if (gen_fr3 && level_json.contains("custom_models") && !level_json.at("custom_models").empty()) {
|
||||
auto models = level_json.at("custom_models").get<std::vector<std::string>>();
|
||||
for (auto& name : models) {
|
||||
add_model_to_level(GameVersion::Jak1, name, pc_level);
|
||||
@@ -329,8 +335,10 @@ bool run_build_level(const std::string& input_file,
|
||||
file_util::write_binary_file(save_path, result.data(), result.size());
|
||||
|
||||
// Save the PC level
|
||||
save_pc_data(file.name, pc_level,
|
||||
file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");
|
||||
if (gen_fr3) {
|
||||
save_pc_data(file.name, pc_level,
|
||||
file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
namespace jak1 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
const std::string& bsp_output_file,
|
||||
const std::string& output_prefix);
|
||||
const std::string& output_prefix,
|
||||
bool gen_fr3);
|
||||
} // namespace jak1
|
||||
@@ -24,6 +24,7 @@ size_t DrawableTreeArray::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
int num_trees = 0;
|
||||
num_trees += tfrags.size();
|
||||
num_trees += ties.size();
|
||||
gen.add_word(num_trees << 16);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
@@ -47,6 +48,10 @@ size_t DrawableTreeArray::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
// gen.set_word(tree_word++, tfrag.add_to_object_file(gen));
|
||||
gen.link_word_to_byte(tree_word++, tfrag.add_to_object_file(gen));
|
||||
}
|
||||
|
||||
for (auto& tie : ties) {
|
||||
gen.link_word_to_byte(tree_word++, tie.add_to_object_file(gen));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "goalc/build_level/collide/common/collide_common.h"
|
||||
#include "goalc/build_level/collide/jak2/collide.h"
|
||||
#include "goalc/build_level/common/Tfrag.h"
|
||||
#include "goalc/build_level/common/Tie.h"
|
||||
#include "goalc/build_level/jak2/Entity.h"
|
||||
#include "goalc/build_level/jak2/FileInfo.h"
|
||||
|
||||
@@ -17,8 +18,6 @@ struct VisibilityString {
|
||||
std::vector<u8> bytes;
|
||||
};
|
||||
|
||||
struct DrawableTreeInstanceTie {};
|
||||
|
||||
struct DrawableTreeActor {};
|
||||
|
||||
struct DrawableTreeInstanceShrub {};
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
namespace jak2 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
const std::string& bsp_output_file,
|
||||
const std::string& output_prefix) {
|
||||
const std::string& output_prefix,
|
||||
bool gen_fr3) {
|
||||
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
|
||||
@@ -71,6 +72,13 @@ bool run_build_level(const std::string& input_file,
|
||||
// TFRAG
|
||||
file.drawable_trees.tfrags.emplace_back("drawable-tree-tfrag", "drawable-inline-array-tfrag");
|
||||
tfrag_from_gltf(mesh_extract_out.tfrag, pc_level.tfrag_trees[0]);
|
||||
|
||||
// TIE
|
||||
if (!mesh_extract_out.tie.base_draws.empty()) {
|
||||
file.drawable_trees.ties.emplace_back();
|
||||
tie_from_gltf(mesh_extract_out.tie, pc_level.tie_trees[0]);
|
||||
}
|
||||
|
||||
pc_level.textures = std::move(tex_pool.textures_by_idx);
|
||||
|
||||
// COLLIDE
|
||||
@@ -90,8 +98,8 @@ bool run_build_level(const std::string& input_file,
|
||||
|
||||
// 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())) {
|
||||
if (gen_fr3 && ((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::Jak2);
|
||||
lg::info("Found ISO path: {}", iso_folder.string());
|
||||
@@ -134,9 +142,14 @@ bool run_build_level(const std::string& input_file,
|
||||
auto textures_out = file_util::get_jak_project_dir() / "decompiler_out/jak2/textures";
|
||||
file_util::create_dir_if_needed(textures_out);
|
||||
db.process_tpages(tex_db, textures_out, config, "");
|
||||
auto replacements_path = file_util::get_jak_project_dir() / "custom_assets" /
|
||||
game_version_names[config.game_version] / "texture_replacements";
|
||||
if (fs::exists(replacements_path)) {
|
||||
tex_db.replace_textures(replacements_path);
|
||||
}
|
||||
|
||||
// find all art groups used by the custom level in other dgos
|
||||
if (level_json.contains("art_groups") && !level_json.at("art_groups").empty()) {
|
||||
if (gen_fr3 && 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
|
||||
@@ -190,10 +203,19 @@ bool run_build_level(const std::string& input_file,
|
||||
}
|
||||
}
|
||||
|
||||
// Save the PC level
|
||||
save_pc_data(file.name, pc_level,
|
||||
file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");
|
||||
// add custom models to fr3
|
||||
if (gen_fr3 && level_json.contains("custom_models") && !level_json.at("custom_models").empty()) {
|
||||
auto models = level_json.at("custom_models").get<std::vector<std::string>>();
|
||||
for (auto& name : models) {
|
||||
add_model_to_level(GameVersion::Jak2, name, pc_level);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the PC level
|
||||
if (gen_fr3) {
|
||||
save_pc_data(file.name, pc_level,
|
||||
file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace jak2
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
namespace jak2 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
const std::string& bsp_output_file,
|
||||
const std::string& output_prefix);
|
||||
const std::string& output_prefix,
|
||||
bool gen_fr3);
|
||||
} // namespace jak2
|
||||
@@ -24,6 +24,7 @@ size_t DrawableTreeArray::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
int num_trees = 0;
|
||||
num_trees += tfrags.size();
|
||||
num_trees += ties.size();
|
||||
gen.add_word(num_trees << 16);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
@@ -47,6 +48,10 @@ size_t DrawableTreeArray::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
// gen.set_word(tree_word++, tfrag.add_to_object_file(gen));
|
||||
gen.link_word_to_byte(tree_word++, tfrag.add_to_object_file(gen));
|
||||
}
|
||||
|
||||
for (auto& tie : ties) {
|
||||
gen.link_word_to_byte(tree_word++, tie.add_to_object_file(gen));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#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/common/Tie.h"
|
||||
#include "goalc/build_level/jak3/Entity.h"
|
||||
#include "goalc/build_level/jak3/FileInfo.h"
|
||||
|
||||
@@ -17,8 +18,6 @@ struct VisibilityString {
|
||||
std::vector<u8> bytes;
|
||||
};
|
||||
|
||||
struct DrawableTreeInstanceTie {};
|
||||
|
||||
struct DrawableTreeActor {};
|
||||
|
||||
struct DrawableTreeInstanceShrub {};
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
namespace jak3 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
const std::string& bsp_output_file,
|
||||
const std::string& output_prefix) {
|
||||
const std::string& output_prefix,
|
||||
bool gen_fr3) {
|
||||
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
|
||||
@@ -69,6 +70,13 @@ bool run_build_level(const std::string& input_file,
|
||||
// TFRAG
|
||||
file.drawable_trees.tfrags.emplace_back("drawable-tree-tfrag", "drawable-inline-array-tfrag");
|
||||
tfrag_from_gltf(mesh_extract_out.tfrag, pc_level.tfrag_trees[0]);
|
||||
|
||||
// TIE
|
||||
if (!mesh_extract_out.tie.base_draws.empty()) {
|
||||
file.drawable_trees.ties.emplace_back();
|
||||
tie_from_gltf(mesh_extract_out.tie, pc_level.tie_trees[0]);
|
||||
}
|
||||
|
||||
pc_level.textures = std::move(tex_pool.textures_by_idx);
|
||||
|
||||
// COLLIDE
|
||||
@@ -88,8 +96,8 @@ bool run_build_level(const std::string& input_file,
|
||||
|
||||
// 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())) {
|
||||
if (gen_fr3 && ((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());
|
||||
@@ -132,9 +140,14 @@ bool run_build_level(const std::string& input_file,
|
||||
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, "");
|
||||
auto replacements_path = file_util::get_jak_project_dir() / "custom_assets" /
|
||||
game_version_names[config.game_version] / "texture_replacements";
|
||||
if (fs::exists(replacements_path)) {
|
||||
tex_db.replace_textures(replacements_path);
|
||||
}
|
||||
|
||||
// find all art groups used by the custom level in other dgos
|
||||
if (level_json.contains("art_groups") && !level_json.at("art_groups").empty()) {
|
||||
if (gen_fr3 && 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
|
||||
@@ -188,10 +201,19 @@ bool run_build_level(const std::string& input_file,
|
||||
}
|
||||
}
|
||||
|
||||
// Save the PC level
|
||||
save_pc_data(file.name, pc_level,
|
||||
file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");
|
||||
// add custom models to fr3
|
||||
if (gen_fr3 && level_json.contains("custom_models") && !level_json.at("custom_models").empty()) {
|
||||
auto models = level_json.at("custom_models").get<std::vector<std::string>>();
|
||||
for (auto& name : models) {
|
||||
add_model_to_level(GameVersion::Jak3, name, pc_level);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the PC level
|
||||
if (gen_fr3) {
|
||||
save_pc_data(file.name, pc_level,
|
||||
file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace jak3
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
namespace jak3 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
const std::string& bsp_output_file,
|
||||
const std::string& output_prefix);
|
||||
const std::string& output_prefix,
|
||||
bool gen_fr3);
|
||||
} // namespace jak3
|
||||
@@ -18,6 +18,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
// game version
|
||||
std::string game, input_json, output_file;
|
||||
bool gen_fr3 = true;
|
||||
fs::path project_path_override;
|
||||
|
||||
// path
|
||||
@@ -38,6 +39,7 @@ int main(int argc, char** argv) {
|
||||
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.add_flag("--fr3", gen_fr3, "Generate FR3 file for this level (default true)");
|
||||
app.validate_positionals();
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
@@ -58,13 +60,13 @@ int main(int argc, char** argv) {
|
||||
|
||||
switch (game_version) {
|
||||
case GameVersion::Jak1:
|
||||
jak1::run_build_level(input_json, output_file, "jak1/");
|
||||
jak1::run_build_level(input_json, output_file, "jak1/", gen_fr3);
|
||||
break;
|
||||
case GameVersion::Jak2:
|
||||
jak2::run_build_level(input_json, output_file, "jak2/");
|
||||
jak2::run_build_level(input_json, output_file, "jak2/", gen_fr3);
|
||||
break;
|
||||
case GameVersion::Jak3:
|
||||
jak3::run_build_level(input_json, output_file, "jak3/");
|
||||
jak3::run_build_level(input_json, output_file, "jak3/", gen_fr3);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED_MSG("unsupported game version");
|
||||
|
||||
@@ -112,6 +112,8 @@ MakeSystem::MakeSystem(const std::optional<REPL::Config> repl_config, const std:
|
||||
add_tool<BuildLevel2Tool>();
|
||||
add_tool<BuildLevel3Tool>();
|
||||
add_tool<BuildActorTool>();
|
||||
add_tool<BuildActor2Tool>();
|
||||
add_tool<BuildActor3Tool>();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
+101
-20
@@ -250,71 +250,152 @@ bool SubtitleV2Tool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
BuildLevelTool::BuildLevelTool() : Tool("build-level") {}
|
||||
|
||||
bool BuildLevelTool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() != 1) {
|
||||
if (task.input.size() > 3) {
|
||||
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);
|
||||
auto rerun = task.input.at(1) == "#t";
|
||||
std::vector in = {task.input.at(0)};
|
||||
return rerun || Tool::needs_run({in, deps, task.output, task.arg}, path_map);
|
||||
}
|
||||
|
||||
bool BuildLevelTool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() != 1) {
|
||||
if (task.input.size() > 3) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
return jak1::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix);
|
||||
auto gen_fr3 = task.input.at(2) == "#t";
|
||||
return jak1::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix,
|
||||
gen_fr3);
|
||||
}
|
||||
|
||||
BuildLevel2Tool::BuildLevel2Tool() : Tool("build-level2") {}
|
||||
|
||||
bool BuildLevel2Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() != 1) {
|
||||
if (task.input.size() > 3) {
|
||||
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);
|
||||
auto rerun = task.input.at(1) == "#t";
|
||||
std::vector in = {task.input.at(0)};
|
||||
return rerun || Tool::needs_run({in, deps, task.output, task.arg}, path_map);
|
||||
}
|
||||
|
||||
bool BuildLevel2Tool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() != 1) {
|
||||
if (task.input.size() > 3) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
return jak2::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix);
|
||||
auto gen_fr3 = task.input.at(2) == "#t";
|
||||
return jak2::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix,
|
||||
gen_fr3);
|
||||
}
|
||||
|
||||
BuildLevel3Tool::BuildLevel3Tool() : Tool("build-level3") {}
|
||||
|
||||
bool BuildLevel3Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() != 1) {
|
||||
if (task.input.size() > 3) {
|
||||
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);
|
||||
auto rerun = task.input.at(1) == "#t";
|
||||
std::vector in = {task.input.at(0)};
|
||||
return rerun || Tool::needs_run({in, deps, task.output, task.arg}, path_map);
|
||||
}
|
||||
|
||||
bool BuildLevel3Tool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() != 1) {
|
||||
if (task.input.size() > 3) {
|
||||
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);
|
||||
auto gen_fr3 = task.input.at(2) == "#t";
|
||||
return jak3::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix,
|
||||
gen_fr3);
|
||||
}
|
||||
|
||||
BuildActorTool::BuildActorTool() : Tool("build-actor") {}
|
||||
|
||||
bool BuildActorTool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
||||
(void)path_map;
|
||||
if (task.input.size() > 2) {
|
||||
if (task.input.size() > 4) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
// std::vector<std::string> deps{};
|
||||
// return Tool::needs_run({task.input, deps, task.output, task.arg}, path_map);
|
||||
return true;
|
||||
auto rerun = task.input.at(2) == "#t";
|
||||
std::vector<std::string> deps{task.input.at(0)};
|
||||
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
|
||||
}
|
||||
|
||||
bool BuildActorTool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
(void)path_map;
|
||||
if (task.input.size() > 2) {
|
||||
if (task.input.size() > 4) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
auto gen_mesh = task.input.at(1) == "#t";
|
||||
return jak1::run_build_actor(task.input.at(0), task.output.at(0), gen_mesh);
|
||||
jak1::BuildActorParams params;
|
||||
params.gen_collide_mesh = task.input.at(1) == "#t";
|
||||
if (task.input.at(3) == "#f") {
|
||||
params.texture_bucket = -1;
|
||||
} else {
|
||||
try {
|
||||
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
|
||||
} catch (std::invalid_argument&) {
|
||||
throw std::runtime_error("[build-actor] texture-bucket must be #f or a valid integer.");
|
||||
}
|
||||
}
|
||||
return jak1::run_build_actor(task.input.at(0), task.output.at(0), params);
|
||||
}
|
||||
|
||||
BuildActor2Tool::BuildActor2Tool() : Tool("build-actor2") {}
|
||||
|
||||
bool BuildActor2Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() > 4) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
auto rerun = task.input.at(2) == "#t";
|
||||
std::vector<std::string> deps{task.input.at(0)};
|
||||
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
|
||||
}
|
||||
|
||||
bool BuildActor2Tool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
(void)path_map;
|
||||
if (task.input.size() > 4) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
jak2::BuildActorParams params;
|
||||
params.gen_collide_mesh = task.input.at(1) == "#t";
|
||||
if (task.input.at(3) == "#f") {
|
||||
params.texture_bucket = -1;
|
||||
} else {
|
||||
try {
|
||||
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
|
||||
} catch (std::invalid_argument&) {
|
||||
throw std::runtime_error("[build-actor2] texture-bucket must be #f or a valid integer.");
|
||||
}
|
||||
}
|
||||
return jak2::run_build_actor(task.input.at(0), task.output.at(0), params);
|
||||
}
|
||||
|
||||
BuildActor3Tool::BuildActor3Tool() : Tool("build-actor3") {}
|
||||
|
||||
bool BuildActor3Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
||||
if (task.input.size() > 4) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
auto rerun = task.input.at(2) == "#t";
|
||||
std::vector<std::string> deps{task.input.at(0)};
|
||||
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
|
||||
}
|
||||
|
||||
bool BuildActor3Tool::run(const ToolInput& task, const PathMap& path_map) {
|
||||
(void)path_map;
|
||||
if (task.input.size() > 4) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
jak3::BuildActorParams params;
|
||||
params.gen_collide_mesh = task.input.at(1) == "#t";
|
||||
if (task.input.at(3) == "#f") {
|
||||
params.texture_bucket = -1;
|
||||
} else {
|
||||
try {
|
||||
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
|
||||
} catch (std::invalid_argument&) {
|
||||
throw std::runtime_error("[build-actor3] texture-bucket must be #f or a valid integer.");
|
||||
}
|
||||
}
|
||||
return jak3::run_build_actor(task.input.at(0), task.output.at(0), params);
|
||||
}
|
||||
@@ -99,3 +99,17 @@ class BuildActorTool : public Tool {
|
||||
bool run(const ToolInput& task, const PathMap& path_map) override;
|
||||
bool needs_run(const ToolInput& task, const PathMap& path_map) override;
|
||||
};
|
||||
|
||||
class BuildActor2Tool : public Tool {
|
||||
public:
|
||||
BuildActor2Tool();
|
||||
bool run(const ToolInput& task, const PathMap& path_map) override;
|
||||
bool needs_run(const ToolInput& task, const PathMap& path_map) override;
|
||||
};
|
||||
|
||||
class BuildActor3Tool : public Tool {
|
||||
public:
|
||||
BuildActor3Tool();
|
||||
bool run(const ToolInput& task, const PathMap& path_map) override;
|
||||
bool needs_run(const ToolInput& task, const PathMap& path_map) override;
|
||||
};
|
||||
Reference in New Issue
Block a user