mirror of
https://github.com/open-goal/jak-project
synced 2026-06-15 14:31:58 -04:00
custom levels: support custom regions in jak2/3 (#4300)
This adds support for generating pairs with `DataObjectGenerator` and defining your own regions and `actor-group`s in custom levels.
This commit is contained in:
@@ -53,9 +53,12 @@
|
||||
// The base actor id for your custom level. If you have multiple levels, this should be unique!
|
||||
"base_id": 100,
|
||||
|
||||
// Base id for regions.
|
||||
"base_region_id": 0,
|
||||
|
||||
// All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file.
|
||||
// Commented out so that the release builds don't have to double-decompile the game
|
||||
// "art_groups": ["yakow-ag"],
|
||||
// "art_groups": ["yakow-ag", "water-anim-fortress-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.
|
||||
@@ -69,6 +72,177 @@
|
||||
// If you want all textures from a tpage, you can just do ["tpage-name"].
|
||||
"textures": ["yak-medfur-end"], // for yakow texture fix
|
||||
|
||||
// Any regions you want to include in your custom level.
|
||||
// Regions run scripts that do things like loading specific levels,
|
||||
// playing ambient sounds, checking if a task is completed in order to e.g. open airlocks, play cutscenes, etc.
|
||||
//
|
||||
// They can have three scripts that run on different conditions:
|
||||
// - A single time when entering the region's bounds (on-enter)
|
||||
// - Once every frame while you are inside a region (on-inside)
|
||||
// - A single time when exiting a region's bounds (on-exit)
|
||||
// Scripts are Lisp pairs, e.g. (want-load 'ctysluma 'ctyindb 'ctywide).
|
||||
// The list of commands that can be used is too exhaustive to show here and there is no proper documentation for this,
|
||||
// but you can refer to https://github.com/open-goal/jak-project/blob/master/goal_src/jak2/engine/util/script.gc
|
||||
// for more information or inspect other regions via the debug menu in-game for examples.
|
||||
//
|
||||
// There are two special cases for script forms: "entity-actor" and "actor-group".
|
||||
// Region scripts can refer to entities or actor groups that are stored inside the level data,
|
||||
// so forms that start with either of these will be changed during the level building process to insert a reference
|
||||
// to the given object.
|
||||
//
|
||||
// Regions can either be spheres, planes or volumes.
|
||||
// Spheres do not need anything special, just the trans and bsphere, but if you want a face or a volume,
|
||||
// you need to additionally specify a "face" or a "volume" key.
|
||||
// A "face" key contains the face normal and a list of points.
|
||||
// A "volume" key contains a list of faces.
|
||||
//
|
||||
// There are different types of regions like ones that get triggered based on camera position, Jak's position, etc.
|
||||
// Each type of region is stored in its own region tree, each of which has its own bsphere that all its regions must be encompassed within.
|
||||
// The full list is as follows:
|
||||
// target (Jak based), camera (camera based), data (?), water (water volumes), city_vis (?),
|
||||
// sample (?), light (presumably for foreground lights), entity (?)
|
||||
"region_trees": {
|
||||
// camera regions are activated when the camera enters their bounds
|
||||
"camera": {
|
||||
// every region tree has its own bsphere. all of a tree's regions must be encompassed within this sphere.
|
||||
"bsphere": [0, 0, 0, 50],
|
||||
"regions": [
|
||||
{
|
||||
"id": 1,
|
||||
"shape": "sphere", // can be "sphere", "face" or "volume"
|
||||
"trans": [-17, 4, 30],
|
||||
"bsphere": [-17, 4, 30, 5],
|
||||
// "(actor-group 2)" will be replaced with an actual reference
|
||||
// to the actor-group in the level data with id 2.
|
||||
"on-enter": "(begin (print 'enter1) (send-event (actor-group 2) 'none))",
|
||||
"on-inside": "(begin (sound-play-loop \"punch\") (sound-play-loop \"spin\"))",
|
||||
"on-exit": "(print 'exit1)"
|
||||
}
|
||||
]
|
||||
},
|
||||
// target regions are activated when Jak enters their bounds
|
||||
"target": {
|
||||
"bsphere": [0, 0, 0, 50],
|
||||
"regions": [
|
||||
{
|
||||
"id": 2,
|
||||
"shape": "face",
|
||||
"on-enter": "(begin (print 'enter14) (send-event \"test-actor\" 'rot))",
|
||||
"bsphere": [3.37, 6.2, 16.59, 7.0711],
|
||||
"face": {
|
||||
"normal": [-0.615662, 0.0, -0.788011, -15.147877],
|
||||
"points": [
|
||||
[-0.570054, 0, 19.668308],
|
||||
[-0.570053, 11, 19.668308],
|
||||
[7.310053, 0, 13.511692],
|
||||
[7.310053, 11, 13.511692]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// water regions are used to define water volumes
|
||||
"water": {
|
||||
"bsphere": [0, 0, 0, 200],
|
||||
"regions": [
|
||||
{
|
||||
"id": 3,
|
||||
"shape": "sphere",
|
||||
// "(entity-actor water-anim-test-zone-1)" will be replaced with an actual reference of the entity with that name.
|
||||
"on-inside": "(water water-anim (entity-actor water-anim-test-zone-1) (swim wade))",
|
||||
"trans": [5.0, 6.0, -66.0],
|
||||
"bsphere": [5.0, 6.0, -66.0, 50.0]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"shape": "volume",
|
||||
"on-inside": "(water height 0.0 (swim wade))",
|
||||
"bsphere": [5.1664, 9.4453, 51.2316, 25.2828],
|
||||
"volume": {
|
||||
"faces": [
|
||||
{
|
||||
"normal": [-1.0, 0.0, 0.0, 11.785238],
|
||||
"points": [
|
||||
[-11.785238, -2.468076, 65.720734],
|
||||
[-11.785238, 21.358631, 65.720734],
|
||||
[-11.785238, -2.468076, 36.742477],
|
||||
[-11.785238, 21.358631, 36.742477]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [0.0, 0.0, -1.0, -36.742475],
|
||||
"points": [
|
||||
[-11.785238, -2.468076, 36.742477],
|
||||
[-11.785238, 21.358631, 36.742477],
|
||||
[22.118128, -2.468076, 36.742477],
|
||||
[22.118128, 21.358631, 36.742477]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [1.0, 0.0, 0.0, 22.118128],
|
||||
"points": [
|
||||
[22.118128, -2.468076, 36.742477],
|
||||
[22.118128, 21.358631, 36.742477],
|
||||
[22.118128, -2.468076, 65.720734],
|
||||
[22.118128, 21.358631, 65.720734]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [0.0, 0.0, 1.0, 65.72073],
|
||||
"points": [
|
||||
[22.118128, -2.468076, 65.720734],
|
||||
[22.118128, 21.358631, 65.720734],
|
||||
[-11.785238, -2.468076, 65.720734],
|
||||
[-11.785238, 21.358631, 65.720734]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [0.0, -1.0, 0.0, 2.468076],
|
||||
"points": [
|
||||
[-11.785238, -2.468076, 36.742477],
|
||||
[22.118128, -2.468076, 36.742477],
|
||||
[-11.785238, -2.468076, 65.720734],
|
||||
[22.118128, -2.468076, 65.720734]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [0.0, 1.0, 0.0, 21.358631],
|
||||
"points": [
|
||||
[22.118128, 21.358631, 36.742477],
|
||||
[-11.785238, 21.358631, 36.742477],
|
||||
[22.118128, 21.358631, 65.720734],
|
||||
[-11.785238, 21.358631, 65.720734]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": {},
|
||||
"city_vis": {},
|
||||
"sample": {},
|
||||
"light": {},
|
||||
"entity": {}
|
||||
},
|
||||
|
||||
// Actor groups you want to include in your level.
|
||||
//
|
||||
// Actor groups are arrays of entities, used for example in regions to send events to a list of actors
|
||||
// or in "battles" as a list of enemies that need to be defeated before being able to progress.
|
||||
// You can give them an id, this has no effect in-game, but it is used for region scripts so the level builder
|
||||
// knows which actor group to insert.
|
||||
"actor_groups": [
|
||||
{
|
||||
"id": 0,
|
||||
"entities": ["test-crate", "test-eco", "test-yakow"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"entities": ["test-crate", "test-actor"]
|
||||
}
|
||||
],
|
||||
|
||||
"actors": [
|
||||
{
|
||||
"trans": [-15.2818, 15.2461, 17.136], // translation
|
||||
@@ -115,6 +289,24 @@
|
||||
"lump": {
|
||||
"name": "test-actor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"trans": [5, 6.778769493103027, -66],
|
||||
"etype": "water-anim-test-zone",
|
||||
"game_task": 0,
|
||||
"quat": [0.0, 0.0, 0.0, 1.0],
|
||||
"bsphere": [5, 6.778769493103027, -66, 50],
|
||||
"lump": {
|
||||
"look": ["int32", 21],
|
||||
"name": "water-anim-test-zone-1",
|
||||
"water-height": [
|
||||
"water-height",
|
||||
6.77869987487793,
|
||||
0.5,
|
||||
2.0,
|
||||
"(water-flags can-swim can-wade)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -53,9 +53,12 @@
|
||||
// The base actor id for your custom level. If you have multiple levels, this should be unique!
|
||||
"base_id": 100,
|
||||
|
||||
// Base id for regions.
|
||||
"base_region_id": 0,
|
||||
|
||||
// All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file.
|
||||
// Commented out so that the release builds don't have to double-decompile the game
|
||||
// "art_groups": ["yakow-ag"],
|
||||
// "art_groups": ["yakow-ag", "water-anim-waspala-ag"],
|
||||
|
||||
// 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.
|
||||
@@ -69,6 +72,177 @@
|
||||
// If you want all textures from a tpage, you can just do ["tpage-name"].
|
||||
"textures": ["yak-medfur-end"], // for yakow texture fix
|
||||
|
||||
// Any regions you want to include in your custom level.
|
||||
// Regions run scripts that do things like loading specific levels,
|
||||
// playing ambient sounds, checking if a task is completed in order to e.g. open airlocks, play cutscenes, etc.
|
||||
//
|
||||
// They can have three scripts that run on different conditions:
|
||||
// - A single time when entering the region's bounds (on-enter)
|
||||
// - Once every frame while you are inside a region (on-inside)
|
||||
// - A single time when exiting a region's bounds (on-exit)
|
||||
// Scripts are Lisp pairs, e.g. (want-load 'ctysluma 'ctyindb 'ctywide).
|
||||
// The list of commands that can be used is too exhaustive to show here and there is no proper documentation for this,
|
||||
// but you can refer to https://github.com/open-goal/jak-project/blob/master/goal_src/jak3/engine/util/script.gc
|
||||
// for more information or inspect other regions via the debug menu in-game for examples.
|
||||
//
|
||||
// There are two special cases for script forms: "entity-actor" and "actor-group".
|
||||
// Region scripts can refer to entities or actor groups that are stored inside the level data,
|
||||
// so forms that start with either of these will be changed during the level building process to insert a reference
|
||||
// to the given object.
|
||||
//
|
||||
// Regions can either be spheres, planes or volumes.
|
||||
// Spheres do not need anything special, just the trans and bsphere, but if you want a face or a volume,
|
||||
// you need to additionally specify a "face" or a "volume" key.
|
||||
// A "face" key contains the face normal and a list of points.
|
||||
// A "volume" key contains a list of faces.
|
||||
//
|
||||
// There are different types of regions like ones that get triggered based on camera position, Jak's position, etc.
|
||||
// Each type of region is stored in its own region tree, each of which has its own bsphere that all its regions must be encompassed within.
|
||||
// The full list is as follows:
|
||||
// target (Jak based), camera (camera based), data (?), water (water volumes), city_vis (?),
|
||||
// sample (?), light (presumably for foreground lights), entity (?)
|
||||
"region_trees": {
|
||||
// camera regions are activated when the camera enters their bounds
|
||||
"camera": {
|
||||
// every region tree has its own bsphere. all of a tree's regions must be encompassed within this sphere.
|
||||
"bsphere": [0, 0, 0, 50],
|
||||
"regions": [
|
||||
{
|
||||
"id": 1,
|
||||
"shape": "sphere", // can be "sphere", "face" or "volume"
|
||||
"trans": [-17, 4, 30],
|
||||
"bsphere": [-17, 4, 30, 5],
|
||||
// "(actor-group 2)" will be replaced with an actual reference
|
||||
// to the actor-group in the level data with id 2.
|
||||
"on-enter": "(begin (print 'enter1) (send-event (actor-group 2) 'none))",
|
||||
"on-inside": "(begin (sound-play-loop \"punch\") (sound-play-loop \"spin\"))",
|
||||
"on-exit": "(print 'exit1)"
|
||||
}
|
||||
]
|
||||
},
|
||||
// target regions are activated when Jak enters their bounds
|
||||
"target": {
|
||||
"bsphere": [0, 0, 0, 50],
|
||||
"regions": [
|
||||
{
|
||||
"id": 2,
|
||||
"shape": "face",
|
||||
"on-enter": "(begin (print 'enter14) (send-event \"test-actor\" 'rot))",
|
||||
"bsphere": [3.37, 6.2, 16.59, 7.0711],
|
||||
"face": {
|
||||
"normal": [-0.615662, 0.0, -0.788011, -15.147877],
|
||||
"points": [
|
||||
[-0.570054, 0, 19.668308],
|
||||
[-0.570053, 11, 19.668308],
|
||||
[7.310053, 0, 13.511692],
|
||||
[7.310053, 11, 13.511692]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// water regions are used to define water volumes
|
||||
"water": {
|
||||
"bsphere": [0, 0, 0, 200],
|
||||
"regions": [
|
||||
{
|
||||
"id": 3,
|
||||
"shape": "sphere",
|
||||
// "(entity-actor water-anim-test-zone-1)" will be replaced with an actual reference of the entity with that name.
|
||||
"on-inside": "(water water-anim (entity-actor water-anim-test-zone-1) (swim wade))",
|
||||
"trans": [5.0, 6.0, -66.0],
|
||||
"bsphere": [5.0, 6.0, -66.0, 50.0]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"shape": "volume",
|
||||
"on-inside": "(water height 0.0 (swim wade))",
|
||||
"bsphere": [5.1664, 9.4453, 51.2316, 25.2828],
|
||||
"volume": {
|
||||
"faces": [
|
||||
{
|
||||
"normal": [-1.0, 0.0, 0.0, 11.785238],
|
||||
"points": [
|
||||
[-11.785238, -2.468076, 65.720734],
|
||||
[-11.785238, 21.358631, 65.720734],
|
||||
[-11.785238, -2.468076, 36.742477],
|
||||
[-11.785238, 21.358631, 36.742477]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [0.0, 0.0, -1.0, -36.742475],
|
||||
"points": [
|
||||
[-11.785238, -2.468076, 36.742477],
|
||||
[-11.785238, 21.358631, 36.742477],
|
||||
[22.118128, -2.468076, 36.742477],
|
||||
[22.118128, 21.358631, 36.742477]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [1.0, 0.0, 0.0, 22.118128],
|
||||
"points": [
|
||||
[22.118128, -2.468076, 36.742477],
|
||||
[22.118128, 21.358631, 36.742477],
|
||||
[22.118128, -2.468076, 65.720734],
|
||||
[22.118128, 21.358631, 65.720734]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [0.0, 0.0, 1.0, 65.72073],
|
||||
"points": [
|
||||
[22.118128, -2.468076, 65.720734],
|
||||
[22.118128, 21.358631, 65.720734],
|
||||
[-11.785238, -2.468076, 65.720734],
|
||||
[-11.785238, 21.358631, 65.720734]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [0.0, -1.0, 0.0, 2.468076],
|
||||
"points": [
|
||||
[-11.785238, -2.468076, 36.742477],
|
||||
[22.118128, -2.468076, 36.742477],
|
||||
[-11.785238, -2.468076, 65.720734],
|
||||
[22.118128, -2.468076, 65.720734]
|
||||
]
|
||||
},
|
||||
{
|
||||
"normal": [0.0, 1.0, 0.0, 21.358631],
|
||||
"points": [
|
||||
[22.118128, 21.358631, 36.742477],
|
||||
[-11.785238, 21.358631, 36.742477],
|
||||
[22.118128, 21.358631, 65.720734],
|
||||
[-11.785238, 21.358631, 65.720734]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": {},
|
||||
"city_vis": {},
|
||||
"sample": {},
|
||||
"light": {},
|
||||
"entity": {}
|
||||
},
|
||||
|
||||
// Actor groups you want to include in your level.
|
||||
//
|
||||
// Actor groups are arrays of entities, used for example in regions to send events to a list of actors
|
||||
// or in "battles" as a list of enemies that need to be defeated before being able to progress.
|
||||
// You can give them an id, this has no effect in-game, but it is used for region scripts so the level builder
|
||||
// knows which actor group to insert.
|
||||
"actor_groups": [
|
||||
{
|
||||
"id": 0,
|
||||
"entities": ["test-crate", "test-eco", "test-yakow"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"entities": ["test-crate", "test-actor"]
|
||||
}
|
||||
],
|
||||
|
||||
"actors": [
|
||||
{
|
||||
"trans": [-15.2818, 15.2461, 17.136], // translation
|
||||
@@ -116,6 +290,24 @@
|
||||
"lump": {
|
||||
"name": "test-actor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"trans": [5, 6.778769493103027, -66],
|
||||
"etype": "water-anim-test-zone",
|
||||
"game_task": 0,
|
||||
"quat": [0.0, 0.0, 0.0, 1.0],
|
||||
"bsphere": [5, 6.778769493103027, -66, 50],
|
||||
"lump": {
|
||||
"look": ["int32", 7],
|
||||
"name": "water-anim-test-zone-1",
|
||||
"water-height": [
|
||||
"water-height",
|
||||
6.77869987487793,
|
||||
0.5,
|
||||
2.0,
|
||||
"(water-flag can-swim can-wade)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
(base vector :inline)
|
||||
(old-base vector :inline)
|
||||
(bob-offset int64)
|
||||
(bob-amount float))
|
||||
(bob-amount float)
|
||||
(last-fast-rot time-frame))
|
||||
(:methods
|
||||
(init-collision! (_type_) object))
|
||||
(:state-methods
|
||||
@@ -74,6 +75,7 @@
|
||||
:event
|
||||
(behavior ((proc process) (argc int) (message symbol) (block event-message-block))
|
||||
(case message
|
||||
(('rot) (set-time! (-> self last-fast-rot)))
|
||||
(('attack 'touch)
|
||||
; (if (= (-> proc type) target)
|
||||
; (send-event proc 'attack #f (static-attack-info ((shove-up (meters 2.5)) (shove-back (meters 7.5)))))
|
||||
@@ -82,7 +84,7 @@
|
||||
:code
|
||||
(behavior ()
|
||||
(loop
|
||||
(quaternion-rotate-y! (-> self root quat) (-> self root quat) (* (degrees 45) (seconds-per-frame)))
|
||||
(quaternion-rotate-y! (-> self root quat) (-> self root quat) (* (degrees (if (not (time-elapsed? (-> self last-fast-rot) (seconds 2))) 240 45)) (seconds-per-frame)))
|
||||
(let ((bob (-> self bob-amount)))
|
||||
(when (< 0.0 bob)
|
||||
(set! (-> self root trans y)
|
||||
@@ -99,3 +101,31 @@
|
||||
; )
|
||||
(suspend)))
|
||||
:post transform-post)
|
||||
|
||||
(deftype water-anim-test-zone (water-anim)
|
||||
()
|
||||
)
|
||||
|
||||
(define ripple-for-water-anim-test-zone (new 'static 'ripple-wave-set
|
||||
:count 3
|
||||
:converted #f
|
||||
:normal-scale 1.0
|
||||
:wave (new 'static 'inline-array ripple-wave 4
|
||||
(new 'static 'ripple-wave :scale 40.0 :xdiv 1 :speed 1.5)
|
||||
(new 'static 'ripple-wave :scale 40.0 :xdiv -1 :zdiv 1 :speed 1.5)
|
||||
(new 'static 'ripple-wave :scale 20.0 :xdiv 5 :zdiv 3 :speed 0.75)
|
||||
(new 'static 'ripple-wave)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defmethod init-water! ((this water-anim-test-zone))
|
||||
(call-parent-method this)
|
||||
(set! (-> this draw ripple) (new 'process 'ripple-control))
|
||||
(set-vector! (-> this draw color-mult) 0.01 0.45 0.5 0.75)
|
||||
(set! (-> this draw ripple global-scale) (meters 0.75))
|
||||
(set! (-> this draw ripple close-fade-dist) (meters 40))
|
||||
(set! (-> this draw ripple far-fade-dist) (meters 60))
|
||||
(set! (-> this draw ripple waveform) ripple-for-water-anim-test-zone)
|
||||
(none)
|
||||
)
|
||||
@@ -6,7 +6,8 @@
|
||||
(base vector :inline)
|
||||
(old-base vector :inline)
|
||||
(bob-offset int64)
|
||||
(bob-amount float))
|
||||
(bob-amount float)
|
||||
(last-fast-rot time-frame))
|
||||
(:methods
|
||||
(init-collision! (_type_) object))
|
||||
(:state-methods
|
||||
@@ -73,6 +74,7 @@
|
||||
:event
|
||||
(behavior ((proc process) (argc int) (message symbol) (block event-message-block))
|
||||
(case message
|
||||
(('rot) (set-time! (-> self last-fast-rot)))
|
||||
(('attack 'touch)
|
||||
; (if (= (-> proc type) target)
|
||||
; (send-event proc 'attack #f (static-attack-info ((shove-up (meters 2.5)) (shove-back (meters 7.5)))))
|
||||
@@ -81,7 +83,7 @@
|
||||
:code
|
||||
(behavior ()
|
||||
(loop
|
||||
(quaternion-rotate-y! (-> self root quat) (-> self root quat) (* (degrees 45) (seconds-per-frame)))
|
||||
(quaternion-rotate-y! (-> self root quat) (-> self root quat) (* (degrees (if (not (time-elapsed? (-> self last-fast-rot) (seconds 2))) 240 45)) (seconds-per-frame)))
|
||||
(let ((bob (-> self bob-amount)))
|
||||
(when (< 0.0 bob)
|
||||
(set! (-> self root trans y)
|
||||
@@ -98,3 +100,27 @@
|
||||
; )
|
||||
(suspend)))
|
||||
:post transform-post)
|
||||
|
||||
(deftype water-anim-test-zone (water-anim) ())
|
||||
|
||||
(define ripple-for-water-anim-test-zone (new 'static 'ripple-wave-set
|
||||
:count 3
|
||||
:converted #f
|
||||
:normal-scale 1.0
|
||||
:wave (new 'static 'inline-array ripple-wave 4
|
||||
(new 'static 'ripple-wave :scale 10.0 :xdiv 1 :speed 1.5)
|
||||
(new 'static 'ripple-wave :scale 10.0 :xdiv -1 :zdiv 1 :speed 1.5)
|
||||
(new 'static 'ripple-wave :scale 5.0 :xdiv 5 :zdiv 3 :speed 0.75)
|
||||
(new 'static 'ripple-wave)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defmethod init-water! ((this water-anim-test-zone))
|
||||
(call-parent-method this)
|
||||
(set! (-> this draw ripple) (new 'process 'ripple-control))
|
||||
(set! (-> this draw ripple global-scale) (meters 0.75))
|
||||
(set! (-> this draw ripple close-fade-dist) (meters 40))
|
||||
(set! (-> this draw ripple far-fade-dist) (meters 60))
|
||||
(set! (-> this draw ripple waveform) ripple-for-water-anim-test-zone)
|
||||
)
|
||||
@@ -36,6 +36,8 @@ add_library(compiler
|
||||
build_level/common/Tfrag.cpp
|
||||
build_level/common/Tie.cpp
|
||||
build_level/jak1/ambient.cpp
|
||||
build_level/jak2/Region.cpp
|
||||
build_level/jak3/Region.cpp
|
||||
compiler/Compiler.cpp
|
||||
compiler/Env.cpp
|
||||
compiler/Val.cpp
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
#include "FileInfo.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "common/versions/versions.h"
|
||||
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
std::string get_current_time_and_date() {
|
||||
auto const now = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
|
||||
std::time_t const t = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm tm{};
|
||||
#if defined(_WIN32)
|
||||
localtime_s(&tm, &t);
|
||||
#else
|
||||
localtime_r(&t, &tm);
|
||||
#endif
|
||||
return fmt::format("{:%a %b %d %H:%M:%S %Y}", tm);
|
||||
}
|
||||
|
||||
size_t FileInfo::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
@@ -13,8 +28,8 @@ size_t FileInfo::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
gen.add_word(major_version);
|
||||
gen.add_word(minor_version);
|
||||
gen.add_ref_to_string_in_pool(maya_file_name);
|
||||
gen.add_ref_to_string_in_pool(tool_debug);
|
||||
gen.add_ref_to_string_in_pool(tool_debug);
|
||||
gen.add_ref_to_string_in_pool(tool_debug + " " + get_current_time_and_date() + "\n");
|
||||
gen.add_ref_to_string_in_pool(mdb_file_name);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "Entity.h"
|
||||
|
||||
#include "common/util/Assert.h"
|
||||
|
||||
namespace jak2 {
|
||||
size_t EntityActor::generate(DataObjectGenerator& gen) const {
|
||||
size_t result = res_lump.generate_header(gen, "entity-actor");
|
||||
@@ -39,8 +41,7 @@ size_t generate_drawable_actor(DataObjectGenerator& gen,
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen,
|
||||
const std::vector<EntityActor>& actors) {
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen, std::vector<EntityActor>& actors) {
|
||||
std::vector<size_t> actor_locs;
|
||||
for (auto& actor : actors) {
|
||||
actor_locs.push_back(actor.generate(gen));
|
||||
@@ -62,6 +63,7 @@ size_t generate_inline_array_actors(DataObjectGenerator& gen,
|
||||
ASSERT((gen.current_offset_bytes() % 16) == 0);
|
||||
|
||||
for (size_t i = 0; i < actors.size(); i++) {
|
||||
actors[i].slot = actor_locs[i];
|
||||
generate_drawable_actor(gen, actors[i], actor_locs[i]);
|
||||
}
|
||||
return result;
|
||||
@@ -94,10 +96,13 @@ void add_actors_from_json(const nlohmann::json& json,
|
||||
actor.bsphere = vectorm4_from_json(actor_json.at("bsphere"));
|
||||
|
||||
if (actor_json.find("lump") != actor_json.end()) {
|
||||
for (auto [key, value] : actor_json.at("lump").items()) {
|
||||
for (auto& [key, value] : actor_json.at("lump").items()) {
|
||||
if (value.is_string()) {
|
||||
std::string value_string = value.get<std::string>();
|
||||
if (value_string.size() > 0 && value_string[0] == '\'') {
|
||||
if (key == "name") {
|
||||
actor.name = value_string;
|
||||
}
|
||||
if (!value_string.empty() && value_string[0] == '\'') {
|
||||
actor.res_lump.add_res(
|
||||
std::make_unique<ResSymbol>(key, value_string.substr(1), -1000000000.0000));
|
||||
} else {
|
||||
@@ -115,4 +120,62 @@ void add_actors_from_json(const nlohmann::json& json,
|
||||
actor.res_lump.sort_res();
|
||||
}
|
||||
}
|
||||
|
||||
size_t generate_actor_group(DataObjectGenerator& gen, const ActorGroup& group) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("actor-group");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
auto length = group.actors.size();
|
||||
gen.add_word(length);
|
||||
gen.add_word(length);
|
||||
gen.add_type_tag("entity-actor");
|
||||
for (auto& actor : group.actors) {
|
||||
gen.link_word_to_byte(gen.add_word(0), actor->slot);
|
||||
gen.add_word(actor->aid);
|
||||
}
|
||||
gen.align(4);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_actor_group_array(DataObjectGenerator& gen, std::vector<ActorGroup>& actor_groups) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
auto length = actor_groups.size();
|
||||
gen.add_word(length);
|
||||
gen.add_word(length);
|
||||
gen.add_type_tag("actor-group");
|
||||
std::vector<size_t> group_slots;
|
||||
for (auto& group : actor_groups) {
|
||||
group_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.align(4);
|
||||
for (int i = 0; i < group_slots.size(); i++) {
|
||||
actor_groups.at(i).slot = generate_actor_group(gen, actor_groups.at(i));
|
||||
gen.link_word_to_byte(group_slots.at(i), actor_groups.at(i).slot);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_actor_groups_from_json(const nlohmann::json& json,
|
||||
const std::vector<EntityActor>& actors,
|
||||
std::vector<ActorGroup>& actor_groups,
|
||||
u32 base_id) {
|
||||
for (const auto& actor_group : json) {
|
||||
auto& group = actor_groups.emplace_back();
|
||||
group.id = actor_group.value("id", base_id + actor_groups.size());
|
||||
auto entity_list = actor_group.value<std::vector<std::string>>("entities", {});
|
||||
for (auto& ent_name : entity_list) {
|
||||
auto ent = std::ranges::find_if(
|
||||
actors, [ent_name](const EntityActor& actor) { return actor.name == ent_name; });
|
||||
if (ent != actors.end()) {
|
||||
group.actors.push_back(&*ent);
|
||||
} else {
|
||||
ASSERT_MSG(false, fmt::format("Failed to find actor \"{}\", declared in actor group id {}.",
|
||||
ent_name, group.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace jak2
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace jak2 {
|
||||
* (quat quaternion :inline :offset-assert 64)
|
||||
*/
|
||||
struct EntityActor {
|
||||
std::string name;
|
||||
ResLump res_lump;
|
||||
math::Vector4f trans; // w = 1 here
|
||||
u32 aid = 0;
|
||||
@@ -24,14 +25,26 @@ struct EntityActor {
|
||||
|
||||
math::Vector4f bsphere;
|
||||
|
||||
size_t slot;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen,
|
||||
const std::vector<EntityActor>& actors);
|
||||
struct ActorGroup {
|
||||
u64 id;
|
||||
std::vector<const EntityActor*> actors;
|
||||
size_t slot;
|
||||
};
|
||||
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen, std::vector<EntityActor>& actors);
|
||||
size_t generate_actor_group_array(DataObjectGenerator& gen, std::vector<ActorGroup>& actor_groups);
|
||||
|
||||
void add_actors_from_json(const nlohmann::json& json,
|
||||
std::vector<EntityActor>& actor_list,
|
||||
u32 base_aid,
|
||||
decompiler::DecompilerTypeSystem& dts);
|
||||
void add_actor_groups_from_json(const nlohmann::json& json,
|
||||
const std::vector<EntityActor>& actors,
|
||||
std::vector<ActorGroup>& actor_groups,
|
||||
u32 base_id);
|
||||
} // namespace jak2
|
||||
@@ -66,7 +66,7 @@ size_t generate_u32_array(const std::vector<u32>& array, DataObjectGenerator& ge
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> LevelFile::save_object_file() const {
|
||||
std::vector<u8> LevelFile::save_object_file() {
|
||||
DataObjectGenerator gen;
|
||||
gen.add_type_tag("bsp-header");
|
||||
|
||||
@@ -78,7 +78,6 @@ std::vector<u8> LevelFile::save_object_file() const {
|
||||
//(info file-info :offset 4)
|
||||
auto file_info_slot = info.add_to_object_file(gen);
|
||||
gen.link_word_to_byte(1, file_info_slot);
|
||||
|
||||
//(bsphere vector :inline :offset-assert 16)
|
||||
//(all-visible-list (pointer uint16) :offset-assert 32)
|
||||
//(visible-list-length int32 :offset-assert 36)
|
||||
@@ -114,8 +113,12 @@ std::vector<u8> LevelFile::save_object_file() const {
|
||||
//(light-hash light-hash :offset-assert 176)
|
||||
//(nav-meshes (array entity-nav-mesh) :offset-assert 180)
|
||||
//(actor-groups (array actor-group) :offset-assert 184)
|
||||
gen.link_word_to_byte(184 / 4, generate_actor_group_array(gen, actor_groups));
|
||||
//(region-trees (array drawable-tree-region-prim) :offset-assert 188)
|
||||
gen.link_word_to_byte(188 / 4,
|
||||
generate_drawable_tree_region_prim_array(gen, region_array, region_trees));
|
||||
//(region-array region-array :offset-assert 192)
|
||||
gen.link_word_to_byte(192 / 4, region_array.slot);
|
||||
//(collide-hash collide-hash :offset-assert 196)
|
||||
gen.link_word_to_byte(196 / 4, add_to_object_file(collide_hash, gen));
|
||||
//(wind-array uint32 :offset 200)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "goalc/build_level/common/Tie.h"
|
||||
#include "goalc/build_level/jak2/Entity.h"
|
||||
#include "goalc/build_level/jak2/FileInfo.h"
|
||||
#include "goalc/build_level/jak2/Region.h"
|
||||
|
||||
namespace jak2 {
|
||||
struct VisibilityString {
|
||||
@@ -22,13 +23,10 @@ struct DrawableTreeActor {};
|
||||
|
||||
struct DrawableTreeInstanceShrub {};
|
||||
|
||||
struct DrawableTreeRegionPrim {};
|
||||
|
||||
struct DrawableTreeArray {
|
||||
std::vector<DrawableTreeTfrag> tfrags;
|
||||
std::vector<DrawableTreeInstanceTie> ties;
|
||||
std::vector<DrawableTreeActor> actors; // unused?
|
||||
std::vector<DrawableTreeRegionPrim> regions;
|
||||
std::vector<DrawableTreeInstanceShrub> shrubs;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen) const;
|
||||
};
|
||||
@@ -49,12 +47,6 @@ struct LightHash {};
|
||||
|
||||
struct EntityNavMesh {};
|
||||
|
||||
struct ActorGroup {};
|
||||
|
||||
struct RegionTree {};
|
||||
|
||||
struct RegionArray {};
|
||||
|
||||
struct CityLevelInfo {};
|
||||
|
||||
struct TextureMasksArray {};
|
||||
@@ -141,7 +133,8 @@ struct LevelFile {
|
||||
// (actor-groups (array actor-group) :offset-assert 184)
|
||||
std::vector<ActorGroup> actor_groups;
|
||||
// (region-trees (array drawable-tree-region-prim) :offset-assert 188)
|
||||
std::vector<RegionTree> region_trees;
|
||||
std::map<int, Region> regions;
|
||||
std::vector<DrawableTreeRegionPrim> region_trees;
|
||||
// (region-array region-array :offset-assert 192)
|
||||
RegionArray region_array;
|
||||
// (collide-hash collide-hash :offset-assert 196)
|
||||
@@ -156,7 +149,7 @@ struct LevelFile {
|
||||
// (vis-spheres-length uint32 :offset 248)
|
||||
|
||||
// (region-tree drawable-tree-region-prim :offset 252)
|
||||
RegionTree region_tree;
|
||||
DrawableTreeRegionPrim region_tree;
|
||||
// (tfrag-masks texture-masks-array :offset-assert 256)
|
||||
// (tfrag-closest (pointer float) :offset-assert 260)
|
||||
// (tfrag-mask-count uint32 :offset 260)
|
||||
@@ -176,6 +169,6 @@ struct LevelFile {
|
||||
// (bsp-scale vector :inline :offset-assert 288)
|
||||
// (bsp-offset vector :inline :offset-assert 304)
|
||||
|
||||
std::vector<u8> save_object_file() const;
|
||||
std::vector<u8> save_object_file();
|
||||
};
|
||||
} // namespace jak2
|
||||
@@ -0,0 +1,423 @@
|
||||
#include "Region.h"
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
#include "common/log/log.h"
|
||||
|
||||
namespace jak2 {
|
||||
|
||||
std::optional<std::map<std::string, size_t>> g_entity_slots;
|
||||
std::optional<std::map<int, size_t>> g_actor_group_slots;
|
||||
|
||||
std::map<std::string, size_t> make_entity_slot_map(std::vector<EntityActor>* actors) {
|
||||
std::map<std::string, size_t> result{};
|
||||
if (actors) {
|
||||
for (const auto& e : *actors) {
|
||||
result.emplace(e.name, e.slot);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<int, size_t> make_actor_group_slot_list(std::vector<ActorGroup>* actor_groups) {
|
||||
std::map<int, size_t> result{};
|
||||
if (actor_groups) {
|
||||
for (const auto& e : *actor_groups) {
|
||||
result.emplace(e.id, e.slot);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t gen_pair(DataObjectGenerator& gen, const goos::Object& pair) {
|
||||
return gen.add_pair(pair, g_entity_slots, g_actor_group_slots);
|
||||
}
|
||||
|
||||
void Region::generate_pairs(DataObjectGenerator& gen, const std::vector<size_t>& pair_slots) {
|
||||
auto on_enter_slot = pair_slots[0];
|
||||
auto on_inside_slot = pair_slots[1];
|
||||
auto on_exit_slot = pair_slots[2];
|
||||
size_t on_enter_byte = 0;
|
||||
size_t on_inside_byte = 0;
|
||||
size_t on_exit_byte = 0;
|
||||
if (on_enter.has_value()) {
|
||||
on_enter_byte = gen_pair(gen, *on_enter.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_enter_slot);
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
on_inside_byte = gen_pair(gen, *on_inside.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_inside_slot);
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
on_exit_byte = gen_pair(gen, *on_exit.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_exit_slot);
|
||||
}
|
||||
if (on_enter.has_value()) {
|
||||
gen.link_word_to_byte(on_enter_slot, on_enter_byte);
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
gen.link_word_to_byte(on_inside_slot, on_inside_byte);
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
gen.link_word_to_byte(on_exit_slot, on_exit_byte);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Region::generate(DataObjectGenerator& gen) const {
|
||||
auto region_slot = gen.add_word(id);
|
||||
auto on_enter_slot = gen.add_word(0);
|
||||
auto on_inside_slot = gen.add_word(0);
|
||||
auto on_exit_slot = gen.add_word(0);
|
||||
size_t on_enter_byte = 0;
|
||||
size_t on_inside_byte = 0;
|
||||
size_t on_exit_byte = 0;
|
||||
if (on_enter.has_value()) {
|
||||
on_enter_byte = gen_pair(gen, *on_enter.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_enter_slot);
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
on_inside_byte = gen_pair(gen, *on_inside.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_inside_slot);
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
on_exit_byte = gen_pair(gen, *on_exit.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_exit_slot);
|
||||
}
|
||||
if (on_enter.has_value()) {
|
||||
gen.link_word_to_byte(on_enter_slot, on_enter_byte);
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
gen.link_word_to_byte(on_inside_slot, on_inside_byte);
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
gen.link_word_to_byte(on_exit_slot, on_exit_byte);
|
||||
}
|
||||
return region_slot;
|
||||
}
|
||||
|
||||
size_t RegionArray::generate(DataObjectGenerator& gen) {
|
||||
g_entity_slots = make_entity_slot_map(entities);
|
||||
g_actor_group_slots = make_actor_group_slot_list(actor_groups);
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("region-array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(data.size()); // 4 (length)
|
||||
gen.add_word(data.size()); // 8 (allocated-length)
|
||||
gen.add_word(0); // 12
|
||||
ASSERT((gen.current_offset_bytes() % 16) == 0);
|
||||
for (auto& region : data) {
|
||||
std::vector<size_t> pairs;
|
||||
region->slot = gen.add_word(region->id);
|
||||
pairs.push_back(gen.add_word(0));
|
||||
pairs.push_back(gen.add_word(0));
|
||||
pairs.push_back(gen.add_word(0));
|
||||
pair_slots.emplace(region->id, pairs);
|
||||
region_slots.emplace(region->id, region->slot);
|
||||
// region->slot = region->generate(gen);
|
||||
}
|
||||
for (auto& region : data) {
|
||||
auto pairs = pair_slots.find(region->id);
|
||||
if (pairs != pair_slots.end()) {
|
||||
region->generate_pairs(gen, pairs->second);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_drawable_tree_region_prim_array(DataObjectGenerator& gen,
|
||||
RegionArray& regions,
|
||||
const std::vector<DrawableTreeRegionPrim>& trees) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
auto length = trees.size();
|
||||
std::vector<size_t> tree_slots;
|
||||
gen.add_word(length);
|
||||
gen.add_word(length);
|
||||
gen.add_type_tag("drawable-tree-region-prim");
|
||||
tree_slots.reserve(trees.size());
|
||||
for (auto& tree : trees) {
|
||||
tree_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.align(4);
|
||||
regions.slot = regions.generate(gen);
|
||||
for (int i = 0; i < trees.size(); i++) {
|
||||
gen.link_word_to_byte(tree_slots[i], trees[i].generate(gen));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableTreeRegionPrim::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-tree-region-prim");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(1 << 16); // 4, 6 (length)
|
||||
gen.add_symbol_link(name); // 8
|
||||
gen.add_word(0);
|
||||
gen.add_word_float(bsphere.x());
|
||||
gen.add_word_float(bsphere.y());
|
||||
gen.add_word_float(bsphere.z());
|
||||
gen.add_word_float(bsphere.w());
|
||||
size_t arr_slot = gen.add_word(0);
|
||||
gen.align(4);
|
||||
gen.link_word_to_byte(arr_slot, data2.generate(gen));
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableInlineArrayRegionPrim::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-inline-array-region-prim");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(data.size() << 16); // 4, 6 (length)
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
for (auto& prim : data) {
|
||||
prim->generate(gen);
|
||||
}
|
||||
// second pass to fill in face and volume references
|
||||
for (auto& prim : data) {
|
||||
if (dynamic_cast<DrawableRegionFace*>(prim)) {
|
||||
auto face = dynamic_cast<DrawableRegionFace*>(prim);
|
||||
gen.link_word_to_byte(face->face_data_slot, face->data.generate(gen));
|
||||
}
|
||||
if (dynamic_cast<DrawableRegionVolume*>(prim)) {
|
||||
auto vol = dynamic_cast<DrawableRegionVolume*>(prim);
|
||||
gen.link_word_to_byte(vol->face_array_slot, vol->faces.generate(gen));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void fill_region_trees(std::vector<DrawableTreeRegionPrim>& trees,
|
||||
std::map<int, Region>& regions,
|
||||
RegionArray& region_arr,
|
||||
const nlohmann::json& json,
|
||||
u32 base_id) {
|
||||
for (const auto& [k, tree_json] : json.items()) {
|
||||
ASSERT_MSG(tree_json.is_object(), fmt::format("key \"{}\" is not an object.", k));
|
||||
if (tree_json.is_object() && !tree_json.empty()) {
|
||||
auto& tree = trees.emplace_back(k);
|
||||
tree.bsphere = vectorm4_from_json(tree_json.at("bsphere"));
|
||||
add_regions_from_json(tree_json.at("regions"), tree, regions, base_id);
|
||||
for (auto& [id, region] : regions) {
|
||||
if (region.tree == tree.name) {
|
||||
DrawableRegionPrim* prim = nullptr;
|
||||
if (region.shape == "sphere") {
|
||||
prim = new DrawableRegionSphere(®ion);
|
||||
tree.data2.data.push_back(prim);
|
||||
} else if (region.shape == "face") {
|
||||
prim = new DrawableRegionFace(®ion);
|
||||
tree.data2.data.push_back(prim);
|
||||
} else if (region.shape == "volume") {
|
||||
prim = new DrawableRegionVolume(®ion);
|
||||
tree.data2.data.push_back(prim);
|
||||
} else {
|
||||
ASSERT_MSG(false, fmt::format("Invalid region shape \"{}\"", region.shape));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& [id, region] : regions) {
|
||||
region_arr.data.push_back(®ion);
|
||||
}
|
||||
}
|
||||
|
||||
void add_regions_from_json(const nlohmann::json& json,
|
||||
DrawableTreeRegionPrim& tree,
|
||||
std::map<int, Region>& regions,
|
||||
u32 base_id) {
|
||||
std::vector<Region> result;
|
||||
auto& reader = pretty_print::get_pretty_printer_reader();
|
||||
for (const auto& region_json : json) {
|
||||
auto id = region_json.value("id", base_id + regions.size());
|
||||
auto p = regions.emplace(id, Region());
|
||||
if (p.second) {
|
||||
auto& region = p.first->second;
|
||||
result.push_back(region);
|
||||
region.id = region_json.value("id", base_id + regions.size());
|
||||
region.tree = tree.name;
|
||||
region.shape = region_json.at("shape").get<std::string>();
|
||||
region.bsphere = vectorm4_from_json(region_json.at("bsphere"));
|
||||
ASSERT_MSG(region.shape == "sphere" || region.shape == "face" || region.shape == "volume",
|
||||
"Region must have shape 'sphere', 'face' or 'volume'.");
|
||||
if (region.shape == "sphere") {
|
||||
region.faces = std::nullopt;
|
||||
}
|
||||
if (region.shape == "face") {
|
||||
ASSERT_MSG(region_json.find("face") != region_json.end(),
|
||||
"Face region must have 'face' key.");
|
||||
auto face = region_json.at("face").get<RegionFaceData>();
|
||||
std::vector<RegionFaceData> faces;
|
||||
faces.push_back(face);
|
||||
region.faces = std::make_optional<std::vector<RegionFaceData>>(faces);
|
||||
}
|
||||
if (region.shape == "volume") {
|
||||
ASSERT_MSG(region_json.find("volume") != region_json.end(),
|
||||
"Volume region must have 'volume' key.");
|
||||
auto arr = region_json.at("volume").get<RegionFaceArray>();
|
||||
region.faces = std::make_optional<std::vector<RegionFaceData>>(arr.faces);
|
||||
}
|
||||
if (region_json.find("on-enter") != region_json.end()) {
|
||||
region.on_enter = std::make_optional(
|
||||
&reader.read_from_string(region_json.at("on-enter").get<std::string>(), false)
|
||||
.as_pair()
|
||||
->car);
|
||||
}
|
||||
if (region_json.find("on-inside") != region_json.end()) {
|
||||
region.on_inside = std::make_optional(
|
||||
&reader.read_from_string(region_json.at("on-inside").get<std::string>(), false)
|
||||
.as_pair()
|
||||
->car);
|
||||
}
|
||||
if (region_json.find("on-exit") != region_json.end()) {
|
||||
region.on_exit = std::make_optional(
|
||||
&reader.read_from_string(region_json.at("on-exit").get<std::string>(), false)
|
||||
.as_pair()
|
||||
->car);
|
||||
}
|
||||
lg::print(region.print());
|
||||
} else {
|
||||
lg::warn("Duplicate region with ID {} in tree {}, skipped", p.first->first, tree.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Region::print() {
|
||||
std::string result;
|
||||
result += fmt::format("Region {} ({}):\n", id, tree);
|
||||
result += fmt::format(" shape: {}\n", shape);
|
||||
if (on_enter.has_value()) {
|
||||
result += fmt::format(" on-enter: {}\n", on_enter.value()->print());
|
||||
} else {
|
||||
result += fmt::format(" on-enter: #f\n");
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
result += fmt::format(" on-inside: {}\n", on_inside.value()->print());
|
||||
} else {
|
||||
result += fmt::format(" on-inside: #f\n");
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
result += fmt::format(" on-exit: {}\n", on_exit.value()->print());
|
||||
} else {
|
||||
result += fmt::format(" on-exit: #f\n");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& json, RegionFaceData& obj) {
|
||||
obj.normal = vector_from_json(json.at("normal"));
|
||||
obj.normal.w() *= METER_LENGTH;
|
||||
std::vector<math::Vector4f> points;
|
||||
for (auto& p : json.at("points")) {
|
||||
points.push_back(vectorm3_from_json(p));
|
||||
}
|
||||
obj.num_points = points.size();
|
||||
obj.points = points;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& json, RegionFaceArray& obj) {
|
||||
(void)obj;
|
||||
for (const auto& face_json : json.at("faces")) {
|
||||
RegionFaceData face;
|
||||
from_json(face_json, face);
|
||||
obj.faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
size_t RegionFaceData::generate(DataObjectGenerator& gen) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word_float(normal.x());
|
||||
gen.add_word_float(normal.y());
|
||||
gen.add_word_float(normal.z());
|
||||
gen.add_word_float(normal.w());
|
||||
gen.add_word(num_points);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
for (auto& p : points) {
|
||||
gen.add_word_float(p.x());
|
||||
gen.add_word_float(p.y());
|
||||
gen.add_word_float(p.z());
|
||||
gen.add_word_float(p.w());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t RegionFaceArray::generate(DataObjectGenerator& gen) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("region-face-array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
auto length = data.size();
|
||||
gen.add_word(length);
|
||||
gen.add_word(length);
|
||||
gen.add_word(0);
|
||||
for (auto& face : data) {
|
||||
face.generate(gen);
|
||||
}
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
auto face = data[i];
|
||||
auto face0 = face.data;
|
||||
gen.link_word_to_byte(face.face_data_slot, face0.generate(gen));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableRegionPrim::generate(DataObjectGenerator& gen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t DrawableRegionSphere::generate(DataObjectGenerator& gen) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-region-sphere");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(region->id);
|
||||
gen.link_word_to_word(gen.add_word(0), region->slot);
|
||||
gen.add_word(0);
|
||||
gen.add_word_float(region->bsphere.x());
|
||||
gen.add_word_float(region->bsphere.y());
|
||||
gen.add_word_float(region->bsphere.z());
|
||||
gen.add_word_float(region->bsphere.w());
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableRegionVolume::generate(DataObjectGenerator& gen
|
||||
/*size_t region_face_array_slot*/) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-region-volume");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(region->id);
|
||||
gen.link_word_to_word(gen.add_word(0), region->slot);
|
||||
face_array_slot = gen.add_word(0);
|
||||
gen.add_word_float(region->bsphere.x());
|
||||
gen.add_word_float(region->bsphere.y());
|
||||
gen.add_word_float(region->bsphere.z());
|
||||
gen.add_word_float(region->bsphere.w());
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableRegionFace::generate(DataObjectGenerator& gen) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-region-face");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(region->id);
|
||||
gen.link_word_to_word(gen.add_word(0), region->slot);
|
||||
face_data_slot = gen.add_word(0);
|
||||
const auto& bsphere = bsphere_override.has_value() ? bsphere_override.value() : region->bsphere;
|
||||
gen.add_word_float(bsphere.x());
|
||||
gen.add_word_float(bsphere.y());
|
||||
gen.add_word_float(bsphere.z());
|
||||
gen.add_word_float(bsphere.w());
|
||||
return result;
|
||||
}
|
||||
} // namespace jak2
|
||||
@@ -0,0 +1,207 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/goos/ParseHelpers.h"
|
||||
#include "common/goos/Printer.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
|
||||
#include "third-party/json.hpp"
|
||||
|
||||
namespace jak2 {
|
||||
|
||||
struct RegionFaceData {
|
||||
// (normal vector :inline :offset-assert 0)
|
||||
// (normal-offset float :offset 12)
|
||||
// (num-points uint32 :offset-assert 16)
|
||||
// (points vector :inline :dynamic :offset-assert 32) ;; guess
|
||||
math::Vector4f normal; // w component is normal offset
|
||||
u32 num_points;
|
||||
std::vector<math::Vector4f> points;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
void from_json(const json& j, RegionFaceData& obj);
|
||||
|
||||
struct RegionFaceArray;
|
||||
|
||||
struct Region {
|
||||
// (id uint32 :offset-assert 0)
|
||||
// (on-enter pair :offset-assert 4)
|
||||
// (on-inside pair :offset-assert 8)
|
||||
// (on-exit pair :offset-assert 12)
|
||||
u32 id;
|
||||
std::optional<goos::Object*> on_enter;
|
||||
std::optional<goos::Object*> on_inside;
|
||||
std::optional<goos::Object*> on_exit;
|
||||
math::Vector4f trans;
|
||||
math::Vector4f bsphere;
|
||||
std::string tree; // target, camera, data, water, city_vis, sample, light, entity
|
||||
std::string shape; // sphere, face, volume
|
||||
std::optional<std::vector<RegionFaceData>> faces;
|
||||
|
||||
size_t slot;
|
||||
std::optional<std::map<std::string, size_t>> actor_slots;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
void generate_pairs(DataObjectGenerator& gen, const std::vector<size_t>& pair_slots);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
std::string print();
|
||||
};
|
||||
|
||||
struct RegionArray {
|
||||
// (data region :inline :dynamic :offset-assert 16)
|
||||
std::vector<Region*> data;
|
||||
std::map<int, std::vector<size_t>> pair_slots;
|
||||
std::map<int, size_t> region_slots;
|
||||
std::map<std::string, size_t> entity_actor_slots;
|
||||
std::vector<EntityActor>* entities;
|
||||
std::vector<ActorGroup>* actor_groups;
|
||||
|
||||
size_t slot;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t region_array) const;
|
||||
};
|
||||
|
||||
struct DrawableRegionPrim {
|
||||
// (deftype drawable-region-prim (drawable)
|
||||
// ((region region :offset 8)
|
||||
// )
|
||||
Region* region;
|
||||
|
||||
explicit DrawableRegionPrim(Region* region) { this->region = region; }
|
||||
|
||||
virtual size_t generate(DataObjectGenerator& gen);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
struct DrawableInlineArrayRegionPrim {
|
||||
// (deftype drawable-inline-array-region-prim (drawable-inline-array)
|
||||
// ((data drawable-region-prim 1 :inline :offset-assert 32)
|
||||
// )
|
||||
std::vector<DrawableRegionPrim*> data;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
struct DrawableTreeRegionPrim {
|
||||
// (deftype drawable-tree-region-prim (drawable-tree)
|
||||
// ((id int16 :offset-assert 4)
|
||||
// (length int16 :offset 6)
|
||||
// (name symbol :offset 8)
|
||||
// (bsphere vector :inline :offset-assert 16)
|
||||
// (data2 drawable-inline-array :dynamic :offset 32 :score 1))
|
||||
// )
|
||||
std::string name;
|
||||
math::Vector4f bsphere;
|
||||
DrawableInlineArrayRegionPrim data2;
|
||||
|
||||
DrawableTreeRegionPrim() = default;
|
||||
explicit DrawableTreeRegionPrim(std::string name_) : name(std::move(name_)) {}
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
size_t generate_drawable_tree_region_prim_array(DataObjectGenerator& gen,
|
||||
RegionArray& regions,
|
||||
const std::vector<DrawableTreeRegionPrim>& trees);
|
||||
|
||||
void fill_region_trees(std::vector<DrawableTreeRegionPrim>& trees,
|
||||
std::map<int, Region>& regions,
|
||||
RegionArray& region_arr,
|
||||
const nlohmann::json& json,
|
||||
u32 base_id);
|
||||
void add_regions_from_json(const nlohmann::json& json,
|
||||
DrawableTreeRegionPrim& tree,
|
||||
std::map<int, Region>& regions,
|
||||
u32 base_id);
|
||||
|
||||
struct DrawableRegionSphere : DrawableRegionPrim {
|
||||
using DrawableRegionPrim::DrawableRegionPrim;
|
||||
size_t generate(DataObjectGenerator& gen) override;
|
||||
};
|
||||
|
||||
struct DrawableRegionFace : DrawableRegionPrim {
|
||||
// (deftype drawable-region-face (drawable-region-prim)
|
||||
// ((data region-face-data :offset 12)
|
||||
// )
|
||||
RegionFaceData data;
|
||||
size_t face_data_slot;
|
||||
std::optional<math::Vector4f> bsphere_override;
|
||||
|
||||
explicit DrawableRegionFace(Region* region) : DrawableRegionPrim(region) {
|
||||
if (region->faces.has_value()) {
|
||||
data = region->faces.value().at(0);
|
||||
}
|
||||
}
|
||||
size_t generate(DataObjectGenerator& gen) override;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
struct RegionFaceArray {
|
||||
// (deftype region-face-array (inline-array-class)
|
||||
// ((data drawable-region-face :inline :dynamic :offset 16)
|
||||
// )
|
||||
std::vector<DrawableRegionFace> data;
|
||||
std::vector<RegionFaceData> faces;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
void from_json(const json& j, RegionFaceArray& obj);
|
||||
|
||||
struct DrawableRegionVolume : DrawableRegionPrim {
|
||||
// (deftype drawable-region-volume (drawable-region-prim)
|
||||
// ((faces region-face-array :offset 12)
|
||||
// )
|
||||
RegionFaceArray faces;
|
||||
size_t face_array_slot;
|
||||
|
||||
explicit DrawableRegionVolume(Region* region) : DrawableRegionPrim(region) {
|
||||
if (this->region->faces.has_value()) {
|
||||
auto face_arr = this->region->faces.value();
|
||||
for (const auto& f : face_arr) {
|
||||
auto& face = faces.data.emplace_back(region);
|
||||
face.data = f;
|
||||
// compute per-face bsphere from the face's points for volumes
|
||||
if (!f.points.empty()) {
|
||||
math::Vector4f center = {0, 0, 0, 0};
|
||||
for (auto& pt : f.points) {
|
||||
center.x() += pt.x();
|
||||
center.y() += pt.y();
|
||||
center.z() += pt.z();
|
||||
}
|
||||
auto n = static_cast<float>(f.points.size());
|
||||
center.x() /= n;
|
||||
center.y() /= n;
|
||||
center.z() /= n;
|
||||
float max_dist_sq = 0;
|
||||
for (auto& pt : f.points) {
|
||||
float dx = pt.x() - center.x();
|
||||
float dy = pt.y() - center.y();
|
||||
float dz = pt.z() - center.z();
|
||||
max_dist_sq = std::max(max_dist_sq, dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
center.w() = std::sqrt(max_dist_sq);
|
||||
face.bsphere_override = center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) override;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
} // namespace jak2
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "goalc/build_level/jak2/Entity.h"
|
||||
#include "goalc/build_level/jak2/FileInfo.h"
|
||||
#include "goalc/build_level/jak2/LevelFile.h"
|
||||
#include <decompiler/level_extractor/extract_collide_frags.h>
|
||||
|
||||
namespace jak2 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
@@ -60,9 +61,19 @@ bool run_build_level(const std::string& input_file,
|
||||
fmt::format("Actor IDs must be unique. Found at least two actors with ID {}",
|
||||
duplicates->aid));
|
||||
file.actors = std::move(actors);
|
||||
// actor groups
|
||||
if (level_json.contains("actor_groups") && !level_json.at("actor_groups").empty()) {
|
||||
add_actor_groups_from_json(level_json.at("actor_groups"), file.actors, file.actor_groups, 0);
|
||||
}
|
||||
// cameras
|
||||
// nodes
|
||||
// regions
|
||||
if (level_json.contains("region_trees") && !level_json.at("region_trees").empty()) {
|
||||
file.region_array.entities = &file.actors;
|
||||
file.region_array.actor_groups = &file.actor_groups;
|
||||
fill_region_trees(file.region_trees, file.regions, file.region_array,
|
||||
level_json.at("region_trees"), level_json.value("base_region_id", 0));
|
||||
}
|
||||
// subdivs
|
||||
// actor birth
|
||||
for (size_t i = 0; i < file.actors.size(); i++) {
|
||||
@@ -89,6 +100,22 @@ bool run_build_level(const std::string& input_file,
|
||||
lg::error("No collision geometry was found");
|
||||
} else {
|
||||
file.collide_hash = construct_collide_hash(mesh_extract_out.collide.faces);
|
||||
// for collision renderer
|
||||
for (auto& face : mesh_extract_out.collide.faces) {
|
||||
math::Vector4f verts[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
verts[i].x() = face.v[i].x();
|
||||
verts[i].y() = face.v[i].y();
|
||||
verts[i].z() = face.v[i].z();
|
||||
verts[i].w() = 1.f;
|
||||
}
|
||||
tfrag3::CollisionMesh::Vertex out_verts[3];
|
||||
decompiler::set_vertices_for_tri(out_verts, verts);
|
||||
for (auto& out : out_verts) {
|
||||
out.pat = face.pat.val;
|
||||
pc_level.collision.vertices.push_back(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the GOAL level
|
||||
|
||||
@@ -39,8 +39,7 @@ size_t generate_drawable_actor(DataObjectGenerator& gen,
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen,
|
||||
const std::vector<EntityActor>& actors) {
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen, std::vector<EntityActor>& actors) {
|
||||
std::vector<size_t> actor_locs;
|
||||
for (auto& actor : actors) {
|
||||
actor_locs.push_back(actor.generate(gen));
|
||||
@@ -62,6 +61,7 @@ size_t generate_inline_array_actors(DataObjectGenerator& gen,
|
||||
ASSERT((gen.current_offset_bytes() % 16) == 0);
|
||||
|
||||
for (size_t i = 0; i < actors.size(); i++) {
|
||||
actors[i].slot = actor_locs[i];
|
||||
generate_drawable_actor(gen, actors[i], actor_locs[i]);
|
||||
}
|
||||
return result;
|
||||
@@ -94,10 +94,13 @@ void add_actors_from_json(const nlohmann::json& json,
|
||||
actor.bsphere = vectorm4_from_json(actor_json.at("bsphere"));
|
||||
|
||||
if (actor_json.find("lump") != actor_json.end()) {
|
||||
for (auto [key, value] : actor_json.at("lump").items()) {
|
||||
for (auto& [key, value] : actor_json.at("lump").items()) {
|
||||
if (value.is_string()) {
|
||||
std::string value_string = value.get<std::string>();
|
||||
if (value_string.size() > 0 && value_string[0] == '\'') {
|
||||
if (key == "name") {
|
||||
actor.name = value_string;
|
||||
}
|
||||
if (!value_string.empty() && value_string[0] == '\'') {
|
||||
actor.res_lump.add_res(
|
||||
std::make_unique<ResSymbol>(key, value_string.substr(1), -1000000000.0000));
|
||||
} else {
|
||||
@@ -115,4 +118,62 @@ void add_actors_from_json(const nlohmann::json& json,
|
||||
actor.res_lump.sort_res();
|
||||
}
|
||||
}
|
||||
|
||||
size_t generate_actor_group(DataObjectGenerator& gen, const ActorGroup& group) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("actor-group");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
auto length = group.actors.size();
|
||||
gen.add_word(length);
|
||||
gen.add_word(length);
|
||||
gen.add_type_tag("entity-actor");
|
||||
for (auto& actor : group.actors) {
|
||||
gen.link_word_to_byte(gen.add_word(0), actor->slot);
|
||||
gen.add_word(actor->aid);
|
||||
}
|
||||
gen.align(4);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_actor_group_array(DataObjectGenerator& gen, std::vector<ActorGroup>& actor_groups) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
auto length = actor_groups.size();
|
||||
gen.add_word(length);
|
||||
gen.add_word(length);
|
||||
gen.add_type_tag("actor-group");
|
||||
std::vector<size_t> group_slots;
|
||||
for (auto& group : actor_groups) {
|
||||
group_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.align(4);
|
||||
for (int i = 0; i < group_slots.size(); i++) {
|
||||
actor_groups.at(i).slot = generate_actor_group(gen, actor_groups.at(i));
|
||||
gen.link_word_to_byte(group_slots.at(i), actor_groups.at(i).slot);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_actor_groups_from_json(const nlohmann::json& json,
|
||||
const std::vector<EntityActor>& actors,
|
||||
std::vector<ActorGroup>& actor_groups,
|
||||
u32 base_id) {
|
||||
for (const auto& actor_group : json) {
|
||||
auto& group = actor_groups.emplace_back();
|
||||
group.id = actor_group.value("id", base_id + actor_groups.size());
|
||||
auto entity_list = actor_group.value<std::vector<std::string>>("entities", {});
|
||||
for (auto& ent_name : entity_list) {
|
||||
auto ent = std::ranges::find_if(
|
||||
actors, [ent_name](const EntityActor& actor) { return actor.name == ent_name; });
|
||||
if (ent != actors.end()) {
|
||||
group.actors.push_back(&*ent);
|
||||
} else {
|
||||
ASSERT_MSG(false, fmt::format("Failed to find actor \"{}\", declared in actor group id {}.",
|
||||
ent_name, group.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace jak3
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace jak3 {
|
||||
* (quat quaternion :inline :offset-assert 64)
|
||||
*/
|
||||
struct EntityActor {
|
||||
std::string name;
|
||||
ResLump res_lump;
|
||||
math::Vector4f trans; // w = 1 here
|
||||
u32 aid = 0;
|
||||
@@ -24,14 +25,26 @@ struct EntityActor {
|
||||
|
||||
math::Vector4f bsphere;
|
||||
|
||||
size_t slot;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen,
|
||||
const std::vector<EntityActor>& actors);
|
||||
struct ActorGroup {
|
||||
u64 id;
|
||||
std::vector<const EntityActor*> actors;
|
||||
size_t slot;
|
||||
};
|
||||
|
||||
size_t generate_inline_array_actors(DataObjectGenerator& gen, std::vector<EntityActor>& actors);
|
||||
size_t generate_actor_group_array(DataObjectGenerator& gen, std::vector<ActorGroup>& actor_groups);
|
||||
|
||||
void add_actors_from_json(const nlohmann::json& json,
|
||||
std::vector<EntityActor>& actor_list,
|
||||
u32 base_aid,
|
||||
decompiler::DecompilerTypeSystem& dts);
|
||||
void add_actor_groups_from_json(const nlohmann::json& json,
|
||||
const std::vector<EntityActor>& actors,
|
||||
std::vector<ActorGroup>& actor_groups,
|
||||
u32 base_id);
|
||||
} // namespace jak3
|
||||
@@ -66,7 +66,7 @@ size_t generate_u32_array(const std::vector<u32>& array, DataObjectGenerator& ge
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> LevelFile::save_object_file() const {
|
||||
std::vector<u8> LevelFile::save_object_file() {
|
||||
DataObjectGenerator gen;
|
||||
gen.add_type_tag("bsp-header");
|
||||
|
||||
@@ -114,8 +114,12 @@ std::vector<u8> LevelFile::save_object_file() const {
|
||||
//(light-hash light-hash :offset-assert 176)
|
||||
//(nav-meshes (array entity-nav-mesh) :offset-assert 180)
|
||||
//(actor-groups (array actor-group) :offset-assert 184)
|
||||
gen.link_word_to_byte(184 / 4, generate_actor_group_array(gen, actor_groups));
|
||||
//(region-trees (array drawable-tree-region-prim) :offset-assert 188)
|
||||
gen.link_word_to_byte(188 / 4,
|
||||
generate_drawable_tree_region_prim_array(gen, region_array, region_trees));
|
||||
//(region-array region-array :offset-assert 192)
|
||||
gen.link_word_to_byte(192 / 4, region_array.slot);
|
||||
//(collide-hash collide-hash :offset-assert 196)
|
||||
gen.link_word_to_byte(196 / 4, add_to_object_file(collide_hash, gen));
|
||||
//(wind-array uint32 :offset 200)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "goalc/build_level/common/Tie.h"
|
||||
#include "goalc/build_level/jak3/Entity.h"
|
||||
#include "goalc/build_level/jak3/FileInfo.h"
|
||||
#include "goalc/build_level/jak3/Region.h"
|
||||
|
||||
namespace jak3 {
|
||||
struct VisibilityString {
|
||||
@@ -22,8 +23,6 @@ struct DrawableTreeActor {};
|
||||
|
||||
struct DrawableTreeInstanceShrub {};
|
||||
|
||||
struct DrawableTreeRegionPrim {};
|
||||
|
||||
struct DrawableTreeArray {
|
||||
std::vector<DrawableTreeTfrag> tfrags;
|
||||
std::vector<DrawableTreeInstanceTie> ties;
|
||||
@@ -49,12 +48,6 @@ struct LightHash {};
|
||||
|
||||
struct EntityNavMesh {};
|
||||
|
||||
struct ActorGroup {};
|
||||
|
||||
struct RegionTree {};
|
||||
|
||||
struct RegionArray {};
|
||||
|
||||
struct CityLevelInfo {};
|
||||
|
||||
struct TextureMasksArray {};
|
||||
@@ -141,7 +134,8 @@ struct LevelFile {
|
||||
// (actor-groups (array actor-group) :offset-assert 184)
|
||||
std::vector<ActorGroup> actor_groups;
|
||||
// (region-trees (array drawable-tree-region-prim) :offset-assert 188)
|
||||
std::vector<RegionTree> region_trees;
|
||||
std::map<int, Region> regions;
|
||||
std::vector<DrawableTreeRegionPrim> region_trees;
|
||||
// (region-array region-array :offset-assert 192)
|
||||
RegionArray region_array;
|
||||
// (collide-hash collide-hash :offset-assert 196)
|
||||
@@ -156,7 +150,7 @@ struct LevelFile {
|
||||
// (vis-spheres-length uint32 :offset 248)
|
||||
|
||||
// (region-tree drawable-tree-region-prim :offset 252)
|
||||
RegionTree region_tree;
|
||||
DrawableTreeRegionPrim region_tree;
|
||||
// (tfrag-masks texture-masks-array :offset-assert 256)
|
||||
// (tfrag-closest (pointer float) :offset-assert 260)
|
||||
// (tfrag-mask-count uint32 :offset 260)
|
||||
@@ -176,6 +170,6 @@ struct LevelFile {
|
||||
// (bsp-scale vector :inline :offset-assert 288)
|
||||
// (bsp-offset vector :inline :offset-assert 304)
|
||||
|
||||
std::vector<u8> save_object_file() const;
|
||||
std::vector<u8> save_object_file();
|
||||
};
|
||||
} // namespace jak3
|
||||
@@ -0,0 +1,423 @@
|
||||
#include "Region.h"
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
#include "common/log/log.h"
|
||||
|
||||
namespace jak3 {
|
||||
|
||||
std::optional<std::map<std::string, size_t>> g_entity_slots;
|
||||
std::optional<std::map<int, size_t>> g_actor_group_slots;
|
||||
|
||||
std::map<std::string, size_t> make_entity_slot_map(std::vector<EntityActor>* actors) {
|
||||
std::map<std::string, size_t> result{};
|
||||
if (actors) {
|
||||
for (const auto& e : *actors) {
|
||||
result.emplace(e.name, e.slot);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<int, size_t> make_actor_group_slot_list(std::vector<ActorGroup>* actor_groups) {
|
||||
std::map<int, size_t> result{};
|
||||
if (actor_groups) {
|
||||
for (const auto& e : *actor_groups) {
|
||||
result.emplace(e.id, e.slot);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t gen_pair(DataObjectGenerator& gen, const goos::Object& pair) {
|
||||
return gen.add_pair(pair, g_entity_slots, g_actor_group_slots);
|
||||
}
|
||||
|
||||
void Region::generate_pairs(DataObjectGenerator& gen, const std::vector<size_t>& pair_slots) {
|
||||
auto on_enter_slot = pair_slots[0];
|
||||
auto on_inside_slot = pair_slots[1];
|
||||
auto on_exit_slot = pair_slots[2];
|
||||
size_t on_enter_byte = 0;
|
||||
size_t on_inside_byte = 0;
|
||||
size_t on_exit_byte = 0;
|
||||
if (on_enter.has_value()) {
|
||||
on_enter_byte = gen_pair(gen, *on_enter.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_enter_slot);
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
on_inside_byte = gen_pair(gen, *on_inside.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_inside_slot);
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
on_exit_byte = gen_pair(gen, *on_exit.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_exit_slot);
|
||||
}
|
||||
if (on_enter.has_value()) {
|
||||
gen.link_word_to_byte(on_enter_slot, on_enter_byte);
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
gen.link_word_to_byte(on_inside_slot, on_inside_byte);
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
gen.link_word_to_byte(on_exit_slot, on_exit_byte);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Region::generate(DataObjectGenerator& gen) const {
|
||||
auto region_slot = gen.add_word(id);
|
||||
auto on_enter_slot = gen.add_word(0);
|
||||
auto on_inside_slot = gen.add_word(0);
|
||||
auto on_exit_slot = gen.add_word(0);
|
||||
size_t on_enter_byte = 0;
|
||||
size_t on_inside_byte = 0;
|
||||
size_t on_exit_byte = 0;
|
||||
if (on_enter.has_value()) {
|
||||
on_enter_byte = gen_pair(gen, *on_enter.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_enter_slot);
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
on_inside_byte = gen_pair(gen, *on_inside.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_inside_slot);
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
on_exit_byte = gen_pair(gen, *on_exit.value());
|
||||
} else {
|
||||
gen.link_word_to_symbol("#f", on_exit_slot);
|
||||
}
|
||||
if (on_enter.has_value()) {
|
||||
gen.link_word_to_byte(on_enter_slot, on_enter_byte);
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
gen.link_word_to_byte(on_inside_slot, on_inside_byte);
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
gen.link_word_to_byte(on_exit_slot, on_exit_byte);
|
||||
}
|
||||
return region_slot;
|
||||
}
|
||||
|
||||
size_t RegionArray::generate(DataObjectGenerator& gen) {
|
||||
g_entity_slots = make_entity_slot_map(entities);
|
||||
g_actor_group_slots = make_actor_group_slot_list(actor_groups);
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("region-array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(data.size()); // 4 (length)
|
||||
gen.add_word(data.size()); // 8 (allocated-length)
|
||||
gen.add_word(0); // 12
|
||||
ASSERT((gen.current_offset_bytes() % 16) == 0);
|
||||
for (auto& region : data) {
|
||||
std::vector<size_t> pairs;
|
||||
region->slot = gen.add_word(region->id);
|
||||
pairs.push_back(gen.add_word(0));
|
||||
pairs.push_back(gen.add_word(0));
|
||||
pairs.push_back(gen.add_word(0));
|
||||
pair_slots.emplace(region->id, pairs);
|
||||
region_slots.emplace(region->id, region->slot);
|
||||
// region->slot = region->generate(gen);
|
||||
}
|
||||
for (auto& region : data) {
|
||||
auto pairs = pair_slots.find(region->id);
|
||||
if (pairs != pair_slots.end()) {
|
||||
region->generate_pairs(gen, pairs->second);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_drawable_tree_region_prim_array(DataObjectGenerator& gen,
|
||||
RegionArray& regions,
|
||||
const std::vector<DrawableTreeRegionPrim>& trees) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
auto length = trees.size();
|
||||
std::vector<size_t> tree_slots;
|
||||
gen.add_word(length);
|
||||
gen.add_word(length);
|
||||
gen.add_type_tag("drawable-tree-region-prim");
|
||||
tree_slots.reserve(trees.size());
|
||||
for (auto& tree : trees) {
|
||||
tree_slots.push_back(gen.add_word(0));
|
||||
}
|
||||
gen.align(4);
|
||||
regions.slot = regions.generate(gen);
|
||||
for (int i = 0; i < trees.size(); i++) {
|
||||
gen.link_word_to_byte(tree_slots[i], trees[i].generate(gen));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableTreeRegionPrim::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-tree-region-prim");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(1 << 16); // 4, 6 (length)
|
||||
gen.add_symbol_link(name); // 8
|
||||
gen.add_word(0);
|
||||
gen.add_word_float(bsphere.x());
|
||||
gen.add_word_float(bsphere.y());
|
||||
gen.add_word_float(bsphere.z());
|
||||
gen.add_word_float(bsphere.w());
|
||||
size_t arr_slot = gen.add_word(0);
|
||||
gen.align(4);
|
||||
gen.link_word_to_byte(arr_slot, data2.generate(gen));
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableInlineArrayRegionPrim::generate(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-inline-array-region-prim");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(data.size() << 16); // 4, 6 (length)
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
for (auto& prim : data) {
|
||||
prim->generate(gen);
|
||||
}
|
||||
// second pass to fill in face and volume references
|
||||
for (auto& prim : data) {
|
||||
if (dynamic_cast<DrawableRegionFace*>(prim)) {
|
||||
auto face = dynamic_cast<DrawableRegionFace*>(prim);
|
||||
gen.link_word_to_byte(face->face_data_slot, face->data.generate(gen));
|
||||
}
|
||||
if (dynamic_cast<DrawableRegionVolume*>(prim)) {
|
||||
auto vol = dynamic_cast<DrawableRegionVolume*>(prim);
|
||||
gen.link_word_to_byte(vol->face_array_slot, vol->faces.generate(gen));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void fill_region_trees(std::vector<DrawableTreeRegionPrim>& trees,
|
||||
std::map<int, Region>& regions,
|
||||
RegionArray& region_arr,
|
||||
const nlohmann::json& json,
|
||||
u32 base_id) {
|
||||
for (const auto& [k, tree_json] : json.items()) {
|
||||
ASSERT_MSG(tree_json.is_object(), fmt::format("key \"{}\" is not an object.", k));
|
||||
if (tree_json.is_object() && !tree_json.empty()) {
|
||||
auto& tree = trees.emplace_back(k);
|
||||
tree.bsphere = vectorm4_from_json(tree_json.at("bsphere"));
|
||||
add_regions_from_json(tree_json.at("regions"), tree, regions, base_id);
|
||||
for (auto& [id, region] : regions) {
|
||||
if (region.tree == tree.name) {
|
||||
DrawableRegionPrim* prim = nullptr;
|
||||
if (region.shape == "sphere") {
|
||||
prim = new DrawableRegionSphere(®ion);
|
||||
tree.data2.data.push_back(prim);
|
||||
} else if (region.shape == "face") {
|
||||
prim = new DrawableRegionFace(®ion);
|
||||
tree.data2.data.push_back(prim);
|
||||
} else if (region.shape == "volume") {
|
||||
prim = new DrawableRegionVolume(®ion);
|
||||
tree.data2.data.push_back(prim);
|
||||
} else {
|
||||
ASSERT_MSG(false, fmt::format("Invalid region shape \"{}\"", region.shape));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& [id, region] : regions) {
|
||||
region_arr.data.push_back(®ion);
|
||||
}
|
||||
}
|
||||
|
||||
void add_regions_from_json(const nlohmann::json& json,
|
||||
DrawableTreeRegionPrim& tree,
|
||||
std::map<int, Region>& regions,
|
||||
u32 base_id) {
|
||||
std::vector<Region> result;
|
||||
auto& reader = pretty_print::get_pretty_printer_reader();
|
||||
for (const auto& region_json : json) {
|
||||
auto id = region_json.value("id", base_id + regions.size());
|
||||
auto p = regions.emplace(id, Region());
|
||||
if (p.second) {
|
||||
auto& region = p.first->second;
|
||||
result.push_back(region);
|
||||
region.id = region_json.value("id", base_id + regions.size());
|
||||
region.tree = tree.name;
|
||||
region.shape = region_json.at("shape").get<std::string>();
|
||||
region.bsphere = vectorm4_from_json(region_json.at("bsphere"));
|
||||
ASSERT_MSG(region.shape == "sphere" || region.shape == "face" || region.shape == "volume",
|
||||
"Region must have shape 'sphere', 'face' or 'volume'.");
|
||||
if (region.shape == "sphere") {
|
||||
region.faces = std::nullopt;
|
||||
}
|
||||
if (region.shape == "face") {
|
||||
ASSERT_MSG(region_json.find("face") != region_json.end(),
|
||||
"Face region must have 'face' key.");
|
||||
auto face = region_json.at("face").get<RegionFaceData>();
|
||||
std::vector<RegionFaceData> faces;
|
||||
faces.push_back(face);
|
||||
region.faces = std::make_optional<std::vector<RegionFaceData>>(faces);
|
||||
}
|
||||
if (region.shape == "volume") {
|
||||
ASSERT_MSG(region_json.find("volume") != region_json.end(),
|
||||
"Volume region must have 'volume' key.");
|
||||
auto arr = region_json.at("volume").get<RegionFaceArray>();
|
||||
region.faces = std::make_optional<std::vector<RegionFaceData>>(arr.faces);
|
||||
}
|
||||
if (region_json.find("on-enter") != region_json.end()) {
|
||||
region.on_enter = std::make_optional(
|
||||
&reader.read_from_string(region_json.at("on-enter").get<std::string>(), false)
|
||||
.as_pair()
|
||||
->car);
|
||||
}
|
||||
if (region_json.find("on-inside") != region_json.end()) {
|
||||
region.on_inside = std::make_optional(
|
||||
&reader.read_from_string(region_json.at("on-inside").get<std::string>(), false)
|
||||
.as_pair()
|
||||
->car);
|
||||
}
|
||||
if (region_json.find("on-exit") != region_json.end()) {
|
||||
region.on_exit = std::make_optional(
|
||||
&reader.read_from_string(region_json.at("on-exit").get<std::string>(), false)
|
||||
.as_pair()
|
||||
->car);
|
||||
}
|
||||
lg::print(region.print());
|
||||
} else {
|
||||
lg::warn("Duplicate region with ID {} in tree {}, skipped", p.first->first, tree.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Region::print() {
|
||||
std::string result;
|
||||
result += fmt::format("Region {} ({}):\n", id, tree);
|
||||
result += fmt::format(" shape: {}\n", shape);
|
||||
if (on_enter.has_value()) {
|
||||
result += fmt::format(" on-enter: {}\n", on_enter.value()->print());
|
||||
} else {
|
||||
result += fmt::format(" on-enter: #f\n");
|
||||
}
|
||||
if (on_inside.has_value()) {
|
||||
result += fmt::format(" on-inside: {}\n", on_inside.value()->print());
|
||||
} else {
|
||||
result += fmt::format(" on-inside: #f\n");
|
||||
}
|
||||
if (on_exit.has_value()) {
|
||||
result += fmt::format(" on-exit: {}\n", on_exit.value()->print());
|
||||
} else {
|
||||
result += fmt::format(" on-exit: #f\n");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& json, RegionFaceData& obj) {
|
||||
obj.normal = vector_from_json(json.at("normal"));
|
||||
obj.normal.w() *= METER_LENGTH;
|
||||
std::vector<math::Vector4f> points;
|
||||
for (auto& p : json.at("points")) {
|
||||
points.push_back(vectorm3_from_json(p));
|
||||
}
|
||||
obj.num_points = points.size();
|
||||
obj.points = points;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& json, RegionFaceArray& obj) {
|
||||
(void)obj;
|
||||
for (const auto& face_json : json.at("faces")) {
|
||||
RegionFaceData face;
|
||||
from_json(face_json, face);
|
||||
obj.faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
size_t RegionFaceData::generate(DataObjectGenerator& gen) {
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word_float(normal.x());
|
||||
gen.add_word_float(normal.y());
|
||||
gen.add_word_float(normal.z());
|
||||
gen.add_word_float(normal.w());
|
||||
gen.add_word(num_points);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
for (auto& p : points) {
|
||||
gen.add_word_float(p.x());
|
||||
gen.add_word_float(p.y());
|
||||
gen.add_word_float(p.z());
|
||||
gen.add_word_float(p.w());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t RegionFaceArray::generate(DataObjectGenerator& gen) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("region-face-array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
auto length = data.size();
|
||||
gen.add_word(length);
|
||||
gen.add_word(length);
|
||||
gen.add_word(0);
|
||||
for (auto& face : data) {
|
||||
face.generate(gen);
|
||||
}
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
auto face = data[i];
|
||||
auto face0 = face.data;
|
||||
gen.link_word_to_byte(face.face_data_slot, face0.generate(gen));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableRegionPrim::generate(DataObjectGenerator& gen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t DrawableRegionSphere::generate(DataObjectGenerator& gen) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-region-sphere");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(region->id);
|
||||
gen.link_word_to_word(gen.add_word(0), region->slot);
|
||||
gen.add_word(0);
|
||||
gen.add_word_float(region->bsphere.x());
|
||||
gen.add_word_float(region->bsphere.y());
|
||||
gen.add_word_float(region->bsphere.z());
|
||||
gen.add_word_float(region->bsphere.w());
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableRegionVolume::generate(DataObjectGenerator& gen
|
||||
/*size_t region_face_array_slot*/) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-region-volume");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(region->id);
|
||||
gen.link_word_to_word(gen.add_word(0), region->slot);
|
||||
face_array_slot = gen.add_word(0);
|
||||
gen.add_word_float(region->bsphere.x());
|
||||
gen.add_word_float(region->bsphere.y());
|
||||
gen.add_word_float(region->bsphere.z());
|
||||
gen.add_word_float(region->bsphere.w());
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableRegionFace::generate(DataObjectGenerator& gen) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-region-face");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(region->id);
|
||||
gen.link_word_to_word(gen.add_word(0), region->slot);
|
||||
face_data_slot = gen.add_word(0);
|
||||
const auto& bsphere = bsphere_override.has_value() ? bsphere_override.value() : region->bsphere;
|
||||
gen.add_word_float(bsphere.x());
|
||||
gen.add_word_float(bsphere.y());
|
||||
gen.add_word_float(bsphere.z());
|
||||
gen.add_word_float(bsphere.w());
|
||||
return result;
|
||||
}
|
||||
} // namespace jak3
|
||||
@@ -0,0 +1,207 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/goos/ParseHelpers.h"
|
||||
#include "common/goos/Printer.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
|
||||
#include "third-party/json.hpp"
|
||||
|
||||
namespace jak3 {
|
||||
|
||||
struct RegionFaceData {
|
||||
// (normal vector :inline :offset-assert 0)
|
||||
// (normal-offset float :offset 12)
|
||||
// (num-points uint32 :offset-assert 16)
|
||||
// (points vector :inline :dynamic :offset-assert 32) ;; guess
|
||||
math::Vector4f normal; // w component is normal offset
|
||||
u32 num_points;
|
||||
std::vector<math::Vector4f> points;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
void from_json(const json& j, RegionFaceData& obj);
|
||||
|
||||
struct RegionFaceArray;
|
||||
|
||||
struct Region {
|
||||
// (id uint32 :offset-assert 0)
|
||||
// (on-enter pair :offset-assert 4)
|
||||
// (on-inside pair :offset-assert 8)
|
||||
// (on-exit pair :offset-assert 12)
|
||||
u32 id;
|
||||
std::optional<goos::Object*> on_enter;
|
||||
std::optional<goos::Object*> on_inside;
|
||||
std::optional<goos::Object*> on_exit;
|
||||
math::Vector4f trans;
|
||||
math::Vector4f bsphere;
|
||||
std::string tree; // target, camera, data, water, city_vis, sample, light, entity
|
||||
std::string shape; // sphere, face, volume
|
||||
std::optional<std::vector<RegionFaceData>> faces;
|
||||
|
||||
size_t slot;
|
||||
std::optional<std::map<std::string, size_t>> actor_slots;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
void generate_pairs(DataObjectGenerator& gen, const std::vector<size_t>& pair_slots);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
std::string print();
|
||||
};
|
||||
|
||||
struct RegionArray {
|
||||
// (data region :inline :dynamic :offset-assert 16)
|
||||
std::vector<Region*> data;
|
||||
std::map<int, std::vector<size_t>> pair_slots;
|
||||
std::map<int, size_t> region_slots;
|
||||
std::map<std::string, size_t> entity_actor_slots;
|
||||
std::vector<EntityActor>* entities;
|
||||
std::vector<ActorGroup>* actor_groups;
|
||||
|
||||
size_t slot;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t region_array) const;
|
||||
};
|
||||
|
||||
struct DrawableRegionPrim {
|
||||
// (deftype drawable-region-prim (drawable)
|
||||
// ((region region :offset 8)
|
||||
// )
|
||||
Region* region;
|
||||
|
||||
explicit DrawableRegionPrim(Region* region) { this->region = region; }
|
||||
|
||||
virtual size_t generate(DataObjectGenerator& gen);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
struct DrawableInlineArrayRegionPrim {
|
||||
// (deftype drawable-inline-array-region-prim (drawable-inline-array)
|
||||
// ((data drawable-region-prim 1 :inline :offset-assert 32)
|
||||
// )
|
||||
std::vector<DrawableRegionPrim*> data;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
struct DrawableTreeRegionPrim {
|
||||
// (deftype drawable-tree-region-prim (drawable-tree)
|
||||
// ((id int16 :offset-assert 4)
|
||||
// (length int16 :offset 6)
|
||||
// (name symbol :offset 8)
|
||||
// (bsphere vector :inline :offset-assert 16)
|
||||
// (data2 drawable-inline-array :dynamic :offset 32 :score 1))
|
||||
// )
|
||||
std::string name;
|
||||
math::Vector4f bsphere;
|
||||
DrawableInlineArrayRegionPrim data2;
|
||||
|
||||
DrawableTreeRegionPrim() = default;
|
||||
explicit DrawableTreeRegionPrim(std::string name_) : name(std::move(name_)) {}
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) const;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
size_t generate_drawable_tree_region_prim_array(DataObjectGenerator& gen,
|
||||
RegionArray& regions,
|
||||
const std::vector<DrawableTreeRegionPrim>& trees);
|
||||
|
||||
void fill_region_trees(std::vector<DrawableTreeRegionPrim>& trees,
|
||||
std::map<int, Region>& regions,
|
||||
RegionArray& region_arr,
|
||||
const nlohmann::json& json,
|
||||
u32 base_id);
|
||||
void add_regions_from_json(const nlohmann::json& json,
|
||||
DrawableTreeRegionPrim& tree,
|
||||
std::map<int, Region>& regions,
|
||||
u32 base_id);
|
||||
|
||||
struct DrawableRegionSphere : DrawableRegionPrim {
|
||||
using DrawableRegionPrim::DrawableRegionPrim;
|
||||
size_t generate(DataObjectGenerator& gen) override;
|
||||
};
|
||||
|
||||
struct DrawableRegionFace : DrawableRegionPrim {
|
||||
// (deftype drawable-region-face (drawable-region-prim)
|
||||
// ((data region-face-data :offset 12)
|
||||
// )
|
||||
RegionFaceData data;
|
||||
size_t face_data_slot;
|
||||
std::optional<math::Vector4f> bsphere_override;
|
||||
|
||||
explicit DrawableRegionFace(Region* region) : DrawableRegionPrim(region) {
|
||||
if (region->faces.has_value()) {
|
||||
data = region->faces.value().at(0);
|
||||
}
|
||||
}
|
||||
size_t generate(DataObjectGenerator& gen) override;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
struct RegionFaceArray {
|
||||
// (deftype region-face-array (inline-array-class)
|
||||
// ((data drawable-region-face :inline :dynamic :offset 16)
|
||||
// )
|
||||
std::vector<DrawableRegionFace> data;
|
||||
std::vector<RegionFaceData> faces;
|
||||
|
||||
size_t generate(DataObjectGenerator& gen);
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
|
||||
void from_json(const json& j, RegionFaceArray& obj);
|
||||
|
||||
struct DrawableRegionVolume : DrawableRegionPrim {
|
||||
// (deftype drawable-region-volume (drawable-region-prim)
|
||||
// ((faces region-face-array :offset 12)
|
||||
// )
|
||||
RegionFaceArray faces;
|
||||
size_t face_array_slot;
|
||||
|
||||
explicit DrawableRegionVolume(Region* region) : DrawableRegionPrim(region) {
|
||||
if (this->region->faces.has_value()) {
|
||||
auto face_arr = this->region->faces.value();
|
||||
for (const auto& f : face_arr) {
|
||||
auto& face = faces.data.emplace_back(region);
|
||||
face.data = f;
|
||||
// compute per-face bsphere from the face's points for volumes
|
||||
if (!f.points.empty()) {
|
||||
math::Vector4f center = {0, 0, 0, 0};
|
||||
for (auto& pt : f.points) {
|
||||
center.x() += pt.x();
|
||||
center.y() += pt.y();
|
||||
center.z() += pt.z();
|
||||
}
|
||||
auto n = static_cast<float>(f.points.size());
|
||||
center.x() /= n;
|
||||
center.y() /= n;
|
||||
center.z() /= n;
|
||||
float max_dist_sq = 0;
|
||||
for (auto& pt : f.points) {
|
||||
float dx = pt.x() - center.x();
|
||||
float dy = pt.y() - center.y();
|
||||
float dz = pt.z() - center.z();
|
||||
max_dist_sq = std::max(max_dist_sq, dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
center.w() = std::sqrt(max_dist_sq);
|
||||
face.bsphere_override = center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t generate(DataObjectGenerator& gen) override;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen, size_t);
|
||||
};
|
||||
} // namespace jak3
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "goalc/build_level/jak3/Entity.h"
|
||||
#include "goalc/build_level/jak3/FileInfo.h"
|
||||
#include "goalc/build_level/jak3/LevelFile.h"
|
||||
#include <decompiler/level_extractor/extract_collide_frags.h>
|
||||
|
||||
namespace jak3 {
|
||||
bool run_build_level(const std::string& input_file,
|
||||
@@ -58,9 +59,19 @@ bool run_build_level(const std::string& input_file,
|
||||
fmt::format("Actor IDs must be unique. Found at least two actors with ID {}",
|
||||
duplicates->aid));
|
||||
file.actors = std::move(actors);
|
||||
// actor groups
|
||||
if (level_json.contains("actor_groups") && !level_json.at("actor_groups").empty()) {
|
||||
add_actor_groups_from_json(level_json.at("actor_groups"), file.actors, file.actor_groups, 0);
|
||||
}
|
||||
// cameras
|
||||
// nodes
|
||||
// regions
|
||||
if (level_json.contains("region_trees") && !level_json.at("region_trees").empty()) {
|
||||
file.region_array.entities = &file.actors;
|
||||
file.region_array.actor_groups = &file.actor_groups;
|
||||
fill_region_trees(file.region_trees, file.regions, file.region_array,
|
||||
level_json.at("region_trees"), level_json.value("base_region_id", 0));
|
||||
}
|
||||
// subdivs
|
||||
// actor birth
|
||||
for (size_t i = 0; i < file.actors.size(); i++) {
|
||||
@@ -87,6 +98,22 @@ bool run_build_level(const std::string& input_file,
|
||||
lg::error("No collision geometry was found");
|
||||
} else {
|
||||
file.collide_hash = construct_collide_hash(mesh_extract_out.collide.faces);
|
||||
// for collision renderer
|
||||
for (auto& face : mesh_extract_out.collide.faces) {
|
||||
math::Vector4f verts[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
verts[i].x() = face.v[i].x();
|
||||
verts[i].y() = face.v[i].y();
|
||||
verts[i].z() = face.v[i].z();
|
||||
verts[i].w() = 1.f;
|
||||
}
|
||||
tfrag3::CollisionMesh::Vertex out_verts[3];
|
||||
decompiler::set_vertices_for_tri(out_verts, verts);
|
||||
for (auto& out : out_verts) {
|
||||
out.pat = face.pat.val;
|
||||
pc_level.collision.vertices.push_back(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the GOAL level
|
||||
|
||||
@@ -61,6 +61,146 @@ int DataObjectGenerator::add_word_float(float f) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataObjectGenerator::add_goos_obj(
|
||||
const goos::Object& obj,
|
||||
const bool last_obj,
|
||||
const std::optional<std::map<std::string, size_t>>& entity_slots,
|
||||
const std::optional<std::map<int, size_t>>& actor_group_slots) {
|
||||
// lg::info("add_goos_obj: {}", obj.inspect());
|
||||
size_t cur_slot = 0;
|
||||
size_t next_slot = 0;
|
||||
if (obj.type == goos::ObjectType::INTEGER || obj.type == goos::ObjectType::FLOAT) {
|
||||
cur_slot = add_word(0);
|
||||
next_slot = add_word(0);
|
||||
}
|
||||
switch (obj.type) {
|
||||
case goos::ObjectType::INTEGER: {
|
||||
add_type_tag("binteger");
|
||||
link_word_to_word(cur_slot, add_word(obj.as_int()));
|
||||
} break;
|
||||
case goos::ObjectType::FLOAT: {
|
||||
add_type_tag("bfloat");
|
||||
link_word_to_word(cur_slot, add_word_float(obj.as_float()));
|
||||
} break;
|
||||
case goos::ObjectType::SYMBOL:
|
||||
add_symbol_link(obj.as_symbol().name_ptr);
|
||||
break;
|
||||
case goos::ObjectType::STRING: {
|
||||
std::string str = obj.as_string()->print();
|
||||
std::erase(str, '\"');
|
||||
add_ref_to_string_in_pool(str);
|
||||
} break;
|
||||
case goos::ObjectType::PAIR: {
|
||||
auto pair_slot = add_word(0);
|
||||
if (last_obj) {
|
||||
add_empty_list();
|
||||
} else {
|
||||
next_slot = add_word(0);
|
||||
}
|
||||
link_word_to_byte(pair_slot, add_pair(obj, entity_slots, actor_group_slots));
|
||||
if (!last_obj) {
|
||||
link_word_to_byte(next_slot, current_offset_bytes() + 2);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
ASSERT_MSG(false, fmt::format("Unsupported object type in pair: {}", obj.inspect()));
|
||||
}
|
||||
if (!obj.is_pair()) {
|
||||
if (last_obj) {
|
||||
if (obj.type == goos::ObjectType::INTEGER || obj.type == goos::ObjectType::FLOAT) {
|
||||
link_word_to_symbol("_empty_", next_slot);
|
||||
} else {
|
||||
add_empty_list();
|
||||
}
|
||||
} else {
|
||||
if (obj.type == goos::ObjectType::INTEGER || obj.type == goos::ObjectType::FLOAT) {
|
||||
link_word_to_byte(next_slot, current_offset_bytes() + 2);
|
||||
} else {
|
||||
link_word_to_byte(add_word(0), current_offset_bytes() + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DataObjectGenerator::add_pair(const goos::Object& obj,
|
||||
const std::optional<std::map<std::string, size_t>>& entity_slots,
|
||||
const std::optional<std::map<int, size_t>>& actor_group_slots) {
|
||||
size_t pair_slot = current_offset_bytes() + 2;
|
||||
// lg::info("add_pair: parsing {}", obj.print());
|
||||
|
||||
goos::Object current = obj;
|
||||
while (!current.is_empty_list()) {
|
||||
// lg::info("add_pair: current {}", current.print());
|
||||
auto next = current.as_pair()->car;
|
||||
// special case for entities or actor groups: script pairs can have references to actor groups
|
||||
// or entities from the level data
|
||||
if (next.is_pair() && next.as_pair()->car.is_symbol()) {
|
||||
if (!strcmp(next.as_pair()->car.as_symbol().name_ptr, "entity-actor")) {
|
||||
if (entity_slots.has_value()) {
|
||||
auto pair = next.as_pair();
|
||||
auto type = pair->car;
|
||||
auto val = pair->cdr.as_pair()->car.as_symbol().name_ptr;
|
||||
auto slot = entity_slots->find(val);
|
||||
if (slot != entity_slots->end()) {
|
||||
link_word_to_byte(add_word(0), slot->second);
|
||||
if (current.as_pair()->cdr.is_empty_list()) {
|
||||
add_empty_list();
|
||||
} else {
|
||||
link_word_to_byte(add_word(0), current_offset_bytes() + 2);
|
||||
}
|
||||
} else {
|
||||
ASSERT_MSG(false, fmt::format("error in pair {}: for {}, entity-actor {} not found",
|
||||
obj.print(), pair->print(), val));
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(next.as_pair()->car.as_symbol().name_ptr, "actor-group")) {
|
||||
if (actor_group_slots.has_value()) {
|
||||
auto pair = next.as_pair();
|
||||
auto type = pair->car;
|
||||
auto val = pair->cdr.as_pair()->car.as_int();
|
||||
auto slot = actor_group_slots->find(val);
|
||||
if (slot != actor_group_slots->end()) {
|
||||
link_word_to_byte(add_word(0), slot->second);
|
||||
if (current.as_pair()->cdr.is_empty_list()) {
|
||||
add_empty_list();
|
||||
} else {
|
||||
link_word_to_byte(add_word(0), current_offset_bytes() + 2);
|
||||
}
|
||||
} else {
|
||||
ASSERT_MSG(false, fmt::format("error in pair {}: for {}, got invalid id {}",
|
||||
obj.print(), pair->print(), val));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (current.as_pair()->cdr.is_empty_list()) {
|
||||
// lg::info("add_pair: next (last elem) {}", next.print());
|
||||
add_goos_obj(next, true, entity_slots, actor_group_slots);
|
||||
} else {
|
||||
// lg::info("add_pair: next {}", next.print());
|
||||
add_goos_obj(next, false, entity_slots, actor_group_slots);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (current.as_pair()->cdr.is_empty_list()) {
|
||||
// lg::info("add_pair: next (last elem) {}", next.print());
|
||||
add_goos_obj(next, true, entity_slots, actor_group_slots);
|
||||
} else {
|
||||
// lg::info("add_pair: next {}", next.print());
|
||||
add_goos_obj(next, false, entity_slots, actor_group_slots);
|
||||
}
|
||||
}
|
||||
current = current.as_pair()->cdr;
|
||||
}
|
||||
return pair_slot;
|
||||
}
|
||||
|
||||
int DataObjectGenerator::add_empty_list() {
|
||||
auto result = int(m_words.size());
|
||||
m_words.push_back(0);
|
||||
m_symbol_links["_empty_"].push_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataObjectGenerator::set_word(u32 word_idx, u32 val) {
|
||||
m_words.at(word_idx) = val;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,21 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/goos/ParseHelpers.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
class DataObjectGenerator {
|
||||
public:
|
||||
int add_word(u32 word);
|
||||
int add_word_float(float f);
|
||||
int add_empty_list();
|
||||
void add_goos_obj(const goos::Object& obj,
|
||||
bool last_obj,
|
||||
const std::optional<std::map<std::string, size_t>>& entity_slots,
|
||||
const std::optional<std::map<int, size_t>>& actor_group_slots);
|
||||
int add_pair(const goos::Object& pair,
|
||||
const std::optional<std::map<std::string, size_t>>& entity_slots,
|
||||
const std::optional<std::map<int, size_t>>& actor_group_slots);
|
||||
void set_word(u32 word_idx, u32 val);
|
||||
void link_word_to_word(int source, int target, int offset = 0);
|
||||
void link_word_to_byte(int source_word, int target_byte);
|
||||
|
||||
Reference in New Issue
Block a user