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)

This commit is contained in:
ecker 2026-06-07 17:04:03 -05:00
parent e4ad164203
commit 6ae24419e7
23 changed files with 306 additions and 164 deletions

1
.gitignore vendored
View File

@ -63,6 +63,7 @@ default/
*.vtf
*.ztmp
maps/
models/
llm/
tmp/

View File

@ -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,

View File

@ -1,6 +0,0 @@
{
"assets": ["./scripts/ambient_generic.lua"],
"behaviors": [
"SoundEmitterBehavior"
]
}

View File

@ -6,7 +6,6 @@
"metadata": {
"physics": {
"mass": 0,
// "inertia": [0, 0, 0],
"type": "bounding box"
// "type": "mesh"
}

View File

@ -1,3 +0,0 @@
{
"assets": ["./scripts/io.lua"]
}

View File

@ -86,6 +86,8 @@
"type": "capsule",
"radius": 1,
"height": 2,
"category": "player",
"mask": "player",
// "type": "bounding box",
// "min": [ -1, -1, -1 ],

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 )
]]

View File

@ -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 ] } }
}

View File

@ -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": {
}
}
}
}

View File

@ -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": {

View File

@ -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"
}

View File

@ -86,9 +86,8 @@ namespace pod {
GLOBAL,
};
uf::stl::KeyMap<uf::stl::vector<pod::Instance>> groupedInstances;
uf::stl::KeyMap<uf::stl::vector<pod::Instance::Addresses>> addresses;
uf::stl::KeyMap<uf::stl::vector<pod::Primitive>> primitives;
uf::stl::KeyMap<uf::stl::vector<pod::Instance>> instances;
uf::stl::KeyMap<uf::Mesh> meshes;
uf::stl::KeyMap<pod::ImageTexture> images;

View File

@ -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,

View File

@ -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");

View File

@ -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<uf::stl::string>() ) {
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<bool>() ? 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<bool>() ) {
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<uf::renderer::Graphic>()) {
hostEntity->getComponent<uf::renderer::Graphic>().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<uf::renderer::Graphic>() ) {
auto& graphic = entity.getComponent<uf::renderer::Graphic>();
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<bool>();
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<bool>() ) {
if ( graphicOwner ) {
bool exists = entity.hasComponent<uf::renderer::Graphic>();
if ( exists ) {
auto& graphic = entity.getComponent<uf::renderer::Graphic>();
@ -2376,4 +2367,4 @@ void uf::graph::update( pod::Graph& graph, float delta ) {
#endif
uf::graph::updateAnimation( graph, delta );
}
}

View File

@ -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));

View File

@ -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;
}

View File

@ -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::stl::string>("");
//UF_MSG_INFO("Entity found: {}", classname);
node.name = classname;
node.mesh = -1;
// parse origin
auto origin = metadata["origin"].as<uf::stl::string>("");
@ -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 );

View File

@ -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<uf::stl::string> 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<uf::stl::string> 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<uf::stl::string> 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<uf::stl::string> 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<impl::mstudiovertex_t> 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<uint16_t, uint32_t> 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;
}

View File

@ -9,6 +9,9 @@ void impl::solvePositions( uf::stl::vector<pod::Manifold>& 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 );