From e348d8163eeee8e8671ed94fec3307f2a6f08b07 Mon Sep 17 00:00:00 2001 From: James Lambert Date: Fri, 23 Dec 2022 19:20:25 -0700 Subject: [PATCH] More work on level generation via luascript --- skelatool64/lua/sk_definition_writer.lua | 8 +- .../src/lua_generator/LuaTransform.cpp | 8 +- tools/export_level.lua | 21 +++ tools/level_scripts/collision_export.lua | 39 ++++- tools/level_scripts/entities.lua | 152 ++++++++++++++++++ tools/level_scripts/room_export.lua | 6 +- tools/level_scripts/signals.lua | 114 +++++++++++++ tools/level_scripts/static_export.lua | 17 ++ tools/level_scripts/trigger.lua | 28 +--- tools/level_scripts/world.lua | 19 ++- 10 files changed, 369 insertions(+), 43 deletions(-) create mode 100644 tools/level_scripts/entities.lua create mode 100644 tools/level_scripts/signals.lua diff --git a/skelatool64/lua/sk_definition_writer.lua b/skelatool64/lua/sk_definition_writer.lua index cfeb469..6a9805d 100644 --- a/skelatool64/lua/sk_definition_writer.lua +++ b/skelatool64/lua/sk_definition_writer.lua @@ -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 diff --git a/skelatool64/src/lua_generator/LuaTransform.cpp b/skelatool64/src/lua_generator/LuaTransform.cpp index 545bde7..6835cd6 100644 --- a/skelatool64/src/lua_generator/LuaTransform.cpp +++ b/skelatool64/src/lua_generator/LuaTransform.cpp @@ -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; } diff --git a/tools/export_level.lua b/tools/export_level.lua index 0baf9b5..56bd86d 100644 --- a/tools/export_level.lua +++ b/tools/export_level.lua @@ -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, }) \ No newline at end of file diff --git a/tools/level_scripts/collision_export.lua b/tools/level_scripts/collision_export.lua index c2ad560..474dd45 100644 --- a/tools/level_scripts/collision_export.lua +++ b/tools/level_scripts/collision_export.lua @@ -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") diff --git a/tools/level_scripts/entities.lua b/tools/level_scripts/entities.lua new file mode 100644 index 0000000..6d7747d --- /dev/null +++ b/tools/level_scripts/entities.lua @@ -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, +} \ No newline at end of file diff --git a/tools/level_scripts/room_export.lua b/tools/level_scripts/room_export.lua index 0e1342a..b9b5b12 100644 --- a/tools/level_scripts/room_export.lua +++ b/tools/level_scripts/room_export.lua @@ -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) diff --git a/tools/level_scripts/signals.lua b/tools/level_scripts/signals.lua new file mode 100644 index 0000000..3b055fd --- /dev/null +++ b/tools/level_scripts/signals.lua @@ -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, +} \ No newline at end of file diff --git a/tools/level_scripts/static_export.lua b/tools/level_scripts/static_export.lua index 4e6068c..d702a70 100644 --- a/tools/level_scripts/static_export.lua +++ b/tools/level_scripts/static_export.lua @@ -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, } \ No newline at end of file diff --git a/tools/level_scripts/trigger.lua b/tools/level_scripts/trigger.lua index 8554ffa..f30c0a2 100644 --- a/tools/level_scripts/trigger.lua +++ b/tools/level_scripts/trigger.lua @@ -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, } \ No newline at end of file diff --git a/tools/level_scripts/world.lua b/tools/level_scripts/world.lua index 68ded0a..8fd9e78 100644 --- a/tools/level_scripts/world.lua +++ b/tools/level_scripts/world.lua @@ -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, } \ No newline at end of file