More work on level generation via luascript

This commit is contained in:
James Lambert 2022-12-23 19:20:25 -07:00
parent 9fc4680753
commit e348d8163e
10 changed files with 369 additions and 43 deletions

View file

@ -86,7 +86,7 @@ local MacroType = {}
---@treturn MacroType result
local function macro(name, ...)
if (type(name) ~= "string") then
error("name should be of type string got " .. type(name))
error("name should be of type string got " .. type(name), 2)
end
return setmetatable({ name = name, args = {...}}, MacroType)
@ -161,15 +161,15 @@ end
---@tparam any data The data of the file definition
local function add_definition(nameHint, dataType, location, data)
if (type(nameHint) ~= "string") then
error("nameHint should be a string")
error("nameHint should be a string", 2)
end
if (type(dataType) ~= "string") then
error("dataType should be a string")
error("dataType should be a string", 2)
end
if (type(location) ~= "string") then
error("location should be a string")
error("location should be a string", 2)
end
if (not validate_definition(data, {}, nameHint)) then

View file

@ -28,9 +28,9 @@ void fromLua(lua_State* L, aiMatrix4x4& matrix) {
/***
@function decompose
@treturn vector3.Vector3 scale
@treturn quaternion.Quaternion rotation
@treturn vector3.Vector3 position
@treturn quaternion.Quaternion rotation
@treturn vector3.Vector3 scale
*/
int luaTransformDecomponse(lua_State* L) {
aiMatrix4x4* mtx = (aiMatrix4x4*)luaL_checkudata(L, 1, "aiMatrix4x4");
@ -40,9 +40,9 @@ int luaTransformDecomponse(lua_State* L) {
aiVector3D position;
mtx->Decompose(scaling, rotation, position);
toLua(L, scaling);
toLua(L, rotation);
toLua(L, position);
toLua(L, rotation);
toLua(L, scaling);
return 3;
}

View file

@ -6,12 +6,15 @@ local collision_export = require('tools.level_scripts.collision_export')
local portal_surfaces = require('tools.level_scripts.portal_surfaces')
local trigger = require('tools.level_scripts.trigger')
local world = require('tools.level_scripts.world')
local entities = require('tools.level_scripts.entities')
local signals = require('tools.level_scripts.signals')
sk_definition_writer.add_definition("level", "struct LevelDefinition", "_geo", {
collisionQuads = sk_definition_writer.reference_to(collision_export.collision_objects, 1),
collisionQuadCount = #collision_export.collision_objects,
staticContent = sk_definition_writer.reference_to(static_export.static_content_elements, 1),
staticContentCount = #static_export.static_content_elements,
staticBoundingBoxes = sk_definition_writer.reference_to(static_export.static_bounding_boxes, 1),
roomStaticMapping = sk_definition_writer.reference_to(static_export.room_ranges, 1),
portalSurfaces = sk_definition_writer.reference_to(portal_surfaces.portal_surfaces, 1),
portalSurfaceCount = #portal_surfaces.portal_surfaces,
@ -25,4 +28,22 @@ sk_definition_writer.add_definition("level", "struct LevelDefinition", "_geo", {
locationCount = #trigger.location_data,
startLocation = trigger.find_location_index("start"),
world = world.world,
boxDroppers = sk_definition_writer.reference_to(entities.box_droppers, 1),
boxDropperCount = #entities.box_droppers,
buttons = sk_definition_writer.reference_to(entities.buttons, 1),
buttonCount = #entities.buttons,
decor = sk_definition_writer.reference_to(entities.decor, 1),
decorCount = #entities.decor,
doors = sk_definition_writer.reference_to(entities.doors, 1),
doorCount = #entities.doors,
elevators = sk_definition_writer.reference_to(entities.elevators, 1),
elevatorCount = #entities.elevators,
fizzlers = sk_definition_writer.reference_to(entities.fizzlers, 1),
fizzlerCount = #entities.fizzlers,
pedestals = sk_definition_writer.reference_to(entities.pedestals, 1),
pedestalCount = #entities.pedestals,
signage = sk_definition_writer.reference_to(entities.signage, 1),
signageCount = #entities.signage,
signalOperators = sk_definition_writer.reference_to(signals.operators, 1),
signalOperatorCount = #signals.operators,
})

View file

@ -36,10 +36,10 @@ local function build_collision_grid(boundaries)
end
local function add_to_collision_grid(grid, box, value)
local min_x = floor((box.min.x - grid.x) / COLLISION_GRID_CELL_SIZE)
local max_x = floor((box.max.x - grid.x) / COLLISION_GRID_CELL_SIZE)
local min_z = floor((box.min.z - grid.z) / COLLISION_GRID_CELL_SIZE)
local max_z = floor((box.max.z - grid.z) / COLLISION_GRID_CELL_SIZE)
local min_x = math.floor((box.min.x - grid.x) / COLLISION_GRID_CELL_SIZE)
local max_x = math.floor((box.max.x - grid.x) / COLLISION_GRID_CELL_SIZE)
local min_z = math.floor((box.min.z - grid.z) / COLLISION_GRID_CELL_SIZE)
local max_z = math.floor((box.max.z - grid.z) / COLLISION_GRID_CELL_SIZE)
if (max_x < 0) then max_x = 0 end
if (min_x >= grid.span_x) then min_x = grid.span_x - 1 end
@ -215,6 +215,30 @@ end
local INSIDE_NORMAL_TOLERANCE = 0.1
local function is_coplanar(collision_quad, mesh, relative_scale)
if sk_math.isVector3(mesh) then
local offset = mesh - collision_quad.corner
local z = offset:dot(collision_quad.plane.normal)
if math.abs(z) >= INSIDE_NORMAL_TOLERANCE then
return false
end
local x = offset:dot(collision_quad.edgeA)
if x < -INSIDE_NORMAL_TOLERANCE or x > collision_quad.edgeALength + INSIDE_NORMAL_TOLERANCE then
return false
end
local y = offset:dot(collision_quad.edgeB)
if y < -INSIDE_NORMAL_TOLERANCE or y > collision_quad.edgeBLength + INSIDE_NORMAL_TOLERANCE then
return false
end
return true
end
for _, vertex in pairs(mesh.vertices) do
local offset = vertex * relative_scale - collision_quad.corner
@ -243,13 +267,16 @@ end
for _, node in pairs(collider_nodes) do
local is_transparent = sk_scene.find_flag_argument(node.arguments, "transparent")
local room_index = room_export.node_nearest_room_index(node.node)
for _, mesh in pairs(node.node.meshes) do
local global_mesh = mesh:transform(node.node.full_transformation)
local collider = create_collision_quad(global_mesh, parse_quad_thickness(node))
if room_grids[i] then
add_to_collision_grid(room_grids[i], collision_quad_bb(collider), #colliders)
local room_grid = room_grids[room_index + 1]
if room_grid then
add_to_collision_grid(room_grid, collision_quad_bb(collider), #colliders)
end
local named_entry = sk_scene.find_named_argument(node.arguments, "name")

View file

@ -0,0 +1,152 @@
local sk_definition_writer = require('sk_definition_writer')
local sk_scene = require('sk_scene')
local room_export = require('tools.level_scripts.room_export')
local trigger = require('tools.level_scripts.trigger')
local world = require('tools.level_scripts.world')
local signals = require('tools.level_scripts.signals')
local box_droppers = {}
for _, dropper in pairs(sk_scene.nodes_for_type('@box_dropper')) do
local position = dropper.node.full_transformation:decompose()
table.insert(box_droppers, {
position,
room_export.node_nearest_room_index(dropper.node),
signals.signal_index_for_name(dropper.arguments[1] or ''),
})
end
sk_definition_writer.add_definition('box_dropper', 'struct BoxDropperDefinition[]', '_geo', box_droppers)
local buttons = {}
for _, button in pairs(sk_scene.nodes_for_type('@button')) do
local position = button.node.full_transformation:decompose()
table.insert(buttons, {
position,
room_export.node_nearest_room_index(dropper.node),
signals.signal_index_for_name(dropper.arguments[1] or ''),
signals.signal_index_for_name(dropper.arguments[2] or ''),
})
end
sk_definition_writer.add_definition('buttons', 'struct ButtonDefinition[]', '_geo', buttons)
local decor = {}
for _, decor_entry in pairs(sk_scene.nodes_for_type('@decor')) do
local position, rotation = decor_entry.node.full_transformation:decompose()
table.insert(decor, {
position,
rotation,
room_export.node_nearest_room_index(decor_entry.node),
'DECOR_TYPE_' .. decor_entry.arguments[1],
})
end
sk_definition_writer.add_definition('decor', 'struct DecorDefinition', '_geo', decor)
sk_definition_writer.add_header('"decor/decor_object_list.h"')
local doors = {}
for _, door in pairs(sk_scene.nodes_for_type('@door')) do
local position, rotation = door.node.full_transformation:decompose()
table.insert(doors, {
position,
rotation,
world.find_coplanar_doorway(position) - 1,
signals.signal_index_for_name(door.arguments[1] or ''),
})
end
sk_definition_writer.add_definition('doors', 'struct DoorDefinition[]', '_geo', doors)
local elevators = {}
local elevator_nodes = sk_scene.nodes_for_type('@elevator')
for _, elevator in pairs(elevator_nodes) do
local position, rotation = elevator.node.full_transformation:decompose()
local target_elevator = -1
if elevator.arguments[2] == 'next_level' then
target_elevator = #elevator_nodes
else
for other_index, other_elevator in pairs(elevator_nodes) do
if other_elevator.arguments[1] == elevator.arguments[2] then
target_elevator = other_index - 1
break
end
end
end
table.insert(elevators, {
position,
rotation,
room_export.node_nearest_room_index(elevator.node),
target_elevator,
})
end
sk_definition_writer.add_definition('elevators', 'struct ElevatorDefinition[]', '_geo', elevators)
local fizzlers = {}
for _, fizzler in pairs(sk_scene.nodes_for_type('@fizzler')) do
local position, rotation = fizzler.node.full_transformation:decompose()
table.insert(fizzlers, {
position,
rotation,
2,
2,
room_export.node_nearest_room_index(fizzler.node),
})
end
sk_definition_writer.add_definition('fizzlers', 'struct FizzlerDefinition[]', '_geo', fizzlers)
local pedestals = {}
for _, pedestal in pairs(sk_scene.nodes_for_type('@pedestal')) do
local position = pedestal.node.full_transformation:decompose()
table.insert(pedestals, {
position,
room_export.node_nearest_room_index(fizzlers.node),
})
end
sk_definition_writer.add_definition('pedestals', 'struct PedestalDefinition[]', '_geo', pedestals)
local signage = {}
for _, signage_element in pairs(sk_scene.nodes_for_type('@signage')) do
local position, rotation = signage_element.node.full_transformation:decompose()
table.insert(signage, {
position,
rotation,
room_export.node_nearest_room_index(signage_element.node),
sk_definition_writer.raw(signage_element.arguments[1]),
})
end
sk_definition_writer.add_definition('signage', 'struct SignageDefinition[]', '_geo', signage)
return {
box_droppers = box_droppers,
buttons = buttons,
decor = decor,
doors = doors,
elevators = elevators,
fizzlers = fizzlers,
pedestals = pedestals,
signage = signage,
}

View file

@ -39,7 +39,11 @@ local function nearest_room_index(from_point, ignore_room)
end
end
return block.room_index, block.bb
if result == nil then
return nil, nil
end
return result.room_index, result.bb
end
local function node_nearest_room_index(from_node, ignore_room)

View file

@ -0,0 +1,114 @@
local sk_definition_writer = require('sk_definition_writer')
local sk_scene = require('sk_scene')
local name_to_index = {}
local signal_count = 0
local function signal_index_for_name(name)
local result = name_to_index[name]
if result then
return result
end
local result = signal_count
name_to_index[name] = result
signal_count = signal_count + 1
return result
end
local function determine_signal_order(operators, result, used_signals, signal_producers, operation_index)
-- check if the signal has already been added
if used_signals[operation_index] then
return
end
used_signals[operation_index] = true
local input_signal = operators[operation_index]
for _, signal_name in pairs(input_signal.input) do
for _, producer_index in pairs(signal_producers[signal_name] or {}) do
determine_signal_order(operators, result, used_signals, signal_producers, operation_index)
end
end
table.insert(input_signal)
end
local function order_signals(operators)
local signal_producers = {}
for index, operation in pairs(operators) do
if signal_producers[operation.output] then
table.insert(signal_producers[operation.output], index)
else
signal_producers[operation.output] = {index}
end
end
local result = {}
local used_signals = {}
for operation_index = 1,#operators do
determine_signal_order(operators, result, used_signals, signal_producers, operation_index)
end
return result
end
local unordered_operators = {}
for _, and_operator in pairs(sk_scene.nodes_for_type('@and')) do
table.insert(unordered_operators, {
type = 'SignalOperatorTypeAnd',
output = and_operator.arguments[1],
input = {table.unpack(and_operator.arguments, 2)},
})
end
for _, or_operator in pairs(sk_scene.nodes_for_type('@or')) do
table.insert(unordered_operators, {
type = 'SignalOperatorTypeOr',
output = or_operator.arguments[1],
input = {table.unpack(or_operator.arguments, 2)},
})
end
for _, not_operator in pairs(sk_scene.nodes_for_type('@not')) do
table.insert(unordered_operators, {
type = 'SignalOperatorTypeNot',
output = not_operator.arguments[1],
input = {not_operator.arguments[2]},
})
end
local ordered_operators = order_signals(unordered_operators)
local function generate_operator_data(operator)
return {
operator.type,
signal_index_for_name(operator.output),
{
signal_index_for_name(operator.input[1]),
operator.input[2] and signal_index_for_name(operator.input[2]) or -1,
},
additionalInputs = {
operator.input[3] and signal_index_for_name(operator.input[3]) or -1,
operator.input[4] and signal_index_for_name(operator.input[4]) or -1,
},
}
end
local operators = {}
for _, operation in pairs(ordered_operators) do
table.insert(operators, generate_operator_data(operation))
end
sk_definition_writer.add_definition('signal_operations', 'struct SignalOperator[]', '_geo', operators)
return {
signal_index_for_name = signal_index_for_name,
operators = operators,
}

View file

@ -2,6 +2,7 @@
local sk_definition_writer = require('sk_definition_writer')
local sk_scene = require('sk_scene')
local sk_mesh = require('sk_mesh')
local sk_input = require('sk_input')
local room_export = require('tools.level_scripts.room_export')
sk_definition_writer.add_header('"../build/assets/materials/static.h"')
@ -9,16 +10,28 @@ sk_definition_writer.add_header('"levels/level_definition.h"')
local function proccessStaticNodes(nodes)
local result = {}
local bb_scale = sk_input.settings.model_scale * sk_input.settings.fixed_point_scale
for k, v in pairs(nodes) do
local renderChunks = sk_mesh.generate_render_chunks(v.node)
for _, chunkV in pairs(renderChunks) do
local gfxName = sk_mesh.generate_mesh({chunkV}, "_geo", {defaultMaterial = chunkV.material})
local mesh_bb = chunkV.mesh.bb * bb_scale
mesh_bb.min.x = math.floor(mesh_bb.min.x + 0.5)
mesh_bb.min.y = math.floor(mesh_bb.min.y + 0.5)
mesh_bb.min.z = math.floor(mesh_bb.min.z + 0.5)
mesh_bb.max.x = math.floor(mesh_bb.max.x + 0.5)
mesh_bb.max.y = math.floor(mesh_bb.max.y + 0.5)
mesh_bb.max.z = math.floor(mesh_bb.max.z + 0.5)
table.insert(result, {
node = v.node,
mesh = chunkV.mesh,
mesh_bb = mesh_bb,
display_list = sk_definition_writer.raw(gfxName),
material_index = sk_definition_writer.raw(chunkV.material.macro_name)
})
@ -41,12 +54,14 @@ table.sort(static_nodes, function(a, b)
end)
local room_ranges = {}
local static_bounding_boxes = {}
for index, static_node in pairs(static_nodes) do
table.insert(static_content_elements, {
displayList = static_node.display_list,
materialIndex = static_node.material_index
})
table.insert(static_bounding_boxes, static_node.mesh_bb)
good_index = index - 1
@ -64,9 +79,11 @@ end
sk_definition_writer.add_definition("static", "struct StaticContentElement[]", "_geo", static_content_elements)
sk_definition_writer.add_definition("room_mapping", "struct Rangeu16[]", "_geo", room_ranges)
sk_definition_writer.add_definition('bounding_boxes', 'struct BoundingBoxs16[]', '_geo', static_bounding_boxes)
return {
static_nodes = static_nodes,
static_content_elements = static_content_elements,
static_bounding_boxes = static_bounding_boxes,
room_ranges = room_ranges,
}

View file

@ -2,26 +2,10 @@
local sk_definition_writer = require('sk_definition_writer')
local sk_scene = require('sk_scene')
local room_export = require('tools.level_scripts.room_export')
local signals = require('tools.level_scripts.signals')
sk_definition_writer.add_header('"../build/src/audio/clips.h"')
local signals = {
name_to_index = {},
signal_count = 0,
}
local function signal_index_for_name(name)
local result = signals.name_to_index[name]
if result then
return result
end
local result = signals.signal_count
signals.name_to_index[name] = result
signals.signal_count = signals.signal_count + 1
return result
end
local function does_belong_to_cutscene(first_step, step)
local offset = step.position - first_step.position
@ -51,7 +35,7 @@ local function generate_locations()
local location_data = {}
for _, location in pairs(sk_scene.nodes_for_type("@location")) do
local scale, rotation, position = location.node.full_transformation:decompose()
local position, rotation, scale = location.node.full_transformation:decompose()
local room_index = room_export.node_nearest_room_index(location.node)
@ -149,13 +133,13 @@ local function generate_cutscene_step(step, step_index, label_locations, cutscen
elseif (step.command == "set_signal" or step.command == "clear_signal") and #step.args >= 1 then
result.type = sk_definition_writer.raw('CutsceneStepTypeSetSignal')
result.setSignal = {
signal_index_for_name(step.args[1]),
signals.signal_index_for_name(step.args[1]),
step.command == 'set_signal' and 1 or 0,
}
elseif step.command == "wait_for_signal" and #step.args >= 1 then
result.type = sk_definition_writer.raw('CutsceneStepTypeWaitForSignal')
result.waitForSignal = {
signal_index_for_name(step.args[1]),
signals.signal_index_for_name(step.args[1]),
}
elseif step.command == "teleport_player" and #step.args >= 2 then
result.type = sk_definition_writer.raw('CutsceneStepTypeTeleportPlayer')
@ -216,7 +200,7 @@ local function generate_cutscenes()
local command = node_info.arguments[1]
local args = {table.unpack(node_info.arguments, 2)}
local scale, rotation, position = node_info.node.transformation:decompose()
local position, rotation, scale = node_info.node.transformation:decompose()
local step = {
command = command,
@ -302,11 +286,11 @@ local cutscenes, cutscene_data = generate_cutscenes()
local triggers = generate_triggers(cutscenes)
sk_definition_writer.add_definition("triggers", "struct Trigger[]", "_geo", triggers)
sk_definition_writer.add_definition("cutscenes", "struct Cutscene[]", "_geo", cutscene_data)
return {
triggers = triggers,
cutscene_data = cutscene_data,
location_data = location_data,
signal_index_for_name = signal_index_for_name,
find_location_index = find_location_index,
}

View file

@ -43,10 +43,6 @@ end
sk_defintion_writer.add_definition('doorways', 'struct Doorway[]', '_geo', doorways)
for i = 1,room_export.room_count do
sk_defintion_writer.add_definition('room_doorways', 'short[]', '_geo', room_doorways[i])
end
local function generate_room(room_index)
local quad_indices = {}
local cell_contents = {}
@ -75,7 +71,7 @@ local function generate_room(room_index)
sk_defintion_writer.add_definition('room_indices', 'short[]', '_geo', quad_indices)
sk_defintion_writer.add_definition('room_cells', 'struct Rangeu16[]', '_geo', cell_contents)
sk_defintion_writer.add_definition('room_doorways', 'short[]', '_geo', room_doorways[room_index])
return {
@ -99,11 +95,22 @@ end
sk_defintion_writer.add_definition('rooms', 'struct Room[]', '_geo', rooms)
local function find_coplanar_doorway(point)
for index, doorway in pairs(doorways) do
if collision_export.is_coplanar(doorway[1], point) then
return index
end
end
return 0
end
return {
world = {
rooms = sk_defintion_writer.reference_to(rooms, 1),
doorways = sk_defintion_writer.reference_to(doorways, 1),
roomCount = #rooms,
doorwayCount = #doorways,
}
},
find_coplanar_doorway = find_coplanar_doorway,
}