From 5931a5dc6fa50aab1c3d0ab9688b3e017800ff33 Mon Sep 17 00:00:00 2001 From: ecker Date: Tue, 9 Jun 2026 23:24:05 -0500 Subject: [PATCH] reverted hook hashes because all non-entity hook dispatches breaks, dilate texture atlases if padding is requested, proper mipmapping in deferred shader, some other code cleanup, parse mass from MDL, load error model as fallback --- bin/data/config.json | 10 +- .../sourceengine/base_sourceengine.json | 11 +- .../scenes/sourceengine/sourceengine.json | 4 +- bin/data/shaders/common/functions.h | 201 ++++++++++++++---- bin/data/shaders/common/structs.h | 2 + bin/data/shaders/display/deferred/comp/comp.h | 25 ++- engine/inc/uf/engine/entity/entity.h | 26 ++- engine/inc/uf/engine/object/object.inl | 17 +- engine/inc/uf/utils/hook/hook.h | 2 +- engine/inc/uf/utils/image/atlas.h | 4 +- engine/inc/uf/utils/string/hash.h | 11 +- engine/src/engine/graph/encode.cpp | 12 +- engine/src/engine/graph/graph.cpp | 23 +- engine/src/engine/object/behavior.cpp | 12 +- engine/src/engine/object/object.cpp | 34 ++- engine/src/ext/lua/usertypes/object.cpp | 102 --------- engine/src/ext/valve/bsp.cpp | 13 +- engine/src/ext/valve/mdl.cpp | 28 +++ engine/src/utils/image/atlas.cpp | 57 ++++- 19 files changed, 389 insertions(+), 205 deletions(-) diff --git a/bin/data/config.json b/bin/data/config.json index 7de2e3b6..8090d3c5 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -1,10 +1,10 @@ { "engine": { "scenes": { - "start": "SourceEngine", + "start": "StartMenu", "matrix": { "reverseInfinite": true }, "lights": { "enabled": true, - "lightmaps": false, + "lightmaps": true, "max": 32, "shadows": { "enabled": false, @@ -87,7 +87,7 @@ ] }, "framebuffer": { - "msaa": 1, + "msaa": 8, "size": 1 // "size": [ 640, 480, "NEAREST" ] // "size": [ 1280, 720 ] @@ -109,7 +109,7 @@ "invariant": { "default stage buffers": true, "default defer buffer destroy": true, - "default command buffer immediate": true, + "default command buffer immediate": false, "n-buffered uniform": false, "multithreaded recording": true }, @@ -359,7 +359,7 @@ }, "debug draw": { "static": false, - "dynamic": false, + "dynamic": true, "trigger": false, "contacts": false, "constraints": true, diff --git a/bin/data/scenes/sourceengine/base_sourceengine.json b/bin/data/scenes/sourceengine/base_sourceengine.json index 6830867b..f02e29ad 100644 --- a/bin/data/scenes/sourceengine/base_sourceengine.json +++ b/bin/data/scenes/sourceengine/base_sourceengine.json @@ -32,13 +32,10 @@ // automatically handled // "/^func_door/": { "action": "load", "payload": { "import": "/door.json" } }, // "/^prop_door/": { "action": "load", "payload": { "import": "/door.json" } }, - - - // regexp matches - "/^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" } }, + // "/^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/sourceengine.json b/bin/data/scenes/sourceengine/sourceengine.json index bdb37541..065d566b 100644 --- a/bin/data/scenes/sourceengine/sourceengine.json +++ b/bin/data/scenes/sourceengine/sourceengine.json @@ -1,7 +1,7 @@ { // "import": "./rp_downtown_v2.json" // "import": "./ss2_medsci1.json" - "import": "./mds_mcdonalds.json" -// "import": "./cs_office.json" +// "import": "./mds_mcdonalds.json" + "import": "./cs_office.json" // "import": "./gm_construct.json" } \ No newline at end of file diff --git a/bin/data/shaders/common/functions.h b/bin/data/shaders/common/functions.h index e4ed6600..bd549aa6 100644 --- a/bin/data/shaders/common/functions.h +++ b/bin/data/shaders/common/functions.h @@ -192,6 +192,13 @@ bool validTextureIndex( uint start, int offset ) { uint textureIndex( uint start, int offset ) { return start + offset; } +vec4 sampleTexture( uint id, vec2 uv, vec2 ddx, vec2 ddy ) { + const Texture t = textures[id]; + vec2 scale = t.lerp.zw - t.lerp.xy; + vec2 final_uv = mix( t.lerp.xy, t.lerp.zw, uv ); + + return textureGrad( samplerTextures[nonuniformEXT(t.index)], final_uv, ddx * scale, ddy * scale ); +} vec4 sampleTexture( uint id, vec2 uv ) { const Texture t = textures[id]; return texture( samplerTextures[nonuniformEXT(t.index)], mix( t.lerp.xy, t.lerp.zw, uv ) ); @@ -205,7 +212,14 @@ vec4 sampleTexture( uint id, vec2 uv, float mip ) { #endif } vec4 sampleTexture( uint id, vec3 uvm ) { return sampleTexture( id, uvm.xy, uvm.z ); } -vec4 sampleTexture( uint id ) { return sampleTexture( id, surface.uv.xy, surface.uv.z ); } +vec4 sampleTexture( uint id ) { +#if QUERY_MIPMAP + return sampleTexture( id, uv ); +#else + return sampleTexture( id, surface.uv.xy, surface.dUvDx, surface.dUvDy ); +#endif +} +// vec4 sampleTexture( uint id ) { return sampleTexture( id, surface.uv.xy, surface.uv.z ); } vec4 sampleTexture( uint id, float mip ) { return sampleTexture( id, surface.uv.xy, mip ); } #endif vec2 rayBoxDst( vec3 boundsMin, vec3 boundsMax, in Ray ray ) { @@ -323,7 +337,7 @@ void populateSurfaceMaterial() { // Light mapping if ( ( bool(ubo.settings.lighting.useLightmaps)) && validTextureIndex( surface.instance.lightmapID ) ) { surface.material.lightmapped = true; // light.a > 0.001; - vec4 light = decodeRGBE( sampleTexture( surface.instance.lightmapID, surface.st.xy ) ); + vec4 light = decodeRGBE( sampleTexture( surface.instance.lightmapID, surface.st.xy, 0.0 ) ); const vec3 F0 = mix(vec3(0.04), surface.material.albedo.rgb, surface.material.metallic); const vec3 Lo = normalize(-surface.position.eye); @@ -355,53 +369,45 @@ uvec4 uvec2_16x4( uvec2 i ) { } #if BUFFER_REFERENCE + void populateSurface( InstanceAddresses addresses, uvec3 indices ) { Triangle triangle; Vertex points[3]; - if ( false && isValidAddress(addresses.vertex) ) { - // Vertices vertices = Vertices(nonuniformEXT(addresses.vertex)); - // #pragma unroll 3 - // for ( uint _ = 0; _ < 3; ++_ ) /*triangle.*/points[_] = vertices.v[/*triangle.*/indices[_]]; + if ( isValidAddress(addresses.position) ) { + VPos buf = VPos(nonuniformEXT(addresses.position)); + #pragma unroll 3 + for ( uint _ = 0; _ < 3; ++_ ) points[_].position = vec3( buf.v[indices[_]*3+0], buf.v[indices[_]*3+1], buf.v[indices[_]*3+2] ); + } + if ( isValidAddress(addresses.uv) ) { + VUv buf = VUv(nonuniformEXT(addresses.uv)); + #pragma unroll 3 + for ( uint _ = 0; _ < 3; ++_ ) points[_].uv = buf.v[indices[_]]; + } + if ( isValidAddress(addresses.st) ) { + VSt buf = VSt(nonuniformEXT(addresses.st)); + #pragma unroll 3 + for ( uint _ = 0; _ < 3; ++_ ) points[_].st = buf.v[indices[_]]; + } + if ( isValidAddress(addresses.normal) ) { + VNormal buf = VNormal(nonuniformEXT(addresses.normal)); + #pragma unroll 3 + for ( uint _ = 0; _ < 3; ++_ ) points[_].normal = vec3( buf.v[indices[_]*3+0], buf.v[indices[_]*3+1], buf.v[indices[_]*3+2] ); + } + if ( isValidAddress(addresses.tangent) ) { + VTangent buf = VTangent(nonuniformEXT(addresses.tangent)); + #pragma unroll 3 + for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = vec3( buf.v[indices[_]*3+0], buf.v[indices[_]*3+1], buf.v[indices[_]*3+2] ); } else { - if ( isValidAddress(addresses.position) ) { - VPos buf = VPos(nonuniformEXT(addresses.position)); - #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) points[_].position = vec3( buf.v[indices[_]*3+0], buf.v[indices[_]*3+1], buf.v[indices[_]*3+2] ); - //for ( uint _ = 0; _ < 3; ++_ ) /*triangle.*/points[_].position[_] = buf.v[/*triangle.*/indices[_]*3+_]; - } - if ( isValidAddress(addresses.uv) ) { - VUv buf = VUv(nonuniformEXT(addresses.uv)); - #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) /*triangle.*/points[_].uv/*[_]*/ = buf.v[/*triangle.*/indices[_]]; - } - if ( isValidAddress(addresses.st) ) { - VSt buf = VSt(nonuniformEXT(addresses.st)); - #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) /*triangle.*/points[_].st/*[_]*/ = buf.v[/*triangle.*/indices[_]]; - } - if ( isValidAddress(addresses.normal) ) { - VNormal buf = VNormal(nonuniformEXT(addresses.normal)); - #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) points[_].normal = vec3( buf.v[indices[_]*3+0], buf.v[indices[_]*3+1], buf.v[indices[_]*3+2] ); - // for ( uint _ = 0; _ < 3; ++_ ) /*triangle.*/points[_].normal[_] = buf.v[/*triangle.*/indices[_]*3+_]; - } - if ( isValidAddress(addresses.tangent) ) { - VTangent buf = VTangent(nonuniformEXT(addresses.tangent)); - #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = vec3( buf.v[indices[_]*3+0], buf.v[indices[_]*3+1], buf.v[indices[_]*3+2] ); - // for ( uint _ = 0; _ < 3; ++_ ) /*triangle.*/points[_].tangent[_] = buf.v[/*triangle.*/indices[_]*3+_]; - } else { - vec3 edge1 = points[1].position - points[0].position; - vec3 edge2 = points[2].position - points[0].position; - vec2 deltaUV1 = points[1].uv - points[0].uv; - vec2 deltaUV2 = points[2].uv - points[0].uv; + vec3 edge1 = points[1].position - points[0].position; + vec3 edge2 = points[2].position - points[0].position; + vec2 deltaUV1 = points[1].uv - points[0].uv; + vec2 deltaUV2 = points[2].uv - points[0].uv; - float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); - vec3 tangent_tri = (edge1 * deltaUV2.y - edge2 * deltaUV1.y) * r; + float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); + vec3 tangent_tri = (edge1 * deltaUV2.y - edge2 * deltaUV1.y) * r; - #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = tangent_tri; - } + #pragma unroll 3 + for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = tangent_tri; } #if BARYCENTRIC_CALCULATE @@ -464,6 +470,115 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) { surface.st.xy = triangle.point.st; surface.st.z = 0; } + + // bind UV derivatives + { + surface.dUvDx = vec2(0.0); + surface.dUvDy = vec2(0.0); + + #if BARYCENTRIC && MULTISAMPLING + ivec2 size = textureSize(samplerId).xy; + int sampleIdx = msaa.currentID; + #elif BARYCENTRIC + ivec2 size = textureSize(samplerId, 0).xy; + int sampleIdx = 0; + #else + ivec2 size = imageSize(outImage).xy; + int sampleIdx = 0; + #endif + +#if BARYCENTRIC + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + int layer = int(gl_GlobalInvocationID.z); + + uvec2 centerID = uvec2(texelFetch(samplerId, ivec3(coord, layer), sampleIdx).xy); + + #if BARYCENTRIC_CALCULATE + #if USE_CAMERA_VIEWPORT + mat4 iProj = inverse( camera.viewport[surface.pass].projection ); + mat4 iView = inverse( camera.viewport[surface.pass].view ); + #else + mat4 iProj = ubo.eyes[surface.pass].iProjection; + mat4 iView = ubo.eyes[surface.pass].iView; + #endif + mat4 invModel = inverse(surface.object.model); + + vec3 pA = points[0].position; + vec3 v0 = points[1].position - pA; + vec3 v1 = points[2].position - pA; + float d00 = dot(v0, v0); + float d01 = dot(v0, v1); + float d11 = dot(v1, v1); + float denom = d00 * d11 - d01 * d01; + #endif + + ivec2 offsetX[2] = ivec2[]( ivec2(1, 0), ivec2(-1, 0) ); + ivec2 offsetY[2] = ivec2[]( ivec2(0, 1), ivec2(0, -1) ); + + #if !BARYCENTRIC_CALCULATE + #define FETCH_NEIGHBOR_UV(OFFSETS, OUT_GRAD) \ + for (int i = 0; i < 2; ++i) { \ + ivec2 off = OFFSETS[i]; \ + ivec2 nCoord = coord + off; \ + if ( nCoord.x >= 0 && nCoord.y >= 0 && nCoord.x < size.x && nCoord.y < size.y ) { \ + if ( uvec2(texelFetch(samplerId, ivec3(nCoord, layer), sampleIdx).xy) == centerID ) { \ + vec3 bN = decodeBarycentrics(texelFetch(samplerBary, ivec3(nCoord, layer), sampleIdx).xy); \ + vec2 uvN = points[0].uv * bN.x + points[1].uv * bN.y + points[2].uv * bN.z; \ + OUT_GRAD = (uvN - surface.uv.xy) * float(off.x + off.y); \ + break; \ + } \ + } \ + } + #else + #define FETCH_NEIGHBOR_UV( OFFSETS, OUT_GRAD ) \ + for (int i = 0; i < 2; ++i) { \ + ivec2 off = OFFSETS[i]; \ + ivec2 nCoord = coord + off; \ + if ( nCoord.x >= 0 && nCoord.y >= 0 && nCoord.x < size.x && nCoord.y < size.y ) { \ + if ( uvec2(texelFetch(samplerId, ivec3(nCoord, layer), sampleIdx).xy) == centerID ) { \ + float dN = texelFetch(samplerDepth, ivec3(nCoord, layer), sampleIdx).r; \ + vec2 inUvN = (vec2(nCoord) / vec2(size)) * 2.0f - 1.0f; \ + vec4 eyeN = iProj * vec4(inUvN, dN, 1.0); \ + vec3 pN = vec3(invModel * vec4(vec3(iView * (eyeN / eyeN.w)), 1.0)); \ + vec3 v2N = pN - pA; \ + float vN = (d11 * dot(v2N, v0) - d01 * dot(v2N, v1)) / denom; \ + float wN = (d00 * dot(v2N, v1) - d01 * dot(v2N, v0)) / denom; \ + vec2 uvN = points[0].uv * (1.0f - vN - wN) + points[1].uv * vN + points[2].uv * wN; \ + OUT_GRAD = (uvN - surface.uv.xy) * float(off.x + off.y); \ + break; \ + } \ + } \ + } + #endif + + FETCH_NEIGHBOR_UV( offsetX, surface.dUvDx ); + FETCH_NEIGHBOR_UV( offsetY, surface.dUvDy ); + + #undef FETCH_NEIGHBOR_UV +#endif + if ( surface.dUvDx == vec2(0.0) && surface.dUvDy == vec2(0.0) ) { + #if USE_CAMERA_VIEWPORT + mat4 proj = camera.viewport[surface.pass].projection; + #else + mat4 proj = ubo.eyes[surface.pass].projection; + #endif + + float pixelSize = abs(surface.position.eye.z) * 2.0 / (proj[1][1] * float(size.y)); + + vec3 e0 = points[1].position - points[0].position; + vec3 e1 = points[2].position - points[0].position; + float geomArea = length(cross(e0, e1)); + + vec2 dUv1 = points[1].uv - points[0].uv; + vec2 dUv2 = points[2].uv - points[0].uv; + float uvArea = abs(dUv1.x * dUv2.y - dUv2.x * dUv1.y); + float uvPerMeter = sqrt(uvArea / max(geomArea, 0.00001)); + float fallback = pixelSize * uvPerMeter; + + surface.dUvDx = vec2(fallback, 0.0); + surface.dUvDy = vec2(0.0, fallback); + } + } populateSurfaceMaterial(); } diff --git a/bin/data/shaders/common/structs.h b/bin/data/shaders/common/structs.h index 30ce6b9a..8e89ad4a 100644 --- a/bin/data/shaders/common/structs.h +++ b/bin/data/shaders/common/structs.h @@ -175,6 +175,8 @@ struct Surface { mat3 tbn; vec3 barycentric; vec2 motion; + vec2 dUvDx; + vec2 dUvDy; Ray ray; diff --git a/bin/data/shaders/display/deferred/comp/comp.h b/bin/data/shaders/display/deferred/comp/comp.h index e7fdf4a9..bd726f6f 100644 --- a/bin/data/shaders/display/deferred/comp/comp.h +++ b/bin/data/shaders/display/deferred/comp/comp.h @@ -145,11 +145,13 @@ layout (binding = 21, set = 0) uniform sampler3D samplerNoise; bool USE_SKYBOX_ON_DIVERGENCE = false; void postProcess() { +#if !MULTISAMPLING if ( USE_SKYBOX_ON_DIVERGENCE ) { if ( 0 <= ubo.settings.lighting.indexSkybox && ubo.settings.lighting.indexSkybox < CUBEMAPS ) { surface.fragment.rgb = texture( samplerCubemaps[ubo.settings.lighting.indexSkybox], surface.ray.direction ).rgb; } } +#endif #if FOG fog( surface.ray, surface.fragment.rgb, surface.fragment.a ); #endif @@ -331,11 +333,12 @@ void indirectLighting() { #if MULTISAMPLING void resolveSurfaceFragment() { + msaa.fragment = vec4(0.0); + for ( int i = 0; i < ubo.settings.mode.msaa; ++i ) { msaa.currentID = i; msaa.IDs[i] = uvec3(IMAGE_LOAD(samplerId)).xy; - // check if ID is already used bool unique = true; for ( int j = msaa.currentID - 1; j >= 0; --j ) { if ( msaa.IDs[j] == msaa.IDs[i] ) { @@ -347,16 +350,24 @@ void resolveSurfaceFragment() { if ( unique ) { populateSurface(); - #if VXGI || RT - indirectLighting(); - #endif - directLighting(); + + if ( msaa.IDs[i].x == 0 || msaa.IDs[i].y == 0 ) { + if ( 0 <= ubo.settings.lighting.indexSkybox && ubo.settings.lighting.indexSkybox < CUBEMAPS ) { + surface.fragment.rgb = texture( samplerCubemaps[ubo.settings.lighting.indexSkybox], surface.ray.direction ).rgb; + surface.fragment.a = 1.0; + } + } else { + #if VXGI || RT + indirectLighting(); + #endif + directLighting(); + } } msaa.fragment += surface.fragment; msaa.fragments[msaa.currentID] = surface.fragment; } - - surface.fragment = msaa.fragment / ubo.settings.mode.msaa; + + surface.fragment = msaa.fragment / float(ubo.settings.mode.msaa); } #endif \ No newline at end of file diff --git a/engine/inc/uf/engine/entity/entity.h b/engine/inc/uf/engine/entity/entity.h index 330f519a..812a6b96 100644 --- a/engine/inc/uf/engine/entity/entity.h +++ b/engine/inc/uf/engine/entity/entity.h @@ -110,11 +110,12 @@ namespace uf { template T loadChild( const uf::Serializer&, bool = true ); template T loadChild( const uf::stl::string&, bool = true ); + #if UF_HOOKS_HASH_KEYS uf::hashed_string formatHookName( const uf::stl::string& n ); static uf::hashed_string formatHookName( const uf::stl::string& n, size_t uid, bool fetch = false ); - inline size_t resolveHookKey(size_t hash) const { return hash; } - inline size_t resolveHookKey(const uf::stl::string& name) { return this->formatHookName(name); } + inline size_t resolveHookKey( size_t hash ) const { return hash; } + inline size_t resolveHookKey( const uf::stl::string& name ) { return this->formatHookName(name); } template size_t addHook( const size_t& name, T function ); template inline size_t addHook( const uf::stl::string& name, T function ) { @@ -135,6 +136,27 @@ namespace uf { template inline uf::Hooks::return_t callHook( const K& name, Args&&... args ) { return uf::hooks.call( this->resolveHookKey(name), std::forward(args)... ); } + #else + uf::stl::string formatHookName( const uf::stl::string& n ); + static uf::stl::string formatHookName( const uf::stl::string& n, size_t uid, bool fetch = false ); + + template size_t addHook( const uf::stl::string& name, T function ); + + template inline void queueHook( const K& name, float timeout = 0 ); + template inline void queueHook( const K& name, const V&, float = 0 ); + + template uf::Hooks::return_t lazyCallHook(const K& name, Args&&... args) { + if ( uf::Object::deferLazyCalls ) { + this->queueHook(name, std::forward(args)..., 0.0f); + return {}; + } + return this->callHook( name, std::forward(args)... ); + } + + template inline uf::Hooks::return_t callHook( const K& name, Args&&... args ) { + return uf::hooks.call( this->formatHookName( name ), std::forward(args)... ); + } + #endif uf::stl::string resolveURI( const uf::stl::string& filename, const uf::stl::string& root = "" ); uf::asset::Payload resolveToPayload( const uf::stl::string& filename, const uf::stl::string& mime = "" ); diff --git a/engine/inc/uf/engine/object/object.inl b/engine/inc/uf/engine/object/object.inl index 3dad55f3..651c2cff 100644 --- a/engine/inc/uf/engine/object/object.inl +++ b/engine/inc/uf/engine/object/object.inl @@ -17,6 +17,7 @@ T uf::Object::loadChild( const uf::stl::string& filename, bool initialize ) { return this->loadChild(filename, initialize); } +#if UF_HOOKS_HASH_KEYS template size_t uf::Object::addHook( const size_t& name, T callback ) { size_t id = uf::hooks.addHook( name, callback ); @@ -24,13 +25,23 @@ size_t uf::Object::addHook( const size_t& name, T callback ) { metadata.hooks.bound[name].emplace_back(id); return id; } +#else +template +size_t uf::Object::addHook( const uf::stl::string& n, T callback ) { + auto name = this->formatHookName( n ); + size_t id = uf::hooks.addHook( name, callback ); + auto& metadata = this->getComponent(); + metadata.hooks.bound[name].emplace_back(id); + return id; +} +#endif template inline void uf::Object::queueHook( const K& name, float d ) { auto& metadata = this->getComponent(); auto& queue = metadata.hooks.queue.emplace_back(uf::ObjectBehavior::Metadata::Queued{ .timeout = uf::time::current + d, }); - if constexpr ( std::is_same::value ) { + if constexpr ( std::is_same_v, size_t> ) { queue.hash = name; } else { queue.name = name; @@ -43,12 +54,12 @@ void uf::Object::queueHook( const K& name, const V& p, float d ) { auto& queue = metadata.hooks.queue.emplace_back(uf::ObjectBehavior::Metadata::Queued{ .timeout = uf::time::current + d, }); - if constexpr ( std::is_same::value ) { + if constexpr ( std::is_same_v, size_t> ) { queue.hash = name; } else { queue.name = name; } - if constexpr ( std::is_same::value ) { + if constexpr ( std::is_same_v, ext::json::Value> ) { queue.type = -1; queue.json = p; } else { diff --git a/engine/inc/uf/utils/hook/hook.h b/engine/inc/uf/utils/hook/hook.h index 5ac0116c..56c44e0d 100644 --- a/engine/inc/uf/utils/hook/hook.h +++ b/engine/inc/uf/utils/hook/hook.h @@ -36,7 +36,7 @@ namespace pod { }; } -#define UF_HOOKS_HASH_KEYS 1 +#define UF_HOOKS_HASH_KEYS 0 namespace uf { class UF_API Hooks { diff --git a/engine/inc/uf/utils/image/atlas.h b/engine/inc/uf/utils/image/atlas.h index cd3126df..9dec78cb 100644 --- a/engine/inc/uf/utils/image/atlas.h +++ b/engine/inc/uf/utils/image/atlas.h @@ -24,8 +24,8 @@ namespace uf { pod::Atlas::hash_t UF_API add( pod::Atlas& atlas, const pod::Image& image, const pod::Atlas::hash_t& hash ); pod::Atlas::hash_t UF_API add( pod::Atlas& atlas, const pod::Image& image ); - void UF_API generate( pod::Atlas& atlas, float padding = 1 ); - void UF_API generate( pod::Atlas& atlas, const uf::stl::vector& images, float padding = 1 ); + void UF_API generate( pod::Atlas& atlas, size_t padding = 0 ); + void UF_API generate( pod::Atlas& atlas, const uf::stl::vector& images, size_t padding = 0 ); void UF_API clear( pod::Atlas& atlas, bool full = true ); bool UF_API has( const pod::Atlas& atlas, const pod::Atlas::hash_t& hash ); diff --git a/engine/inc/uf/utils/string/hash.h b/engine/inc/uf/utils/string/hash.h index 9ade4bfc..29174ff1 100644 --- a/engine/inc/uf/utils/string/hash.h +++ b/engine/inc/uf/utils/string/hash.h @@ -22,13 +22,12 @@ namespace uf { typedef size_t hash_t; size_t hash; + uf::stl::string string; - constexpr hashed_string() : hash(0) {} - constexpr hashed_string(size_t h) : hash(h) {} - constexpr hashed_string(const char* s) : hash(uf::algo::fnv1a(s)) {} - - inline hashed_string(const uf::stl::string_view s) : hash(uf::algo::fnv1a(s)) {} - inline hashed_string(const uf::stl::string& s) : hash(uf::algo::fnv1a(s)) {} + constexpr hashed_string() : hash(0), string("NULL") {} + constexpr hashed_string(size_t h) : hash(h), string("NULL") {} + constexpr hashed_string(const char* s) : hash(uf::algo::fnv1a(s)), string(s) {} + inline hashed_string(const uf::stl::string& s) : hash(uf::algo::fnv1a(s)), string(s) {} constexpr operator size_t() const { return hash; } diff --git a/engine/src/engine/graph/encode.cpp b/engine/src/engine/graph/encode.cpp index 9822c257..2cbbf65e 100644 --- a/engine/src/engine/graph/encode.cpp +++ b/engine/src/engine/graph/encode.cpp @@ -210,7 +210,7 @@ namespace { ext::json::reserve( json["buffers"], mesh.buffers.size() ); for ( auto i = 0; i < mesh.buffers.size(); ++i ) { - const uf::stl::string filename = ::fmt::format("{}.buffer.{}.{}", settings.filename, i, settings.compression == "none" ? "bin" : settings.compression ); + const uf::stl::string filename = ::fmt::format("{}.buffer.{}.{}", settings.filename, i, ( settings.compression == "none" ? "bin" : settings.compression ) ); uf::io::write( filename, mesh.buffers[i] ); json["buffers"].emplace_back(uf::io::filename( filename )); } @@ -335,15 +335,15 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string& for ( size_t i = 0; i < graph.images.size(); ++i ) { auto& name = graph.images[i]; auto& image = /*graph.storage*/storage.images.map.at(name).data; - uf::stl::string f = ::fmt::format("{}/image.{}.png", directory, i ); - image.save(directory + "/" + f); + uf::stl::string f = ::fmt::format("image.{}.png", i ); + image.save(::fmt::format("{}/{}", directory, f)); // export DC's .dtex #if UF_USE_DC_TEXCONV // to-do: properly scale per my script auto converted = image.scale( {32, 32}, "nearest" ); auto dtex = ext::texconv::convert( converted ); - ext::texconv::save( dtex, ::fmt::format("{}/image.{}", directory, i ) ); + ext::texconv::save( dtex, ::fmt::format("{}/image.{}", directory, i) ); #endif uf::Serializer json; @@ -407,9 +407,9 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string& if ( !settings.combined ) { for ( auto i = 0; i < graph.animations.size(); ++i ) { auto& name = graph.animations[i]; - uf::stl::string f =::fmt::format("animation.{}.json", i); + uf::stl::string f = ::fmt::format( "animation.{}.json", i ); auto& animation = /*graph.storage*/storage.animations.map.at(name); - encode(animation, settings, graph).writeToFile(::fmt::format("{}/{}", directory, f)); + encode(animation, settings, graph).writeToFile(directory+"/"+f); serializer["animations"].emplace_back(f); } } else { diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index ede6dcfd..4caac42f 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -1229,6 +1229,27 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) node.metadata["door"] = metadataValve["door"]; loadJson["imports"].emplace_back("ent://door.json"); } + // bind prop + else if ( ( node.name.starts_with("prop_") || node.name == "func_physbox" ) && ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) ) { + auto& meshName = graph.meshes[node.mesh]; + + // get flags + int spawnflags = metadataValve["spawnflags"].as(0); + bool motionDisabled = (spawnflags & 8) != 0; + bool preventPickup = (spawnflags & 512) != 0; + + // get mass + float baseMass = graph.metadata["valve"]["models"][meshName]["mass"].as(1.0f); + float massScale = metadataValve["massScale"].as(1.0f); + // flag as static + if ( node.name.starts_with("prop_static") || motionDisabled ) massScale = 0; + float mass = baseMass * massScale; + + node.metadata["physics"]["type"] = "mesh"; + node.metadata["physics"]["mass"] = mass; + + node.metadata["holdable"] = (mass <= 35.0f) && !motionDisabled && !preventPickup; + } // assume all other funcs are to have a physics body else if ( node.name.starts_with("func_") ) { if ( ext::json::isNull( node.metadata["physics"] ) ) { @@ -1237,7 +1258,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) 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]]; diff --git a/engine/src/engine/object/behavior.cpp b/engine/src/engine/object/behavior.cpp index fd684745..5e6367b3 100644 --- a/engine/src/engine/object/behavior.cpp +++ b/engine/src/engine/object/behavior.cpp @@ -329,16 +329,20 @@ void uf::ObjectBehavior::tick( uf::Object& self ) { } for ( auto& q : executeQueue ) { if ( q.hash ) { + #if UF_UF_HOOKS_HASH_KEYS if ( q.type == 1 ) { - this->callHook( q.hash, q.userdata ); + this->callHook( q.hash, static_cast(q.userdata) ); } - else if ( q.type == -1 ) this->callHook( q.hash, q.json ); + else if ( q.type == -1 ) this->callHook( q.hash, static_cast(q.json) ); else this->callHook( q.hash ); + #else + UF_EXCEPTION("unimplemented"); + #endif } else { if ( q.type == 1 ) { - this->callHook( q.name, q.userdata ); + this->callHook( q.name, static_cast(q.userdata) ); } - else if ( q.type == -1 ) this->callHook( q.name, q.json ); + else if ( q.type == -1 ) this->callHook( q.name, static_cast(q.json) ); else this->callHook( q.name ); } } diff --git a/engine/src/engine/object/object.cpp b/engine/src/engine/object/object.cpp index d0fc907a..e23809f3 100644 --- a/engine/src/engine/object/object.cpp +++ b/engine/src/engine/object/object.cpp @@ -65,6 +65,7 @@ void uf::Object::queueDeletion() { this->callHook("entity:Destroy.%UID%"); } +#if UF_HOOKS_HASH_KEYS uf::hashed_string uf::Object::formatHookName( const uf::stl::string& n, size_t uid, bool fetch ) { if ( fetch ) { auto* object = (uf::Object*) uf::Entity::globalFindByUid( uid ); @@ -75,7 +76,9 @@ uf::hashed_string uf::Object::formatHookName( const uf::stl::string& n, size_t u uf::hash( hash, n ); if ( n.ends_with("%UID%") ) uf::hash( hash, uid ); - return hash; + uf::hashed_string res = hash; + res.string = n; + return res; } @@ -88,8 +91,35 @@ uf::hashed_string uf::Object::formatHookName( const uf::stl::string& n ) { if ( n.ends_with("%P-UID%") ) uf::hash( hash, parent ); else if ( n.ends_with("%UID%") ) uf::hash( hash, uid ); - return hash; + uf::hashed_string res = hash; + res.string = n; + return res; } +#else +uf::stl::string uf::Object::formatHookName( const uf::stl::string& n, size_t uid, bool fetch ) { + if ( fetch ) { + auto* object = (uf::Object*) uf::Entity::globalFindByUid( uid ); + if ( object ) return object->formatHookName( n ); + } + + uf::stl::string res = n; + if ( n.ends_with("%UID%") ) res = uf::string::replace( res, "%UID%", ::fmt::format( "{}", uid ) ); + + return res; +} + + +uf::stl::string uf::Object::formatHookName( const uf::stl::string& n ) { + size_t uid = this->getUid(); + size_t parent = this->hasParent() ? this->getParent().getUid() : uid; + + uf::stl::string res = n; + if ( n.ends_with("%P-UID%") ) res = uf::string::replace( res, "%P-UID%", ::fmt::format( "{}", parent ) ); + else if ( n.ends_with("%UID%") ) res = uf::string::replace( res, "%UID%", ::fmt::format( "{}", uid ) ); + + return res; +} +#endif bool uf::Object::load( const uf::stl::string& f, bool inheritRoot ) { uf::Serializer json; diff --git a/engine/src/ext/lua/usertypes/object.cpp b/engine/src/ext/lua/usertypes/object.cpp index d5a871c5..708068a7 100644 --- a/engine/src/ext/lua/usertypes/object.cpp +++ b/engine/src/ext/lua/usertypes/object.cpp @@ -12,112 +12,10 @@ uf::stl::unordered_map ext::lua::componentGetters; namespace binds { - /* - namespace enums { - enum Components { - Metadata, - Transform, - Audio, - // Asset, - Camera, - Physics, - PhysicsState, - }; - - static uf::StaticInitialization TOKEN_PASTE(STATIC_INITIALIZATION_, __LINE__)( []{ - #define UF_LUA_REGISTER_ENUM(E) #E, enums::Components::E - ext::lua::onInitialization( []{ - auto enums = ext::lua::state.new_enum("Components", - UF_LUA_REGISTER_ENUM(Metadata), - UF_LUA_REGISTER_ENUM(Transform), - UF_LUA_REGISTER_ENUM(Audio), - // UF_LUA_REGISTER_ENUM(Asset), - UF_LUA_REGISTER_ENUM(Camera), - UF_LUA_REGISTER_ENUM(Physics), - UF_LUA_REGISTER_ENUM(PhysicsState) - ); - }); - }); - } - */ - uf::hashed_string formatHookName(uf::Object& self, const uf::stl::string n ){ return self.formatHookName(n); } - /* - sol::object getComponentFromEnum( uf::Object& self, binds::enums::Components type ) { - #define UF_LUA_RETRIEVE_COMPONENT_FROM_ENUM( E, T )\ - case enums::Components::E: return sol::make_object( ext::lua::state, std::ref(self.getComponent()) ); - - switch ( type ) { - case enums::Components::Metadata: { - self.callHook( "object:Serialize.%UID%" ); - auto& metadata = self.getComponent(); - auto decoded = ext::lua::decode( metadata ); - if ( decoded ) { - sol::table table = decoded.value(); - return sol::make_object( ext::lua::state, table ); - } - UF_MSG_ERROR("Failed to deserialize metadata for {}: {}", self.getName(), self.getUid()); - } break; - UF_LUA_RETRIEVE_COMPONENT_FROM_ENUM( Transform, pod::Transform<> ); - UF_LUA_RETRIEVE_COMPONENT_FROM_ENUM( Audio, uf::Audio ); - // UF_LUA_RETRIEVE_COMPONENT_FROM_ENUM( Asset, uf::asset ); - UF_LUA_RETRIEVE_COMPONENT_FROM_ENUM( Camera, uf::Camera ); - UF_LUA_RETRIEVE_COMPONENT_FROM_ENUM( Physics, pod::Physics ); - UF_LUA_RETRIEVE_COMPONENT_FROM_ENUM( PhysicsState, pod::PhysicsState ); - } - UF_MSG_ERROR("Invalid component of {} requested for {}: {}", type, self.getName(), self.getUid()); - - return sol::make_object( ext::lua::state, sol::lua_nil ); - } - sol::object getComponentFromString( uf::Object& self, const uf::stl::string& type ) { - #define UF_LUA_RETRIEVE_COMPONENT_FROM_STRING( T )\ - else if ( type == UF_NS_GET_LAST(T) ) return sol::make_object( ext::lua::state, std::ref(self.getComponent()) ); - - if ( type == "Metadata" ) { - self.callHook( "object:Serialize.%UID%" ); - auto& metadata = self.getComponent(); - auto decoded = ext::lua::decode( metadata ); - if ( decoded ) { - sol::table table = decoded.value(); - return sol::make_object( ext::lua::state, table ); - } - UF_MSG_ERROR("Failed to deserialize metadata for {}: {}", self.getName(), self.getUid()); - } - UF_LUA_RETRIEVE_COMPONENT_FROM_STRING(pod::Transform<>) - UF_LUA_RETRIEVE_COMPONENT_FROM_STRING(uf::Audio) - // UF_LUA_RETRIEVE_COMPONENT_FROM_STRING(uf::asset) - UF_LUA_RETRIEVE_COMPONENT_FROM_STRING(uf::Camera) - UF_LUA_RETRIEVE_COMPONENT_FROM_STRING(pod::Physics) - UF_LUA_RETRIEVE_COMPONENT_FROM_STRING(pod::PhysicsState) - UF_MSG_ERROR("Invalid component of {} requested for {}: {}", type, self.getName(), self.getUid()); - return sol::make_object( ext::lua::state, sol::lua_nil ); - } - void setComponent(uf::Object& self, const uf::stl::string& type, sol::object value ) { - #define UF_LUA_UPDATE_COMPONENT( T )\ - else if ( type == UF_NS_GET_LAST(T) ) self.getComponent() = std::move(value.as()); - - if ( type == "Metadata" ) { - auto encoded = ext::lua::encode( value.as() ); - if ( encoded ) { - uf::stl::string str = encoded.value(); - ext::json::Value json; - ext::json::decode( json, str ); - self.callHook( "object:Deserialize.%UID%", json ); - } - } - UF_LUA_UPDATE_COMPONENT(pod::Transform<>) - UF_LUA_UPDATE_COMPONENT(uf::Audio) - // UF_LUA_UPDATE_COMPONENT(uf::asset) - UF_LUA_UPDATE_COMPONENT(uf::Camera) - UF_LUA_UPDATE_COMPONENT(pod::Physics) - UF_LUA_UPDATE_COMPONENT(pod::PhysicsState) - - - } - */ sol::object getComponent( uf::Object& self, const uf::stl::string& type ) { if ( type == "Metadata" ) { self.callHook( "object:Serialize.%UID%" ); diff --git a/engine/src/ext/valve/bsp.cpp b/engine/src/ext/valve/bsp.cpp index 12f8edfe..8c27ac9b 100644 --- a/engine/src/ext/valve/bsp.cpp +++ b/engine/src/ext/valve/bsp.cpp @@ -509,6 +509,17 @@ namespace impl { auto meshID = graph.meshes.size(); if ( ext::valve::loadMdl(graph, model) ) { node.mesh = meshID; + } else { + uf::stl::string model = "models/error.mdl"; + auto it = std::find(graph.meshes.begin(), graph.meshes.end(), model); + + if ( it != graph.meshes.end() ) { + node.mesh = (int32_t)std::distance(graph.meshes.begin(), it); + } else if ( ext::valve::loadMdl( graph, model ) ) { + node.mesh = (int32_t)(graph.meshes.size() - 1); + } else { + node.mesh = -1; + } } } else { node.mesh = (int32_t)std::distance(graph.meshes.begin(), it); @@ -689,7 +700,7 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co { UF_MSG_DEBUG("Generating new lightmap atlas..."); - uf::atlas::generate( context.lightmapAtlas, 0.0f ); + uf::atlas::generate( context.lightmapAtlas, 1 ); //UF_MSG_DEBUG("Generated lightmap atlas."); auto& imageKey = graph.images.emplace_back("lightmap_atlas"); diff --git a/engine/src/ext/valve/mdl.cpp b/engine/src/ext/valve/mdl.cpp index 6a82bbed..2a3590cd 100644 --- a/engine/src/ext/valve/mdl.cpp +++ b/engine/src/ext/valve/mdl.cpp @@ -106,6 +106,31 @@ namespace impl { int32_t skinindex; int32_t numbodyparts; int32_t bodypartindex; + int32_t numlocalattachments; + int32_t localattachmentindex; + int32_t numlocalnodes; + int32_t localnodeindex; + int32_t localnodenameindex; + int32_t numflexdesc; + int32_t flexdescindex; + int32_t numflexcontrollers; + int32_t flexcontrollerindex; + int32_t numflexrules; + int32_t flexruleindex; + int32_t numikchains; + int32_t ikchainindex; + int32_t nummouths; + int32_t mouthindex; + int32_t numlocalposeparameters; + int32_t localposeparamindex; + int32_t surfacepropindex; + int32_t keyvalueindex; + int32_t keyvaluesize; + int32_t numlocalikautoplaylocks; + int32_t localikautoplaylockindex; + + float mass; + int32_t contents; // etc }; @@ -192,6 +217,9 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { return false; } + // Read metadata + graph.metadata["valve"]["models"][filename]["mass"] = mdlHdr->mass; + // Read VVD file uf::stl::string vvdPath = filename.substr(0, filename.find_last_of('.')) + ".vvd"; uf::stl::vector vvdBuffer; diff --git a/engine/src/utils/image/atlas.cpp b/engine/src/utils/image/atlas.cpp index 6f1de4b2..85da3688 100644 --- a/engine/src/utils/image/atlas.cpp +++ b/engine/src/utils/image/atlas.cpp @@ -17,7 +17,7 @@ pod::Atlas::hash_t uf::atlas::add( pod::Atlas& atlas, const pod::Image& image ) return uf::atlas::add( atlas, image, uf::image::hash( image ) ); } -void uf::atlas::generate( pod::Atlas& atlas, float padding ) { +void uf::atlas::generate( pod::Atlas& atlas, size_t padding ) { if ( atlas.tiles.empty() ) return; uf::stl::vector rects; @@ -34,15 +34,15 @@ void uf::atlas::generate( pod::Atlas& atlas, float padding ) { stbrp_rect rect; rect.id = static_cast(rects.size()); - rect.w = dim.x; - rect.h = dim.y; + rect.w = dim.x + padding * 2; + rect.h = dim.y + padding * 2; rects.push_back(rect); hashes.push_back(hash); area += dim.x * dim.y; } - size_t side = std::sqrt( area ) * std::max(1.0f, padding); + size_t side = std::sqrt( area ); pod::Vector2ui size = { std::bit_ceil(side), std::bit_ceil(side) }; bool all_packed = false; @@ -71,8 +71,8 @@ void uf::atlas::generate( pod::Atlas& atlas, float padding ) { auto hash = hashes[rect.id]; auto& tile = atlas.tiles[hash]; - tile.coord = { rect.x, rect.y }; - tile.size = { rect.w, rect.h }; + tile.coord = { rect.x + padding , rect.y + padding }; + tile.size = { rect.w - padding * 2, rect.h - padding * 2 }; auto& image = tile.image; auto& srcBuffer = image.pixels; @@ -94,10 +94,44 @@ void uf::atlas::generate( pod::Atlas& atlas, float padding ) { } } } + + if ( padding > 0 ) { + // top and bottom + for ( size_t py = 1; py <= padding; ++py ) { + size_t topDstY = tile.coord.y - py; + size_t topSrcY = tile.coord.y; + size_t botDstY = tile.coord.y + tile.size.y - 1 + py; + size_t botSrcY = tile.coord.y + tile.size.y - 1; + + size_t rowBytes = tile.size.x * channels * sizeof(decltype(dstBuffer[0])); + memcpy( &dstBuffer[topDstY * size.x * channels + tile.coord.x * channels], &dstBuffer[topSrcY * size.x * channels + tile.coord.x * channels], rowBytes ); + memcpy( &dstBuffer[botDstY * size.x * channels + tile.coord.x * channels], &dstBuffer[botSrcY * size.x * channels + tile.coord.x * channels], rowBytes ); + } + + // left and right + size_t yStart = tile.coord.y - padding; + size_t yCount = tile.size.y + padding * 2; + for ( size_t y = 0; y < yCount; ++y ) { + size_t currentY = yStart + y; + size_t rowStart = currentY * size.x * channels; + + for ( size_t px = 1; px <= padding; ++px ) { + size_t leftDstX = tile.coord.x - px; + size_t leftSrcX = tile.coord.x; + size_t rightDstX = tile.coord.x + tile.size.x - 1 + px; + size_t rightSrcX = tile.coord.x + tile.size.x - 1; + + for ( size_t c = 0; c < channels; ++c ) { + dstBuffer[rowStart + leftDstX * channels + c] = dstBuffer[rowStart + leftSrcX * channels + c]; + dstBuffer[rowStart + rightDstX * channels + c] = dstBuffer[rowStart + rightSrcX * channels + c]; + } + } + } + } } } } -void uf::atlas::generate( pod::Atlas& atlas, const uf::stl::vector& images, float padding ) { +void uf::atlas::generate( pod::Atlas& atlas, const uf::stl::vector& images, size_t padding ) { for ( auto& image : images ) uf::atlas::add( atlas, image ); uf::atlas::generate( atlas, padding ); } @@ -118,11 +152,12 @@ pod::Vector2f uf::atlas::mapUv( const pod::Atlas& atlas, const pod::Vector2f& uv if ( it != atlas.tiles.end() ) { auto& tile = it->second; auto& size = atlas.image.size; - pod::Vector2ui coord = { - uv.x * tile.size.x + tile.coord.x, - uv.y * tile.size.y + tile.coord.y + + pod::Vector2f coord = { + uv.x * (float) tile.size.x + (float) tile.coord.x, + uv.y * (float) tile.size.y + (float) tile.coord.y }; - return pod::Vector2f{ (float) coord.x / (float) size.x, (float) coord.y / (float) size.y }; + return pod::Vector2f{ coord.x / (float)size.x, coord.y / (float)size.y }; } return uv; }