From 6ae24419e70f4a12d86f7e23fc63127bd855dc9d Mon Sep 17 00:00:00 2001 From: ecker Date: Sun, 7 Jun 2026 17:04:03 -0500 Subject: [PATCH] more bug fixes (fixed that weird instancing bug where things get rendered at origin because of instanced graphics also rendering, mdl fixes, fixed lightmaps with displacements, fixed trigger physics bodies) --- .gitignore | 1 + bin/data/config.json | 5 +- bin/data/entities/ambient_generic.json | 6 - bin/data/entities/door.json | 1 - bin/data/entities/io.json | 3 - bin/data/entities/player.json | 2 + bin/data/entities/scripts/ambient_generic.lua | 8 +- bin/data/entities/scripts/io.lua | 12 +- bin/data/entities/scripts/player.lua | 6 +- bin/data/entities/scripts/trigger.lua | 20 +++ .../sourceengine/base_sourceengine.json | 11 +- bin/data/scenes/sourceengine/cs_office.json | 15 ++ .../scenes/sourceengine/mds_mcdonalds.json | 4 +- .../scenes/sourceengine/sourceengine.json | 6 +- engine/inc/uf/engine/graph/graph.h | 3 +- engine/inc/uf/utils/math/physics/structs.h | 6 +- engine/src/engine/ext/player/behavior.cpp | 4 +- engine/src/engine/graph/graph.cpp | 113 ++++++------- engine/src/ext/audio/vorbis.cpp | 4 +- engine/src/ext/audio/wav.cpp | 2 +- engine/src/ext/valve/bsp.cpp | 84 ++++++++-- engine/src/ext/valve/mdl.cpp | 151 ++++++++++++------ engine/src/utils/math/physics/solvers/ngs.cpp | 3 + 23 files changed, 306 insertions(+), 164 deletions(-) delete mode 100644 bin/data/entities/ambient_generic.json delete mode 100644 bin/data/entities/io.json create mode 100644 bin/data/entities/scripts/trigger.lua create mode 100644 bin/data/scenes/sourceengine/cs_office.json diff --git a/.gitignore b/.gitignore index 8117a600..9671834c 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,7 @@ default/ *.vtf *.ztmp +maps/ models/ llm/ tmp/ \ No newline at end of file diff --git a/bin/data/config.json b/bin/data/config.json index edefcd60..2aa4715b 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -358,8 +358,9 @@ "max": 0.01 // 0.2 }, "debug draw": { - "static": true, - "dynamic": true, + "static": false, + "dynamic": false, + "trigger": false, "contacts": false, "constraints": true, "rays": false, diff --git a/bin/data/entities/ambient_generic.json b/bin/data/entities/ambient_generic.json deleted file mode 100644 index e7eb7cf0..00000000 --- a/bin/data/entities/ambient_generic.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "assets": ["./scripts/ambient_generic.lua"], - "behaviors": [ - "SoundEmitterBehavior" - ] -} \ No newline at end of file diff --git a/bin/data/entities/door.json b/bin/data/entities/door.json index 7f878e12..65aa8a49 100644 --- a/bin/data/entities/door.json +++ b/bin/data/entities/door.json @@ -6,7 +6,6 @@ "metadata": { "physics": { "mass": 0, - // "inertia": [0, 0, 0], "type": "bounding box" // "type": "mesh" } diff --git a/bin/data/entities/io.json b/bin/data/entities/io.json deleted file mode 100644 index c74e96cb..00000000 --- a/bin/data/entities/io.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "assets": ["./scripts/io.lua"] -} \ No newline at end of file diff --git a/bin/data/entities/player.json b/bin/data/entities/player.json index 4cc07e44..236a1c3c 100644 --- a/bin/data/entities/player.json +++ b/bin/data/entities/player.json @@ -86,6 +86,8 @@ "type": "capsule", "radius": 1, "height": 2, + "category": "player", + "mask": "player", // "type": "bounding box", // "min": [ -1, -1, -1 ], diff --git a/bin/data/entities/scripts/ambient_generic.lua b/bin/data/entities/scripts/ambient_generic.lua index 276fc129..2fac284c 100644 --- a/bin/data/entities/scripts/ambient_generic.lua +++ b/bin/data/entities/scripts/ambient_generic.lua @@ -1,11 +1,11 @@ local ent = ent local metadata = ent:getComponent("Metadata") -local metadataVale = metadata["valve"] or {} +local metadataValve = metadata["valve"] or {} -local soundFile = metadataVale["message"] or "" -local flags = metadataVale["spawnflags"] or 0 +local soundFile = metadataValve["message"] or "" +local flags = metadataValve["spawnflags"] or 0 -local volume = tonumber(metadataVale["health"]) or 10.0 +local volume = tonumber(metadataValve["health"]) or 10.0 volume = volume / 10.0 local playEverywhere = (math.floor(flags / 1) % 2) ~= 0 diff --git a/bin/data/entities/scripts/io.lua b/bin/data/entities/scripts/io.lua index 76aa545b..85e3b65c 100644 --- a/bin/data/entities/scripts/io.lua +++ b/bin/data/entities/scripts/io.lua @@ -38,7 +38,7 @@ ent:addHook("io:Input.%UID%", function(payload) ent:callHook("io:FireOutput.%UID%", { output = "OnUser3" }) elseif input == "FireUser4" then ent:callHook("io:FireOutput.%UID%", { output = "OnUser4" }) - -- colors + -- colors elseif input == "Alpha" then local alphaVal = tonumber(param) / 255.0 if alphaVal then @@ -73,8 +73,7 @@ ent:bind("tick", function(self) for i = #pendingOutputs, 1, -1 do local job = pendingOutputs[i] - if currentTime >= job.fireTime then - + if currentTime >= job.fireTime then local targetUIDs = _G.IOTargets[job.target] if targetUIDs then for _, targetUID in ipairs(targetUIDs) do @@ -97,18 +96,19 @@ ent:bind("tick", function(self) end) ent:addHook("io:FireOutput.%UID%", function(payload) - local outputName = payload.output + local output = payload.output for i = 1, #connections do local conn = connections[i] - if conn.output == outputName then + if conn.output == output then local limit = conn.times or -1 if limit == -1 or timesFired[i] < limit then timesFired[i] = timesFired[i] + 1 local delay = conn.delay or 0.0 + table.insert(pendingOutputs, { fireTime = timer:elapsed() + delay, target = conn.target, @@ -136,8 +136,8 @@ end) ent:addHook("entity:Use.%UID%", function(payload) if payload.user == ent:uid() then return end - ent:callHook("io:FireOutput.%UID%", { output = "OnPlayerUse" }) ent:callHook("io:FireOutput.%UID%", { output = "OnUse" }) ent:callHook("io:FireOutput.%UID%", { output = "OnPressed" }) + ent:callHook("io:FireOutput.%UID%", { output = "OnIn" }) end) \ No newline at end of file diff --git a/bin/data/entities/scripts/player.lua b/bin/data/entities/scripts/player.lua index 9ad07016..e1a4d265 100644 --- a/bin/data/entities/scripts/player.lua +++ b/bin/data/entities/scripts/player.lua @@ -138,14 +138,14 @@ local function tickUse( transform, axes, inputs ) timers.use:reset() local center = transform.position local direction = axes.forward * useDistance - local prop, depth = physicsBody:rayCast(center, direction) + local hit, depth = physicsBody:rayCast(center, direction) local payload = { user = ent:uid(), - uid = prop and prop:uid() or 0, + uid = hit and hit:uid() or 0, depth = depth, } - if prop then prop:lazyCallHook("entity:Use.%UID%", payload) end + if hit then hit:lazyCallHook("entity:Use.%UID%", payload) end ent:lazyCallHook("entity:Use.%UID%", payload) end end diff --git a/bin/data/entities/scripts/trigger.lua b/bin/data/entities/scripts/trigger.lua new file mode 100644 index 00000000..e16337e2 --- /dev/null +++ b/bin/data/entities/scripts/trigger.lua @@ -0,0 +1,20 @@ +local ent = ent +local scene = entities.currentScene() +local metadata = ent:getComponent("Metadata") +local metadataValve = metadata["valve"] or {} +local physicsBody = ent:getComponent("PhysicsBody") + +local timer = Timer.new() +if not timer:running() then + timer:start() +end + +--[[ +ent:bind( "tick", function(self) + local collisionEvents = physicsBody:getCollisionEvents() + for i, event in ipairs(collisionEvents) do + -- do something + -- print( event.state, event.a, event.b, event.point, event.normal, event.impulse ) + end +end ) +]] \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/base_sourceengine.json b/bin/data/scenes/sourceengine/base_sourceengine.json index 46ba51bf..6830867b 100644 --- a/bin/data/scenes/sourceengine/base_sourceengine.json +++ b/bin/data/scenes/sourceengine/base_sourceengine.json @@ -27,17 +27,18 @@ "resolution": 2048 } },*/ + "ambient_generic": { "action": "load", "payload": { "assets": ["ent://scripts/ambient_generic.lua"], "behaviors": ["SoundEmitterBehavior"] } }, + // automatically handled // "/^func_door/": { "action": "load", "payload": { "import": "/door.json" } }, // "/^prop_door/": { "action": "load", "payload": { "import": "/door.json" } }, - "/ambient_generic/": { "action": "load", "payload": { "import": "/ambient_generic.json" } }, // regexp matches - "/^prop_static/": { "action": "load", "payload": { "import": "/prop.json" } }, - "/^prop_dynamic/": { "action": "load", "payload": { "import": "/prop.json" } }, - "/^func_physbox/": { "action": "load", "payload": { "import": "/prop.json" } }, - "/^prop_physics/": { "action": "load", "payload": { "import": "/prop.json" } }, + "/^prop_static/": { "action": "load", "payload": { "import": "ent://prop.json" } }, + "/^prop_dynamic/": { "action": "load", "payload": { "import": "ent://prop.json" } }, + "/^func_physbox/": { "action": "load", "payload": { "import": "ent://prop.json" } }, + "/^prop_physics/": { "action": "load", "payload": { "import": "ent://prop.json" } }, "/^tools\\/toolsnodraw/": { "material": { "base": [ 1.0, 1.0, 1.0, 0.0 ] } } } diff --git a/bin/data/scenes/sourceengine/cs_office.json b/bin/data/scenes/sourceengine/cs_office.json new file mode 100644 index 00000000..f181a19f --- /dev/null +++ b/bin/data/scenes/sourceengine/cs_office.json @@ -0,0 +1,15 @@ +{ + "import": "./base_sourceengine.json", + "assets": [ + { "filename": "./maps/cs_office.bsp" } + // { "filename": "./maps/cs_office/graph.json" } + ], + "metadata": { + "graph": { + // realistically shouldn't ever need to define additional tags for BSPs + "tags": { + + } + } + } +} \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/mds_mcdonalds.json b/bin/data/scenes/sourceengine/mds_mcdonalds.json index a42a865b..93efcd9c 100644 --- a/bin/data/scenes/sourceengine/mds_mcdonalds.json +++ b/bin/data/scenes/sourceengine/mds_mcdonalds.json @@ -1,8 +1,8 @@ { "import": "./base_sourceengine.json", "assets": [ - // { "filename": "./models/mds_mcdonalds.bsp" } - { "filename": "./models/mds_mcdonalds/graph.json" } + { "filename": "./maps/mds_mcdonalds.bsp" } + // { "filename": "./maps/mds_mcdonalds/graph.json" } ], "metadata": { "graph": { diff --git a/bin/data/scenes/sourceengine/sourceengine.json b/bin/data/scenes/sourceengine/sourceengine.json index 95a20fe6..065d566b 100644 --- a/bin/data/scenes/sourceengine/sourceengine.json +++ b/bin/data/scenes/sourceengine/sourceengine.json @@ -1,9 +1,7 @@ { // "import": "./rp_downtown_v2.json" // "import": "./ss2_medsci1.json" -// "import": "./test_grid.json" -// "import": "./sh2_mcdonalds.json" -// "import": "./animal_crossing.json" - "import": "./mds_mcdonalds.json" +// "import": "./mds_mcdonalds.json" + "import": "./cs_office.json" // "import": "./gm_construct.json" } \ No newline at end of file diff --git a/engine/inc/uf/engine/graph/graph.h b/engine/inc/uf/engine/graph/graph.h index 237d925e..5664d041 100644 --- a/engine/inc/uf/engine/graph/graph.h +++ b/engine/inc/uf/engine/graph/graph.h @@ -86,9 +86,8 @@ namespace pod { GLOBAL, }; - uf::stl::KeyMap> groupedInstances; - uf::stl::KeyMap> addresses; uf::stl::KeyMap> primitives; + uf::stl::KeyMap> instances; uf::stl::KeyMap meshes; uf::stl::KeyMap images; diff --git a/engine/inc/uf/utils/math/physics/structs.h b/engine/inc/uf/utils/math/physics/structs.h index 076cb745..484ecafb 100644 --- a/engine/inc/uf/utils/math/physics/structs.h +++ b/engine/inc/uf/utils/math/physics/structs.h @@ -333,9 +333,9 @@ namespace pod { enum CollisionMask : uint32_t { MASK_NONE = 0, MASK_STATIC = CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC | CATEGORY_PROJECTILE, - MASK_DYNAMIC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC, - MASK_PLAYER = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_NPC | CATEGORY_PROJECTILE, - MASK_NPC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_PROJECTILE, + MASK_DYNAMIC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC | CATEGORY_TRIGGER, + MASK_PLAYER = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_NPC | CATEGORY_PROJECTILE | CATEGORY_TRIGGER, + MASK_NPC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_PROJECTILE | CATEGORY_TRIGGER, MASK_TRIGGER = CATEGORY_PLAYER | CATEGORY_NPC, MASK_PROJECTILE = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC, MASK_CHARACTER = MASK_PLAYER | MASK_NPC, diff --git a/engine/src/engine/ext/player/behavior.cpp b/engine/src/engine/ext/player/behavior.cpp index 618e9a72..677d3e93 100644 --- a/engine/src/engine/ext/player/behavior.cpp +++ b/engine/src/engine/ext/player/behavior.cpp @@ -426,8 +426,8 @@ void ext::PlayerBehavior::tick( uf::Object& self ) { metadata.system.noclipped = state; if ( !state ) { uf::physics::setGravity( physicsBody ); - uf::physics::setColliderCategory( physicsBody, "DYNAMIC"); - uf::physics::setColliderMask( physicsBody, "DYNAMIC"); + uf::physics::setColliderCategory( physicsBody, "PLAYER"); + uf::physics::setColliderMask( physicsBody, "PLAYER"); } else { uf::physics::setGravity( physicsBody, pod::Vector3f{0,0,0}); uf::physics::setColliderCategory( physicsBody, "NONE"); diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index ef9f93bf..d72a236e 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -734,7 +734,7 @@ void uf::graph::initializeGraphics( pod::Graph& graph, uf::Object& entity, uf::M // query materials if culling needs to be disabled for ( auto& primitive : primitives ) { auto materialID = primitive.instance.materialID; - if ( 0 < materialID && materialID <= graph.materials.size() ) { + if ( 0 <= materialID && materialID <= graph.materials.size() ) { auto& materialName = graph.materials[materialID]; auto& material = storage.materials[materialName]; if ( material.modeCull == 0 ) { @@ -1121,7 +1121,7 @@ void uf::graph::process( pod::Graph& graph ) { } } - for ( auto& instance : storage.groupedInstances.map[name] ) { + for ( auto& instance : storage.instances.map[name] ) { if ( 0 <= instance.materialID && instance.materialID < graph.materials.size() ) { auto& keys = storage.materials.keys; auto& indices = storage.materials.indices; @@ -1228,18 +1228,49 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) metadataJson["system"]["graph"]["index"] = index; uf::Serializer loadJson; + // convert metadata["valve"] into internal values: auto& metadataValve = node.metadata["valve"]; if ( ext::json::isObject( metadataValve ) ) { // bind door script if ( ext::json::isObject( metadataValve["door"] ) ) { node.metadata["door"] = metadataValve["door"]; - loadJson["imports"].emplace_back("/door.json"); + loadJson["imports"].emplace_back("ent://door.json"); } // bind io connectivity if ( ext::json::isArray( metadataValve["connections"] ) || metadataValve["targetname"].is() ) { node.metadata["connections"] = metadataValve["connections"]; - loadJson["imports"].emplace_back("/io.json"); + loadJson["assets"].emplace_back("ent://scripts/io.lua"); + } + + // assume all funcs are to have a physics body + if ( node.name.starts_with("func_") ) { + if ( ext::json::isNull( node.metadata["physics"] ) ) { + //node.metadata["physics"]["type"] = "bounding box"; + node.metadata["physics"]["type"] = "mesh"; + node.metadata["physics"]["category"] = "trigger"; + } + } + + // check if trigger + if ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) { + auto& primitives = storage.primitives.map[graph.primitives[node.mesh]]; + for ( auto& primitive : primitives ) { + auto materialID = primitive.instance.materialID; + if ( 0 <= materialID && materialID <= graph.materials.size() ) { + auto& materialName = graph.materials[materialID]; + // attach trigger script + physics body + if ( materialName == "tools/toolstrigger" ) { + loadJson["assets"].emplace_back("ent://scripts/trigger.lua"); + // signal to assign a physics body + if ( ext::json::isNull( node.metadata["physics"] ) ) { + node.metadata["physics"]["type"] = "bounding box"; + node.metadata["physics"]["category"] = "trigger"; + } + break; + } + } + } } } @@ -1382,18 +1413,9 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) auto primitiveName = graph.primitives[node.mesh]; auto& primitives = storage.primitives.map[primitiveName]; - node.object = ::allocateObjectID( storage ); - auto objectKeyName = std::to_string( node.object ); - - storage.entities[objectKeyName] = &entity; - storage.objects[objectKeyName] = pod::Instance::Object{ - .model = model, - .previous = model, - }; - pod::Instance::Bounds bounds = {}; - auto& grouped = storage.groupedInstances[primitiveName]; + auto& grouped = storage.instances[primitiveName]; for ( auto drawID = 0; drawID < primitives.size(); ++drawID ) { pod::Instance newInstance = primitives[drawID].instance; newInstance.objectID = node.object; @@ -1411,41 +1433,6 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) uf::graph::initializeGraphics( graph, entity, mesh, primitives ); } #endif - - #if 0 - auto& mesh = storage.meshes.map[graph.meshes[node.mesh]]; - auto& primitives = storage.primitives.map[graph.primitives[node.mesh]]; - - pod::Instance::Bounds bounds = {}; - size_t baseInstanceID = ::allocateInstanceID( storage, graph.primitives[node.mesh] ); - // setup instances - for ( auto drawID = 0; drawID < primitives.size(); ++drawID ) { - auto& primitive = primitives[drawID]; - auto& instance = primitive.instance; - size_t instanceID = baseInstanceID + drawID; - - instance.objectID = node.object; - instance.jointID = graphMetadataJson["renderer"]["skinned"].as() ? 0 : -1; - - primitive.drawCommand.instanceID = instanceID; - - bounds.min = uf::vector::min( bounds.min, instance.bounds.min ); - bounds.max = uf::vector::max( bounds.max, instance.bounds.max ); - - if ( mesh.indirect.count && mesh.indirect.count <= primitives.size() ) { - auto& attribute = mesh.indirect.attributes.front(); - auto& buffer = mesh.buffers[attribute.buffer]; - pod::DrawCommand* drawCommands = (pod::DrawCommand*) buffer.data(); - auto& drawCommand = drawCommands[drawID]; - drawCommand.instanceID = instanceID; - } - } - #if !UF_GRAPH_EXTENDED - if ( graphMetadataJson["renderer"]["render"].as() ) { - uf::graph::initializeGraphics( graph, entity, mesh, addresses ); - } - #endif - #endif { auto phyziks = tag["physics"]; @@ -1608,7 +1595,7 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) { if ( storage.stale ) { for ( auto& key : storage.primitives.keys ) { auto& primitives = storage.primitives.map[key]; - auto& grouped = storage.groupedInstances.map[key]; + auto& grouped = storage.instances.map[key]; auto& mesh = storage.meshes.map[key]; pod::DrawCommand* commands = nullptr; @@ -1641,11 +1628,13 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) { } if ( commands && !grouped.empty() ) { - auto hostKeyName = std::to_string(grouped.front().objectID); - if (storage.entities.map.count(hostKeyName) > 0) { - auto* hostEntity = storage.entities.map[hostKeyName]; - if (hostEntity && hostEntity->hasComponent()) { - hostEntity->getComponent().updateMesh(mesh); + auto objectKeyName = std::to_string(grouped.front().objectID); + if ( storage.entities.map.count(objectKeyName) > 0 ) { + auto& entity = *storage.entities.map[objectKeyName]; + if ( entity.hasComponent() ) { + auto& graphic = entity.getComponent(); + auto& attr = mesh.indirect.attributes.front(); + graphic.updateBuffer( (const void*) attr.pointer, attr.length, graphic.metadata.buffers["indirect["+attr.descriptor.name+"]"] ); } } } @@ -1754,12 +1743,9 @@ void uf::graph::aggregate( uf::Object& object, pod::Graph::Storage& storage ) { drawCommands.emplace_back( primitive.drawCommand ); instances.emplace_back( primitive.instance ); lodMetadata.emplace_back( primitive.lod ); + addresses.emplace_back( primitive.addresses ); } } - - for ( auto& key : storage.addresses.keys ) { - addresses.insert( addresses.end(), storage.addresses.map[key].begin(), storage.addresses.map[key].end() ); - } } bool rebuild = false; @@ -2281,8 +2267,13 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) { storage.stale = true; // force rebuffering the draw commands + bool graphicOwner = graphMetadataJson["renderer"]["render"].as(); + if ( graphicOwner ) { + auto objectKeyName = std::to_string(storage.instances.map[graph.primitives[node.mesh]].front().objectID); + graphicOwner = storage.entities[objectKeyName] == &entity; + } // update graphic - if ( graphMetadataJson["renderer"]["render"].as() ) { + if ( graphicOwner ) { bool exists = entity.hasComponent(); if ( exists ) { auto& graphic = entity.getComponent(); @@ -2376,4 +2367,4 @@ void uf::graph::update( pod::Graph& graph, float delta ) { #endif uf::graph::updateAnimation( graph, delta ); -} +} \ No newline at end of file diff --git a/engine/src/ext/audio/vorbis.cpp b/engine/src/ext/audio/vorbis.cpp index 649c5bfa..39804ebf 100644 --- a/engine/src/ext/audio/vorbis.cpp +++ b/engine/src/ext/audio/vorbis.cpp @@ -218,8 +218,8 @@ void ext::vorbis::stream(uf::Audio::Metadata& metadata) { } totalRead += result; } - if (totalRead == 0) { - UF_MSG_WARNING("Vorbis: consumed file stream before buffers are filled: {} {}", (int)queuedBuffers, metadata.filename); + if ( totalRead == 0 ) { + if ( queuedBuffers == 0 ) UF_MSG_WARNING("Vorbis: consumed file stream before {} buffers were filled: {}", (int)queuedBuffers, metadata.filename); break; } AL_CHECK_RESULT(alBufferData(metadata.al.buffer.getIndex(queuedBuffers), metadata.info.format, buffer, totalRead, metadata.info.frequency)); diff --git a/engine/src/ext/audio/wav.cpp b/engine/src/ext/audio/wav.cpp index f6e08ef3..6b49b4d9 100644 --- a/engine/src/ext/audio/wav.cpp +++ b/engine/src/ext/audio/wav.cpp @@ -216,7 +216,7 @@ void ext::wav::stream(uf::Audio::Metadata& metadata) { } if (bytesRead == 0) { - UF_MSG_WARNING("WAV: consumed file stream before buffers are filled: {} {}", (int)queuedBuffers, metadata.filename); + if ( queuedBuffers == 0 ) UF_MSG_WARNING("WAV: consumed file stream before {} buffers were filled: {}", (int) queuedBuffers, metadata.filename); break; } diff --git a/engine/src/ext/valve/bsp.cpp b/engine/src/ext/valve/bsp.cpp index a47a8294..a8632875 100644 --- a/engine/src/ext/valve/bsp.cpp +++ b/engine/src/ext/valve/bsp.cpp @@ -117,21 +117,46 @@ namespace impl { uint16_t smoothingGroups; }; + #pragma pack(push, 1) + + struct BspDispSubNeighbor { + uint16_t neighbor; + uint8_t neighborOrientation; + uint8_t span; + uint8_t neighborSpan; + uint8_t padding; + }; + + struct BspDispNeighbor { + BspDispSubNeighbor subNeighbors[2]; + }; + + struct BspDispCornerNeighbors { + uint16_t neighbors[4]; + uint8_t numNeighbors; + uint8_t padding; + }; + struct BspDispInfo { pod::Vector3f startPosition; int32_t dispVertStart; int32_t dispTriStart; int32_t power; int32_t minTess; - int32_t maxTess; - int32_t smoothingAngle; + float smoothingAngle; int32_t contents; uint16_t mapFace; - int16_t lightmapAlphaStart; + uint16_t padding; + int32_t lightmapAlphaStart; int32_t lightmapSamplePositionStart; - uint8_t padding[130]; + + BspDispNeighbor edgeNeighbors[4]; + BspDispCornerNeighbors cornerNeighbors[4]; + uint32_t allowedVerts[10]; }; + #pragma pack(pop) + struct BspDispVert { pod::Vector3f vec; float dist; @@ -259,8 +284,8 @@ namespace impl { corners[i].uv.x = uf::vector::dot( v, texInfo.textureVecs[0] ) / texWidth; corners[i].uv.y = uf::vector::dot( v, texInfo.textureVecs[1] ) / texHeight; - corners[i].st.x = (uf::vector::dot( v, texInfo.lightmapVecs[0] ) - face.lightmapTextureMins.x) / (face.lightmapTextureSize.x + 1.0f); - corners[i].st.y = (uf::vector::dot( v, texInfo.lightmapVecs[1] ) - face.lightmapTextureMins.y) / (face.lightmapTextureSize.y + 1.0f); + corners[i].st.x = (uf::vector::dot( v, texInfo.lightmapVecs[0] ) + 0.5f - face.lightmapTextureMins.x) / (face.lightmapTextureSize.x + 1.0f); + corners[i].st.y = (uf::vector::dot( v, texInfo.lightmapVecs[1] ) + 0.5f - face.lightmapTextureMins.y) / (face.lightmapTextureSize.y + 1.0f); } int startIndex = 0; @@ -283,13 +308,36 @@ namespace impl { pod::Vector2f uvE1 = uf::vector::lerp( corners[3].uv, corners[2].uv, ty ); pod::Vector2f stE0 = uf::vector::lerp( corners[0].st, corners[1].st, ty ); pod::Vector2f stE1 = uf::vector::lerp( corners[3].st, corners[2].st, ty ); + /* + pod::Vector3f posE0 = uf::vector::lerp( corners[0].pos, corners[1].pos, ty ); + pod::Vector3f posE1 = uf::vector::lerp( corners[3].pos, corners[2].pos, ty ); + pod::Vector2f uvE0 = uf::vector::lerp( corners[0].uv, corners[1].uv, ty ); + pod::Vector2f uvE1 = uf::vector::lerp( corners[3].uv, corners[2].uv, ty ); + pod::Vector2f stE0 = uf::vector::lerp( corners[0].st, corners[1].st, ty ); + pod::Vector2f stE1 = uf::vector::lerp( corners[3].st, corners[2].st, ty ); + */ for ( int x = 0; x < side; ++x ) { float tx = (float)x / (side - 1); pod::Vector3f basePos = uf::vector::lerp( posE0, posE1, tx ); - pod::Vector2f finalUv = uf::vector::lerp( uvE0, uvE1, tx ); - pod::Vector2f finalSt = uf::vector::lerp( stE0, stE1, tx ); + + pod::Vector4f vBase = basePos; + vBase.w = 1.0f; + + pod::Vector2f finalUv; + finalUv.x = uf::vector::dot( vBase, texInfo.textureVecs[0] ) / texWidth; + finalUv.y = uf::vector::dot( vBase, texInfo.textureVecs[1] ) / texHeight; + + pod::Vector2f finalSt; + finalSt.x = tx; + finalSt.y = ty; + /* + finalSt.x = (uf::vector::dot( vBase, texInfo.lightmapVecs[0] ) + 0.5f - face.lightmapTextureMins.x) / (face.lightmapTextureSize.x + 1.0f); + finalSt.y = (uf::vector::dot( vBase, texInfo.lightmapVecs[1] ) + 0.5f - face.lightmapTextureMins.y) / (face.lightmapTextureSize.y + 1.0f); + //finalSt.x = 1.0f - finalSt.x; // ? + finalSt.y = 1.0f - finalSt.y; // ? + */ int dispIdx = info.dispVertStart + y * side + x; const auto& dVert = context.dispverts[dispIdx]; @@ -321,14 +369,24 @@ namespace impl { } } + auto isVertAllowed = [&](int x, int y) -> bool { + int index = y * side + x; + int arrayIndex = index / 32; + int bitIndex = index % 32; + return ( info.allowedVerts[arrayIndex] & (1 << bitIndex) ) != 0; + }; + for ( int y = 0; y < side - 1; ++y ) { for ( int x = 0; x < side - 1; ++x ) { + if ( !isVertAllowed(x, y) || !isVertAllowed(x + 1, y) || !isVertAllowed(x, y + 1) || !isVertAllowed(x + 1, y + 1) ) { + continue; + } + uint32_t v0 = startVertexID + y * side + x; uint32_t v1 = startVertexID + y * side + (x + 1); uint32_t v2 = startVertexID + (y + 1) * side + x; uint32_t v3 = startVertexID + (y + 1) * side + (x + 1); - // might need to flip winding order meshlet.indices.emplace_back(v0); meshlet.indices.emplace_back(v2); meshlet.indices.emplace_back(v1); meshlet.indices.emplace_back(v1); meshlet.indices.emplace_back(v2); meshlet.indices.emplace_back(v3); } @@ -418,6 +476,7 @@ namespace impl { auto classname = metadata["classname"].as(""); //UF_MSG_INFO("Entity found: {}", classname); node.name = classname; + node.mesh = -1; // parse origin auto origin = metadata["origin"].as(""); @@ -445,8 +504,9 @@ namespace impl { } else if ( model.length() > 4 && model.ends_with(".mdl") ) { auto it = std::find(graph.meshes.begin(), graph.meshes.end(), model); if ( it == graph.meshes.end() ) { + auto meshID = graph.meshes.size(); if ( ext::valve::loadMdl(graph, model) ) { - node.mesh = (int32_t)(graph.meshes.size() - 1); + node.mesh = meshID; } } else { node.mesh = (int32_t)std::distance(graph.meshes.begin(), it); @@ -554,7 +614,7 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co const impl::BspHeader* header = (const impl::BspHeader*)(buffer.data()); if ( header->magic != 0x50534256 ) { - UF_MSG_ERROR("Invalid VBSP magic number: {}", filename); + UF_MSG_ERROR("Invalid VBSP magic number: {:X}", header->magic); return; } @@ -856,6 +916,8 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co image.loadFromBuffer( missing_pixels, { 2, 2 }, 8, 4 ); } + // disable exporting if loaded from a VPK + if ( filename.starts_with("valve://") ) graph.metadata["exporter"]["enabled"] = false; graph.metadata["exporter"]["unwrap"] = false; // not necessary to unwrap uf::graph::postprocess( graph ); diff --git a/engine/src/ext/valve/mdl.cpp b/engine/src/ext/valve/mdl.cpp index 13899834..c9798233 100644 --- a/engine/src/ext/valve/mdl.cpp +++ b/engine/src/ext/valve/mdl.cpp @@ -6,6 +6,41 @@ namespace impl { #pragma pack(push, 1) + struct mstudiomesh_t { + int32_t material; + int32_t modelindex; + int32_t numvertices; + int32_t vertexoffset; + int32_t flexes; + int32_t flexindex; + int32_t materialtype; + int32_t materialparam; + int32_t meshid; + pod::Vector3f center; + int32_t vertexdata_ptr; + int32_t vertexdata_lod[8]; + uint8_t pad[32]; + }; + + struct mstudiomodel_t { + char name[64]; + int32_t type; + float boundingradius; + int32_t nummeshes; + int32_t meshindex; + int32_t numvertices; + int32_t vertexindex; + int32_t tangentsindex; + uint8_t pad[56]; + }; + + struct mstudiobodyparts_t { + int32_t sznameindex; + int32_t nummodels; + int32_t base; + int32_t modelindex; + }; + struct vertexFileHeader_t { int32_t magic; int32_t version; @@ -18,6 +53,12 @@ namespace impl { int32_t tangentDataStart; }; + struct vertexFileFixup_t { + int32_t lod; + int32_t sourceVertexID; + int32_t numVertexes; + }; + struct mstudioboneweight_t { float weight[3]; int8_t bone[3]; @@ -68,8 +109,7 @@ namespace impl { // etc }; -#pragma pack(pop) -#pragma pack(push, 1) + struct vtxVertex_t { uint8_t boneWeightIndex[3]; uint8_t numBones; @@ -167,38 +207,53 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { } // extract material directories (cdtextures) - uf::stl::vector cdmaterials(mdlHdr->numcdtextures); - for ( int i = 0; i < mdlHdr->numcdtextures; ++i ) { - int32_t cdOffset = *(int32_t*)(mdlBuffer.data() + mdlHdr->cdtextureindex + (i * 4)); - uf::stl::string cdPath = (const char*)(mdlBuffer.data() + cdOffset); + uf::stl::vector cdmaterials(mdlHdr->numcdtextures); + for ( int i = 0; i < mdlHdr->numcdtextures; ++i ) { + int32_t cdOffset = *(int32_t*)(mdlBuffer.data() + mdlHdr->cdtextureindex + (i * 4)); + uf::stl::string cdPath = (const char*)(mdlBuffer.data() + cdOffset); - std::replace(cdPath.begin(), cdPath.end(), '\\', '/'); - std::transform(cdPath.begin(), cdPath.end(), cdPath.begin(), ::tolower); - cdmaterials[i] = cdPath; - } + std::replace(cdPath.begin(), cdPath.end(), '\\', '/'); + std::transform(cdPath.begin(), cdPath.end(), cdPath.begin(), ::tolower); + cdmaterials[i] = cdPath; + } - // extract material names from MDL and resolve their full relative paths - uf::stl::vector materials(mdlHdr->numtextures); - for ( int i = 0; i < mdlHdr->numtextures; ++i ) { - int32_t texStructOffset = mdlHdr->textureindex + (i * 64); - int32_t nameOffset = *(int32_t*)(mdlBuffer.data() + texStructOffset); - uf::stl::string baseName = (const char*)(mdlBuffer.data() + texStructOffset + nameOffset); - std::transform(baseName.begin(), baseName.end(), baseName.begin(), ::tolower); + // extract material names from MDL and resolve their full relative paths + uf::stl::vector materials(mdlHdr->numtextures); + for ( int i = 0; i < mdlHdr->numtextures; ++i ) { + int32_t texStructOffset = mdlHdr->textureindex + (i * 64); + int32_t nameOffset = *(int32_t*)(mdlBuffer.data() + texStructOffset); + uf::stl::string baseName = (const char*)(mdlBuffer.data() + texStructOffset + nameOffset); + std::transform(baseName.begin(), baseName.end(), baseName.begin(), ::tolower); - materials[i] = baseName; + materials[i] = baseName; - for ( const auto& cd : cdmaterials ) { - uf::stl::string attempt = cd + baseName; - if ( uf::vfs::exists("materials/" + attempt + ".vmt") ) { - materials[i] = attempt; - break; - } - } - } + for ( const auto& cd : cdmaterials ) { + uf::stl::string attempt = cd + baseName; + if ( uf::vfs::exists("materials/" + attempt + ".vmt") ) { + materials[i] = attempt; + break; + } + } + } - // extract LOD0 ertices from VVD + // extract LOD0 vertices from VVD const impl::mstudiovertex_t* vvdVertices = (const impl::mstudiovertex_t*)(vvdBuffer.data() + vvdHdr->vertexDataStart); - int numLOD0Verts = vvdHdr->numLODVertexes[0]; + uf::stl::vector lod0Vertices; + if ( vvdHdr->numFixups == 0 ) { + lod0Vertices.assign(vvdVertices, vvdVertices + vvdHdr->numLODVertexes[0]); + } else { + lod0Vertices.resize(vvdHdr->numLODVertexes[0]); + const impl::vertexFileFixup_t* fixups = (const impl::vertexFileFixup_t*)(vvdBuffer.data() + vvdHdr->fixupTableStart); + int targetIndex = 0; + for ( int i = 0; i < vvdHdr->numFixups; ++i ) { + if ( fixups[i].lod >= 0 ) { + std::memcpy(&lod0Vertices[targetIndex], &vvdVertices[fixups[i].sourceVertexID], fixups[i].numVertexes * sizeof(impl::mstudiovertex_t)); + targetIndex += fixups[i].numVertexes; + } + } + } + /* + */ // read VTX file uf::stl::string vtxPath = filename.substr(0, filename.find_last_of('.')) + ".dx90.vtx"; @@ -213,18 +268,22 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { // traverse: BodyPart -> Model -> LOD0 -> Mesh -> StripGroup -> Indices const impl::vtxBodyPart_t* bodyParts = (const impl::vtxBodyPart_t*)(vtxBuffer.data() + vtxHdr->bodyPartOffset); + const impl::mstudiobodyparts_t* mdlBodyParts = (const impl::mstudiobodyparts_t*)((uint8_t*)mdlHdr + mdlHdr->bodypartindex); for ( int bp = 0; bp < vtxHdr->numBodyParts; ++bp ) { const impl::vtxModel_t* models = (const impl::vtxModel_t*)((uint8_t*)&bodyParts[bp] + bodyParts[bp].modelOffset); + const impl::mstudiomodel_t* mdlModels = (const impl::mstudiomodel_t*)((uint8_t*)&mdlBodyParts[bp] + mdlBodyParts[bp].modelindex); for ( int m = 0; m < bodyParts[bp].numModels; ++m ) { const impl::vtxModelLOD_t* lods = (const impl::vtxModelLOD_t*)((uint8_t*)&models[m] + models[m].lodOffset); const impl::vtxModelLOD_t& lod0 = lods[0]; const impl::vtxMesh_t* meshes = (const impl::vtxMesh_t*)((uint8_t*)&lod0 + lod0.meshOffset); + const impl::mstudiomesh_t* mdlMeshes = (const impl::mstudiomesh_t*)((uint8_t*)&mdlModels[m] + mdlModels[m].meshindex); for ( int meshID = 0; meshID < lod0.numMeshes; ++meshID ) { const impl::vtxMesh_t& mesh = meshes[meshID]; + const impl::mstudiomesh_t& mdlMesh = mdlMeshes[meshID]; auto& meshlet = meshlets.emplace_back(); uf::stl::unordered_map vertRemap; @@ -245,7 +304,7 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { vertRemap[originalVvdID] = meshlet.vertices.size(); auto& vert = meshlet.vertices.emplace_back(); - const auto& srcVert = vvdVertices[originalVvdID]; + const auto& srcVert = lod0Vertices[mdlMesh.vertexoffset + originalVvdID]; vert.position = impl::convertPos( srcVert.m_vecPosition ); vert.normal = uf::vector::normalize( impl::convertPos( srcVert.m_vecNormal, 1.0f ) ); @@ -277,16 +336,13 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { size_t materialID = 0; uf::stl::string matName = "missing_texture"; - if ( meshID < materials.size() ) matName = materials[meshID]; - if ( storage.materials.map.count(matName) > 0 ) { - // to-do: add an indexOf - for ( ; materialID < graph.materials.size(); ++materialID ) { - if ( graph.materials[materialID] == matName ) break; - } - - } else { + if ( mdlMesh.material < materials.size() ) matName = materials[mdlMesh.material]; + auto it = std::find(graph.materials.begin(), graph.materials.end(), matName); + if ( it == graph.materials.end() ) { materialID = graph.materials.size(); auto& material = impl::addMaterial( graph, matName ); + } else { + materialID = (int32_t)std::distance(graph.materials.begin(), it); } meshlet.primitive.instance.materialID = materialID; @@ -294,16 +350,19 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { } } - if ( !meshlets.empty() ) { - auto meshName = filename; - graph.meshes.emplace_back(meshName); - graph.primitives.emplace_back(meshName); - - auto& mesh = storage.meshes[meshName]; - auto& primitives = storage.primitives[meshName]; - - mesh.compile( meshlets, primitives ); + if ( meshlets.empty() ) { + UF_MSG_DEBUG("Empty mesh={}", filename); + return false; } + auto meshName = filename; + graph.meshes.emplace_back(meshName); + graph.primitives.emplace_back(meshName); + + auto& mesh = storage.meshes[meshName]; + auto& primitives = storage.primitives[meshName]; + + mesh.compile( meshlets, primitives ); + return true; } \ No newline at end of file diff --git a/engine/src/utils/math/physics/solvers/ngs.cpp b/engine/src/utils/math/physics/solvers/ngs.cpp index 9db289dc..46e7da86 100644 --- a/engine/src/utils/math/physics/solvers/ngs.cpp +++ b/engine/src/utils/math/physics/solvers/ngs.cpp @@ -9,6 +9,9 @@ void impl::solvePositions( uf::stl::vector& manifolds, float dt, for ( auto& manifold : manifolds ) { auto& a = *manifold.a; auto& b = *manifold.b; + + if ( (a.collider.category & pod::Collider::CATEGORY_TRIGGER) || (b.collider.category & pod::Collider::CATEGORY_TRIGGER) ) return; + auto tA = impl::getTransform( a ); auto tB = impl::getTransform( b );