more fixes (chasing down demons because of forgetting to support BGR888......), theoretical HDR image saving/loading and animated textures (to-do: actually animate them)
This commit is contained in:
parent
def459e72c
commit
e0fbed4a14
@ -1,7 +1,7 @@
|
||||
{
|
||||
"import": "./base_sourceengine.json",
|
||||
"assets": [
|
||||
{ "filename": "./maps/cs_office.bsp" }
|
||||
// { "filename": "./maps/cs_office/graph.json" }
|
||||
// { "filename": "./maps/cs_office.bsp" }
|
||||
{ "filename": "./maps/cs_office/graph.json" }
|
||||
]
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
// "import": "./rp_downtown_v2.json"
|
||||
// "import": "./ss2_medsci1.json"
|
||||
// "import": "./mds_mcdonalds.json"
|
||||
// "import": "./cs_office.json"
|
||||
"import": "./de_dust2.json"
|
||||
"import": "./cs_office.json"
|
||||
// "import": "./de_dust2.json"
|
||||
// "import": "./gm_construct.json"
|
||||
}
|
||||
@ -402,8 +402,6 @@ uvec4 uvec2_16x4( uvec2 i ) {
|
||||
void populateSurface( InstanceAddresses addresses, uvec3 indices ) {
|
||||
Triangle triangle;
|
||||
Vertex points[3];
|
||||
|
||||
float uvHandedness = 1.0;
|
||||
if ( isValidAddress(addresses.position) ) {
|
||||
VPos buf = VPos(nonuniformEXT(addresses.position));
|
||||
#pragma unroll 3
|
||||
@ -419,31 +417,38 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) {
|
||||
#pragma unroll 3
|
||||
for ( uint _ = 0; _ < 3; ++_ ) points[_].st = buf.v[indices[_]];
|
||||
}
|
||||
|
||||
vec3 e0 = points[1].position - points[0].position;
|
||||
vec3 e1 = points[2].position - points[0].position;
|
||||
vec2 dUv1 = points[1].uv - points[0].uv;
|
||||
vec2 dUv2 = points[2].uv - points[0].uv;
|
||||
float det = (dUv1.x * dUv2.y - dUv1.y * dUv2.x);
|
||||
float handedness = (det < 0.0) ? -1.0 : 1.0;
|
||||
|
||||
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] );
|
||||
} else {
|
||||
vec3 normal = normalize(cross(e0, e1));
|
||||
#pragma unroll 3
|
||||
for ( uint _ = 0; _ < 3; ++_ ) points[_].normal = normal;
|
||||
}
|
||||
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; ++_ ) points[_].tangent = vec4( buf.v[indices[_]*4+0], buf.v[indices[_]*4+1], buf.v[indices[_]*4+2], buf.v[indices[_]*4+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;
|
||||
|
||||
float det = (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
|
||||
float r = 1.0f / det;
|
||||
vec3 tangent_tri = (edge1 * deltaUV2.y - edge2 * deltaUV1.y) * r;
|
||||
|
||||
uvHandedness = (det < 0.0) ? -1.0 : 1.0;
|
||||
vec3 tangent = (e0 * dUv2.y - e1 * dUv1.y);
|
||||
if ( abs(det) < 0.000001 ) {
|
||||
tangent = abs(points[0].normal.y) < 0.999 ? vec3(0, 1, 0) : vec3(1, 0, 0);
|
||||
} else {
|
||||
tangent /= det;
|
||||
}
|
||||
|
||||
#pragma unroll 3
|
||||
for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = tangent_tri;
|
||||
for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = vec4(tangent, handedness);
|
||||
}
|
||||
|
||||
#if BARYCENTRIC_CALCULATE
|
||||
{
|
||||
const vec3 p = vec3(inverse( surface.object.model ) * vec4(surface.position.world, 1));
|
||||
@ -470,12 +475,12 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// triangle.geomNormal = normalize(cross(points[1].position - points[0].position, points[2].position - points[0].position));
|
||||
triangle.point.position = /*triangle.*/points[0].position * surface.barycentric[0] + /*triangle.*/points[1].position * surface.barycentric[1] + /*triangle.*/points[2].position * surface.barycentric[2];
|
||||
triangle.point.normal = /*triangle.*/points[0].normal * surface.barycentric[0] + /*triangle.*/points[1].normal * surface.barycentric[1] + /*triangle.*/points[2].normal * surface.barycentric[2];
|
||||
triangle.point.uv = /*triangle.*/points[0].uv * surface.barycentric[0] + /*triangle.*/points[1].uv * surface.barycentric[1] + /*triangle.*/points[2].uv * surface.barycentric[2];
|
||||
triangle.point.st = /*triangle.*/points[0].st * surface.barycentric[0] + /*triangle.*/points[1].st * surface.barycentric[1] + /*triangle.*/points[2].st * surface.barycentric[2];
|
||||
triangle.point.tangent = /*triangle.*/points[0].tangent * surface.barycentric[0] + /*triangle.*/points[1].tangent * surface.barycentric[1] + /*triangle.*/points[2].tangent * surface.barycentric[2];
|
||||
triangle.point.position = points[0].position * surface.barycentric[0] + points[1].position * surface.barycentric[1] + points[2].position * surface.barycentric[2];
|
||||
triangle.point.normal = points[0].normal * surface.barycentric[0] + points[1].normal * surface.barycentric[1] + points[2].normal * surface.barycentric[2];
|
||||
triangle.point.uv = points[0].uv * surface.barycentric[0] + points[1].uv * surface.barycentric[1] + points[2].uv * surface.barycentric[2];
|
||||
triangle.point.st = points[0].st * surface.barycentric[0] + points[1].st * surface.barycentric[1] + points[2].st * surface.barycentric[2];
|
||||
triangle.point.tangent = points[0].tangent * surface.barycentric[0] + points[1].tangent * surface.barycentric[1] + points[2].tangent * surface.barycentric[2];
|
||||
|
||||
|
||||
// bind position (seems to muck with the skybox + fog)
|
||||
#if 0 && BARYCENTRIC_CALCULATE
|
||||
@ -490,11 +495,11 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) {
|
||||
// surface.normal.eye = vec3( VIEW_MATRIX * vec4(surface.normal.world, 0.0) );
|
||||
}
|
||||
// bind tangent
|
||||
if ( triangle.point.tangent != vec3(0) ) {
|
||||
surface.tangent.world = normalize(vec3( surface.object.model * vec4(triangle.point.tangent, 0.0) ));
|
||||
{
|
||||
surface.tangent.world = vec3( surface.object.model * vec4(triangle.point.tangent.xyz, 0.0) );
|
||||
surface.tangent.world = normalize(surface.tangent.world - dot(surface.tangent.world, surface.normal.world) * surface.normal.world);
|
||||
vec3 bitangent = normalize(cross(surface.normal.world, surface.tangent.world)) * sign(triangle.point.tangent.w);
|
||||
|
||||
vec3 bitangent = normalize(vec3( surface.object.model * vec4(cross( triangle.point.normal, triangle.point.tangent ) * uvHandedness, 0.0) ));
|
||||
surface.tbn = mat3(surface.tangent.world, bitangent, surface.normal.world);
|
||||
}
|
||||
// bind UVs
|
||||
@ -598,13 +603,7 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) {
|
||||
#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;
|
||||
@ -636,7 +635,7 @@ void populateSurface( uint instanceID, uint primitiveID ) {
|
||||
|
||||
|
||||
#pragma unroll 3
|
||||
for ( uint _ = 0; _ < 3; ++_ ) /*triangle.*/indices[_] += drawCommand.vertexID;
|
||||
for ( uint _ = 0; _ < 3; ++_ ) indices[_] += drawCommand.vertexID;
|
||||
|
||||
populateSurface( addresses, indices );
|
||||
}
|
||||
|
||||
@ -303,7 +303,7 @@ struct Vertex {
|
||||
uint color;
|
||||
vec2 st;
|
||||
vec3 normal;
|
||||
vec3 tangent;
|
||||
vec4 tangent;
|
||||
uvec2 joints;
|
||||
vec4 weights;
|
||||
};
|
||||
|
||||
@ -179,6 +179,21 @@ void postProcess() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
const Material material = materials[surface.instance.materialID >= materials.length() ? 0 : surface.instance.materialID];
|
||||
|
||||
int cubemapIndex = -1;
|
||||
if ( 0 <= surface.instance.cubemapID ) cubemapIndex = surface.instance.cubemapID;
|
||||
else if ( 0 <= material.indexCubemap ) cubemapIndex = material.indexCubemap;
|
||||
|
||||
if ( 0 <= cubemapIndex ) {
|
||||
const Texture texture = textures[cubemapIndex];
|
||||
outFragColor.rgb = textureLod(samplerCubemaps[nonuniformEXT(texture.index)], surface.ray.direction, 0).rgb;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
IMAGE_STORE( imageColor, outFragColor );
|
||||
//IMAGE_STORE( imageBright, outFragBright );
|
||||
IMAGE_STORE( imageMotion, vec4(outFragMotion, 0, 0) );
|
||||
|
||||
@ -10,7 +10,7 @@ layout (location = 1) in vec2 inUv;
|
||||
layout (location = 2) in vec4 inColor;
|
||||
layout (location = 3) in vec2 inSt;
|
||||
layout (location = 4) in vec3 inNormal;
|
||||
layout (location = 5) in vec3 inTangent;
|
||||
layout (location = 5) in vec4 inTangent;
|
||||
#if SKINNED
|
||||
layout (location = 6) in uvec4 inJoints;
|
||||
layout (location = 7) in vec4 inWeights;
|
||||
@ -104,6 +104,6 @@ void main() {
|
||||
outSt = inSt;
|
||||
outColor = inColor * object.color;
|
||||
outNormal = normalize(vec3(model * vec4(inNormal.xyz, 0.0)));
|
||||
outTangent = normalize(vec3(model * vec4(inTangent.xyz, 0.0)));
|
||||
outTangent = normalize(vec3(model * vec4(inTangent)));
|
||||
outBitangent = normalize(vec3(model * vec4(cross( inNormal.xyz, inTangent.xyz ), 0.0)));
|
||||
}
|
||||
@ -7,7 +7,7 @@ namespace uf {
|
||||
pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 };
|
||||
pod::Vector2f st{};
|
||||
pod::Vector3f normal{};
|
||||
pod::Vector3f tangent{};
|
||||
pod::Vector4f tangent{};
|
||||
|
||||
static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
|
||||
static UF_API Base interpolate( const Base& p1, const Base& p2, float t );
|
||||
@ -18,7 +18,7 @@ namespace uf {
|
||||
pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 };
|
||||
pod::Vector2f st{};
|
||||
pod::Vector3f normal{};
|
||||
pod::Vector3f tangent{};
|
||||
pod::Vector4f tangent{};
|
||||
pod::Vector4us joints{};
|
||||
pod::Vector4f weights{};
|
||||
|
||||
@ -32,7 +32,7 @@ namespace uf {
|
||||
pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 };
|
||||
pod::Vector2f16 st{};
|
||||
pod::Vector3f16 normal{};
|
||||
pod::Vector3f16 tangent{};
|
||||
pod::Vector4f16 tangent{};
|
||||
|
||||
static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
|
||||
static UF_API Base_16f interpolate( const Base_16f& p1, const Base_16f& p2, float t );
|
||||
@ -43,7 +43,7 @@ namespace uf {
|
||||
pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 };
|
||||
pod::Vector2f16 st{};
|
||||
pod::Vector3f16 normal{};
|
||||
pod::Vector3f16 tangent{};
|
||||
pod::Vector4f16 tangent{};
|
||||
pod::Vector4us joints{};
|
||||
pod::Vector3f16 weights{};
|
||||
|
||||
@ -57,7 +57,7 @@ namespace uf {
|
||||
pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 };
|
||||
pod::Vector2us st{};
|
||||
pod::Vector3us normal{};
|
||||
pod::Vector3us tangent{};
|
||||
pod::Vector4us tangent{};
|
||||
|
||||
static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
|
||||
static UF_API Base_u16q interpolate( const Base_u16q& p1, const Base_u16q& p2, float t );
|
||||
@ -68,7 +68,7 @@ namespace uf {
|
||||
pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 };
|
||||
pod::Vector2us st{};
|
||||
pod::Vector3us normal{};
|
||||
pod::Vector3us tangent{};
|
||||
pod::Vector4us tangent{};
|
||||
pod::Vector4us joints{};
|
||||
pod::Vector3us weights{};
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ namespace pod {
|
||||
size_t bpp = 8 * 4;
|
||||
size_t channels = 4;
|
||||
size_t format = 0;
|
||||
size_t layers = 1;
|
||||
};
|
||||
}
|
||||
|
||||
@ -34,6 +35,8 @@ namespace uf {
|
||||
pod::Image::pixel_t UF_API at( pod::Image&, const pod::Vector2ui& at );
|
||||
uf::stl::string UF_API hash( const pod::Image& );
|
||||
|
||||
void UF_API layers( pod::Image&, size_t );
|
||||
|
||||
void UF_API flip( pod::Image& );
|
||||
void UF_API padToPowerOfTwo( pod::Image& );
|
||||
void UF_API convert( pod::Image&, const uf::stl::string&, const uf::stl::string& = "rgba" );
|
||||
@ -92,6 +95,8 @@ namespace uf {
|
||||
uf::stl::string getHash() const;
|
||||
size_t getFormat() const;
|
||||
|
||||
void setLayers( size_t );
|
||||
|
||||
Image::pixel_t at( const pod::Vector2ui& at );
|
||||
|
||||
// Modifiers
|
||||
|
||||
@ -795,41 +795,21 @@ void uf::mesh::tangents( uf::stl::vector<T>& vertices ) {
|
||||
auto uv20 = uv2 - uv0;
|
||||
|
||||
auto det = (uv10.x * uv20.y - uv10.y * uv20.x);
|
||||
float r = 1.0f / det;
|
||||
|
||||
float r = (det != 0.0f) ? (1.0f / det) : 0.0f;
|
||||
|
||||
auto t = (p10 * uv20.y - p20 * uv10.y) * r;
|
||||
auto b = (p20 * uv10.x - p10 * uv20.x) * r;
|
||||
|
||||
for ( auto j = 0; j < 3; ++j ) {
|
||||
auto& n = vertices[idx[j]].normal;
|
||||
auto& tangent = vertices[idx[j]].tangent;
|
||||
tangent = uf::vector::normalize(t - n * uf::vector::dot(n, t));
|
||||
if ( uf::vector::dot( uf::vector::cross(n, tangent), b) < 0.0f ) tangent = -tangent;
|
||||
|
||||
auto t_ortho = uf::vector::normalize(t - n * uf::vector::dot(n, t));
|
||||
float w = (uf::vector::dot( uf::vector::cross(n, t_ortho), b) < 0.0f) ? -1.0f : 1.0f;
|
||||
|
||||
tangent = { t_ortho.x, t_ortho.y, t_ortho.z, w };
|
||||
}
|
||||
/*
|
||||
pod::Vector3f position[3] = {
|
||||
vertices[idx[0]].position, vertices[idx[1]].position, vertices[idx[2]].position
|
||||
};
|
||||
pod::Vector2f uv[3] = {
|
||||
vertices[idx[0]].uv, vertices[idx[1]].uv, vertices[idx[2]].uv
|
||||
};
|
||||
|
||||
pod::Vector3f dPosition[2] = { position[1] - position[0], position[2] - position[0] };
|
||||
pod::Vector2f dUV[2] = { uv[1] - uv[0], uv[2] - uv[0] };
|
||||
|
||||
float det = (dUV[0].x * dUV[1].y - dUV[0].y * dUV[1].x);
|
||||
if ( det == 0.0f ) continue;
|
||||
float r = 1.0f / det;
|
||||
|
||||
auto t = (dPosition[0] * dUV[1].y - dPosition[1] * dUV[0].y) * r;
|
||||
auto b = (dPosition[1] * dUV[0].x - dPosition[0] * dUV[1].x) * r;
|
||||
|
||||
for ( auto j = 0; j < 3; ++j ) {
|
||||
auto& normal = vertices[idx[j]].normal;
|
||||
auto& tangent = vertices[idx[j]].tangent;
|
||||
tangent = uf::vector::normalize(t - normal * uf::vector::dot(normal, t));
|
||||
if ( uf::vector::dot(uf::vector::cross(normal, tangent), b) < 0.0f ) tangent = -tangent;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -856,36 +836,21 @@ void uf::mesh::tangents( uf::stl::vector<T>& vertices, const uf::stl::vector<U>&
|
||||
auto uv20 = uv2 - uv0;
|
||||
|
||||
auto det = (uv10.x * uv20.y - uv10.y * uv20.x);
|
||||
float r = 1.0f / det;
|
||||
|
||||
float r = (det != 0.0f) ? (1.0f / det) : 0.0f;
|
||||
|
||||
auto t = (p10 * uv20.y - p20 * uv10.y) * r;
|
||||
auto b = (p20 * uv10.x - p10 * uv20.x) * r;
|
||||
|
||||
for ( auto j = 0; j < 3; ++j ) {
|
||||
auto& n = vertices[idx[j]].normal;
|
||||
auto& tangent = vertices[idx[j]].tangent;
|
||||
tangent = uf::vector::normalize(t - n * uf::vector::dot(n, t));
|
||||
if ( uf::vector::dot( uf::vector::cross(n, tangent), b) < 0.0f ) tangent = -tangent;
|
||||
|
||||
auto t_ortho = uf::vector::normalize(t - n * uf::vector::dot(n, t));
|
||||
float w = (uf::vector::dot( uf::vector::cross(n, t_ortho), b) < 0.0f) ? -1.0f : 1.0f;
|
||||
|
||||
tangent = { t_ortho.x, t_ortho.y, t_ortho.z, w };
|
||||
}
|
||||
/*
|
||||
pod::Vector3f position[3] = { vertices[idx[0]].position, vertices[idx[1]].position, vertices[idx[2]].position };
|
||||
pod::Vector2f uv[3] = { vertices[idx[0]].uv, vertices[idx[1]].uv, vertices[idx[2]].uv };
|
||||
pod::Vector3f dPosition[2] = { position[1] - position[0], position[2] - position[0] };
|
||||
pod::Vector2f dUV[2] = { uv[1] - uv[0], uv[2] - uv[0] };
|
||||
|
||||
float det = (dUV[0].x * dUV[1].y - dUV[0].y * dUV[1].x);
|
||||
if ( det == 0.0f ) continue;
|
||||
float r = 1.0f / det;
|
||||
|
||||
auto t = (dPosition[0] * dUV[1].y - dPosition[1] * dUV[0].y) * r;
|
||||
auto b = (dPosition[1] * dUV[0].x - dPosition[0] * dUV[1].x) * r;
|
||||
|
||||
for ( auto j = 0; j < 3; ++j ) {
|
||||
auto& normal = vertices[idx[j]].normal;
|
||||
auto& tangent = vertices[idx[j]].tangent;
|
||||
tangent = uf::vector::normalize(t - normal * uf::vector::dot(normal, t));
|
||||
if ( uf::vector::dot(uf::vector::cross(normal, tangent), b) < 0.0f ) tangent = -tangent;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -71,13 +71,13 @@ void uf::asset::processQueue() {
|
||||
bool async = uf::asset::asyncQueue; // a bit buggy
|
||||
auto tasks = uf::thread::schedule(async ? uf::thread::asyncThreadName : uf::thread::mainThreadName, !true);
|
||||
|
||||
if ( !finishedJobs.empty() ) {
|
||||
tasks.queue([jobs = std::move(finishedJobs)]() {
|
||||
for ( auto& job : jobs ) {
|
||||
uf::hooks.call( job.callback, job.payload );
|
||||
}
|
||||
});
|
||||
}
|
||||
if ( !finishedJobs.empty() ) {
|
||||
tasks.queue([jobs = std::move(finishedJobs)]() {
|
||||
for ( auto& job : jobs ) {
|
||||
uf::hooks.call( job.callback, job.payload );
|
||||
}
|
||||
});
|
||||
}
|
||||
for ( auto& job : jobs ) tasks.queue([=]{
|
||||
auto callback = job.callback;
|
||||
auto type = job.type;
|
||||
|
||||
@ -152,14 +152,14 @@ void uf::graph::animate( pod::Graph& graph, const uf::stl::string& _name, float
|
||||
uf::stl::string name = key + _name;
|
||||
|
||||
if ( storage.animations.map.count( name ) == 0 ) {
|
||||
::loadAnimation( graph, name );
|
||||
::loadAnimation( graph, name );
|
||||
}
|
||||
if ( storage.animations.map.count( name ) > 0 ) {
|
||||
auto& animation = storage.animations.map[name];
|
||||
|
||||
if ( !animation.samplers.empty() && animation.samplers[0].inputs.empty() ) {
|
||||
::loadAnimation( graph, name );
|
||||
}
|
||||
if ( !animation.samplers.empty() && animation.samplers[0].inputs.empty() ) {
|
||||
::loadAnimation( graph, name );
|
||||
}
|
||||
|
||||
// if already playing, ignore it
|
||||
if ( !graph.sequence.empty() && graph.sequence.front() == name ) return;
|
||||
@ -262,7 +262,7 @@ void uf::graph::updateAnimation( pod::Graph& graph, pod::Node& node ) {
|
||||
auto& skinName = graph.skins[node.skin];
|
||||
auto& skin = storage.skins[skinName];
|
||||
auto objectKeyName = ::keyedID(node.object);
|
||||
auto& joints = storage.joints[objectKeyName];
|
||||
auto& joints = storage.joints[objectKeyName];
|
||||
joints.resize( skin.joints.size() );
|
||||
for ( size_t i = 0; i < skin.joints.size(); ++i ) {
|
||||
auto nodeID = skin.joints[i];
|
||||
|
||||
@ -29,7 +29,7 @@ namespace {
|
||||
uf::Image image;
|
||||
|
||||
uf::stl::string filename = "";
|
||||
size_t offset = 0, length = 0;
|
||||
size_t offset = 0, length = 0, layers = json["layers"].as<size_t>(1);
|
||||
uf::stl::string formatHint = "";
|
||||
|
||||
#if UF_ENV_DREAMCAST
|
||||
@ -51,6 +51,7 @@ namespace {
|
||||
size_t channels = json["channels"].as<size_t>();
|
||||
auto pixels = uf::base64::decode( json["data"].as<uf::stl::string>() );
|
||||
image.loadFromBuffer( &pixels[0], size, bpp, channels, true );
|
||||
image.setLayers( layers );
|
||||
return image;
|
||||
}
|
||||
|
||||
@ -69,6 +70,7 @@ namespace {
|
||||
}
|
||||
|
||||
uf::image::open( image, buffer, formatHint, false );
|
||||
uf::image::layers( image, layers );
|
||||
image.setFilename(fullPath);
|
||||
}
|
||||
|
||||
@ -76,10 +78,10 @@ namespace {
|
||||
}
|
||||
|
||||
pod::Animation decodeAnimation( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& animName, const uf::stl::vector<uint8_t>& megaBuffer ) {
|
||||
pod::Animation animation = {};
|
||||
animation.name = json["name"].as(animation.name);
|
||||
animation.start = json["start"].as<float>(0.0f);
|
||||
animation.end = json["end"].as<float>(1.0f);
|
||||
pod::Animation animation = {};
|
||||
animation.name = json["name"].as(animation.name);
|
||||
animation.start = json["start"].as<float>(0.0f);
|
||||
animation.end = json["end"].as<float>(1.0f);
|
||||
|
||||
uf::stl::string binPath = "";
|
||||
if (json["buffer"].is<uf::stl::string>()) {
|
||||
|
||||
@ -406,6 +406,7 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string&
|
||||
json["offset"] = offset;
|
||||
json["length"] = length;
|
||||
}
|
||||
json["layers"] = image.layers;
|
||||
|
||||
#if UF_USE_DC_TEXCONV
|
||||
auto converted = image.scale( {32, 32}, "nearest" );
|
||||
|
||||
@ -531,7 +531,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Base,
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R8G8B8A8_UNORM, color)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32_SFLOAT, st)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32B32_SFLOAT, normal)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32B32_SFLOAT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32B32A32_SFLOAT, tangent)
|
||||
);
|
||||
// it'd be super sugoi if I could somehow macro this annoyance
|
||||
UF_VERTEX_INTERPOLATE(uf::graph::mesh::Base, {
|
||||
@ -551,7 +551,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Skinned,
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R8G8B8A8_UNORM, color)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32_SFLOAT, st)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32B32_SFLOAT, normal)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32B32_SFLOAT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32B32A32_SFLOAT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R16G16B16A16_UINT, joints)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32B32A32_SFLOAT, weights)
|
||||
);
|
||||
@ -575,7 +575,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Base_16f,
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R8G8B8A8_UNORM, color)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R16G16_SFLOAT, st)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R16G16B16_SFLOAT, normal)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R16G16B16_SFLOAT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R16G16B16A16_SFLOAT, tangent)
|
||||
);
|
||||
UF_VERTEX_INTERPOLATE(uf::graph::mesh::Base_16f, {
|
||||
return t < 0.5 ? p1 : p2;
|
||||
@ -587,7 +587,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Skinned_16f,
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R8G8B8A8_UNORM, color)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16_SFLOAT, st)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16_SFLOAT, normal)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16_SFLOAT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16A16_SFLOAT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16A16_UINT, joints)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16A16_SFLOAT, weights)
|
||||
);
|
||||
@ -602,7 +602,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Base_u16q,
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R8G8B8A8_UNORM, color)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R16G16_UINT, st)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R16G16B16_UINT, normal)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R16G16B16_UINT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R16G16B16A16_UINT, tangent)
|
||||
);
|
||||
UF_VERTEX_INTERPOLATE(uf::graph::mesh::Base_u16q, {
|
||||
return t < 0.5 ? p1 : p2;
|
||||
@ -614,7 +614,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Skinned_u16q,
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R8G8B8A8_UNORM, color)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16_UINT, st)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16_UINT, normal)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16_UINT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16A16_UINT, tangent)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16A16_UINT, joints)
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16A16_UINT, weights)
|
||||
);
|
||||
@ -946,6 +946,11 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
auto texName = graph.textures[material.indexAlbedo];
|
||||
textureDescriptors[texName].srgb = true;
|
||||
}
|
||||
|
||||
if ( (0 <= material.indexNormal && material.indexNormal < graph.textures.size() ) ) {
|
||||
auto texName = graph.textures[material.indexNormal];
|
||||
textureDescriptors[texName].srgb = false;
|
||||
}
|
||||
}
|
||||
|
||||
UF_DEBUG_TIMER_MULTITRACE("Processing images...");
|
||||
|
||||
@ -147,17 +147,17 @@ bool UF_API ext::texconv::save( const pod::Dtex& dtex, const uf::stl::string& fi
|
||||
}
|
||||
bool UF_API ext::texconv::save( const pod::Dtex& dtex, uf::stl::vector<uint8_t>& buffer ) {
|
||||
size_t totalSize = dtex.imageData.size() + dtex.paletteData.size();
|
||||
buffer.reserve(buffer.size() + totalSize);
|
||||
buffer.reserve(buffer.size() + totalSize);
|
||||
|
||||
if ( !dtex.imageData.empty() ) {
|
||||
buffer.insert( buffer.end(), dtex.imageData.begin(), dtex.imageData.end() );
|
||||
}
|
||||
if ( !dtex.imageData.empty() ) {
|
||||
buffer.insert( buffer.end(), dtex.imageData.begin(), dtex.imageData.end() );
|
||||
}
|
||||
|
||||
if ( !dtex.paletteData.empty() ) {
|
||||
buffer.insert( buffer.end(), dtex.paletteData.begin(), dtex.paletteData.end() );
|
||||
}
|
||||
if ( !dtex.paletteData.empty() ) {
|
||||
buffer.insert( buffer.end(), dtex.paletteData.begin(), dtex.paletteData.end() );
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// maintains original main()
|
||||
|
||||
@ -436,12 +436,6 @@ namespace impl {
|
||||
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];
|
||||
@ -469,10 +463,14 @@ namespace impl {
|
||||
|
||||
pod::Vector3f normal = uf::vector::normalize(uf::vector::cross(bitangent, tangent));
|
||||
|
||||
// float w = (uf::vector::dot(uf::vector::cross(normal, tangent), bitangent) < 0.0f) ? -1.0f : 1.0f;
|
||||
pod::Vector3f t = uf::vector::normalize( impl::convertPos( texInfo.textureVecs[0], 1.0f ) );
|
||||
pod::Vector3f b = uf::vector::normalize( impl::convertPos( texInfo.textureVecs[1], 1.0f ) );
|
||||
|
||||
t = uf::vector::normalize(t - normal * uf::vector::dot(normal, t));
|
||||
float w = (uf::vector::dot( uf::vector::cross(normal, t), b ) < 0.0f) ? -1.0f : 1.0f;
|
||||
|
||||
meshlet.vertices[id].normal = normal;
|
||||
meshlet.vertices[id].tangent = tangent = uf::vector::normalize(tangent - normal * uf::vector::dot(normal, tangent));
|
||||
meshlet.vertices[id].tangent = { t.x, t.y, t.z, w };
|
||||
}
|
||||
}
|
||||
|
||||
@ -520,6 +518,7 @@ namespace impl {
|
||||
v.position = pos;
|
||||
v.color = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
v.normal = normal;
|
||||
v.tangent = { 1.0f, 0.0f, 0.0f, 1.0f };
|
||||
|
||||
// has texture information
|
||||
if ( face.texinfo >= 0 && face.texinfo < context.texinfos.size() ) {
|
||||
@ -539,8 +538,10 @@ namespace impl {
|
||||
pod::Vector3f t = uf::vector::normalize( impl::convertPos( info.textureVecs[0], 1.0f ) );
|
||||
pod::Vector3f b = uf::vector::normalize( impl::convertPos( info.textureVecs[1], 1.0f ) );
|
||||
|
||||
v.tangent = uf::vector::normalize(t - normal * uf::vector::dot(normal, t));
|
||||
// float w = (uf::vector::dot( uf::vector::cross(normal, t), b ) < 0.0f) ? -1.0f : 1.0f;
|
||||
t = uf::vector::normalize(t - normal * uf::vector::dot(normal, t));
|
||||
float w = (uf::vector::dot( uf::vector::cross(normal, t), b ) < 0.0f) ? -1.0f : 1.0f;
|
||||
|
||||
v.tangent = { t.x, t.y, t.z, w };
|
||||
}
|
||||
};
|
||||
|
||||
@ -1047,10 +1048,14 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
uint16_t pivotVertID = pivotSurfEdge >= 0 ? context.edges[pivotSurfEdge].x : context.edges[-pivotSurfEdge].y;
|
||||
pod::Vector3f p0 = impl::convertPos( context.vertices[pivotVertID] );
|
||||
|
||||
/*
|
||||
// some faces are wrong when doing it this way
|
||||
const auto& plane = context.planes[face.planenum];
|
||||
pod::Vector3f faceNormal = uf::vector::normalize( impl::convertPos( plane.normal, 1.0f ) );
|
||||
if ( face.side != 0 ) faceNormal = -faceNormal;
|
||||
*/
|
||||
|
||||
pod::Vector3f faceNormal = {0.0f, 0.0f, 0.0f};
|
||||
|
||||
for ( int16_t i = 1; i < face.numedges - 1; ++i ) {
|
||||
int32_t se1 = context.surfedges[edgeID + i];
|
||||
int32_t se2 = context.surfedges[edgeID + i + 1];
|
||||
@ -1063,7 +1068,6 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
|
||||
faceNormal += uf::vector::cross(p1 - p0, p2 - p0);
|
||||
}
|
||||
|
||||
faceNormal = uf::vector::normalize(faceNormal);
|
||||
|
||||
for ( int16_t i = 1; i < face.numedges - 1; ++i ) {
|
||||
@ -1197,9 +1201,6 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
// load materials
|
||||
uf::stl::vector<uint8_t> missing_pixels = { 255, 0, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255, 255 };
|
||||
for ( auto matName : graph.materials ) {
|
||||
if ( matName == "" ) {
|
||||
continue;
|
||||
}
|
||||
uf::Serializer vmt;
|
||||
auto vmtPath = ::fmt::format("materials/{}.vmt", matName);
|
||||
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
|
||||
@ -1256,7 +1257,7 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
}
|
||||
// bumpmap
|
||||
if ( vmt["$ssbump"].as<int>(0) == 1 ) {
|
||||
material.indexNormal = -1;
|
||||
// to-do: handle bumpmaps
|
||||
// normal map
|
||||
} else if ( vmt["$bumpmap"].is<uf::stl::string>() ) {
|
||||
auto matName = uf::string::lowercase(vmt["$bumpmap"].as<uf::stl::string>());
|
||||
@ -1270,7 +1271,9 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
impl::addMaterial( graph, matName, textureID );
|
||||
|
||||
auto& image = storage.images[matName].data;
|
||||
if ( ext::valve::loadVtf( image, vtfPath ) ) material.indexNormal = textureID;
|
||||
if ( ext::valve::loadVtf( image, vtfPath ) ) {
|
||||
material.indexNormal = textureID;
|
||||
}
|
||||
}
|
||||
}
|
||||
// metallic/roughness/occlusion map
|
||||
|
||||
@ -34,6 +34,11 @@ size_t impl::addMaterial( pod::Graph& graph, const uf::stl::string& name, int32_
|
||||
material.factorMetallic = 0.0f;
|
||||
material.factorRoughness = 1.0f;
|
||||
material.factorOcclusion = 1.0f;
|
||||
material.indexNormal = -1;
|
||||
material.indexEmissive = -1;
|
||||
material.indexMetallicRoughness = -1;
|
||||
material.indexOcclusion = -1;
|
||||
material.indexCubemap = -1;
|
||||
|
||||
return materialID;
|
||||
}
|
||||
|
||||
@ -320,66 +320,69 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) {
|
||||
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];
|
||||
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;
|
||||
auto& meshlet = meshlets.emplace_back();
|
||||
uf::stl::unordered_map<uint16_t, uint32_t> vertRemap;
|
||||
|
||||
const impl::vtxStripGroup_t* stripGroups = (const impl::vtxStripGroup_t*)((uint8_t*)&mesh + mesh.stripGroupHeaderOffset);
|
||||
for ( int sg = 0; sg < mesh.numStripGroups; ++sg ) {
|
||||
const impl::vtxStripGroup_t& stripGroup = stripGroups[sg];
|
||||
const impl::vtxStripGroup_t* stripGroups = (const impl::vtxStripGroup_t*)((uint8_t*)&mesh + mesh.stripGroupHeaderOffset);
|
||||
for ( int sg = 0; sg < mesh.numStripGroups; ++sg ) {
|
||||
const impl::vtxStripGroup_t& stripGroup = stripGroups[sg];
|
||||
|
||||
const uint16_t* indices = (const uint16_t*)((uint8_t*)&stripGroup + stripGroup.indexOffset);
|
||||
const impl::vtxVertex_t* vtxVerts = (const impl::vtxVertex_t*)((uint8_t*)&stripGroup + stripGroup.vertOffset);
|
||||
const uint16_t* indices = (const uint16_t*)((uint8_t*)&stripGroup + stripGroup.indexOffset);
|
||||
const impl::vtxVertex_t* vtxVerts = (const impl::vtxVertex_t*)((uint8_t*)&stripGroup + stripGroup.vertOffset);
|
||||
|
||||
for ( int i = 0; i < stripGroup.numIndices; i += 3 ) {
|
||||
uint32_t tri[3];
|
||||
for ( int j = 0; j < 3; ++j ) {
|
||||
uint16_t localVertIndex = indices[i + j];
|
||||
const impl::vtxVertex_t& vtxVert = vtxVerts[localVertIndex];
|
||||
uint16_t originalVvdID = vtxVert.origMeshVertID;
|
||||
for ( int i = 0; i < stripGroup.numIndices; i += 3 ) {
|
||||
uint32_t tri[3];
|
||||
for ( int j = 0; j < 3; ++j ) {
|
||||
uint16_t localVertIndex = indices[i + j];
|
||||
const impl::vtxVertex_t& vtxVert = vtxVerts[localVertIndex];
|
||||
uint16_t originalVvdID = vtxVert.origMeshVertID;
|
||||
|
||||
if ( vertRemap.find(originalVvdID) == vertRemap.end() ) {
|
||||
vertRemap[originalVvdID] = meshlet.vertices.size();
|
||||
auto& vert = meshlet.vertices.emplace_back();
|
||||
if ( vertRemap.find(originalVvdID) == vertRemap.end() ) {
|
||||
vertRemap[originalVvdID] = meshlet.vertices.size();
|
||||
auto& vert = meshlet.vertices.emplace_back();
|
||||
|
||||
const auto& srcVert = lod0Vertices[mdlMesh.vertexoffset + 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 ) );
|
||||
vert.position = impl::convertPos( srcVert.m_vecPosition );
|
||||
vert.normal = uf::vector::normalize( impl::convertPos( srcVert.m_vecNormal, 1.0f ) );
|
||||
vert.tangent = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
if ( !lod0Tangents.empty() ) {
|
||||
vert.tangent = uf::vector::normalize( impl::convertPos( lod0Tangents[mdlMesh.vertexoffset + originalVvdID], 1.0f ) );
|
||||
}
|
||||
if ( !lod0Tangents.empty() ) {
|
||||
auto srcTangent = lod0Tangents[mdlMesh.vertexoffset + originalVvdID];
|
||||
pod::Vector3f tangent = uf::vector::normalize( impl::convertPos( srcTangent, 1.0f ) );
|
||||
vert.tangent = { tangent.x, tangent.y, tangent.z, srcTangent.w }; // might need to -w
|
||||
}
|
||||
|
||||
vert.uv = srcVert.m_vecTexCoord;
|
||||
vert.color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
vert.joints.x = srcVert.m_BoneWeights.numbones > 0 ? std::max<int8_t>(0, srcVert.m_BoneWeights.bone[0]) : 0;
|
||||
vert.joints.y = srcVert.m_BoneWeights.numbones > 1 ? std::max<int8_t>(0, srcVert.m_BoneWeights.bone[1]) : 0;
|
||||
vert.joints.z = srcVert.m_BoneWeights.numbones > 2 ? std::max<int8_t>(0, srcVert.m_BoneWeights.bone[2]) : 0;
|
||||
vert.joints.w = 0;
|
||||
vert.uv = srcVert.m_vecTexCoord;
|
||||
vert.color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
vert.joints.x = srcVert.m_BoneWeights.numbones > 0 ? std::max<int8_t>(0, srcVert.m_BoneWeights.bone[0]) : 0;
|
||||
vert.joints.y = srcVert.m_BoneWeights.numbones > 1 ? std::max<int8_t>(0, srcVert.m_BoneWeights.bone[1]) : 0;
|
||||
vert.joints.z = srcVert.m_BoneWeights.numbones > 2 ? std::max<int8_t>(0, srcVert.m_BoneWeights.bone[2]) : 0;
|
||||
vert.joints.w = 0;
|
||||
|
||||
vert.weights.x = srcVert.m_BoneWeights.numbones > 0 ? srcVert.m_BoneWeights.weight[0] : 1.0f;
|
||||
vert.weights.y = srcVert.m_BoneWeights.numbones > 1 ? srcVert.m_BoneWeights.weight[1] : 0.0f;
|
||||
vert.weights.z = srcVert.m_BoneWeights.numbones > 2 ? srcVert.m_BoneWeights.weight[2] : 0.0f;
|
||||
vert.weights.w = 0.0f;
|
||||
vert.weights.x = srcVert.m_BoneWeights.numbones > 0 ? srcVert.m_BoneWeights.weight[0] : 1.0f;
|
||||
vert.weights.y = srcVert.m_BoneWeights.numbones > 1 ? srcVert.m_BoneWeights.weight[1] : 0.0f;
|
||||
vert.weights.z = srcVert.m_BoneWeights.numbones > 2 ? srcVert.m_BoneWeights.weight[2] : 0.0f;
|
||||
vert.weights.w = 0.0f;
|
||||
|
||||
auto& bounds = meshlet.primitive.instance.bounds;
|
||||
if ( vertRemap.size() == 1 ) {
|
||||
bounds.min = bounds.max = vert.position;
|
||||
} else {
|
||||
bounds.min = uf::vector::min( bounds.min, vert.position );
|
||||
bounds.max = uf::vector::max( bounds.max, vert.position );
|
||||
}
|
||||
}
|
||||
auto& bounds = meshlet.primitive.instance.bounds;
|
||||
if ( vertRemap.size() == 1 ) {
|
||||
bounds.min = bounds.max = vert.position;
|
||||
} else {
|
||||
bounds.min = uf::vector::min( bounds.min, vert.position );
|
||||
bounds.max = uf::vector::max( bounds.max, vert.position );
|
||||
}
|
||||
}
|
||||
|
||||
meshlet.indices.push_back(tri[j] = vertRemap[originalVvdID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
meshlet.indices.emplace_back(tri[j] = vertRemap[originalVvdID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( lod0Tangents.empty() ) uf::mesh::tangents( meshlet.vertices, meshlet.indices );
|
||||
if ( lod0Tangents.empty() ) uf::mesh::tangents( meshlet.vertices, meshlet.indices );
|
||||
|
||||
size_t materialID = 0;
|
||||
uf::stl::string matName = "missing_texture";
|
||||
|
||||
@ -6,13 +6,80 @@
|
||||
|
||||
namespace impl {
|
||||
constexpr uint32_t IMAGE_FORMAT_RGBA8888 = 0;
|
||||
constexpr uint32_t IMAGE_FORMAT_RGB888 = 2;
|
||||
constexpr uint32_t IMAGE_FORMAT_BGR888 = 3;
|
||||
constexpr uint32_t IMAGE_FORMAT_BGR565 = 4;
|
||||
constexpr uint32_t IMAGE_FORMAT_BGRX8888 = 11;
|
||||
constexpr uint32_t IMAGE_FORMAT_BGRA8888 = 12;
|
||||
constexpr uint32_t IMAGE_FORMAT_DXT1 = 13;
|
||||
constexpr uint32_t IMAGE_FORMAT_DXT3 = 14;
|
||||
constexpr uint32_t IMAGE_FORMAT_DXT5 = 15;
|
||||
constexpr uint32_t IMAGE_FORMAT_RGBA16161616F = 24;
|
||||
|
||||
constexpr uint32_t TEXTUREFLAGS_ENVMAP = 0x00002000;
|
||||
constexpr uint32_t TEXTUREFLAGS_NORMAL = 0x00000080;
|
||||
constexpr uint32_t MATERIAL_VAR_DEBUG = 0x0001; // $debug
|
||||
constexpr uint32_t MATERIAL_VAR_NO_DEBUG_OVERRIDE = 0x0002; // $no_fullbright
|
||||
constexpr uint32_t MATERIAL_VAR_NO_DRAW = 0x0004; // $no_draw
|
||||
constexpr uint32_t MATERIAL_VAR_USE_IN_FILLRATE_MODE = 0x0008; // $use_in_fillrate_mode
|
||||
constexpr uint32_t MATERIAL_VAR_VERTEXCOLOR = 0x0010; // $vertexcolor
|
||||
constexpr uint32_t MATERIAL_VAR_VERTEXALPHA = 0x0020; // $vertexalpha
|
||||
constexpr uint32_t MATERIAL_VAR_SELFILLUM = 0x0040; // $selfillum
|
||||
constexpr uint32_t MATERIAL_VAR_ADDITIVE = 0x0080; // $additive
|
||||
constexpr uint32_t MATERIAL_VAR_ALPHATEST = 0x0100; // $alphatest
|
||||
constexpr uint32_t MATERIAL_VAR_MULTIPASS = 0x0200; // $multipass
|
||||
constexpr uint32_t MATERIAL_VAR_ZNEARER = 0x0400; // $znearer
|
||||
constexpr uint32_t MATERIAL_VAR_MODEL = 0x0800; // $model
|
||||
constexpr uint32_t MATERIAL_VAR_FLAT = 0x1000; // $flat
|
||||
constexpr uint32_t MATERIAL_VAR_NOCULL = 0x2000; // $nocull
|
||||
constexpr uint32_t MATERIAL_VAR_NOFOG = 0x4000; // $nofog
|
||||
constexpr uint32_t MATERIAL_VAR_IGNOREZ = 0x8000; // $ignorez
|
||||
constexpr uint32_t MATERIAL_VAR_DECAL = 0x10000; // $decal
|
||||
constexpr uint32_t MATERIAL_VAR_ENVMAPSPHERE = 0x20000; // $envmapsphere
|
||||
constexpr uint32_t MATERIAL_VAR_NOALPHAMOD = 0x40000; // $noalphamod
|
||||
constexpr uint32_t MATERIAL_VAR_ENVMAPCAMERASPACE = 0x80000; // $envmapcameraspace
|
||||
constexpr uint32_t MATERIAL_VAR_BASEALPHAENVMAPMASK = 0x100000; // $basealphaenvmapmask
|
||||
constexpr uint32_t MATERIAL_VAR_TRANSLUCENT = 0x200000; // $translucent
|
||||
constexpr uint32_t MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK = 0x400000; // $normalmapalphaenvmapmask
|
||||
constexpr uint32_t MATERIAL_VAR_NEEDS_SOFTWARE_SKINNING = 0x800000; // $softwareskin
|
||||
constexpr uint32_t MATERIAL_VAR_OPAQUETEXTURE = 0x1000000; // $opaquetexture
|
||||
constexpr uint32_t MATERIAL_VAR_ENVMAPMODE = 0x2000000; // $envmapmode
|
||||
constexpr uint32_t MATERIAL_VAR_SUPPRESS_DECALS = 0x4000000; // $nodecal
|
||||
constexpr uint32_t MATERIAL_VAR_HALFLAMBERT = 0x8000000; // $halflambert
|
||||
constexpr uint32_t MATERIAL_VAR_WIREFRAME = 0x10000000; // $wireframe
|
||||
constexpr uint32_t MATERIAL_VAR_ALLOWALPHATOCOVERAGE = 0x20000000; // $allowalphatocoverage
|
||||
constexpr uint32_t MATERIAL_VAR_IGNORE_ALPHA_MODULATION = 0x40000000; //
|
||||
|
||||
constexpr uint32_t TEXTUREFLAGS_POINTSAMPLE = 1;
|
||||
constexpr uint32_t TEXTUREFLAGS_TRILINEAR = 2;
|
||||
constexpr uint32_t TEXTUREFLAGS_CLAMPS = 4;
|
||||
constexpr uint32_t TEXTUREFLAGS_CLAMPT = 8;
|
||||
constexpr uint32_t TEXTUREFLAGS_ANISOTROPIC = 16;
|
||||
constexpr uint32_t TEXTUREFLAGS_HINT_DXT5 = 32;
|
||||
constexpr uint32_t TEXTUREFLAGS_PWL_CORRECTED = 64;
|
||||
constexpr uint32_t TEXTUREFLAGS_NORMAL = 128;
|
||||
constexpr uint32_t TEXTUREFLAGS_NOMIP = 256;
|
||||
constexpr uint32_t TEXTUREFLAGS_NOLOD = 512;
|
||||
constexpr uint32_t TEXTUREFLAGS_ALL_MIPS = 1024;
|
||||
constexpr uint32_t TEXTUREFLAGS_PROCEDURAL = 2048;
|
||||
constexpr uint32_t TEXTUREFLAGS_ONEBITALPHA = 4096;
|
||||
constexpr uint32_t TEXTUREFLAGS_EIGHTBITALPHA = 8192;
|
||||
constexpr uint32_t TEXTUREFLAGS_ENVMAP = 16384;
|
||||
constexpr uint32_t TEXTUREFLAGS_RENDERTARGET = 32768;
|
||||
constexpr uint32_t TEXTUREFLAGS_DEPTHRENDERTARGET = 65536;
|
||||
constexpr uint32_t TEXTUREFLAGS_NODEBUGOVERRIDE = 131072;
|
||||
constexpr uint32_t TEXTUREFLAGS_SINGLECOPY = 262144;
|
||||
constexpr uint32_t TEXTUREFLAGS_STAGING_MEMORY = 524288;
|
||||
constexpr uint32_t TEXTUREFLAGS_IMMEDIATE_CLEANUP = 1048576;
|
||||
constexpr uint32_t TEXTUREFLAGS_IGNORE_PICMIP = 2097152;
|
||||
constexpr uint32_t TEXTUREFLAGS_UNUSED_00400000 = 4194304;
|
||||
constexpr uint32_t TEXTUREFLAGS_NODEPTHBUFFER = 8388608;
|
||||
constexpr uint32_t TEXTUREFLAGS_UNUSED_01000000 = 16777216;
|
||||
constexpr uint32_t TEXTUREFLAGS_CLAMPU = 33554432;
|
||||
constexpr uint32_t TEXTUREFLAGS_VERTEXTEXTURE = 67108864;
|
||||
constexpr uint32_t TEXTUREFLAGS_SSBUMP = 134217728;
|
||||
constexpr uint32_t TEXTUREFLAGS_UNUSED_10000000 = 268435456;
|
||||
constexpr uint32_t TEXTUREFLAGS_BORDER = 536870912;
|
||||
constexpr uint32_t TEXTUREFLAGS_STREAMABLE_COARSE = 1073741824;
|
||||
constexpr uint32_t TEXTUREFLAGS_STREAMABLE_FINE = 2147483648;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct VTFHeader {
|
||||
@ -35,6 +102,32 @@ namespace impl {
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
inline size_t getMipSize( uint32_t format, int width, int height ) {
|
||||
int blocksX = (width + 3) / 4;
|
||||
int blocksY = (height + 3) / 4;
|
||||
|
||||
switch ( format ) {
|
||||
case impl::IMAGE_FORMAT_DXT1:
|
||||
return blocksX * blocksY * 8;
|
||||
case impl::IMAGE_FORMAT_DXT3:
|
||||
case impl::IMAGE_FORMAT_DXT5:
|
||||
return blocksX * blocksY * 16;
|
||||
case impl::IMAGE_FORMAT_BGR888:
|
||||
case impl::IMAGE_FORMAT_RGB888:
|
||||
return width * height * 3;
|
||||
case impl::IMAGE_FORMAT_BGRA8888:
|
||||
case impl::IMAGE_FORMAT_BGRX8888:
|
||||
case impl::IMAGE_FORMAT_RGBA8888:
|
||||
return width * height * 4;
|
||||
case impl::IMAGE_FORMAT_BGR565:
|
||||
return width * height * 2;
|
||||
case impl::IMAGE_FORMAT_RGBA16161616F:
|
||||
return width * height * 8;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// to-do: cram this inside the image functions
|
||||
inline void decodeRGB565( uint16_t color, uint8_t& r, uint8_t& g, uint8_t& b ) {
|
||||
r = (uint8_t)(((color >> 11) & 0x1F) * 255 / 31);
|
||||
@ -42,6 +135,32 @@ namespace impl {
|
||||
b = (uint8_t)((color & 0x1F) * 255 / 31);
|
||||
}
|
||||
|
||||
inline uint8_t halfTo8Bit(uint16_t h) {
|
||||
uint32_t exp = (h >> 10) & 0x1F;
|
||||
uint32_t mant = h & 0x3FF;
|
||||
if (exp == 0) return 0;
|
||||
if (exp == 31) return 255;
|
||||
|
||||
exp = exp + (127 - 15);
|
||||
uint32_t f = (exp << 23) | (mant << 13);
|
||||
float val;
|
||||
std::memcpy(&val, &f, sizeof(float));
|
||||
|
||||
return (uint8_t)(std::min(std::max(val, 0.0f), 1.0f) * 255.0f);
|
||||
}
|
||||
|
||||
inline float halfToFloat(uint16_t h) {
|
||||
uint32_t exp = (h >> 10) & 0x1F;
|
||||
uint32_t mant = h & 0x3FF;
|
||||
if (exp == 0 && mant == 0) return 0.0f;
|
||||
|
||||
exp = exp + (127 - 15);
|
||||
uint32_t f = (exp << 23) | (mant << 13);
|
||||
float val;
|
||||
std::memcpy(&val, &f, sizeof(float));
|
||||
return val;
|
||||
}
|
||||
|
||||
void decompressDXT1Block( const uint8_t* block, uint8_t* out, int x, int y, int width, int height ) {
|
||||
uint16_t color0 = *(const uint16_t*)(block + 0);
|
||||
uint16_t color1 = *(const uint16_t*)(block + 2);
|
||||
@ -73,6 +192,38 @@ namespace impl {
|
||||
}
|
||||
}
|
||||
|
||||
void decompressDXT3Block(const uint8_t* block, uint8_t* out, int x, int y, int width, int height) {
|
||||
uint16_t color0 = *(const uint16_t*)(block + 8);
|
||||
uint16_t color1 = *(const uint16_t*)(block + 10);
|
||||
uint32_t colorIndices = *(const uint32_t*)(block + 12);
|
||||
|
||||
uint8_t r[4], g[4], b[4];
|
||||
decodeRGB565(color0, r[0], g[0], b[0]);
|
||||
decodeRGB565(color1, r[1], g[1], b[1]);
|
||||
|
||||
r[2] = (2 * r[0] + r[1]) / 3; g[2] = (2 * g[0] + g[1]) / 3; b[2] = (2 * b[0] + b[1]) / 3;
|
||||
r[3] = (r[0] + 2 * r[1]) / 3; g[3] = (g[0] + 2 * g[1]) / 3; b[3] = (b[0] + 2 * b[1]) / 3;
|
||||
|
||||
for ( int py = 0; py < 4; ++py ) {
|
||||
for ( int px = 0; px < 4; ++px ) {
|
||||
if (x + px >= width || y + py >= height) continue;
|
||||
|
||||
uint8_t colorIdx = (colorIndices >> ((py * 4 + px) * 2)) & 0x03;
|
||||
|
||||
int alphaOffset = (py * 4 + px) / 2;
|
||||
int alphaShift = ((py * 4 + px) % 2) * 4;
|
||||
uint8_t alpha4 = (block[alphaOffset] >> alphaShift) & 0x0F;
|
||||
uint8_t alpha = (alpha4 << 4) | alpha4;
|
||||
|
||||
int offset = ((y + py) * width + (x + px)) * 4;
|
||||
out[offset + 0] = r[colorIdx];
|
||||
out[offset + 1] = g[colorIdx];
|
||||
out[offset + 2] = b[colorIdx];
|
||||
out[offset + 3] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decompressDXT5Block(const uint8_t* block, uint8_t* out, int x, int y, int width, int height) {
|
||||
uint8_t a0 = block[0];
|
||||
uint8_t a1 = block[1];
|
||||
@ -155,158 +306,173 @@ bool ext::valve::loadVtf( pod::Image& image, const uf::stl::string& filename ) {
|
||||
const impl::VTFHeader* header = (const impl::VTFHeader*)(buffer.data());
|
||||
if ( strncmp(header->signature, "VTF", 3) != 0 ) return false;
|
||||
|
||||
bool isCubemap = (header->flags & impl::TEXTUREFLAGS_ENVMAP) != 0;
|
||||
int numFrames = std::max<int>(1, header->frames);
|
||||
switch ( header->highResImageFormat ) {
|
||||
case impl::IMAGE_FORMAT_RGBA8888:
|
||||
case impl::IMAGE_FORMAT_RGB888:
|
||||
case impl::IMAGE_FORMAT_BGR888:
|
||||
case impl::IMAGE_FORMAT_BGR565:
|
||||
case impl::IMAGE_FORMAT_BGRX8888:
|
||||
case impl::IMAGE_FORMAT_BGRA8888:
|
||||
case impl::IMAGE_FORMAT_DXT1:
|
||||
case impl::IMAGE_FORMAT_DXT3:
|
||||
case impl::IMAGE_FORMAT_DXT5:
|
||||
case impl::IMAGE_FORMAT_RGBA16161616F: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
UF_MSG_ERROR("VTF '{}' has unrecognized format: 0x{:x}", filename, header->highResImageFormat);
|
||||
} break;
|
||||
}
|
||||
|
||||
size_t singleFaceSize = 0;
|
||||
for ( int mip = header->mipmapCount - 1; mip >= 0; --mip ) {
|
||||
for ( int mip = 0; mip < header->mipmapCount; ++mip ) {
|
||||
int mipWidth = std::max(1, header->width >> mip);
|
||||
int mipHeight = std::max(1, header->height >> mip);
|
||||
int blocksX = (mipWidth + 3) / 4;
|
||||
int blocksY = (mipHeight + 3) / 4;
|
||||
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) singleFaceSize += blocksX * blocksY * 8;
|
||||
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) singleFaceSize += blocksX * blocksY * 16;
|
||||
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) singleFaceSize += mipWidth * mipHeight * 4;
|
||||
singleFaceSize += impl::getMipSize(header->highResImageFormat, mipWidth, mipHeight);
|
||||
}
|
||||
|
||||
size_t offset = header->headerSize;
|
||||
if ( header->lowResImageFormat != 0xFFFFFFFF ) {
|
||||
offset += std::max<size_t>(1, (header->lowResImageWidth * header->lowResImageHeight) / 2);
|
||||
offset += impl::getMipSize(header->lowResImageFormat, header->lowResImageWidth, header->lowResImageHeight);
|
||||
}
|
||||
|
||||
size_t remainingBytes = buffer.size() - offset;
|
||||
size_t bytesPerFace = singleFaceSize * numFrames;
|
||||
int actualFaces = bytesPerFace > 0 ? (remainingBytes / bytesPerFace) : 1;
|
||||
|
||||
if ( actualFaces == 6 || actualFaces == 7 ) {
|
||||
isCubemap = true;
|
||||
}
|
||||
|
||||
int numFaces = 1;
|
||||
if ( isCubemap ) {
|
||||
if ( actualFaces >= 6 ) {
|
||||
numFaces = actualFaces;
|
||||
} else {
|
||||
isCubemap = false;
|
||||
numFaces = 1;
|
||||
}
|
||||
int numFrames = std::max<int>(1, header->frames);
|
||||
if ( singleFaceSize > 0 && numFrames > 0 ) {
|
||||
numFaces = (buffer.size() - offset) / (singleFaceSize * numFrames);
|
||||
}
|
||||
|
||||
bool isHDR = (header->highResImageFormat == impl::IMAGE_FORMAT_RGBA16161616F);
|
||||
bool isCubemap = (header->flags & impl::TEXTUREFLAGS_ENVMAP) != 0 && numFaces >= 6;
|
||||
|
||||
for ( int mip = header->mipmapCount - 1; mip > 0; --mip ) {
|
||||
int mipWidth = std::max(1, header->width >> mip);
|
||||
int mipHeight = std::max(1, header->height >> mip);
|
||||
int blocksX = (mipWidth + 3) / 4;
|
||||
int blocksY = (mipHeight + 3) / 4;
|
||||
|
||||
size_t mipSize = 0;
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) mipSize = blocksX * blocksY * 8;
|
||||
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) mipSize = blocksX * blocksY * 16;
|
||||
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) mipSize = mipWidth * mipHeight * 4;
|
||||
|
||||
offset += mipSize * numFrames * numFaces;
|
||||
offset += impl::getMipSize(header->highResImageFormat, mipWidth, mipHeight) * numFrames * numFaces;
|
||||
}
|
||||
|
||||
int outputFaces = isCubemap ? 6 : 1;
|
||||
|
||||
int bytesPerPixel = isHDR ? 8 : 4;
|
||||
image.size = { header->width, header->height * outputFaces };
|
||||
image.channels = 4;
|
||||
image.bpp = 8 * 4;
|
||||
image.pixels.resize( header->width * header->height * 4 * outputFaces );
|
||||
image.bpp = 8 * bytesPerPixel;
|
||||
image.layers = numFrames;
|
||||
image.format = isHDR ? uf::renderer::enums::Format::R16G16B16A16_SFLOAT : uf::renderer::enums::Format::R8G8B8A8_UNORM;
|
||||
image.pixels.resize( header->width * header->height * bytesPerPixel * outputFaces * numFrames );
|
||||
|
||||
int faceSizePixels = header->width * header->height;
|
||||
int faceSize = header->width * header->height;
|
||||
int blocksX = (header->width + 3) / 4;
|
||||
int blocksY = (header->height + 3) / 4;
|
||||
|
||||
const int faceMap[6] = { 4, 5, 0, 1, 2, 3 };
|
||||
const int faceModes[6] = { 2, 0, 6, 1, 1, 3 };
|
||||
for ( int face = 0; face < outputFaces; ++face ) {
|
||||
const uint8_t* data = buffer.data() + offset;
|
||||
int mappedFace = (isCubemap && face < 6) ? faceMap[face] : face;
|
||||
uint8_t* outPixels = image.pixels.data() + (mappedFace * faceSizePixels * 4);
|
||||
for ( int frame = 0; frame < numFrames; ++frame ) {
|
||||
for ( int face = 0; face < outputFaces; ++face ) {
|
||||
const uint8_t* data = buffer.data() + offset;
|
||||
int mappedFace = (isCubemap && face < 6) ? faceMap[face] : face;
|
||||
size_t outOffset = (frame * outputFaces + mappedFace) * faceSize * bytesPerPixel;
|
||||
uint8_t* outPixels = image.pixels.data() + outOffset;
|
||||
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) {
|
||||
for ( int by = 0; by < blocksY; ++by ) {
|
||||
for ( int bx = 0; bx < blocksX; ++bx ) {
|
||||
impl::decompressDXT1Block(data + (by * blocksX + bx) * 8, outPixels, bx * 4, by * 4, header->width, header->height);
|
||||
}
|
||||
}
|
||||
offset += blocksX * blocksY * 8;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
|
||||
for ( int by = 0; by < blocksY; ++by) {
|
||||
for ( int bx = 0; bx < blocksX; ++bx) {
|
||||
impl::decompressDXT5Block(data + (by * blocksX + bx) * 16, outPixels, bx * 4, by * 4, header->width, header->height);
|
||||
}
|
||||
}
|
||||
offset += blocksX * blocksY * 16;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) {
|
||||
for ( auto i = 0; i < faceSizePixels; ++i ) {
|
||||
outPixels[i * 4 + 0] = data[i * 4 + 2];
|
||||
outPixels[i * 4 + 1] = data[i * 4 + 1];
|
||||
outPixels[i * 4 + 2] = data[i * 4 + 0];
|
||||
outPixels[i * 4 + 3] = data[i * 4 + 3];
|
||||
}
|
||||
offset += faceSizePixels * 4;
|
||||
}
|
||||
|
||||
if ( isCubemap ) {
|
||||
int mode = faceModes[mappedFace];
|
||||
if ( mode == 0 ) continue;
|
||||
uf::stl::vector<uint8_t> temp(faceSizePixels * 4);
|
||||
memcpy(temp.data(), outPixels, faceSizePixels * 4);
|
||||
|
||||
int size = header->width;
|
||||
int max = size - 1;
|
||||
|
||||
for ( int y = 0; y < size; ++y ) {
|
||||
for ( int x = 0; x < size; ++x ) {
|
||||
int srcX = x;
|
||||
int srcY = y;
|
||||
|
||||
switch ( mode ) {
|
||||
case 1: srcX = y; srcY = max - x; break; // 90 CW
|
||||
case 2: srcX = max - x; srcY = max - y; break; // 180
|
||||
case 3: srcX = max - y; srcY = x; break; // 90 CCW
|
||||
case 4: srcX = max - x; srcY = y; break; // Flip Horizontal
|
||||
case 5: srcX = x; srcY = max - y; break; // Flip Vertical
|
||||
case 6: srcX = y; srcY = x; break; // Transpose (Diagonal Flip)
|
||||
case 7: srcX = max - y; srcY = max - x; break; // Anti-Transpose
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) {
|
||||
for ( int by = 0; by < blocksY; ++by ) {
|
||||
for ( int bx = 0; bx < blocksX; ++bx ) {
|
||||
impl::decompressDXT1Block(data + (by * blocksX + bx) * 8, outPixels, bx * 4, by * 4, header->width, header->height);
|
||||
}
|
||||
}
|
||||
offset += blocksX * blocksY * 8;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT3 ) {
|
||||
for ( int by = 0; by < blocksY; ++by) {
|
||||
for ( int bx = 0; bx < blocksX; ++bx) {
|
||||
impl::decompressDXT3Block(data + (by * blocksX + bx) * 16, outPixels, bx * 4, by * 4, header->width, header->height);
|
||||
}
|
||||
}
|
||||
offset += blocksX * blocksY * 16;
|
||||
|
||||
int srcIdx = (srcY * size + srcX) * 4;
|
||||
int dstIdx = (y * size + x) * 4;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
|
||||
for ( int by = 0; by < blocksY; ++by) {
|
||||
for ( int bx = 0; bx < blocksX; ++bx) {
|
||||
impl::decompressDXT5Block(data + (by * blocksX + bx) * 16, outPixels, bx * 4, by * 4, header->width, header->height);
|
||||
}
|
||||
}
|
||||
offset += blocksX * blocksY * 16;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRX8888 || header->highResImageFormat == impl::IMAGE_FORMAT_RGBA8888 ) {
|
||||
bool isRgba = (header->highResImageFormat == impl::IMAGE_FORMAT_RGBA8888);
|
||||
for ( auto i = 0; i < faceSize; ++i ) {
|
||||
outPixels[i * 4 + 0] = data[i * 4 + (isRgba ? 0 : 2)];
|
||||
outPixels[i * 4 + 1] = data[i * 4 + 1];
|
||||
outPixels[i * 4 + 2] = data[i * 4 + (isRgba ? 2 : 0)];
|
||||
outPixels[i * 4 + 3] = isRgba ? data[i * 4 + 3] : 255;
|
||||
}
|
||||
offset += faceSize * 4;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGR888 ) {
|
||||
for ( auto i = 0; i < faceSize; ++i ) {
|
||||
outPixels[i * 4 + 0] = data[i * 3 + 2];
|
||||
outPixels[i * 4 + 1] = data[i * 3 + 1];
|
||||
outPixels[i * 4 + 2] = data[i * 3 + 0];
|
||||
outPixels[i * 4 + 3] = 255;
|
||||
}
|
||||
offset += faceSize * 3;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) {
|
||||
for ( auto i = 0; i < faceSize; ++i ) {
|
||||
outPixels[i * 4 + 0] = data[i * 4 + 2];
|
||||
outPixels[i * 4 + 1] = data[i * 4 + 1];
|
||||
outPixels[i * 4 + 2] = data[i * 4 + 0];
|
||||
outPixels[i * 4 + 3] = data[i * 4 + 3];
|
||||
}
|
||||
offset += faceSize * 4;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_RGB888 ) {
|
||||
for ( auto i = 0; i < faceSize; ++i ) {
|
||||
outPixels[i * 4 + 0] = data[i * 3 + 0];
|
||||
outPixels[i * 4 + 1] = data[i * 3 + 1];
|
||||
outPixels[i * 4 + 2] = data[i * 3 + 2];
|
||||
outPixels[i * 4 + 3] = 255;
|
||||
}
|
||||
offset += faceSize * 3;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGR565 ) {
|
||||
for ( auto i = 0; i < faceSize; ++i ) {
|
||||
uint16_t color = *(const uint16_t*)(data + i * 2);
|
||||
impl::decodeRGB565(color, outPixels[i * 4 + 0], outPixels[i * 4 + 1], outPixels[i * 4 + 2]);
|
||||
outPixels[i * 4 + 3] = 255;
|
||||
}
|
||||
offset += faceSize * 2;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_RGBA16161616F ) {
|
||||
std::memcpy(outPixels, data, faceSize * 8);
|
||||
offset += faceSize * 8;
|
||||
} else {
|
||||
UF_MSG_ERROR("VTF '{}' has unimplemented format: 0x{:x}", filename, header->highResImageFormat );
|
||||
}
|
||||
|
||||
outPixels[dstIdx + 0] = temp[srcIdx + 0];
|
||||
outPixels[dstIdx + 1] = temp[srcIdx + 1];
|
||||
outPixels[dstIdx + 2] = temp[srcIdx + 2];
|
||||
outPixels[dstIdx + 3] = temp[srcIdx + 3];
|
||||
if ( isCubemap ) {
|
||||
int mode = faceModes[mappedFace];
|
||||
if ( mode == 0 ) continue;
|
||||
uf::stl::vector<uint8_t> temp(faceSize * bytesPerPixel);
|
||||
std::memcpy(temp.data(), outPixels, faceSize * bytesPerPixel);
|
||||
|
||||
int size = header->width;
|
||||
int max = size - 1;
|
||||
|
||||
for ( int y = 0; y < size; ++y ) {
|
||||
for ( int x = 0; x < size; ++x ) {
|
||||
int srcX = x, srcY = y;
|
||||
|
||||
switch ( mode ) {
|
||||
case 1: srcX = y; srcY = max - x; break; // 90 CW
|
||||
case 2: srcX = max - x; srcY = max - y; break; // 180
|
||||
case 3: srcX = max - y; srcY = x; break; // 90 CCW
|
||||
case 4: srcX = max - x; srcY = y; break; // Flip Horizontal
|
||||
case 5: srcX = x; srcY = max - y; break; // Flip Vertical
|
||||
case 6: srcX = y; srcY = x; break; // Transpose
|
||||
case 7: srcX = max - y; srcY = max - x; break; // Anti-Transpose
|
||||
}
|
||||
|
||||
int srcIdx = (srcY * size + srcX) * bytesPerPixel;
|
||||
int dstIdx = (y * size + x) * bytesPerPixel;
|
||||
|
||||
std::memcpy(&outPixels[dstIdx], &temp[srcIdx], bytesPerPixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( (header->flags & impl::TEXTUREFLAGS_NORMAL) != 0 && header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
|
||||
size_t pixelCount = image.pixels.size() / 4;
|
||||
for ( size_t i = 0; i < pixelCount; ++i ) {
|
||||
uint8_t& r = image.pixels[i * 4 + 0];
|
||||
uint8_t& g = image.pixels[i * 4 + 1];
|
||||
uint8_t& b = image.pixels[i * 4 + 2];
|
||||
uint8_t& a = image.pixels[i * 4 + 3];
|
||||
|
||||
float x = (a / 255.0f) * 2.0f - 1.0f;
|
||||
float y = (g / 255.0f) * 2.0f - 1.0f;
|
||||
|
||||
y = -y;
|
||||
|
||||
float z = std::sqrt(std::max(1.0f - (x * x + y * y), 0.0f));
|
||||
|
||||
r = (uint8_t)((x * 0.5f + 0.5f) * 255.0f);
|
||||
g = (uint8_t)((y * 0.5f + 0.5f) * 255.0f);
|
||||
b = (uint8_t)((z * 0.5f + 0.5f) * 255.0f);
|
||||
|
||||
a = 255;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -405,59 +405,41 @@ void ext::vulkan::Texture::loadFromImage(
|
||||
VkImageLayout layout,
|
||||
VkImageCreateFlags flags
|
||||
) {
|
||||
/*
|
||||
switch ( format ) {
|
||||
case enums::Format::R8_SRGB:
|
||||
case enums::Format::R8G8_SRGB:
|
||||
case enums::Format::R8G8B8_SRGB:
|
||||
case enums::Format::R8G8B8A8_SRGB:
|
||||
srgb = true;
|
||||
break;
|
||||
}
|
||||
*/
|
||||
switch ( image.getChannels() ) {
|
||||
// R
|
||||
case 1:
|
||||
switch ( image.getBpp() ) {
|
||||
case 8:
|
||||
format = srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM;
|
||||
break;
|
||||
default:
|
||||
UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() );
|
||||
break;
|
||||
case 8: format = srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM; break;
|
||||
case 16: format = VK_FORMAT_R16_SFLOAT; break; // Half-float
|
||||
case 32: format = VK_FORMAT_R32_SFLOAT; break; // Full-float
|
||||
default: UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); break;
|
||||
}
|
||||
break;
|
||||
// RG
|
||||
case 2:
|
||||
switch ( image.getBpp() ) {
|
||||
case 16:
|
||||
format = srgb ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM;
|
||||
break;
|
||||
default:
|
||||
UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() );
|
||||
break;
|
||||
case 16: format = srgb ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM; break;
|
||||
case 32: format = VK_FORMAT_R16G16_SFLOAT; break;
|
||||
case 64: format = VK_FORMAT_R32G32_SFLOAT; break;
|
||||
default: UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); break;
|
||||
}
|
||||
break;
|
||||
// RGB
|
||||
case 3:
|
||||
switch ( image.getBpp() ) {
|
||||
case 24:
|
||||
format = srgb ? VK_FORMAT_R8G8B8_SRGB : VK_FORMAT_R8G8B8_UNORM;
|
||||
break;
|
||||
default:
|
||||
UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() );
|
||||
break;
|
||||
case 24: format = srgb ? VK_FORMAT_R8G8B8_SRGB : VK_FORMAT_R8G8B8_UNORM; break;
|
||||
case 48: format = VK_FORMAT_R16G16B16_SFLOAT; break;
|
||||
case 96: format = VK_FORMAT_R32G32B32_SFLOAT; break;
|
||||
default: UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); break;
|
||||
}
|
||||
break;
|
||||
// RGBA
|
||||
case 4:
|
||||
switch ( image.getBpp() ) {
|
||||
case 32:
|
||||
format = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
|
||||
break;
|
||||
default:
|
||||
UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() );
|
||||
break;
|
||||
case 32: format = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; break;
|
||||
case 64: format = VK_FORMAT_R16G16B16A16_SFLOAT; break; // 16-bit HDR
|
||||
case 128: format = VK_FORMAT_R32G32B32A32_SFLOAT; break; // 32-bit HDR
|
||||
default: UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -465,9 +447,6 @@ void ext::vulkan::Texture::loadFromImage(
|
||||
break;
|
||||
}
|
||||
|
||||
// convert to power of two
|
||||
//image.padToPowerOfTwo();
|
||||
|
||||
this->fromBuffers(
|
||||
(void*) image.getPixelsPtr(),
|
||||
image.getPixels().size(),
|
||||
|
||||
@ -20,6 +20,32 @@ namespace {
|
||||
auto* bytes = static_cast<uint8_t*>(data);
|
||||
buffer->insert(buffer->end(), bytes, bytes + size);
|
||||
}
|
||||
|
||||
inline uint8_t halfTo8Bit(uint16_t h) {
|
||||
uint32_t exp = (h >> 10) & 0x1F;
|
||||
uint32_t mant = h & 0x3FF;
|
||||
if (exp == 0) return 0;
|
||||
if (exp == 31) return 255;
|
||||
|
||||
exp = exp + (127 - 15);
|
||||
uint32_t f = (exp << 23) | (mant << 13);
|
||||
float val;
|
||||
std::memcpy(&val, &f, sizeof(float));
|
||||
|
||||
return (uint8_t)(std::min(std::max(val, 0.0f), 1.0f) * 255.0f);
|
||||
}
|
||||
|
||||
inline float halfToFloat(uint16_t h) {
|
||||
uint32_t exp = (h >> 10) & 0x1F;
|
||||
uint32_t mant = h & 0x3FF;
|
||||
if (exp == 0 && mant == 0) return 0.0f;
|
||||
|
||||
exp = exp + (127 - 15);
|
||||
uint32_t f = (exp << 23) | (mant << 13);
|
||||
float val;
|
||||
std::memcpy(&val, &f, sizeof(float));
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
namespace impl {
|
||||
@ -142,10 +168,10 @@ bool uf::image::open( pod::Image& image, const uf::stl::vector<uint8_t>& buffer,
|
||||
|
||||
// palette data: buffer.data() + sizeof(header) + header.size
|
||||
|
||||
bool twiddled = (header.type & (1 << 26)) < 1;
|
||||
bool compressed = (header.type & (1 << 30)) > 0;
|
||||
bool mipmapped = (header.type & (1 << 31)) > 0;
|
||||
bool strided = (header.type & (1 << 25)) > 0;
|
||||
bool twiddled = (header.type & (1 << 26)) < 1;
|
||||
bool compressed = (header.type & (1 << 30)) > 0;
|
||||
bool mipmapped = (header.type & (1 << 31)) > 0;
|
||||
bool strided = (header.type & (1 << 25)) > 0;
|
||||
uint32_t format = (header.type >> 27) & 0b111;
|
||||
width = header.width;
|
||||
height = header.height;
|
||||
@ -173,22 +199,40 @@ bool uf::image::open( pod::Image& image, const uf::stl::vector<uint8_t>& buffer,
|
||||
#endif
|
||||
{
|
||||
stbi_set_flip_vertically_on_load( flip );
|
||||
uint8_t* stbi_pixels = stbi_load_from_memory( buffer.data(), buffer.size(), &width, &height, &channelsDud, STBI_rgb_alpha );
|
||||
|
||||
if ( stbi_is_hdr_from_memory( buffer.data(), buffer.size() ) ) {
|
||||
float* stbi_pixels = stbi_loadf_from_memory( buffer.data(), buffer.size(), &width, &height, &channelsDud, STBI_rgb_alpha );
|
||||
if ( !stbi_pixels ) {
|
||||
UF_EXCEPTION("Image error: stb_image failed to decode HDR buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !stbi_pixels ) {
|
||||
UF_EXCEPTION("Image error: stb_image failed to decode buffer");
|
||||
return false;
|
||||
size_t len = width * height * 4 * sizeof(float);
|
||||
image.pixels.resize( len );
|
||||
std::memcpy( image.pixels.data(), stbi_pixels, len );
|
||||
stbi_image_free(stbi_pixels);
|
||||
|
||||
bpp = 32;
|
||||
channels = 4;
|
||||
} else {
|
||||
uint8_t* stbi_pixels = stbi_load_from_memory( buffer.data(), buffer.size(), &width, &height, &channelsDud, STBI_rgb_alpha );
|
||||
if ( !stbi_pixels ) {
|
||||
UF_EXCEPTION("Image error: stb_image failed to decode buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len = width * height * 4;
|
||||
image.pixels.resize( len );
|
||||
std::memcpy( image.pixels.data(), stbi_pixels, len );
|
||||
stbi_image_free(stbi_pixels);
|
||||
|
||||
bpp = 8;
|
||||
channels = 4;
|
||||
}
|
||||
|
||||
size_t len = width * height * channels;
|
||||
image.pixels.resize( len );
|
||||
memcpy( image.pixels.data(), stbi_pixels, len );
|
||||
|
||||
stbi_image_free(stbi_pixels);
|
||||
}
|
||||
|
||||
image.size.x = width;
|
||||
image.size.y = height;
|
||||
image.size.y = height / MAX(1, image.layers);
|
||||
image.bpp = bpp * channels;
|
||||
image.channels = channels;
|
||||
return true;
|
||||
@ -235,12 +279,25 @@ void uf::image::save( const pod::Image& image, uf::stl::vector<uint8_t>& buffer,
|
||||
if ( image.pixels.empty() ) return;
|
||||
|
||||
uint w = image.size.x;
|
||||
uint h = image.size.y;
|
||||
uint h = image.pixels.size() / (w * (image.bpp / 8));
|
||||
auto* pixels = &image.pixels[0];
|
||||
uf::stl::string extension = image.filename.empty() ? "png" : uf::io::extension( image.filename );
|
||||
stbi_flip_vertically_on_write(flip);
|
||||
|
||||
if ( extension == "png" ) {
|
||||
if ( extension == "hdr" ) {
|
||||
if ( image.bpp == 128 ) {
|
||||
stbi_write_hdr_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, (const float*)pixels);
|
||||
} else if ( image.bpp == 64 ) {
|
||||
size_t pixelCount = w * h * image.channels;
|
||||
|
||||
uf::stl::vector<float> temp32(pixelCount);
|
||||
const uint16_t* halfPixels = (const uint16_t*)pixels;
|
||||
for ( size_t i = 0; i < pixelCount; ++i ) temp32[i] = ::halfToFloat(halfPixels[i]);
|
||||
stbi_write_hdr_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, temp32.data());
|
||||
} else {
|
||||
UF_MSG_ERROR("Cannot save 8-bit image natively as HDR.");
|
||||
}
|
||||
} else if ( extension == "png" ) {
|
||||
stbi_write_png_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, pixels, w * image.channels);
|
||||
} else if ( extension == "jpg" || extension == "jpeg" ) {
|
||||
stbi_write_jpg_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, pixels, 90); // quality
|
||||
@ -252,6 +309,11 @@ void uf::image::save( const pod::Image& image, std::ostream& stream ) {
|
||||
|
||||
}
|
||||
|
||||
void uf::image::layers( pod::Image& image, size_t layers ) {
|
||||
if ( image.layers != layers ) image.size.y /= MAX(1, layers);
|
||||
image.layers = layers;
|
||||
}
|
||||
|
||||
pod::Image::pixel_t uf::image::at( pod::Image& image, const pod::Vector2ui& at ) {
|
||||
size_t i = at.x * image.channels + image.size.x * image.channels * at.y;
|
||||
return {
|
||||
@ -414,66 +476,7 @@ pod::Image uf::image::scale( const pod::Image& image, const pod::Vector2ui& size
|
||||
if ( filter == "linear" || filter == "bilinear" ) return impl::scaleBilinear( image, size );\
|
||||
UF_EXCEPTION("unrecognized scale filter: {}", filter );
|
||||
}
|
||||
/*
|
||||
uf::Image::Image() {
|
||||
size = {0,0};
|
||||
bpp = 8;
|
||||
channels = 4;
|
||||
format = 0;
|
||||
}
|
||||
|
||||
uf::Image::Image(const pod::Vector2ui& s) {
|
||||
size = s;
|
||||
bpp = 8;
|
||||
channels = 4;
|
||||
format = 0;
|
||||
pixels.resize(size.x * size.y * channels);
|
||||
}
|
||||
|
||||
uf::Image::Image( pod::Image::container_t&& move, const pod::Vector2ui& s ) {
|
||||
pixels = std::move( move );
|
||||
size = s;
|
||||
bpp = 8;
|
||||
channels = 4;
|
||||
format = 0;
|
||||
}
|
||||
|
||||
uf::Image::Image( const pod::Image::container_t& copy, const pod::Vector2ui& s ) {
|
||||
pixels = copy;
|
||||
size = s;
|
||||
bpp = 8;
|
||||
channels = 4;
|
||||
format = 0;
|
||||
}
|
||||
|
||||
uf::Image::Image( const uf::Image& copy ) {
|
||||
this->copy( copy );
|
||||
}
|
||||
|
||||
uf::Image::Image( uf::Image&& move ) noexcept {
|
||||
//this->move( move );
|
||||
pixels = std::move( move.pixels );
|
||||
size = move.size;
|
||||
bpp = move.bpp;
|
||||
channels = move.channels;
|
||||
format = move.format;
|
||||
}
|
||||
|
||||
uf::Image& uf::Image::operator=( const uf::Image& copy ) {
|
||||
this->copy( copy );
|
||||
return *this;
|
||||
}
|
||||
|
||||
uf::Image& uf::Image::operator=( uf::Image&& move ) noexcept {
|
||||
//this->move( move );
|
||||
pixels = std::move( move.pixels );
|
||||
size = move.size;
|
||||
bpp = move.bpp;
|
||||
channels = move.channels;
|
||||
format = move.format;
|
||||
return *this;
|
||||
}
|
||||
*/
|
||||
uf::stl::string uf::Image::getFilename() const {
|
||||
return this->filename;
|
||||
}
|
||||
@ -573,6 +576,9 @@ size_t uf::Image::getFormat() const {
|
||||
uf::stl::string uf::Image::getHash() const {
|
||||
return uf::image::hash( *this );
|
||||
}
|
||||
void uf::Image::setLayers( size_t layers ) {
|
||||
return uf::image::layers( *this, layers );
|
||||
}
|
||||
pod::Image::pixel_t uf::Image::at( const pod::Vector2ui& at ) {
|
||||
return uf::image::at( *this, at );
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user