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

This commit is contained in:
ecker 2026-06-09 23:24:05 -05:00
parent f9d7cf2dc8
commit 5931a5dc6f
19 changed files with 389 additions and 205 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -175,6 +175,8 @@ struct Surface {
mat3 tbn;
vec3 barycentric;
vec2 motion;
vec2 dUvDx;
vec2 dUvDy;
Ray ray;

View File

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

View File

@ -110,11 +110,12 @@ namespace uf {
template<typename T> T loadChild( const uf::Serializer&, bool = true );
template<typename T> 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<typename T> size_t addHook( const size_t& name, T function );
template<typename T> inline size_t addHook( const uf::stl::string& name, T function ) {
@ -135,6 +136,27 @@ namespace uf {
template<typename K, typename... Args> inline uf::Hooks::return_t callHook( const K& name, Args&&... args ) {
return uf::hooks.call( this->resolveHookKey(name), std::forward<Args>(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<typename T> size_t addHook( const uf::stl::string& name, T function );
template<typename K> inline void queueHook( const K& name, float timeout = 0 );
template<typename K, typename V> inline void queueHook( const K& name, const V&, float = 0 );
template<typename K, typename... Args> uf::Hooks::return_t lazyCallHook(const K& name, Args&&... args) {
if ( uf::Object::deferLazyCalls ) {
this->queueHook(name, std::forward<Args>(args)..., 0.0f);
return {};
}
return this->callHook( name, std::forward<Args>(args)... );
}
template<typename K, typename... Args> inline uf::Hooks::return_t callHook( const K& name, Args&&... args ) {
return uf::hooks.call( this->formatHookName( name ), std::forward<Args>(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 = "" );

View File

@ -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<typename T>
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<typename T>
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<uf::ObjectBehavior::Metadata>();
metadata.hooks.bound[name].emplace_back(id);
return id;
}
#endif
template<typename K> inline void uf::Object::queueHook( const K& name, float d ) {
auto& metadata = this->getComponent<uf::ObjectBehavior::Metadata>();
auto& queue = metadata.hooks.queue.emplace_back(uf::ObjectBehavior::Metadata::Queued{
.timeout = uf::time::current + d,
});
if constexpr ( std::is_same<K, size_t>::value ) {
if constexpr ( std::is_same_v<std::decay_t<K>, 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<K, size_t>::value ) {
if constexpr ( std::is_same_v<std::decay_t<K>, size_t> ) {
queue.hash = name;
} else {
queue.name = name;
}
if constexpr ( std::is_same<V, ext::json::Value>::value ) {
if constexpr ( std::is_same_v<std::decay_t<V>, ext::json::Value> ) {
queue.type = -1;
queue.json = p;
} else {

View File

@ -36,7 +36,7 @@ namespace pod {
};
}
#define UF_HOOKS_HASH_KEYS 1
#define UF_HOOKS_HASH_KEYS 0
namespace uf {
class UF_API Hooks {

View File

@ -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<pod::Image>& 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<pod::Image>& 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 );

View File

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

View File

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

View File

@ -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<int>(0);
bool motionDisabled = (spawnflags & 8) != 0;
bool preventPickup = (spawnflags & 512) != 0;
// get mass
float baseMass = graph.metadata["valve"]["models"][meshName]["mass"].as<float>(1.0f);
float massScale = metadataValve["massScale"].as<float>(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]];

View File

@ -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<const pod::Hook::userdata_t&>(q.userdata) );
}
else if ( q.type == -1 ) this->callHook( q.hash, q.json );
else if ( q.type == -1 ) this->callHook( q.hash, static_cast<const ext::json::Value&>(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<const pod::Hook::userdata_t&>(q.userdata) );
}
else if ( q.type == -1 ) this->callHook( q.name, q.json );
else if ( q.type == -1 ) this->callHook( q.name, static_cast<const ext::json::Value&>(q.json) );
else this->callHook( q.name );
}
}

View File

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

View File

@ -12,112 +12,10 @@
uf::stl::unordered_map<uf::stl::string, ext::lua::GetComponent> 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<T>()) );
switch ( type ) {
case enums::Components::Metadata: {
self.callHook( "object:Serialize.%UID%" );
auto& metadata = self.getComponent<uf::Serializer>();
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<T>()) );
if ( type == "Metadata" ) {
self.callHook( "object:Serialize.%UID%" );
auto& metadata = self.getComponent<uf::Serializer>();
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<T>() = std::move(value.as<T>());
if ( type == "Metadata" ) {
auto encoded = ext::lua::encode( value.as<sol::table>() );
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%" );

View File

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

View File

@ -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<uint8_t> vvdBuffer;

View File

@ -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<stbrp_rect> rects;
@ -34,15 +34,15 @@ void uf::atlas::generate( pod::Atlas& atlas, float padding ) {
stbrp_rect rect;
rect.id = static_cast<int>(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<pod::Image>& images, float padding ) {
void uf::atlas::generate( pod::Atlas& atlas, const uf::stl::vector<pod::Image>& 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;
}