overhauled saving/loading graphs, made joints global IDs, fixed some quirk with subsequent graphs loading, fixed quirks with async graph loading, some other things
This commit is contained in:
parent
941ad2ead9
commit
aa2740979c
@ -3,10 +3,10 @@
|
||||
"scenes": {
|
||||
"start": "StartMenu",
|
||||
"lights": { "enabled": true,
|
||||
"lightmaps": true,
|
||||
"lightmaps": false,
|
||||
"max": 32,
|
||||
"shadows": {
|
||||
"enabled": false,
|
||||
"enabled": true,
|
||||
"update": 4,
|
||||
"max": 16,
|
||||
"samples": 1
|
||||
@ -426,7 +426,7 @@
|
||||
},
|
||||
"loader": {
|
||||
"assert": true,
|
||||
"async": false
|
||||
"async": true
|
||||
},
|
||||
"hooks": {
|
||||
"defer lazy calls": true
|
||||
|
||||
@ -4,14 +4,14 @@
|
||||
"ignore": false,
|
||||
"import": "./craetureModel.json",
|
||||
"assets": [
|
||||
"./scripts/craeture.lua"
|
||||
// "./scripts/craeture.lua"
|
||||
],
|
||||
"behaviors": [
|
||||
"CraetureBehavior",
|
||||
"AudioEmitterBehavior"
|
||||
],
|
||||
"transform": {
|
||||
"position": [ -7, -3.5, -44 ],
|
||||
"position": [ -36.8997, 4.49179, 16.0879 ],
|
||||
//"position": [ 0, 1.5, 21 ],
|
||||
//"position": [ 16.3489, 1.37972, -68.1571 ],
|
||||
//"scale": [ 0.09, 0.09, 0.09 ]
|
||||
@ -32,7 +32,7 @@
|
||||
}
|
||||
},
|
||||
"physics": {
|
||||
"ragdoll": true
|
||||
"ragdoll": false
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
"skinned": true
|
||||
},
|
||||
"animations": {
|
||||
// "animation": "idle"
|
||||
"animation": "idle_wank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@
|
||||
"stream": {
|
||||
"tag": "worldspawn",
|
||||
"player": "info_player_spawn",
|
||||
"enabled": "auto",
|
||||
"enabled": false, // "auto",
|
||||
"radius": 16,
|
||||
"every": 1
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "Object",
|
||||
"name": "Player: Model",
|
||||
"ignore": true,
|
||||
"ignore": false,
|
||||
"import": "/model.json",
|
||||
"assets": [
|
||||
// "/player/pbear.glb"
|
||||
@ -49,7 +49,7 @@
|
||||
},
|
||||
"animations": {
|
||||
"animation": "wank",
|
||||
"speed": 8.0
|
||||
"speed": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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" }
|
||||
]
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
"assets": [
|
||||
// { "filename": "./maps/mcdonalds-mds.bsp" }
|
||||
{ "filename": "./maps/mcdonalds-mds/graph.json" },
|
||||
{ "filename": "ent://burger.json", "delay": 4 }
|
||||
{ "filename": "ent://burger.json", "delay": 1 },
|
||||
{ "filename": "ent://craeture.json", "delay": 2 }
|
||||
]
|
||||
}
|
||||
@ -19,43 +19,42 @@ uvec4 uvec2_16x4( uvec2 i ) {
|
||||
return converted;
|
||||
}
|
||||
|
||||
layout (binding = 0) uniform UBO {
|
||||
layout (push_constant) uniform SkinningPush {
|
||||
uint jointID;
|
||||
uint padding1;
|
||||
uint padding2;
|
||||
uint padding3;
|
||||
} ubo;
|
||||
uint vertexOffset;
|
||||
} push;
|
||||
|
||||
layout (std140, binding = 1) readonly buffer Joints {
|
||||
layout (std140, binding = 0) readonly buffer Joints {
|
||||
mat4 joints[];
|
||||
};
|
||||
|
||||
layout (binding = 2) readonly buffer VertexInputPosition {
|
||||
/*vec3 verticesInPos[];*/
|
||||
layout (binding = 1) readonly buffer VertexInputPosition {
|
||||
float verticesInPos[];
|
||||
};
|
||||
layout (binding = 3) readonly buffer VertexInputJoints {
|
||||
layout (binding = 2) readonly buffer VertexInputJoints {
|
||||
uvec2 verticesInJoints[];
|
||||
};
|
||||
layout (binding = 4) readonly buffer VertexInputWeights {
|
||||
layout (binding = 3) readonly buffer VertexInputWeights {
|
||||
vec4 verticesInWeights[];
|
||||
};
|
||||
|
||||
layout (binding = 5) buffer VertexOutputPosition {
|
||||
/*vec3 verticesOutPos[];*/
|
||||
layout (binding = 4) buffer VertexOutputPosition {
|
||||
float verticesOutPos[];
|
||||
};
|
||||
|
||||
void main() {
|
||||
const uint i = gl_GlobalInvocationID.x;
|
||||
|
||||
|
||||
if ( i * 3 >= verticesInPos.length() || i * 3 >= verticesOutPos.length() ) return;
|
||||
|
||||
const vec3 inPos = vec3( verticesInPos[i * 3 + 0], verticesInPos[i * 3 + 1], verticesInPos[i * 3 + 2] );
|
||||
const uvec4 inJoints = uvec2_16x4(verticesInJoints[i]);
|
||||
const vec4 inWeights = verticesInWeights[i];
|
||||
|
||||
const mat4 skinned = inWeights.x * joints[ubo.jointID + int(inJoints.x)] + inWeights.y * joints[ubo.jointID + int(inJoints.y)] + inWeights.z * joints[ubo.jointID + int(inJoints.z)] + inWeights.w * joints[ubo.jointID + int(inJoints.w)];
|
||||
const mat4 skinned = inWeights.x * joints[push.jointID + int(inJoints.x)]
|
||||
+ inWeights.y * joints[push.jointID + int(inJoints.y)]
|
||||
+ inWeights.z * joints[push.jointID + int(inJoints.z)]
|
||||
+ inWeights.w * joints[push.jointID + int(inJoints.w)];
|
||||
|
||||
const vec3 outPos = vec3(skinned * vec4(inPos, 1));
|
||||
verticesOutPos[i * 3 + 0] = outPos[0];
|
||||
@ -1,50 +0,0 @@
|
||||
#version 450
|
||||
#pragma shader_stage(compute)
|
||||
|
||||
layout (local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
#define COMPUTE 1
|
||||
|
||||
#include "../../common/macros.h"
|
||||
#include "../../common/structs.h"
|
||||
|
||||
uvec4 uvec2_16x4( uvec2 i ) {
|
||||
uvec4 converted;
|
||||
|
||||
converted.x = (i.x >> 0) & 0xFFFF;
|
||||
converted.y = (i.x >> 16) & 0xFFFF;
|
||||
converted.z = (i.y >> 0) & 0xFFFF;
|
||||
converted.w = (i.y >> 16) & 0xFFFF;
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
layout (binding = 0) uniform UBO {
|
||||
uint jointID;
|
||||
uint padding1;
|
||||
uint padding2;
|
||||
uint padding3;
|
||||
} ubo;
|
||||
layout (std140, binding = 1) readonly buffer Joints {
|
||||
mat4 joints[];
|
||||
};
|
||||
layout (binding = 2) readonly buffer VertexInput {
|
||||
Vertex verticesIn[];
|
||||
};
|
||||
layout (binding = 3) buffer VertexOutput {
|
||||
Vertex verticesOut[];
|
||||
};
|
||||
|
||||
void main() {
|
||||
const uint i = gl_GlobalInvocationID.x;
|
||||
|
||||
if ( i >= verticesIn.length() || i >= verticesOut.length() ) return;
|
||||
|
||||
const vec3 inPos = verticesIn[i].position;
|
||||
const uvec4 inJoints = uvec2_16x4(verticesIn[i].joints);
|
||||
const vec4 inWeights = verticesIn[i].weights;
|
||||
|
||||
const mat4 skinned = inWeights.x * joints[ubo.jointID + int(inJoints.x)] + inWeights.y * joints[ubo.jointID + int(inJoints.y)] + inWeights.z * joints[ubo.jointID + int(inJoints.z)] + inWeights.w * joints[ubo.jointID + int(inJoints.w)];
|
||||
|
||||
verticesOut[i].position = vec3(skinned * vec4(verticesIn[i].position,1));
|
||||
}
|
||||
@ -33,6 +33,7 @@ namespace uf {
|
||||
|
||||
extern UF_API uf::stl::unordered_map<uf::stl::string, uf::asset::userdata_t> map;
|
||||
extern UF_API Job::container_t jobs;
|
||||
extern UF_API Job::container_t finishedJobs;
|
||||
extern UF_API uf::Serializer metadata;
|
||||
|
||||
// extern UF_API uf::Serializer map;
|
||||
|
||||
@ -75,6 +75,13 @@ namespace pod {
|
||||
} stream;
|
||||
} settings;
|
||||
|
||||
struct StreamRegistry {
|
||||
uf::stl::unordered_map<uf::stl::string, pod::AnimationStream> animations;
|
||||
uf::stl::unordered_map<uf::stl::string, pod::SkinStream> skins;
|
||||
uf::stl::unordered_map<uf::stl::string, pod::MeshStream> meshes;
|
||||
uf::stl::unordered_map<uf::stl::string, pod::ImageStream> images;
|
||||
} streams;
|
||||
|
||||
// Local storage, used for save/load
|
||||
struct Storage {
|
||||
enum StorageType : uint32_t {
|
||||
@ -137,7 +144,7 @@ namespace uf {
|
||||
namespace graph {
|
||||
extern UF_API size_t initialBufferElements;
|
||||
extern UF_API uint32_t storageMode;
|
||||
extern UF_API pod::Graph::Storage storage;
|
||||
extern UF_API pod::Graph::Storage globalStorage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -113,4 +113,32 @@ namespace pod {
|
||||
uf::Image data;
|
||||
uf::renderer::Texture2D handle;
|
||||
};
|
||||
}
|
||||
|
||||
namespace pod {
|
||||
struct StreamRegion {
|
||||
uf::stl::string filename = "";
|
||||
size_t offset = 0;
|
||||
size_t length = 0;
|
||||
};
|
||||
|
||||
struct AnimationStream {
|
||||
struct SamplerStream {
|
||||
pod::StreamRegion inputs;
|
||||
pod::StreamRegion outputs;
|
||||
};
|
||||
uf::stl::vector<SamplerStream> samplers;
|
||||
};
|
||||
|
||||
struct MeshStream {
|
||||
uf::stl::vector<pod::StreamRegion> buffers;
|
||||
};
|
||||
|
||||
struct SkinStream {
|
||||
pod::StreamRegion inverseBindMatrices;
|
||||
};
|
||||
|
||||
struct ImageStream {
|
||||
StreamRegion buffer;
|
||||
};
|
||||
}
|
||||
@ -58,6 +58,15 @@ namespace ext {
|
||||
template<typename T> inline T as(const T& fallback) const;
|
||||
|
||||
Value& reserve( size_t );
|
||||
|
||||
inline bool isTable() const;
|
||||
inline bool isObject() const;
|
||||
inline bool isArray() const;
|
||||
inline bool isNull() const;
|
||||
|
||||
inline Value& array();
|
||||
inline Value& object();
|
||||
inline Value& null();
|
||||
};
|
||||
|
||||
inline bool isTable( const Value& v ) { return v.is_array() || v.is_object(); }
|
||||
@ -74,4 +83,9 @@ namespace ext {
|
||||
}
|
||||
}
|
||||
|
||||
bool ext::json::Value::isTable() const { return ext::json::isTable( *this ); }
|
||||
bool ext::json::Value::isObject() const { return ext::json::isObject( *this ); }
|
||||
bool ext::json::Value::isArray() const { return ext::json::isArray( *this ); }
|
||||
bool ext::json::Value::isNull() const { return ext::json::isNull( *this ); }
|
||||
|
||||
#include "nlohmann.inl"
|
||||
@ -51,6 +51,7 @@ template<> inline bool ext::json::Value::is<uint64_t>(bool strict) const { retur
|
||||
template<> inline bool ext::json::Value::is<float>(bool strict) const { return strict ? is_number_float() : is_number(); }
|
||||
template<> inline bool ext::json::Value::is<double>(bool strict) const { return strict ? is_number_float() : is_number(); }
|
||||
template<> inline bool ext::json::Value::is<uf::stl::string>(bool strict) const { return is_string(); }
|
||||
template<> inline bool ext::json::Value::is<uf::stl::vector<uf::stl::string>>(bool strict) const { return is_array(); }
|
||||
|
||||
// template<> template<typename T, size_t N> inline bool ext::json::Value::is<pod::Vector<T,N>>(bool strict) const { return is_array() && size() == N; }
|
||||
// template<> inline bool ext::json::Value::is<std::string>(bool strict) const { return is_string(); }
|
||||
|
||||
@ -46,5 +46,7 @@ namespace ext {
|
||||
|
||||
// dumps to disk
|
||||
bool UF_API save( const pod::Dtex&, const uf::stl::string&, bool = false );
|
||||
// dumps to buffer
|
||||
bool UF_API save( const pod::Dtex&, uf::stl::vector<uint8_t>& );
|
||||
}
|
||||
}
|
||||
@ -21,12 +21,14 @@ namespace pod {
|
||||
namespace uf {
|
||||
namespace image {
|
||||
bool UF_API open( pod::Image&, const uf::stl::string&, bool = true );
|
||||
bool UF_API open( pod::Image&, const uf::stl::vector<uint8_t>&, const uf::stl::string&, bool = true );
|
||||
void UF_API clear( pod::Image& );
|
||||
|
||||
void UF_API load( pod::Image&, const pod::Image::pixel_t::type_t* pointer, const pod::Vector2ui& size, size_t bpp, size_t channels, bool flip = false );
|
||||
void UF_API load( pod::Image&, const pod::Image::container_t& container, const pod::Vector2ui& size, size_t bpp, size_t channels, bool flip = false );
|
||||
|
||||
bool UF_API save( const pod::Image&, const uf::stl::string& filename, bool flip = false );
|
||||
void UF_API save( const pod::Image&, uf::stl::vector<uint8_t>&, bool flip = false );
|
||||
void UF_API save( const pod::Image&, std::ostream& stream );
|
||||
|
||||
pod::Image::pixel_t UF_API at( pod::Image&, const pod::Vector2ui& at );
|
||||
@ -57,6 +59,7 @@ namespace uf {
|
||||
Image& operator=( Image&& ) noexcept = default;
|
||||
|
||||
bool open( const uf::stl::string& filename, bool = true ); // from file
|
||||
bool open( const uf::stl::vector<uint8_t>& buffer, const uf::stl::string&, bool = true ); // from buffer
|
||||
void open( const std::istream& stream ); // from stream
|
||||
void move( Image::container_t&& move, const pod::Vector2ui& size ); // move from vector of pixels
|
||||
void move( uf::Image&& ); // move from image object
|
||||
@ -95,6 +98,7 @@ namespace uf {
|
||||
void flip();
|
||||
void padToPowerOfTwo();
|
||||
bool save( const uf::stl::string& filename, bool flip = false ) const; // to file
|
||||
bool save( uf::stl::vector<uint8_t>&, bool flip = false ) const; // to buffer
|
||||
void save( std::ostream& stream ) const; // to stream
|
||||
void convert( const uf::stl::string&, const uf::stl::string& = "rgba" );
|
||||
Image overlay(const Image& top, const pod::Vector2ui& corner = {} ) const; // Merges one image on top of another
|
||||
|
||||
@ -98,13 +98,19 @@ uf::stl::vector<T> uf::stl::KeyMap<T,Key>::flattenByIndex() const {
|
||||
|
||||
template<typename T, typename Key>
|
||||
void uf::stl::KeyMap<T,Key>::merge( KeyMap<T, Key>&& other ) {
|
||||
this->reserve( this->keys.size() + other.keys.size() );
|
||||
this->reserve( this->keys.size() + other.keys.size() );
|
||||
|
||||
for ( auto& key : other.keys ) {
|
||||
(*this)[key] = std::move( other.map[key] );
|
||||
}
|
||||
for ( auto& key : other.keys ) {
|
||||
// Bypass custom operator[] and force a direct map assignment
|
||||
if ( this->map.count(key) == 0 ) {
|
||||
this->indices[key] = this->keys.size();
|
||||
this->keys.emplace_back(key);
|
||||
}
|
||||
// Explicitly copy to prevent any weird std::move state corruption
|
||||
this->map[key] = other.map.at(key);
|
||||
}
|
||||
|
||||
other.clear();
|
||||
other.clear();
|
||||
}
|
||||
|
||||
template<typename T, typename Key>
|
||||
|
||||
@ -209,8 +209,6 @@ namespace uf {
|
||||
|
||||
uf::stl::vector<buffer_t> buffers;
|
||||
|
||||
// crunge, but it's better this way for streaming in mesh data
|
||||
uf::stl::vector<uf::stl::string> buffer_paths;
|
||||
// mega cringe, but i'd like to have a way to cache it
|
||||
uf::stl::vector<uf::Mesh::View> buffer_views;
|
||||
protected:
|
||||
|
||||
@ -53,19 +53,31 @@ namespace {
|
||||
bool uf::asset::assertionLoad = true;
|
||||
bool uf::asset::asyncQueue = true;
|
||||
uf::asset::Job::container_t uf::asset::jobs;
|
||||
uf::asset::Job::container_t uf::asset::finishedJobs;
|
||||
uf::stl::unordered_map<uf::stl::string, uf::asset::userdata_t> uf::asset::map;
|
||||
uf::Serializer uf::asset::metadata;
|
||||
|
||||
void uf::asset::processQueue() {
|
||||
if ( uf::asset::jobs.empty() ) return;
|
||||
if ( uf::asset::jobs.empty() && uf::asset::finishedJobs.empty() ) return;
|
||||
|
||||
STATIC_THREAD_LOCAL(uf::asset::Job::container_t, jobs);
|
||||
uf::asset::Job::container_t finishedJobs;
|
||||
|
||||
mutex.lock();
|
||||
std::swap( jobs, uf::asset::jobs );
|
||||
std::swap( finishedJobs, uf::asset::finishedJobs );
|
||||
mutex.unlock();
|
||||
|
||||
bool async = false; // uf::asset::asyncQueue; // a bit buggy
|
||||
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 );
|
||||
}
|
||||
});
|
||||
}
|
||||
for ( auto& job : jobs ) tasks.queue([=]{
|
||||
auto callback = job.callback;
|
||||
auto type = job.type;
|
||||
@ -76,7 +88,9 @@ void uf::asset::processQueue() {
|
||||
uf::stl::string filename = type == "cache" ? uf::asset::cache(payload) : uf::asset::load(payload);
|
||||
|
||||
if ( callback != "" && filename != "" ) {
|
||||
uf::hooks.call(callback, payload);
|
||||
mutex.lock();
|
||||
uf::asset::finishedJobs.emplace_back( job );
|
||||
mutex.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -16,58 +16,58 @@
|
||||
#include <uf/engine/ext.h>
|
||||
|
||||
namespace {
|
||||
uf::stl::string keyedID( size_t id ) {
|
||||
return ::fmt::format("{}", id);
|
||||
}
|
||||
|
||||
// lazy load animations if requested
|
||||
void loadAnimation( pod::Graph& graph, const uf::stl::string& name ) {
|
||||
auto& storage = uf::graph::getStorage( graph );
|
||||
|
||||
|
||||
if ( storage.animations.map.count(name) == 0 ) return;
|
||||
if ( graph.streams.animations.count(name) == 0 ) return;
|
||||
|
||||
auto& animation = storage.animations.map[name];
|
||||
auto& animStream = graph.streams.animations[name];
|
||||
|
||||
//UF_ASSERT( animation.path != "" );
|
||||
if ( animation.path == "" ) {
|
||||
return;
|
||||
for ( size_t i = 0; i < animation.samplers.size(); ++i ) {
|
||||
auto& sampler = animation.samplers[i];
|
||||
auto& stream = animStream.samplers[i];
|
||||
|
||||
if ( !sampler.inputs.empty() ) continue;
|
||||
|
||||
uf::stl::vector<uint8_t> ioBuf;
|
||||
|
||||
if ( stream.inputs.length > 0 ) {
|
||||
if ( uf::io::readAsBuffer(ioBuf, stream.inputs.filename, stream.inputs.offset, stream.inputs.length) ) {
|
||||
sampler.inputs.resize( stream.inputs.length / sizeof(float) );
|
||||
memcpy( sampler.inputs.data(), ioBuf.data(), stream.inputs.length );
|
||||
}
|
||||
}
|
||||
|
||||
if ( stream.outputs.length > 0 ) {
|
||||
if ( uf::io::readAsBuffer(ioBuf, stream.outputs.filename, stream.outputs.offset, stream.outputs.length) ) {
|
||||
sampler.outputs.resize( stream.outputs.length / sizeof(pod::Vector4f) );
|
||||
memcpy( sampler.outputs.data(), ioBuf.data(), stream.outputs.length );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uf::Serializer json;
|
||||
json.readFromFile( animation.path );
|
||||
|
||||
animation.name = json["name"].as(animation.name);
|
||||
animation.start = json["start"].as(animation.start);
|
||||
animation.end = json["end"].as(animation.end);
|
||||
|
||||
if ( animation.samplers.empty() ) ext::json::forEach( json["samplers"], [&]( ext::json::Value& value ){
|
||||
auto& sampler = animation.samplers.emplace_back();
|
||||
sampler.interpolator = value["interpolator"].as(sampler.interpolator);
|
||||
|
||||
sampler.inputs.reserve( value["inputs"].size() );
|
||||
ext::json::forEach( value["inputs"], [&]( ext::json::Value& input ){
|
||||
sampler.inputs.emplace_back( input.as<float>() );
|
||||
});
|
||||
|
||||
sampler.outputs.reserve( value["outputs"].size() );
|
||||
ext::json::forEach( value["outputs"], [&]( ext::json::Value& output ){
|
||||
sampler.outputs.emplace_back( uf::vector::decode( output, pod::Vector4f{} ) );
|
||||
});
|
||||
});
|
||||
|
||||
if ( animation.channels.empty() ) ext::json::forEach( json["channels"], [&]( ext::json::Value& value ){
|
||||
auto& channel = animation.channels.emplace_back();
|
||||
channel.path = value["path"].as(channel.path);
|
||||
channel.node = value["node"].as(channel.node);
|
||||
channel.sampler = value["sampler"].as(channel.sampler);
|
||||
});
|
||||
}
|
||||
|
||||
void unloadAnimation( pod::Graph& graph, const uf::stl::string& name ) {
|
||||
auto& storage = uf::graph::getStorage( graph );
|
||||
|
||||
|
||||
if ( storage.animations.map.count(name) == 0 ) return;
|
||||
auto& animation = storage.animations.map[name];
|
||||
|
||||
animation.samplers.clear();
|
||||
animation.channels.clear();
|
||||
#if UF_ENV_DREAMCAST
|
||||
animation.samplers.shrink_to_fit();
|
||||
animation.channels.shrink_to_fit();
|
||||
#endif
|
||||
for ( auto& sampler : animation.samplers ) {
|
||||
sampler.inputs.clear();
|
||||
sampler.outputs.clear();
|
||||
#if UF_ENV_DREAMCAST
|
||||
sampler.inputs.shrink_to_fit();
|
||||
sampler.outputs.shrink_to_fit();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
pod::Matrix4f localMatrix( const pod::Graph& graph, int32_t index ) {
|
||||
@ -121,6 +121,9 @@ void uf::graph::override( pod::Graph& graph ) {
|
||||
|
||||
// load animation data
|
||||
// if ( animation.channels.empty() || animation.samplers.empty() ) ::loadAnimation( graph, name );
|
||||
if ( !animation.samplers.empty() && animation.samplers[0].inputs.empty() ) {
|
||||
::loadAnimation( graph, name );
|
||||
}
|
||||
|
||||
for ( auto& channel : animation.channels ) {
|
||||
auto& override = graph.settings.animations.override.map[channel.node];
|
||||
@ -148,9 +151,16 @@ void uf::graph::animate( pod::Graph& graph, const uf::stl::string& _name, float
|
||||
if ( key != "" ) key += ":";
|
||||
uf::stl::string name = key + _name;
|
||||
|
||||
if ( storage.animations.map.count( name ) == 0 ) ::loadAnimation( graph, name );
|
||||
//UF_MSG_DEBUG("name={}, count={}", name, storage.animations.map.count( name ) );
|
||||
if ( storage.animations.map.count( name ) == 0 ) {
|
||||
::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 already playing, ignore it
|
||||
if ( !graph.sequence.empty() && graph.sequence.front() == name ) return;
|
||||
if ( immediate ) {
|
||||
@ -170,6 +180,7 @@ void uf::graph::animate( pod::Graph& graph, const uf::stl::string& _name, float
|
||||
|
||||
void uf::graph::updateAnimation( pod::Graph& graph, float delta ) {
|
||||
// update our instances
|
||||
auto& storage = uf::graph::getStorage( graph );
|
||||
|
||||
// no skins
|
||||
if ( !(graph.metadata["renderer"]["skinned"].as<bool>()) ) {
|
||||
@ -248,9 +259,10 @@ void uf::graph::updateAnimation( pod::Graph& graph, pod::Node& node ) {
|
||||
invArmatureMatrix = uf::matrix::inverse( uf::transform::model( armTf ) );
|
||||
}
|
||||
|
||||
auto& name = graph.skins[node.skin];
|
||||
auto& skin = storage.skins[name];
|
||||
auto& joints = storage.joints[name];
|
||||
auto& skinName = graph.skins[node.skin];
|
||||
auto& skin = storage.skins[skinName];
|
||||
auto objectKeyName = ::keyedID(node.object);
|
||||
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];
|
||||
|
||||
@ -25,106 +25,97 @@
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
uf::Image decodeImage( ext::json::Value& json, pod::Graph& graph ) {
|
||||
uf::Image decodeImage( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& imageName ) {
|
||||
uf::Image image;
|
||||
|
||||
uf::stl::string filename = "";
|
||||
size_t offset = 0, length = 0;
|
||||
uf::stl::string formatHint = "";
|
||||
|
||||
#if UF_ENV_DREAMCAST
|
||||
if (json["dtex"].isObject()) {
|
||||
filename = json["dtex"]["filename"].as<uf::stl::string>();
|
||||
offset = json["dtex"]["offset"].as<size_t>();
|
||||
length = json["dtex"]["length"].as<size_t>();
|
||||
formatHint = "dtex";
|
||||
} else
|
||||
#endif
|
||||
if ( json["filename"].is<uf::stl::string>() ) {
|
||||
const uf::stl::string directory = uf::io::directory( graph.name );
|
||||
const uf::stl::string filename = uf::io::filename( json["filename"].as<uf::stl::string>() );
|
||||
const uf::stl::string name = directory + "/" + filename;
|
||||
if ( graph.settings.stream.textures ) {
|
||||
image.setFilename(name);
|
||||
} else {
|
||||
image.open(name, false);
|
||||
}
|
||||
filename = json["filename"].as<uf::stl::string>();
|
||||
offset = json["offset"].as<size_t>(0);
|
||||
length = json["length"].as<size_t>(0);
|
||||
formatHint = uf::io::extension(filename);
|
||||
} else {
|
||||
auto size = uf::vector::decode( json["size"], pod::Vector2ui{} );
|
||||
size_t bpp = json["bpp"].as<size_t>();
|
||||
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 );
|
||||
return image;
|
||||
}
|
||||
|
||||
uf::stl::string fullPath = uf::io::directory( graph.name ) + "/" + filename;
|
||||
|
||||
if ( graph.settings.stream.textures ) {
|
||||
auto& storage = uf::graph::getStorage(graph);
|
||||
graph.streams.images[imageName] = { fullPath, offset, length };
|
||||
image.setFilename(fullPath);
|
||||
} else {
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
if (length > 0) {
|
||||
uf::io::readAsBuffer(buffer, fullPath, offset, length);
|
||||
} else {
|
||||
uf::io::readAsBuffer(buffer, fullPath);
|
||||
}
|
||||
|
||||
uf::image::open( image, buffer, formatHint, false );
|
||||
image.setFilename(fullPath);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
pod::Texture decodeTexture( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::Texture texture;
|
||||
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);
|
||||
|
||||
texture.index = json["index"].as(texture.index);
|
||||
texture.sampler = json["sampler"].as(texture.sampler);
|
||||
texture.remap = json["remap"].as(texture.remap);
|
||||
texture.blend = json["blend"].as(texture.blend);
|
||||
texture.lerp = uf::vector::decode( json["lerp"], pod::Vector4f{} );
|
||||
|
||||
return texture;
|
||||
}
|
||||
uf::stl::string binPath = "";
|
||||
if (json["buffer"].is<uf::stl::string>()) {
|
||||
binPath = uf::io::directory(graph.name) + "/" + json["buffer"].as<uf::stl::string>();
|
||||
}
|
||||
|
||||
uf::renderer::Sampler decodeSampler( ext::json::Value& json, pod::Graph& graph ) {
|
||||
uf::renderer::Sampler sampler;
|
||||
auto& storage = uf::graph::getStorage(graph);
|
||||
auto& animStream = graph.streams.animations[animName];
|
||||
|
||||
sampler.descriptor.filter.min = (uf::renderer::enums::Filter::type_t) json["min"].as<size_t>();
|
||||
sampler.descriptor.filter.mag = (uf::renderer::enums::Filter::type_t) json["mag"].as<size_t>();
|
||||
sampler.descriptor.addressMode.u = (uf::renderer::enums::AddressMode::type_t) json["u"].as<size_t>();
|
||||
sampler.descriptor.addressMode.v = (uf::renderer::enums::AddressMode::type_t) json["v"].as<size_t>();
|
||||
sampler.descriptor.addressMode.w = sampler.descriptor.addressMode.v;
|
||||
|
||||
#if UF_ENV_DREAMCAST
|
||||
sampler.descriptor.filter.min = uf::renderer::enums::Filter::NEAREST;
|
||||
sampler.descriptor.filter.mag = uf::renderer::enums::Filter::NEAREST;
|
||||
#endif
|
||||
|
||||
return sampler;
|
||||
}
|
||||
|
||||
pod::Material decodeMaterial( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::Material material;
|
||||
|
||||
material.colorBase = uf::vector::decode( json["base"], material.colorBase );
|
||||
material.colorEmissive = uf::vector::decode( json["emissive"], material.colorEmissive );
|
||||
material.factorMetallic = json["fMetallic"].as(material.factorMetallic);
|
||||
material.factorRoughness = json["fRoughness"].as(material.factorRoughness);
|
||||
material.factorOcclusion = json["fOcclusion"].as(material.factorOcclusion);
|
||||
material.factorAlphaCutoff = json["fAlphaCutoff"].as(material.factorAlphaCutoff);
|
||||
material.indexAlbedo = json["iAlbedo"].as(material.indexAlbedo);
|
||||
material.indexNormal = json["iNormal"].as(material.indexNormal);
|
||||
material.indexEmissive = json["iEmissive"].as(material.indexEmissive);
|
||||
material.indexOcclusion = json["iOcclusion"].as(material.indexOcclusion);
|
||||
material.indexMetallicRoughness = json["iMetallicRoughness"].as(material.indexMetallicRoughness);
|
||||
material.indexCubemap = json["iCubemap"].as(material.indexCubemap);
|
||||
material.modeCull = json["modeCull"].as(material.modeCull);
|
||||
material.modeAlpha = json["modeAlpha"].as(material.modeAlpha);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
pod::Light decodeLight( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::Light light;
|
||||
light.color = uf::vector::decode( json["color"], light.color );
|
||||
light.intensity = json["intensity"].as(light.intensity);
|
||||
light.range = json["range"].as(light.range);
|
||||
return light;
|
||||
}
|
||||
|
||||
pod::Animation decodeAnimation( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::Animation animation;
|
||||
|
||||
animation.name = json["name"].as(animation.name);
|
||||
animation.start = json["start"].as(animation.start);
|
||||
animation.end = json["end"].as(animation.end);
|
||||
|
||||
ext::json::forEach( json["samplers"], [&]( ext::json::Value& value ){
|
||||
ext::json::forEach( json["samplers"], [&]( ext::json::Value& value ){
|
||||
auto& sampler = animation.samplers.emplace_back();
|
||||
sampler.interpolator = value["interpolator"].as(sampler.interpolator);
|
||||
|
||||
sampler.inputs.reserve( value["inputs"].size() );
|
||||
ext::json::forEach( value["inputs"], [&]( ext::json::Value& input ){
|
||||
sampler.inputs.emplace_back( input.as<float>() );
|
||||
});
|
||||
|
||||
sampler.outputs.reserve( value["outputs"].size() );
|
||||
ext::json::forEach( value["outputs"], [&]( ext::json::Value& output ){
|
||||
sampler.outputs.emplace_back( uf::vector::decode( output, pod::Vector4f{} ) );
|
||||
});
|
||||
size_t inputsCount = value["inputs"]["count"].as<size_t>();
|
||||
size_t inputsOffset = value["inputs"]["offset"].as<size_t>();
|
||||
size_t inputsLen = value["inputs"]["length"].as<size_t>();
|
||||
|
||||
size_t outputsCount = value["outputs"]["count"].as<size_t>();
|
||||
size_t outputsOffset = value["outputs"]["offset"].as<size_t>();
|
||||
size_t outputsLen = value["outputs"]["length"].as<size_t>();
|
||||
|
||||
if ( graph.settings.stream.animations ) {
|
||||
pod::AnimationStream::SamplerStream sStream;
|
||||
sStream.inputs = { binPath, inputsOffset, inputsLen };
|
||||
sStream.outputs = { binPath, outputsOffset, outputsLen };
|
||||
animStream.samplers.emplace_back(sStream);
|
||||
} else {
|
||||
if (inputsLen > 0 && !megaBuffer.empty()) {
|
||||
sampler.inputs.resize(inputsCount);
|
||||
memcpy(sampler.inputs.data(), megaBuffer.data() + inputsOffset, inputsLen);
|
||||
}
|
||||
if (outputsLen > 0 && !megaBuffer.empty()) {
|
||||
sampler.outputs.resize(outputsCount);
|
||||
memcpy(sampler.outputs.data(), megaBuffer.data() + outputsOffset, outputsLen);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ext::json::forEach( json["channels"], [&]( ext::json::Value& value ){
|
||||
@ -133,94 +124,44 @@ namespace {
|
||||
channel.node = value["node"].as(channel.node);
|
||||
channel.sampler = value["sampler"].as(channel.sampler);
|
||||
});
|
||||
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
pod::Skin decodeSkin( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::Skin decodeSkin( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& skinName, const uf::stl::vector<uint8_t>& megaBuffer ) {
|
||||
pod::Skin skin;
|
||||
|
||||
skin.name = json["name"].as(skin.name);
|
||||
|
||||
|
||||
skin.joints.reserve( json["joints"].size() );
|
||||
ext::json::forEach( json["joints"], [&]( ext::json::Value& value ){
|
||||
skin.joints.emplace_back( value.as<int32_t>() );
|
||||
});
|
||||
|
||||
skin.inverseBindMatrices.reserve( json["inverseBindMatrices"].size() );
|
||||
ext::json::forEach( json["inverseBindMatrices"], [&]( ext::json::Value& value ){
|
||||
skin.inverseBindMatrices.emplace_back( uf::matrix::decode( value, pod::Matrix4f{} ) );
|
||||
});
|
||||
|
||||
|
||||
if (json["inverseBindMatrices"].isObject()) {
|
||||
auto& invJson = json["inverseBindMatrices"];
|
||||
size_t count = invJson["count"].as<size_t>();
|
||||
size_t offset = invJson["offset"].as<size_t>();
|
||||
size_t length = invJson["length"].as<size_t>();
|
||||
uf::stl::string binPath = uf::io::directory(graph.name) + "/" + invJson["buffer"].as<uf::stl::string>();
|
||||
|
||||
auto& storage = uf::graph::getStorage(graph);
|
||||
auto& skinStream = graph.streams.skins[skinName];
|
||||
|
||||
if ( graph.settings.stream.enabled ) {
|
||||
skinStream.inverseBindMatrices = { binPath, offset, length };
|
||||
} else {
|
||||
if (length > 0 && !megaBuffer.empty()) {
|
||||
skin.inverseBindMatrices.resize(count);
|
||||
memcpy(skin.inverseBindMatrices.data(), megaBuffer.data() + offset, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return skin;
|
||||
}
|
||||
|
||||
pod::Instance decodeInstance( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::Instance instance;
|
||||
//instance.model = uf::matrix::decode( json["model"], instance.model );
|
||||
//instance.color = uf::vector::decode( json["color"], instance.color );
|
||||
instance.materialID = json["materialID"].as( instance.materialID );
|
||||
instance.primitiveID = json["primitiveID"].as( instance.primitiveID );
|
||||
instance.meshID = json["meshID"].as( instance.meshID );
|
||||
instance.lightmapID = json["lightmapID"].as( instance.lightmapID );
|
||||
instance.cubemapID = json["cubemapID"].as( -1 /*instance.cubemapID*/ );
|
||||
instance.auxID = json["auxID"].as( instance.auxID );
|
||||
instance.objectID = json["objectID"].as( instance.objectID );
|
||||
instance.bounds.min = uf::vector::decode( json["bounds"]["min"], instance.bounds.min );
|
||||
instance.bounds.max = uf::vector::decode( json["bounds"]["max"], instance.bounds.max );
|
||||
return instance;
|
||||
}
|
||||
|
||||
pod::DrawCommand decodeDrawCommand( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::DrawCommand drawCommand;
|
||||
drawCommand.indices = json["indices"].as( drawCommand.indices );
|
||||
drawCommand.instances = json["instances"].as( drawCommand.instances );
|
||||
drawCommand.indexID = json["indexID"].as( drawCommand.indexID );
|
||||
drawCommand.vertexID = json["vertexID"].as( drawCommand.vertexID );
|
||||
drawCommand.instanceID = json["instanceID"].as( drawCommand.instanceID );
|
||||
drawCommand.auxID = json["auxID"].as( drawCommand.auxID );
|
||||
drawCommand.materialID = json["materialID"].as( drawCommand.materialID );
|
||||
drawCommand.vertices = json["vertices"].as( drawCommand.vertices );
|
||||
return drawCommand;
|
||||
}
|
||||
|
||||
pod::LODMetadata decodeLODMetadata( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::LODMetadata lodMetadata;
|
||||
ext::json::forEach( json, [&]( size_t i, ext::json::Value& value ){
|
||||
lodMetadata.levels[i].indices = value["indices"].as( lodMetadata.levels[i].indices );
|
||||
lodMetadata.levels[i].indexID = value["indexID"].as( lodMetadata.levels[i].indexID );
|
||||
lodMetadata.levels[i].vertices = value["vertices"].as( lodMetadata.levels[i].vertices );
|
||||
lodMetadata.levels[i].vertexID = value["vertexID"].as( lodMetadata.levels[i].vertexID );
|
||||
});
|
||||
return lodMetadata;
|
||||
}
|
||||
|
||||
pod::Primitive decodePrimitive( ext::json::Value& json, pod::Graph& graph ) {
|
||||
pod::Primitive prim;
|
||||
prim.instance = decodeInstance( json["instance"], graph );
|
||||
prim.drawCommand = decodeDrawCommand( json["drawCommand"], graph );
|
||||
prim.lod = decodeLODMetadata( json["lod"], graph );
|
||||
return prim;
|
||||
}
|
||||
|
||||
uf::stl::vector<pod::Primitive> decodePrimitives( ext::json::Value& json, pod::Graph& graph ) {
|
||||
uf::stl::vector<pod::Primitive> primitives;
|
||||
auto name = json["name"].as<uf::stl::string>();
|
||||
ext::json::forEach( json["primitives"], [&]( ext::json::Value& value ){
|
||||
primitives.emplace_back( decodePrimitive( value, graph ) );
|
||||
});
|
||||
return primitives;
|
||||
}
|
||||
uf::stl::vector<pod::DrawCommand> decodeDrawCommands( ext::json::Value& json, pod::Graph& graph ) {
|
||||
uf::stl::vector<pod::DrawCommand> drawCommands;
|
||||
auto name = json["name"].as<uf::stl::string>();
|
||||
ext::json::forEach( json["drawCommands"], [&]( ext::json::Value& value ){
|
||||
drawCommands.emplace_back( decodeDrawCommand( value, graph ) );
|
||||
});
|
||||
return drawCommands;
|
||||
}
|
||||
|
||||
uf::Mesh decodeMesh( ext::json::Value& json, pod::Graph& graph ) {
|
||||
uf::Mesh decodeMesh( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& meshName, const uf::stl::vector<uint8_t>& megaBuffer ) {
|
||||
uf::Mesh mesh;
|
||||
|
||||
#define DESERIALIZE_MESH(N) {\
|
||||
@ -249,38 +190,67 @@ namespace {
|
||||
DESERIALIZE_MESH(index);
|
||||
DESERIALIZE_MESH(instance);
|
||||
DESERIALIZE_MESH(indirect);
|
||||
#undef DESERIALIZE_MESH
|
||||
|
||||
auto& storage = uf::graph::getStorage(graph);
|
||||
auto& meshStream = graph.streams.meshes[meshName];
|
||||
|
||||
mesh.buffers.reserve( json["buffers"].size() );
|
||||
mesh.buffer_paths.reserve( json["buffers"].size() );
|
||||
|
||||
uf::stl::vector<pod::StreamRegion> localRegions;
|
||||
localRegions.reserve( json["buffers"].size() );
|
||||
|
||||
ext::json::forEach( json["buffers"], [&]( ext::json::Value& value ){
|
||||
const uf::stl::string filename = value.as<uf::stl::string>();
|
||||
const uf::stl::string directory = uf::io::directory( graph.name );
|
||||
// uf::io::readAsBuffer( directory + "/" + filename )
|
||||
#if !UF_GRAPH_EXTENDED
|
||||
mesh.buffers.emplace_back(uf::io::readAsBuffer( directory + "/" + filename ));
|
||||
#else
|
||||
uf::stl::string filename;
|
||||
size_t offset = 0, length = 0;
|
||||
|
||||
if (value.isObject()) {
|
||||
filename = value["filename"].as<uf::stl::string>();
|
||||
offset = value["offset"].as<size_t>();
|
||||
length = value["length"].as<size_t>();
|
||||
} else {
|
||||
filename = value.as<uf::stl::string>();
|
||||
}
|
||||
|
||||
uf::stl::string fullPath = uf::io::directory( graph.name ) + "/" + filename;
|
||||
pod::StreamRegion region = { fullPath, offset, length };
|
||||
|
||||
if ( graph.settings.stream.enabled ) {
|
||||
mesh.buffers.emplace_back();
|
||||
mesh.buffer_paths.emplace_back(directory + "/" + filename);
|
||||
meshStream.buffers.push_back(region);
|
||||
} else {
|
||||
mesh.buffers.emplace_back(uf::io::readAsBuffer( directory + "/" + filename ));
|
||||
uf::stl::vector<uint8_t> buf;
|
||||
if (length > 0 && !megaBuffer.empty()) {
|
||||
buf.assign(megaBuffer.begin() + offset, megaBuffer.begin() + offset + length);
|
||||
} else if (length > 0) {
|
||||
uf::io::readAsBuffer(buf, fullPath, offset, length);
|
||||
} else {
|
||||
uf::io::readAsBuffer(buf, fullPath);
|
||||
}
|
||||
mesh.buffers.emplace_back(std::move(buf));
|
||||
localRegions.push_back(region);
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
// load non vertex/index buffers
|
||||
auto getRegion = [&](size_t bufferIdx) -> pod::StreamRegion {
|
||||
if ( graph.settings.stream.enabled ) return meshStream.buffers[bufferIdx];
|
||||
return localRegions[bufferIdx];
|
||||
};
|
||||
|
||||
for ( size_t i = 0; i < mesh.instance.attributes.size(); ++i ) {
|
||||
auto& attribute = mesh.instance.attributes[i];
|
||||
if ( !mesh.buffers[attribute.buffer].empty() ) continue;
|
||||
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );
|
||||
auto& attr = mesh.instance.attributes[i];
|
||||
if ( !mesh.buffers[attr.buffer].empty() ) continue;
|
||||
auto region = getRegion(attr.buffer);
|
||||
uf::io::readAsBuffer(mesh.buffers[attr.buffer], region.filename, region.offset, region.length);
|
||||
}
|
||||
for ( size_t i = 0; i < mesh.indirect.attributes.size(); ++i ) {
|
||||
auto& attribute = mesh.indirect.attributes[i];
|
||||
if ( !mesh.buffers[attribute.buffer].empty() ) continue;
|
||||
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );
|
||||
auto& attr = mesh.indirect.attributes[i];
|
||||
if ( !mesh.buffers[attr.buffer].empty() ) continue;
|
||||
auto region = getRegion(attr.buffer);
|
||||
uf::io::readAsBuffer(mesh.buffers[attr.buffer], region.filename, region.offset, region.length);
|
||||
}
|
||||
|
||||
#if UF_ENV_DREAMCAST
|
||||
#if UF_ENV_DREAMCAST
|
||||
// remove extraneous buffers
|
||||
// if ( graph.metadata["renderer"]["separate"].as<bool>() )
|
||||
{
|
||||
@ -291,7 +261,6 @@ namespace {
|
||||
auto& attribute = mesh.vertex.attributes[i];
|
||||
if ( std::find( attributesKept.begin(), attributesKept.end(), attribute.descriptor.name ) != attributesKept.end() ) continue;
|
||||
remove.insert(remove.begin(), i);
|
||||
UF_MSG_DEBUG("Removing mesh attribute: {}", attribute.descriptor.name);
|
||||
}
|
||||
for ( auto& i : remove ) {
|
||||
mesh.buffers[mesh.vertex.attributes[i].buffer].clear();
|
||||
@ -323,7 +292,6 @@ namespace {
|
||||
}
|
||||
|
||||
mesh.updateDescriptor();
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
@ -361,20 +329,9 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
uf::Serializer serializer;
|
||||
UF_DEBUG_TIMER_MULTITRACE_START("Reading {}", filename);
|
||||
serializer.readFromFile( filename );
|
||||
// load metadata
|
||||
graph.name = filename; //serializer["name"].as<uf::stl::string>();
|
||||
// graph.metadata = metadata; // serializer["metadata"];
|
||||
|
||||
// UF_MSG_DEBUG("A: {}", serializer["metadata"].dump(1, '\t'));
|
||||
// UF_MSG_DEBUG("B: {}", metadata.dump(1, '\t'));
|
||||
#if 0
|
||||
graph.metadata = serializer["metadata"];
|
||||
graph.metadata.merge( metadata, false );
|
||||
#else
|
||||
graph.name = filename;
|
||||
graph.metadata = metadata;
|
||||
graph.metadata.merge( serializer["metadata"], true );
|
||||
#endif
|
||||
// UF_MSG_DEBUG("C: {}", graph.metadata.dump(1, '\t'));
|
||||
|
||||
#if UF_GRAPH_LOAD_MULTITHREAD
|
||||
auto tasks = uf::thread::schedule(true);
|
||||
@ -440,185 +397,222 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
if ( key != "" ) key += ":";
|
||||
|
||||
tasks.queue([&]{
|
||||
// load images
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading material information...");
|
||||
auto& node = serializer["materials"];
|
||||
if (node.isObject() && node["buffer"].is<uf::stl::string>()) {
|
||||
uf::stl::string binName = node["buffer"].as<uf::stl::string>();
|
||||
uf::stl::vector<uint8_t> ioBuf;
|
||||
|
||||
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
|
||||
pod::Material* rawMaterials = reinterpret_cast<pod::Material*>(ioBuf.data());
|
||||
auto names = node["names"].as<uf::stl::vector<uf::stl::string>>();
|
||||
|
||||
graph.materials.reserve(names.size());
|
||||
for (size_t i = 0; i < names.size(); ++i) {
|
||||
auto name = key + names[i];
|
||||
storage.materials[name] = rawMaterials[i];
|
||||
graph.materials.emplace_back(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read material information");
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading texture information...");
|
||||
auto& node = serializer["textures"];
|
||||
if (node.isObject() && node["buffer"].is<uf::stl::string>()) {
|
||||
uf::stl::string binName = node["buffer"].as<uf::stl::string>();
|
||||
uf::stl::vector<uint8_t> ioBuf;
|
||||
|
||||
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
|
||||
pod::Texture* rawTextures = reinterpret_cast<pod::Texture*>(ioBuf.data());
|
||||
auto names = node["names"].as<uf::stl::vector<uf::stl::string>>();
|
||||
|
||||
graph.textures.reserve(names.size());
|
||||
for (size_t i = 0; i < names.size(); ++i) {
|
||||
auto name = key + names[i];
|
||||
storage.textures[name] = rawTextures[i];
|
||||
graph.textures.emplace_back(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read texture information");
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading sampler information...");
|
||||
auto& node = serializer["samplers"];
|
||||
if (node.isObject() && node["buffer"].is<uf::stl::string>()) {
|
||||
uf::stl::string binName = node["buffer"].as<uf::stl::string>();
|
||||
uf::stl::vector<uint8_t> ioBuf;
|
||||
|
||||
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
|
||||
uf::renderer::Sampler* rawSamplers = reinterpret_cast<uf::renderer::Sampler*>(ioBuf.data());
|
||||
auto names = node["names"].as<uf::stl::vector<uf::stl::string>>();
|
||||
|
||||
graph.samplers.reserve(names.size());
|
||||
for (size_t i = 0; i < names.size(); ++i) {
|
||||
auto name = key + names[i];
|
||||
storage.samplers[name] = rawSamplers[i];
|
||||
graph.samplers.emplace_back(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read sampler information");
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading lighting information...");
|
||||
auto& node = serializer["lights"];
|
||||
if (node.isObject() && node["buffer"].is<uf::stl::string>()) {
|
||||
uf::stl::string binName = node["buffer"].as<uf::stl::string>();
|
||||
uf::stl::vector<uint8_t> ioBuf;
|
||||
|
||||
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
|
||||
pod::Light* rawLights = reinterpret_cast<pod::Light*>(ioBuf.data());
|
||||
auto names = node["names"].as<uf::stl::vector<uf::stl::string>>();
|
||||
|
||||
graph.lights.reserve(names.size());
|
||||
for (size_t i = 0; i < names.size(); ++i) {
|
||||
auto name = key + names[i];
|
||||
graph.lights[name] = rawLights[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read lighting information");
|
||||
});
|
||||
tasks.queue([&]{
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading primitives...");
|
||||
graph.primitives.reserve( serializer["primitives"].size() );
|
||||
ext::json::forEach( serializer["primitives"], [&]( ext::json::Value& value ){
|
||||
|
||||
uf::stl::string binName = "primitives.bin";
|
||||
uf::stl::vector<uint8_t> ioBuf;
|
||||
pod::Primitive* allPrimitives = nullptr;
|
||||
|
||||
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
|
||||
allPrimitives = reinterpret_cast<pod::Primitive*>(ioBuf.data());
|
||||
}
|
||||
|
||||
auto& primNode = serializer["primitives"];
|
||||
graph.primitives.reserve( primNode.size() );
|
||||
|
||||
ext::json::forEach( primNode, [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
storage.primitives[name] = decodePrimitives( value, graph );
|
||||
|
||||
graph.primitives.emplace_back(name);
|
||||
|
||||
bool hasOffset = !value["offset"].isNull();
|
||||
|
||||
if (allPrimitives && hasOffset) {
|
||||
size_t count = value["count"].as<size_t>();
|
||||
size_t offsetBytes = value["offset"].as<size_t>();
|
||||
size_t startIndex = offsetBytes / sizeof(pod::Primitive);
|
||||
|
||||
storage.primitives[name].assign(&allPrimitives[startIndex], &allPrimitives[startIndex + count]);
|
||||
} else {
|
||||
UF_MSG_WARNING("Primitive '{}' missing binary data. Buffer Loaded: {} | Offset in JSON: {}",
|
||||
name, (allPrimitives != nullptr), hasOffset);
|
||||
}
|
||||
});
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read primitives.");
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
// load mesh information
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading meshes...");
|
||||
graph.meshes.reserve( serializer["meshes"].size() );
|
||||
ext::json::forEach( serializer["meshes"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
storage.meshes[name] = decodeMesh( value, graph );
|
||||
graph.meshes.emplace_back(name);
|
||||
});
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read meshes");
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// load images
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading images...");
|
||||
graph.images.reserve( serializer["images"].size() );
|
||||
ext::json::forEach( serializer["images"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
|
||||
storage.images[name] = {
|
||||
.data = decodeImage( value, graph ),
|
||||
.data = decodeImage( value, graph, name ),
|
||||
};
|
||||
graph.images.emplace_back(name);
|
||||
});
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read images");
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
#if 0 && !UF_ENV_DREAMCAST
|
||||
if ( !ext::json::isNull( serializer["atlas"] ) ) {
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading atlas...");
|
||||
auto& image = graph.atlas.getAtlas();
|
||||
auto& value = serializer["atlas"];
|
||||
if ( value.is<uf::stl::string>() ) {
|
||||
uf::stl::string filename = directory + "/" + value.as<uf::stl::string>();
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading atlas " << filename);
|
||||
image.open(filename, false);
|
||||
} else {
|
||||
decode( value, image, graph );
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading meshes...");
|
||||
graph.meshes.reserve( serializer["meshes"].size() );
|
||||
|
||||
uf::stl::vector<uint8_t> megaBuffer;
|
||||
bool bufferAttempted = false;
|
||||
|
||||
ext::json::forEach( serializer["meshes"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
|
||||
if (!bufferAttempted && value["buffers"].size() > 0 && value["buffers"][0].isObject()) {
|
||||
uf::stl::string binName = value["buffers"][0]["filename"].as<uf::stl::string>();
|
||||
uf::io::readAsBuffer(megaBuffer, directory + binName);
|
||||
bufferAttempted = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// load texture information
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading texture information...");
|
||||
graph.textures.reserve( serializer["textures"].size() );
|
||||
ext::json::forEach( serializer["textures"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
storage.textures[name] = decodeTexture( value, graph );
|
||||
graph.textures.emplace_back(name);
|
||||
|
||||
storage.meshes[name] = decodeMesh( value, graph, name, megaBuffer );
|
||||
graph.meshes.emplace_back(name);
|
||||
});
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read texture information");
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read meshes");
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
// load sampler information
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading sampler information...");
|
||||
graph.samplers.reserve( serializer["samplers"].size() );
|
||||
ext::json::forEach( serializer["samplers"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
storage.samplers[name] = decodeSampler( value, graph );
|
||||
graph.samplers.emplace_back(name);
|
||||
});
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read sampler information");
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// load material information
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading material information...");
|
||||
graph.materials.reserve( serializer["materials"].size() );
|
||||
ext::json::forEach( serializer["materials"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
storage.materials[name] = decodeMaterial( value, graph );
|
||||
graph.materials.emplace_back(name);
|
||||
});
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read material information");
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// load light information
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading lighting information...");
|
||||
graph.lights.reserve( serializer["lights"].size() );
|
||||
ext::json::forEach( serializer["lights"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
graph.lights[name] = decodeLight( value, graph );
|
||||
});
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read lighting information");
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
});
|
||||
#if 1
|
||||
tasks.queue([&]{
|
||||
// load animation information
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading animation information...");
|
||||
storage.animations.map.reserve( serializer["animations"].size() );
|
||||
ext::json::forEach( serializer["animations"], [&]( ext::json::Value& value ){
|
||||
if ( value.is<uf::stl::string>() ) {
|
||||
auto path = directory + "/" + value.as<uf::stl::string>();
|
||||
auto& animNode = serializer["animations"];
|
||||
|
||||
uf::stl::vector<uint8_t> megaBuffer;
|
||||
bool bufferAttempted = false;
|
||||
|
||||
if (animNode.isObject()) {
|
||||
storage.animations.map.reserve( animNode.size() );
|
||||
ext::json::forEach( animNode, [&]( const uf::stl::string& rawName, ext::json::Value& value ){
|
||||
auto name = key + rawName;
|
||||
|
||||
if (!bufferAttempted && value["buffer"].is<uf::stl::string>()) {
|
||||
uf::stl::string binName = value["buffer"].as<uf::stl::string>();
|
||||
uf::io::readAsBuffer(megaBuffer, directory + binName);
|
||||
bufferAttempted = true;
|
||||
}
|
||||
|
||||
storage.animations[name] = decodeAnimation( value, graph, name, megaBuffer );
|
||||
graph.animations.emplace_back(name);
|
||||
});
|
||||
}
|
||||
else if (animNode.isArray()) {
|
||||
storage.animations.map.reserve( animNode.size() );
|
||||
ext::json::forEach( animNode, [&]( ext::json::Value& value ){
|
||||
uf::stl::string path = directory + "/" + value.as<uf::stl::string>();
|
||||
uf::Serializer json;
|
||||
json.readFromFile( path );
|
||||
auto name = key + json["name"].as<uf::stl::string>();
|
||||
if ( graph.settings.stream.animations ) {
|
||||
storage.animations[name].path = path;
|
||||
} else {
|
||||
storage.animations[name] = decodeAnimation( json, graph );
|
||||
}
|
||||
graph.animations.emplace_back(name);
|
||||
} else {
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
if ( value["filename"].is<uf::stl::string>() ) {
|
||||
auto path = directory + "/" + value.as<uf::stl::string>();
|
||||
uf::Serializer json;
|
||||
json.readFromFile( path );
|
||||
|
||||
auto name = key + json["name"].as<uf::stl::string>();
|
||||
if ( graph.settings.stream.animations ) {
|
||||
storage.animations[name].path = path;
|
||||
} else {
|
||||
storage.animations[name] = decodeAnimation( json, graph );
|
||||
}
|
||||
graph.animations.emplace_back(name);
|
||||
} else {
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
storage.animations[name] = decodeAnimation( value, graph );
|
||||
graph.animations.emplace_back(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
storage.animations[name] = decodeAnimation( json, graph, name, megaBuffer );
|
||||
graph.animations.emplace_back(name);
|
||||
});
|
||||
}
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read animations");
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
// load skin information
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading skinning information...");
|
||||
graph.skins.reserve( serializer["skins"].size() );
|
||||
|
||||
uf::stl::vector<uint8_t> megaBuffer;
|
||||
bool bufferAttempted = false;
|
||||
|
||||
ext::json::forEach( serializer["skins"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
// UF_MSG_DEBUG("{}", name);
|
||||
storage.skins[name] = decodeSkin( value, graph );
|
||||
|
||||
if (!bufferAttempted && value["inverseBindMatrices"].isObject()) {
|
||||
uf::stl::string binName = value["inverseBindMatrices"]["buffer"].as<uf::stl::string>();
|
||||
uf::io::readAsBuffer(megaBuffer, directory + binName);
|
||||
bufferAttempted = true;
|
||||
}
|
||||
|
||||
storage.skins[name] = decodeSkin( value, graph, name, megaBuffer );
|
||||
graph.skins.emplace_back(name);
|
||||
});
|
||||
#if UF_ENV_DREAMCAST
|
||||
DC_STATS();
|
||||
#endif
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read skins");
|
||||
});
|
||||
#endif
|
||||
|
||||
tasks.queue([&]{
|
||||
// load node information
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading nodes...");
|
||||
graph.nodes.reserve( serializer["nodes"].size() );
|
||||
ext::json::forEach( serializer["nodes"], [&]( ext::json::Value& value ){
|
||||
|
||||
@ -20,145 +20,77 @@ namespace {
|
||||
uf::stl::string conversion = "";
|
||||
};
|
||||
|
||||
uf::Serializer encode( const uf::Image& image, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
json["size"] = uf::vector::encode( image.getDimensions() );
|
||||
json["bpp"] = image.getBpp() / image.getChannels();
|
||||
json["channels"] = image.getChannels();
|
||||
json["data"] = uf::base64::encode( image.getPixels() );
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::Texture& texture, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
json["index"] = texture.index;
|
||||
json["sampler"] = texture.sampler;
|
||||
json["remap"] = texture.remap;
|
||||
json["blend"] = texture.blend;
|
||||
json["lerp"] = uf::vector::encode( texture.lerp, settings );
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const uf::renderer::Sampler& sampler, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
json["min"] = sampler.descriptor.filter.min;
|
||||
json["mag"] = sampler.descriptor.filter.mag;
|
||||
json["u"] = sampler.descriptor.addressMode.u;
|
||||
json["v"] = sampler.descriptor.addressMode.v;
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::Material& material, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
json["base"] = uf::vector::encode( material.colorBase, settings );
|
||||
json["emissive"] = uf::vector::encode( material.colorEmissive, settings );
|
||||
json["fMetallic"] = material.factorMetallic;
|
||||
json["fRoughness"] = material.factorRoughness;
|
||||
json["fOcclusion"] = material.factorOcclusion;
|
||||
json["fAlphaCutoff"] = material.factorAlphaCutoff;
|
||||
if ( material.indexAlbedo >= 0 ) json["iAlbedo"] = material.indexAlbedo;
|
||||
if ( material.indexNormal >= 0 ) json["iNormal"] = material.indexNormal;
|
||||
if ( material.indexEmissive >= 0 ) json["iEmissive"] = material.indexEmissive;
|
||||
if ( material.indexOcclusion >= 0 ) json["iOcclusion"] = material.indexOcclusion;
|
||||
if ( material.indexMetallicRoughness >= 0 ) json["iMetallicRoughness"] = material.indexMetallicRoughness;
|
||||
if ( material.indexCubemap >= 0 ) json["iCubemap"] = material.indexCubemap;
|
||||
json["modeCull"] = material.modeCull;
|
||||
json["modeAlpha"] = material.modeAlpha;
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::Light& light, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
json["color"] = uf::vector::encode( light.color, settings );
|
||||
json["intensity"] = light.intensity;
|
||||
json["range"] = light.range;
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::Animation& animation, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
|
||||
uf::Serializer encode( const pod::Animation& animation, const EncodingSettings& settings, const pod::Graph& graph, uf::stl::vector<uint8_t>& outBuffer, const uf::stl::string& binFilename ) {
|
||||
uf::Serializer json;
|
||||
json["name"] = animation.name;
|
||||
json["start"] = animation.start;
|
||||
json["end"] = animation.end;
|
||||
|
||||
auto appendToBuffer = [&](const void* data, size_t size) -> size_t {
|
||||
size_t offset = outBuffer.size();
|
||||
if (size > 0) {
|
||||
const uint8_t* bytes = static_cast<const uint8_t*>(data);
|
||||
outBuffer.insert(outBuffer.end(), bytes, bytes + size);
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
ext::json::reserve( json["samplers"], animation.samplers.size() );
|
||||
auto& samplers = json["samplers"];
|
||||
for ( auto& sampler : animation.samplers ) {
|
||||
auto& json = samplers.emplace_back();
|
||||
json["interpolator"] = sampler.interpolator;
|
||||
for ( auto& input : sampler.inputs ) {
|
||||
json["inputs"].emplace_back(input);
|
||||
}
|
||||
for ( auto& output : sampler.outputs ) {
|
||||
json["outputs"].emplace_back(uf::vector::encode( output, settings ));
|
||||
}
|
||||
auto& sJson = samplers.emplace_back();
|
||||
sJson["interpolator"] = sampler.interpolator;
|
||||
|
||||
size_t inputsSize = sampler.inputs.size() * sizeof(float);
|
||||
sJson["inputs"]["count"] = sampler.inputs.size();
|
||||
sJson["inputs"]["offset"] = appendToBuffer(sampler.inputs.data(), inputsSize);
|
||||
sJson["inputs"]["length"] = inputsSize;
|
||||
|
||||
size_t outputsSize = sampler.outputs.size() * sizeof(pod::Vector4f);
|
||||
sJson["outputs"]["count"] = sampler.outputs.size();
|
||||
sJson["outputs"]["offset"] = appendToBuffer(sampler.outputs.data(), outputsSize);
|
||||
sJson["outputs"]["length"] = outputsSize;
|
||||
}
|
||||
|
||||
json["buffer"] = binFilename;
|
||||
|
||||
ext::json::reserve( json["channels"], animation.channels.size() );
|
||||
auto& channels = json["channels"];
|
||||
for ( auto& channel : animation.channels ) {
|
||||
auto& json = channels.emplace_back();
|
||||
json["path"] = channel.path;
|
||||
json["node"] = channel.node;
|
||||
json["sampler"] = channel.sampler;
|
||||
auto& cJson = channels.emplace_back();
|
||||
cJson["path"] = channel.path;
|
||||
cJson["node"] = channel.node;
|
||||
cJson["sampler"] = channel.sampler;
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::Skin& skin, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
}
|
||||
uf::Serializer encode( const pod::Skin& skin, const EncodingSettings& settings, const pod::Graph& graph, uf::stl::vector<uint8_t>& outBuffer, const uf::stl::string& binFilename ) {
|
||||
uf::Serializer json;
|
||||
json["name"] = skin.name;
|
||||
|
||||
ext::json::reserve( json["joints"], skin.joints.size() );
|
||||
for ( auto& joint : skin.joints ) json["joints"].emplace_back( joint );
|
||||
|
||||
ext::json::reserve( json["inverseBindMatrices"], skin.inverseBindMatrices.size() );
|
||||
for ( auto& inverseBindMatrix : skin.inverseBindMatrices )
|
||||
json["inverseBindMatrices"].emplace_back( uf::matrix::encode(inverseBindMatrix, settings) );
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::Instance& instance, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
// json["model"] = uf::matrix::encode( instance.model, settings );
|
||||
// json["color"] = uf::vector::encode( instance.color, settings );
|
||||
json["materialID"] = instance.materialID;
|
||||
json["primitiveID"] = instance.primitiveID;
|
||||
json["meshID"] = instance.meshID;
|
||||
json["lightmapID"] = instance.lightmapID;
|
||||
json["cubemapID"] = instance.cubemapID;
|
||||
json["objectID"] = instance.objectID;
|
||||
json["auxID"] = instance.auxID;
|
||||
|
||||
json["bounds"]["min"] = uf::vector::encode( instance.bounds.min, settings );
|
||||
json["bounds"]["max"] = uf::vector::encode( instance.bounds.max, settings );
|
||||
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::DrawCommand& drawCommand, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
json["indices"] = drawCommand.indices;
|
||||
json["instances"] = drawCommand.instances;
|
||||
json["indexID"] = drawCommand.indexID;
|
||||
json["vertexID"] = drawCommand.vertexID;
|
||||
json["instanceID"] = drawCommand.instanceID;
|
||||
json["auxID"] = drawCommand.auxID;
|
||||
json["materialID"] = drawCommand.materialID;
|
||||
json["vertices"] = drawCommand.vertices;
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::LODMetadata& lodMetadata, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
ext::json::reserve( json, 4 );
|
||||
for ( size_t i = 0; i < 4; ++i ) {
|
||||
auto& value = json.emplace_back();
|
||||
value["indices"] = lodMetadata.levels[i].indices;
|
||||
value["indexID"] = lodMetadata.levels[i].indexID;
|
||||
value["vertexID"] = lodMetadata.levels[i].vertexID;
|
||||
value["vertices"] = lodMetadata.levels[i].vertices;
|
||||
for ( auto& joint : skin.joints ) {
|
||||
json["joints"].emplace_back( joint );
|
||||
}
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::Primitive& primitive, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
uf::Serializer json;
|
||||
json["drawCommand"] = encode( primitive.drawCommand, settings, graph );
|
||||
json["instance"] = encode( primitive.instance, settings, graph );
|
||||
json["lod"] = encode( primitive.lod, settings, graph );
|
||||
return json;
|
||||
}
|
||||
|
||||
size_t matricesSize = skin.inverseBindMatrices.size() * sizeof(pod::Matrix4f);
|
||||
size_t offset = outBuffer.size();
|
||||
|
||||
if ( matricesSize > 0 ) {
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(skin.inverseBindMatrices.data());
|
||||
outBuffer.insert(outBuffer.end(), bytes, bytes + matricesSize);
|
||||
}
|
||||
|
||||
json["inverseBindMatrices"]["buffer"] = binFilename;
|
||||
json["inverseBindMatrices"]["count"] = skin.inverseBindMatrices.size();
|
||||
json["inverseBindMatrices"]["offset"] = offset;
|
||||
json["inverseBindMatrices"]["length"] = matricesSize;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
uf::Mesh reencode( const uf::Mesh& _mesh, const uf::stl::string& conversion ) {
|
||||
uf::Mesh mesh = _mesh.copy();
|
||||
if ( conversion != "" ) {
|
||||
@ -177,9 +109,9 @@ namespace {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
uf::Serializer encode( const uf::Mesh& mesh, const EncodingSettings& settings, const pod::Graph& graph, bool checkForConversions = true ) {
|
||||
uf::Serializer encode( const uf::Mesh& mesh, const EncodingSettings& settings, const pod::Graph& graph, uf::stl::vector<uint8_t>& outBuffer, const uf::stl::string& binFilename, bool checkForConversions = true ) {
|
||||
if ( checkForConversions && settings.conversion != "" ) {
|
||||
// return encode( reencode( mesh, settings.conversion ), settings, graph, false );
|
||||
// return encode( reencode( mesh, settings.conversion ), settings, graph, outBuffer, binFilename, false );
|
||||
}
|
||||
|
||||
uf::Serializer json;
|
||||
@ -212,10 +144,19 @@ 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 ) );
|
||||
uf::io::write( filename, mesh.buffers[i] );
|
||||
json["buffers"].emplace_back(uf::io::filename( filename ));
|
||||
size_t offset = outBuffer.size();
|
||||
size_t length = mesh.buffers[i].size();
|
||||
|
||||
outBuffer.insert(outBuffer.end(), mesh.buffers[i].begin(), mesh.buffers[i].end());
|
||||
|
||||
auto& bufJson = json["buffers"].emplace_back();
|
||||
bufJson["filename"] = binFilename;
|
||||
bufJson["offset"] = offset;
|
||||
bufJson["length"] = length;
|
||||
}
|
||||
|
||||
#undef SERIALIZE_MESH
|
||||
|
||||
return json;
|
||||
}
|
||||
uf::Serializer encode( const pod::Node& node, const EncodingSettings& settings, const pod::Graph& graph ) {
|
||||
@ -273,164 +214,229 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string&
|
||||
auto& storage = uf::graph::getStorage( graph );
|
||||
|
||||
tasks.queue([&]{
|
||||
uf::stl::vector<pod::Material> flatMaterials;
|
||||
flatMaterials.reserve(graph.materials.size());
|
||||
for ( auto& name : graph.materials ) {
|
||||
flatMaterials.push_back(storage.materials.map.at(name));
|
||||
}
|
||||
|
||||
if (!flatMaterials.empty()) {
|
||||
size_t length = flatMaterials.size() * sizeof(pod::Material);
|
||||
uf::stl::string binName = "materials.bin";
|
||||
uf::io::write(directory + "/" + binName, flatMaterials.data(), length);
|
||||
|
||||
serializer["materials"]["names"] = graph.materials;
|
||||
serializer["materials"]["buffer"] = binName;
|
||||
serializer["materials"]["count"] = flatMaterials.size();
|
||||
serializer["materials"]["length"] = length;
|
||||
}
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
uf::stl::vector<pod::Texture> flatTextures;
|
||||
flatTextures.reserve(graph.textures.size());
|
||||
for ( auto& name : graph.textures ) {
|
||||
flatTextures.push_back(storage.textures.map.at(name));
|
||||
}
|
||||
|
||||
if (!flatTextures.empty()) {
|
||||
size_t length = flatTextures.size() * sizeof(pod::Texture);
|
||||
uf::stl::string binName = "textures.bin";
|
||||
uf::io::write(directory + "/" + binName, flatTextures.data(), length);
|
||||
|
||||
serializer["textures"]["names"] = graph.textures;
|
||||
serializer["textures"]["buffer"] = binName;
|
||||
serializer["textures"]["count"] = flatTextures.size();
|
||||
serializer["textures"]["length"] = length;
|
||||
}
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
uf::stl::vector<uf::renderer::Sampler> flatSamplers;
|
||||
flatSamplers.reserve(graph.samplers.size());
|
||||
for ( auto& name : graph.samplers ) {
|
||||
flatSamplers.push_back(storage.samplers.map.at(name));
|
||||
}
|
||||
|
||||
if (!flatSamplers.empty()) {
|
||||
size_t length = flatSamplers.size() * sizeof(uf::renderer::Sampler);
|
||||
uf::stl::string binName = "samplers.bin";
|
||||
uf::io::write(directory + "/" + binName, flatSamplers.data(), length);
|
||||
|
||||
serializer["samplers"]["names"] = graph.samplers;
|
||||
serializer["samplers"]["buffer"] = binName;
|
||||
serializer["samplers"]["count"] = flatSamplers.size();
|
||||
serializer["samplers"]["length"] = length;
|
||||
}
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
uf::stl::vector<pod::Light> flatLights;
|
||||
uf::stl::vector<uf::stl::string> lightNames;
|
||||
flatLights.reserve(graph.lights.size());
|
||||
lightNames.reserve(graph.lights.size());
|
||||
|
||||
for ( auto& pair : graph.lights ) {
|
||||
lightNames.push_back(pair.first);
|
||||
flatLights.push_back(pair.second);
|
||||
}
|
||||
|
||||
if (!flatLights.empty()) {
|
||||
size_t length = flatLights.size() * sizeof(pod::Light);
|
||||
uf::stl::string binName = "lights.bin";
|
||||
uf::io::write(directory + "/" + binName, flatLights.data(), length);
|
||||
|
||||
serializer["lights"]["names"] = lightNames;
|
||||
serializer["lights"]["buffer"] = binName;
|
||||
serializer["lights"]["count"] = flatLights.size();
|
||||
serializer["lights"]["length"] = length;
|
||||
}
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
uf::stl::vector<pod::Primitive> flatPrimitives;
|
||||
size_t totalPrimitives = 0;
|
||||
for ( auto& name : graph.primitives ) {
|
||||
totalPrimitives += storage.primitives.map.at(name).size();
|
||||
}
|
||||
flatPrimitives.reserve(totalPrimitives);
|
||||
|
||||
ext::json::reserve( serializer["primitives"], graph.primitives.size() );
|
||||
|
||||
for ( size_t i = 0; i < graph.primitives.size(); ++i ) {
|
||||
auto& name = graph.primitives[i];
|
||||
auto& primitives = /*graph.storage*/storage.primitives.map.at(name);
|
||||
auto& primArray = storage.primitives.map.at(name);
|
||||
|
||||
size_t byteOffset = flatPrimitives.size() * sizeof(pod::Primitive);
|
||||
size_t byteLength = primArray.size() * sizeof(pod::Primitive);
|
||||
|
||||
flatPrimitives.insert(flatPrimitives.end(), primArray.begin(), primArray.end());
|
||||
|
||||
auto& json = serializer["primitives"].emplace_back();
|
||||
json["name"] = name;
|
||||
// ext::json::reserve( json["primitives"], primitives.size() );
|
||||
for ( auto& primitive : primitives ) {
|
||||
json["primitives"].emplace_back( encode( primitive, settings, graph ) );
|
||||
}
|
||||
json["count"] = primArray.size();
|
||||
json["offset"] = byteOffset;
|
||||
json["length"] = byteLength;
|
||||
}
|
||||
|
||||
if ( !flatPrimitives.empty() ) {
|
||||
uf::stl::string binName = "primitives.bin";
|
||||
uf::io::write(directory + "/" + binName, flatPrimitives.data(), flatPrimitives.size() * sizeof(pod::Primitive));
|
||||
serializer["metadata"]["buffers"]["primitives"] = binName;
|
||||
}
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
// store mesh information
|
||||
ext::json::reserve( serializer["meshes"], graph.meshes.size() );
|
||||
if ( !settings.combined ) {
|
||||
::EncodingSettings s = settings;
|
||||
for ( size_t i = 0; i < graph.meshes.size(); ++i ) {
|
||||
auto& name = graph.meshes[i];
|
||||
auto& mesh = /*graph.storage*/storage.meshes.map.at(name);
|
||||
if ( !s.encodeBuffers ) {
|
||||
s.filename = ::fmt::format("{}/mesh.{}.json", directory, i );
|
||||
encode(mesh, s, graph).writeToFile(s.filename);
|
||||
uf::Serializer json;
|
||||
json["name"] = name;
|
||||
json["filename"] = uf::io::filename(s.filename);
|
||||
serializer["meshes"].emplace_back( json );
|
||||
} else {
|
||||
s.filename = ::fmt::format("{}/mesh.{}", directory, i );
|
||||
auto json = encode(mesh, s, graph);
|
||||
json["name"] = name;
|
||||
serializer["meshes"].emplace_back(json);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( auto& name : graph.meshes ) {
|
||||
auto& mesh = /*graph.storage*/storage.meshes.map.at(name);
|
||||
auto json = encode(mesh, settings, graph);
|
||||
json["name"] = name;
|
||||
serializer["meshes"].emplace_back(json);
|
||||
}
|
||||
|
||||
uf::stl::vector<uint8_t> meshesBuffer;
|
||||
uf::stl::string binName = "meshes." + (settings.compression == "none" ? "bin" : settings.compression);
|
||||
|
||||
for ( auto& name : graph.meshes ) {
|
||||
auto& mesh = storage.meshes.map.at(name);
|
||||
auto json = encode(mesh, settings, graph, meshesBuffer, binName);
|
||||
json["name"] = name;
|
||||
serializer["meshes"].emplace_back(json);
|
||||
}
|
||||
|
||||
if ( !meshesBuffer.empty() ) {
|
||||
uf::io::write(directory + "/" + binName, meshesBuffer);
|
||||
}
|
||||
});
|
||||
#if 0
|
||||
|
||||
tasks.queue([&]{
|
||||
if ( uf::graphs::storage.atlases[graph.atlas].generated() ) {
|
||||
auto atlasName = filename + "/" + "atlas";
|
||||
auto& atlas = /*graph.storage*/storage.atlases[atlasName];
|
||||
auto& image = atlas.getAtlas();
|
||||
if ( !settings.combined ) {
|
||||
image.save(directory + "/atlas.png");
|
||||
serializer["atlas"] = "atlas.png";
|
||||
} else {
|
||||
serializer["atlas"] = encode(image, settings, graph);
|
||||
}
|
||||
ext::json::reserve( serializer["animations"], graph.animations.size() );
|
||||
|
||||
uf::stl::vector<uint8_t> animsBuffer;
|
||||
uf::stl::string binName = "animations.bin";
|
||||
|
||||
for ( auto& name : graph.animations ) {
|
||||
auto& animation = storage.animations.map.at(name);
|
||||
serializer["animations"][name] = encode(animation, settings, graph, animsBuffer, binName);
|
||||
}
|
||||
|
||||
if ( !animsBuffer.empty() ) {
|
||||
uf::io::write(directory + "/" + binName, animsBuffer);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
tasks.queue([&]{
|
||||
ext::json::reserve( serializer["skins"], graph.skins.size() );
|
||||
|
||||
uf::stl::vector<uint8_t> skinsBuffer;
|
||||
uf::stl::string binName = "skins.bin";
|
||||
|
||||
for ( auto& name : graph.skins ) {
|
||||
auto& skin = storage.skins.map.at(name);
|
||||
serializer["skins"].emplace_back( encode(skin, settings, graph, skinsBuffer, binName) );
|
||||
}
|
||||
|
||||
if ( !skinsBuffer.empty() ) {
|
||||
uf::io::write(directory + "/" + binName, skinsBuffer);
|
||||
}
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
ext::json::reserve( serializer["images"], graph.images.size() );
|
||||
if ( !settings.combined ) {
|
||||
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::vector<uint8_t> imagesBuffer;
|
||||
uf::stl::vector<uint8_t> dtexBuffer;
|
||||
uf::stl::string binName = "images.bin";
|
||||
uf::stl::string dtexBinName = "images.dtex.bin";
|
||||
|
||||
for ( size_t i = 0; i < graph.images.size(); ++i ) {
|
||||
auto& name = graph.images[i];
|
||||
auto& image = storage.images.map.at(name).data;
|
||||
uf::Serializer json;
|
||||
json["name"] = name;
|
||||
|
||||
if ( !settings.combined ) {
|
||||
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) );
|
||||
#endif
|
||||
|
||||
uf::Serializer json;
|
||||
json["name"] = name;
|
||||
json["filename"] = f;
|
||||
serializer["images"].emplace_back( json );
|
||||
}
|
||||
} else {
|
||||
for ( auto& name : graph.images ) {
|
||||
auto& image = /*graph.storage*/storage.images.map.at(name).data;
|
||||
auto json = encode(image, settings, graph);
|
||||
json["name"] = name;
|
||||
serializer["images"].emplace_back( json );
|
||||
} else {
|
||||
uf::stl::vector<uint8_t> pngBytes;
|
||||
image.save( pngBytes );
|
||||
|
||||
size_t offset = imagesBuffer.size();
|
||||
size_t length = pngBytes.size();
|
||||
imagesBuffer.insert(imagesBuffer.end(), pngBytes.begin(), pngBytes.end());
|
||||
|
||||
json["filename"] = binName;
|
||||
json["offset"] = offset;
|
||||
json["length"] = length;
|
||||
}
|
||||
|
||||
#if UF_USE_DC_TEXCONV
|
||||
auto converted = image.scale( {32, 32}, "nearest" );
|
||||
uf::stl::vector<uint8_t> dtexBytes;
|
||||
auto dtex = ext::texconv::convert( converted );
|
||||
ext::texconv::save( dtex, dtexBytes );
|
||||
|
||||
size_t dtexOffset = dtexBuffer.size();
|
||||
size_t dtexLength = dtexBytes.size();
|
||||
dtexBuffer.insert(dtexBuffer.end(), dtexBytes.begin(), dtexBytes.end());
|
||||
|
||||
json["dtex"]["filename"] = dtexBinName;
|
||||
json["dtex"]["offset"] = dtexOffset;
|
||||
json["dtex"]["length"] = dtexLength;
|
||||
#endif
|
||||
|
||||
serializer["images"].emplace_back( json );
|
||||
}
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// store texture information
|
||||
ext::json::reserve( serializer["textures"], graph.textures.size() );
|
||||
for ( auto& name : graph.textures ) {
|
||||
auto& texture = /*graph.storage*/storage.textures.map.at(name);
|
||||
auto json = encode(texture, settings, graph);
|
||||
json["name"] = name;
|
||||
serializer["textures"].emplace_back(json);
|
||||
|
||||
if ( settings.combined && !imagesBuffer.empty() ) {
|
||||
uf::io::write(directory + "/" + binName, imagesBuffer);
|
||||
}
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// store sampler information
|
||||
ext::json::reserve( serializer["samplers"], graph.samplers.size() );
|
||||
for ( auto& name : graph.samplers ) {
|
||||
auto& sampler = /*graph.storage*/storage.samplers.map.at(name);
|
||||
auto json = encode(sampler, settings, graph);
|
||||
json["name"] = name;
|
||||
serializer["samplers"].emplace_back(json);
|
||||
|
||||
#if UF_USE_DC_TEXCONV
|
||||
if ( !dtexBuffer.empty() ) {
|
||||
uf::io::write(directory + "/" + dtexBinName, dtexBuffer);
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
tasks.queue([&]{
|
||||
// store material information
|
||||
ext::json::reserve( serializer["materials"], graph.materials.size() );
|
||||
for ( auto& name : graph.materials ) {
|
||||
auto& material = /*graph.storage*/storage.materials.map.at(name);
|
||||
auto json = encode(material, settings, graph);
|
||||
json["name"] = name;
|
||||
serializer["materials"].emplace_back(json);
|
||||
}
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// store light information
|
||||
ext::json::reserve( serializer["lights"], graph.lights.size() );
|
||||
for ( auto pair : graph.lights ) {
|
||||
auto& name = pair.first;
|
||||
auto& light = pair.second;
|
||||
auto json = encode(light, settings, graph);
|
||||
json["name"] = name;
|
||||
serializer["lights"].emplace_back(json);
|
||||
}
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// store animation information
|
||||
ext::json::reserve( serializer["animations"], graph.animations.size() );
|
||||
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 );
|
||||
auto& animation = /*graph.storage*/storage.animations.map.at(name);
|
||||
encode(animation, settings, graph).writeToFile(directory+"/"+f);
|
||||
serializer["animations"].emplace_back(f);
|
||||
}
|
||||
} else {
|
||||
for ( auto& name : graph.animations ) {
|
||||
auto& animation = /*graph.storage*/storage.animations.map.at(name);
|
||||
serializer["animations"][name] = encode(animation, settings, graph);
|
||||
}
|
||||
}
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// store skin information
|
||||
ext::json::reserve( serializer["skins"], graph.skins.size() );
|
||||
for ( auto& name : graph.skins ) {
|
||||
auto& skin = /*graph.storage*/storage.skins.map.at(name);
|
||||
serializer["skins"].emplace_back( encode(skin, settings, graph) );
|
||||
}
|
||||
});
|
||||
tasks.queue([&]{
|
||||
// store node information
|
||||
ext::json::reserve( serializer["nodes"], graph.nodes.size() );
|
||||
for ( auto& node : graph.nodes ) serializer["nodes"].emplace_back( encode(node, settings, graph) );
|
||||
serializer["root"] = encode(graph.root, settings, graph);
|
||||
|
||||
@ -50,6 +50,14 @@ namespace {
|
||||
}
|
||||
return instanceID;
|
||||
}
|
||||
size_t allocateJointID( pod::Graph::Storage& storage, const uf::stl::string& name ) {
|
||||
size_t jointID = 0;
|
||||
for ( auto& key : storage.joints.keys ) {
|
||||
if ( key == name ) break;
|
||||
jointID += storage.joints.map[key].size();
|
||||
}
|
||||
return jointID;
|
||||
}
|
||||
|
||||
// removes non-uniform aliased buffers
|
||||
void resetBuffers( uf::renderer::Shader& shader ) {
|
||||
@ -68,7 +76,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
void bindShaders( pod::Graph& graph, uf::Object& entity, uf::Mesh& mesh ) {
|
||||
void bindShaders( pod::Graph& graph, uf::Object& entity, uf::Mesh& mesh, uf::stl::vector<pod::Primitive>& primitives ) {
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& sceneTextures = scene.getComponent<pod::SceneTextures>();
|
||||
auto& sceneMetadataJson = scene.getComponent<uf::Serializer>();
|
||||
@ -278,7 +286,7 @@ namespace {
|
||||
uint32_t jointID;
|
||||
};
|
||||
|
||||
uf::stl::string compShaderFilename = graphMetadataJson["shaders"]["skinning"]["compute"].as<uf::stl::string>("/graph/skinning/skinning.deinterleaved.comp.spv"); {
|
||||
uf::stl::string compShaderFilename = graphMetadataJson["shaders"]["skinning"]["compute"].as<uf::stl::string>("/graph/skinning/skinning.comp.spv"); {
|
||||
compShaderFilename = entity.resolveURI( compShaderFilename, root );
|
||||
}
|
||||
|
||||
@ -328,13 +336,14 @@ namespace {
|
||||
|
||||
auto& shader = graphic.material.getShader("compute", "skinning");
|
||||
|
||||
struct {
|
||||
struct SkinningPush {
|
||||
uint32_t jointID;
|
||||
} uniforms = {
|
||||
.jointID = 0
|
||||
};
|
||||
|
||||
shader.updateBuffer( (const void*) &uniforms, sizeof(uniforms), shader.getUniformBuffer("UBO") );
|
||||
auto& pushConstant = shader.pushConstants.front().get<SkinningPush>();
|
||||
pushConstant = {
|
||||
.jointID = (uint32_t) primitives.front().instance.jointID
|
||||
};
|
||||
|
||||
// bind buffers
|
||||
::resetBuffers( shader );
|
||||
@ -514,7 +523,7 @@ namespace {
|
||||
size_t uf::graph::initialBufferElements = 1024;
|
||||
|
||||
uint32_t uf::graph::storageMode = pod::Graph::Storage::SCENE;
|
||||
pod::Graph::Storage uf::graph::storage;
|
||||
pod::Graph::Storage uf::graph::globalStorage;
|
||||
|
||||
UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Base,
|
||||
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32B32_SFLOAT, position)
|
||||
@ -641,7 +650,7 @@ pod::Graph::Storage& uf::graph::getStorage( pod::Graph& graph ) {
|
||||
}
|
||||
case pod::Graph::Storage::GLOBAL:
|
||||
default: {
|
||||
return uf::graph::storage;
|
||||
return uf::graph::globalStorage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -674,7 +683,7 @@ pod::Graph::Storage& uf::graph::getStorage( uf::Object& object ) {
|
||||
}
|
||||
case pod::Graph::Storage::GLOBAL:
|
||||
default: {
|
||||
return uf::graph::storage;
|
||||
return uf::graph::globalStorage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -751,7 +760,7 @@ void uf::graph::initializeGraphics( pod::Graph& graph, uf::Object& entity, uf::M
|
||||
|
||||
|
||||
::bindTextures( graph, graphic );
|
||||
::bindShaders( graph, entity, mesh );
|
||||
::bindShaders( graph, entity, mesh, primitives );
|
||||
::bindBuffers( graph, graphic, mesh );
|
||||
::bindAddresses( graph, graphic, mesh, primitives );
|
||||
|
||||
@ -1166,17 +1175,6 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
auto& needle = graph.textures[instance.cubemapID];
|
||||
instance.cubemapID = indices[needle];
|
||||
}
|
||||
|
||||
// remap a skinID as an actual jointID
|
||||
if ( 0 <= instance.jointID && instance.jointID < graph.skins.size() ) {
|
||||
auto& name = graph.skins[instance.jointID];
|
||||
instance.jointID = 0;
|
||||
for ( auto key : storage.joints.keys ) {
|
||||
if ( key == name ) break;
|
||||
auto& joints = storage.joints[key];
|
||||
instance.jointID += joints.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto& instance : storage.instances.map[name] ) {
|
||||
@ -1198,15 +1196,6 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
auto& needle = graph.textures[instance.cubemapID];
|
||||
instance.cubemapID = indices[needle];
|
||||
}
|
||||
|
||||
if ( 0 <= instance.jointID && instance.jointID < graph.skins.size() ) {
|
||||
auto& skinName = graph.skins[instance.jointID];
|
||||
instance.jointID = 0;
|
||||
for ( auto key : storage.joints.keys ) {
|
||||
if ( key == skinName ) break;
|
||||
instance.jointID += storage.joints[key].size();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
@ -1506,15 +1495,17 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
|
||||
//
|
||||
if ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) {
|
||||
{
|
||||
node.object = ::allocateObjectID( storage );
|
||||
auto objectKeyName = ::keyedID( node.object );
|
||||
node.object = ::allocateObjectID( storage );
|
||||
auto objectKeyName = ::keyedID( node.object );
|
||||
|
||||
storage.entities[objectKeyName] = &entity;
|
||||
storage.objects[objectKeyName] = pod::Instance::Object{
|
||||
.model = model,
|
||||
.previous = model,
|
||||
};
|
||||
storage.entities[objectKeyName] = &entity;
|
||||
storage.objects[objectKeyName] = pod::Instance::Object{
|
||||
.model = model,
|
||||
.previous = model,
|
||||
};
|
||||
|
||||
if ( node.skin >= 0 && node.skin < graph.skins.size() ) {
|
||||
storage.joints[objectKeyName] = {};
|
||||
}
|
||||
|
||||
auto& mesh = storage.meshes.map[graph.meshes[node.mesh]];
|
||||
@ -1527,7 +1518,11 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
for ( auto drawID = 0; drawID < primitives.size(); ++drawID ) {
|
||||
pod::Instance newInstance = primitives[drawID].instance;
|
||||
newInstance.objectID = node.object;
|
||||
newInstance.jointID = graphMetadataJson["renderer"]["skinned"].as<bool>() ? 0 : -1;
|
||||
if ( node.skin >= 0 && node.skin < graph.skins.size() ) {
|
||||
newInstance.jointID = ::allocateJointID( storage, ::keyedID(node.object) );
|
||||
} else {
|
||||
newInstance.jointID = -1;
|
||||
}
|
||||
|
||||
bounds.min = uf::vector::min( bounds.min, newInstance.bounds.min );
|
||||
bounds.max = uf::vector::max( bounds.max, newInstance.bounds.max );
|
||||
@ -1535,9 +1530,12 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
grouped.emplace_back(newInstance);
|
||||
}
|
||||
|
||||
bool isFirstInstance = ( grouped.size() == primitives.size() );
|
||||
#if !UF_GRAPH_EXTENDED
|
||||
if ( graphMetadataJson["renderer"]["render"].as<bool>() && isFirstInstance ) {
|
||||
bool isFirstInstance = ( grouped.size() == primitives.size() );
|
||||
bool isSkinned = graphMetadataJson["renderer"]["skinned"].as<bool>();
|
||||
bool shouldInitializeRender = graphMetadataJson["renderer"]["render"].as<bool>();
|
||||
|
||||
if ( shouldInitializeRender && (isFirstInstance || isSkinned) ) {
|
||||
uf::graph::initializeGraphics( graph, entity, mesh, primitives );
|
||||
}
|
||||
#endif
|
||||
@ -1644,7 +1642,7 @@ void uf::graph::tick() {
|
||||
// tick only one graph if scene/global
|
||||
switch ( uf::graph::storageMode ) {
|
||||
case pod::Graph::Storage::GLOBAL: {
|
||||
auto& storage = uf::graph::storage;
|
||||
auto& storage = uf::graph::globalStorage;
|
||||
storage.shouldRebind = uf::graph::tick( storage );
|
||||
return;
|
||||
} break;
|
||||
@ -1689,7 +1687,7 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
|
||||
auto& entity = *storage.entities.map[key];
|
||||
auto& object = storage.objects.map[key];
|
||||
|
||||
if ( entity.isValid() ) {
|
||||
if ( entity.hasComponent<pod::Transform<>>() ) {
|
||||
auto& metadata = entity.getComponent<uf::ObjectBehavior::Metadata>();
|
||||
auto& transform = entity.getComponent<pod::Transform<>>();
|
||||
|
||||
@ -1746,14 +1744,21 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
|
||||
|
||||
#if UF_USE_VULKAN
|
||||
if ( commands && !grouped.empty() ) {
|
||||
auto objectKeyName = ::keyedID(grouped.front().objectID);
|
||||
if ( storage.entities.map.count(objectKeyName) > 0 ) {
|
||||
uf::stl::unordered_set<uf::Object*> updatedGraphics;
|
||||
|
||||
for ( auto& instance : grouped ) {
|
||||
auto objectKeyName = ::keyedID(instance.objectID);
|
||||
if ( storage.entities.map.count(objectKeyName) == 0 ) continue;
|
||||
auto& entity = *storage.entities.map[objectKeyName];
|
||||
if ( entity.hasComponent<uf::renderer::Graphic>() ) {
|
||||
auto& graphic = entity.getComponent<uf::renderer::Graphic>();
|
||||
auto& attr = mesh.indirect.attributes.front();
|
||||
graphic.updateBuffer( (const void*) attr.pointer, attr.length, graphic.metadata.buffers["indirect["+attr.descriptor.name+"]"] );
|
||||
}
|
||||
if ( !entity.hasComponent<uf::renderer::Graphic>() ) continue;
|
||||
if ( updatedGraphics.find(&entity) != updatedGraphics.end() ) continue;
|
||||
|
||||
auto& graphic = entity.getComponent<uf::renderer::Graphic>();
|
||||
auto& attr = mesh.indirect.attributes.front();
|
||||
|
||||
graphic.updateBuffer( (const void*) attr.pointer, attr.length, graphic.metadata.buffers["indirect["+attr.descriptor.name+"]"] );
|
||||
|
||||
updatedGraphics.insert(&entity);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1802,7 +1807,7 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
|
||||
}
|
||||
|
||||
void uf::graph::aggregate() {
|
||||
return uf::graph::aggregate( uf::scene::getCurrentScene(), uf::graph::storage );
|
||||
return uf::graph::aggregate( uf::scene::getCurrentScene(), uf::graph::globalStorage );
|
||||
}
|
||||
void uf::graph::aggregate( uf::Object& object, pod::Graph::Storage& storage ) {
|
||||
STATIC_THREAD_LOCAL(uf::stl::vector<pod::Instance>, instances);
|
||||
@ -1891,7 +1896,7 @@ void uf::graph::render() {
|
||||
// render only one graph if scene/global
|
||||
switch ( uf::graph::storageMode ) {
|
||||
case pod::Graph::Storage::GLOBAL: {
|
||||
auto& storage = uf::graph::storage;
|
||||
auto& storage = uf::graph::globalStorage;
|
||||
uf::graph::render( storage );
|
||||
return;
|
||||
} break;
|
||||
@ -2037,7 +2042,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
|
||||
bool meshUpdated = false;
|
||||
auto model = uf::transform::model( transform );
|
||||
auto& mesh = storage.meshes.map[graph.meshes[node.mesh]];
|
||||
|
||||
auto meshName = graph.meshes[node.mesh];
|
||||
auto& mesh = storage.meshes.map[meshName];
|
||||
auto& meshStream = graph.streams.meshes[meshName];
|
||||
auto& primitives = storage.primitives.map[graph.primitives[node.mesh]];
|
||||
|
||||
float radius = graph.settings.stream.radius;
|
||||
@ -2054,7 +2062,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
radius = 0;
|
||||
}
|
||||
|
||||
if ( mesh.buffer_paths.empty() ) {
|
||||
if ( meshStream.buffers.empty() ) {
|
||||
radius = 0;
|
||||
}
|
||||
|
||||
@ -2207,7 +2215,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
if ( ranges.count(attribute.buffer) <= 0 ) { \
|
||||
mesh.buffers[attribute.buffer].clear();\
|
||||
} else {\
|
||||
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], mesh.buffer_paths[attribute.buffer], ranges[attribute.buffer] );\
|
||||
auto& region = meshStream.buffers[attribute.buffer];\
|
||||
auto adjustedRanges = ranges[attribute.buffer];\
|
||||
for (auto& r : adjustedRanges) r.start += region.offset;\
|
||||
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], region.filename, adjustedRanges );\
|
||||
}\
|
||||
}
|
||||
|
||||
@ -2249,8 +2260,9 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
|
||||
#define STREAM_MESH_DATA( N ) \
|
||||
for ( auto& attribute : mesh.N.attributes ) {\
|
||||
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;\
|
||||
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], mesh.buffer_paths[attribute.buffer] );\
|
||||
if ( !mesh.buffers[attribute.buffer].empty() || meshStream.buffers.empty() ) continue;\
|
||||
auto& region = meshStream.buffers[attribute.buffer];\
|
||||
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], region.filename, region.offset, region.length );\
|
||||
}
|
||||
|
||||
STREAM_MESH_DATA( index );
|
||||
@ -2297,7 +2309,21 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
|
||||
if ( visible && (!texture.generated() || texture.aliased) ) {
|
||||
// load image
|
||||
if ( image.getPixels().empty() ) image.open(image.getFilename(), false);
|
||||
if ( image.getPixels().empty() ) {
|
||||
auto& imgStream = graph.streams.images[key];
|
||||
uf::stl::vector<uint8_t> buf;
|
||||
|
||||
if (imgStream.buffer.length > 0) {
|
||||
uf::io::readAsBuffer(buf, imgStream.buffer.filename, imgStream.buffer.offset, imgStream.buffer.length);
|
||||
} else {
|
||||
uf::io::readAsBuffer(buf, imgStream.buffer.filename);
|
||||
}
|
||||
|
||||
uf::stl::string formatHint = uf::io::extension(image.getFilename());
|
||||
if (imgStream.buffer.filename.find(".dtex") != uf::stl::string::npos) formatHint = "dtex";
|
||||
|
||||
uf::image::open(image, buf, formatHint, false);
|
||||
}
|
||||
|
||||
auto filter = uf::renderer::enums::Filter::LINEAR;
|
||||
auto tag = ext::json::find( key, graphMetadataJson["tags"] );
|
||||
@ -2345,9 +2371,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
// load mesh if not already loaded
|
||||
#define LOAD_MESH_DATA( N ) \
|
||||
for ( auto& attribute : mesh.N.attributes ) {\
|
||||
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;\
|
||||
if ( !mesh.buffers[attribute.buffer].empty() || meshStream.buffers.empty() ) continue;\
|
||||
meshUpdated = true;\
|
||||
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], mesh.buffer_paths[attribute.buffer] );\
|
||||
auto& region = meshStream.buffers[attribute.buffer];\
|
||||
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], region.filename, region.offset, region.length );\
|
||||
}
|
||||
|
||||
LOAD_MESH_DATA( index );
|
||||
@ -2385,14 +2412,19 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
uf::renderer::states::rebuild = true;
|
||||
#endif
|
||||
|
||||
storage.stale = true; // force rebuffering the draw commands
|
||||
storage.stale = true;
|
||||
|
||||
bool graphicOwner = graphMetadataJson["renderer"]["render"].as<bool>();
|
||||
bool isSkinned = graphMetadataJson["renderer"]["skinned"].as<bool>();
|
||||
if ( graphicOwner ) {
|
||||
auto objectKeyName = ::keyedID(storage.instances.map[graph.primitives[node.mesh]].front().objectID);
|
||||
graphicOwner = storage.entities[objectKeyName] == &entity;
|
||||
if ( isSkinned ) {
|
||||
graphicOwner = true;
|
||||
} else {
|
||||
auto objectKeyName = ::keyedID(storage.instances.map[graph.primitives[node.mesh]].front().objectID);
|
||||
graphicOwner = storage.entities[objectKeyName] == &entity;
|
||||
}
|
||||
}
|
||||
// update graphic
|
||||
|
||||
if ( graphicOwner ) {
|
||||
bool exists = entity.hasComponent<uf::renderer::Graphic>();
|
||||
if ( exists ) {
|
||||
@ -2448,7 +2480,16 @@ void uf::graph::reload( pod::Graph& graph ) {
|
||||
// ::combineMesh( graph );
|
||||
}
|
||||
void uf::graph::reload() {
|
||||
storage.stale = true;
|
||||
switch ( uf::graph::storageMode ) {
|
||||
case pod::Graph::Storage::SCENE: {
|
||||
auto& storage = uf::scene::getCurrentScene().getComponent<pod::Graph::Storage>();
|
||||
storage.stale = true;
|
||||
}
|
||||
case pod::Graph::Storage::GLOBAL:
|
||||
default: {
|
||||
uf::graph::globalStorage.stale = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uf::graph::update( pod::Graph& graph ) {
|
||||
@ -2475,7 +2516,7 @@ void uf::graph::update( pod::Graph& graph, float delta ) {
|
||||
::bindBuffers( graph, graphic, mesh );
|
||||
::bindAddresses( graph, graphic, mesh, primitives );
|
||||
}
|
||||
storage.shouldRebind = false;
|
||||
//storage.shouldRebind = false;
|
||||
}
|
||||
|
||||
// get last update time
|
||||
@ -2495,6 +2536,7 @@ uf::stl::string uf::graph::getMaterialName( pod::Graph& graph, size_t materialID
|
||||
return storage.materials.keys[materialID];
|
||||
}
|
||||
pod::Material uf::graph::getMaterial( pod::Graph& graph, size_t materialID ) {
|
||||
auto& storage = uf::graph::getStorage( graph );
|
||||
auto key = uf::graph::getMaterialName( graph, materialID );
|
||||
return storage.materials.map[key];
|
||||
}
|
||||
|
||||
@ -145,6 +145,20 @@ bool UF_API ext::texconv::save( const pod::Dtex& dtex, const uf::stl::string& fi
|
||||
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
|
||||
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() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// maintains original main()
|
||||
bool ext::texconv::convert( const pod::TextureOptions& opts ) {
|
||||
|
||||
@ -102,32 +102,45 @@ bool uf::image::open( pod::Image& image, const uf::stl::string& filename, bool f
|
||||
if ( !uf::io::readAsBuffer( buffer, filename ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto extension = uf::io::extension( filename );
|
||||
#if UF_USE_OPENGL_GLDC
|
||||
auto extension = uf::io::extension( filename );
|
||||
if ( extension != "dtex" ) UF_MSG_WARNING("non-dtex loading is highly discouraged on this platform: {}", filename);
|
||||
if ( extension != "dtex" ) {
|
||||
UF_MSG_WARNING("non-dtex loading is highly discouraged on this platform: {}", filename);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
image.filename = filename;
|
||||
return uf::image::open(image, buffer, extension, flip);
|
||||
}
|
||||
bool uf::image::open( pod::Image& image, const uf::stl::vector<uint8_t>& buffer, const uf::stl::string& formatHint, bool flip ) {
|
||||
image.pixels.clear();
|
||||
int width = 0, height = 0, channelsDud = 0, bpp = 8, channels = 4;
|
||||
|
||||
#if UF_USE_OPENGL_GLDC
|
||||
if ( extension == "dtex" ) {
|
||||
if ( formatHint == "dtex" ) {
|
||||
struct {
|
||||
char id[4]; // 'DTEX'
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint32_t type;
|
||||
uint32_t size;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint32_t type;
|
||||
uint32_t size;
|
||||
} header;
|
||||
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
if ( !uf::io::readAsBuffer( buffer, filename ) ) UF_EXCEPTION("IO error: could not read DTEX: {}", filename);
|
||||
if ( buffer.size() < sizeof(header) ) UF_EXCEPTION("IO error: DTEX file is too small to contain a header: {}", filename);
|
||||
if ( buffer.size() < sizeof(header) ) {
|
||||
UF_EXCEPTION("IO error: DTEX buffer is too small to contain a header");
|
||||
return false;
|
||||
}
|
||||
memcpy(&header, buffer.data(), sizeof(header));
|
||||
if ( buffer.size() < sizeof(header) + header.size ) UF_EXCEPTION("IO error: DTEX file is truncated or corrupted: {}", filename);
|
||||
if ( buffer.size() < sizeof(header) + header.size ) {
|
||||
UF_EXCEPTION("IO error: DTEX buffer is truncated or corrupted");
|
||||
return false;
|
||||
}
|
||||
|
||||
image.pixels.resize(header.size);
|
||||
memcpy(image.pixels.data(), buffer.data() + sizeof(header), header.size);
|
||||
memcpy( image.pixels.data(), buffer.data() + sizeof(header), header.size );
|
||||
|
||||
// palette data: buffer.data() + sizeof(header) + header.size
|
||||
|
||||
bool twiddled = (header.type & (1 << 26)) < 1;
|
||||
bool compressed = (header.type & (1 << 30)) > 0;
|
||||
@ -137,8 +150,6 @@ bool uf::image::open( pod::Image& image, const uf::stl::string& filename, bool f
|
||||
width = header.width;
|
||||
height = header.height;
|
||||
|
||||
uint32_t expected = 2 * header.width * header.height;
|
||||
uint32_t ratio = (uint32_t) (((float) expected) / ((float) header.size));
|
||||
bpp = 4;
|
||||
image.format = format;
|
||||
if ( compressed ) {
|
||||
@ -147,25 +158,33 @@ bool uf::image::open( pod::Image& image, const uf::stl::string& filename, bool f
|
||||
case 1: image.format = mipmapped ? GL_COMPRESSED_RGB_565_VQ_MIPMAP_TWID_KOS : GL_COMPRESSED_RGB_565_VQ_TWID_KOS; channels = 3; break;
|
||||
case 0: image.format = mipmapped ? GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_TWID_KOS : GL_COMPRESSED_ARGB_1555_VQ_TWID_KOS; break;
|
||||
case 2: image.format = mipmapped ? GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_TWID_KOS : GL_COMPRESSED_ARGB_4444_VQ_TWID_KOS; break;
|
||||
default: UF_EXCEPTION("Image error: invalid texture format: {}", filename); return false;
|
||||
default: UF_EXCEPTION("Image error: invalid texture format"); return false;
|
||||
}
|
||||
} else {
|
||||
switch ( format ) {
|
||||
case 1: image.format = mipmapped ? GL_COMPRESSED_RGB_565_VQ_MIPMAP_KOS : GL_COMPRESSED_RGB_565_VQ_KOS; channels = 3; break;
|
||||
case 0: image.format = mipmapped ? GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_KOS : GL_COMPRESSED_ARGB_1555_VQ_KOS; break;
|
||||
case 2: image.format = mipmapped ? GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_KOS : GL_COMPRESSED_ARGB_4444_VQ_KOS; break;
|
||||
default: UF_EXCEPTION("Image error: invalid texture format: {}", filename); return false;
|
||||
default: UF_EXCEPTION("Image error: invalid texture format"); return false;
|
||||
}
|
||||
}
|
||||
} else { UF_EXCEPTION("Image error: not a compressed texture: {}", filename); return false; }
|
||||
} else
|
||||
} else { UF_EXCEPTION("Image error: not a compressed texture"); return false; }
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
stbi_set_flip_vertically_on_load(flip);
|
||||
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_pixels ) {
|
||||
UF_EXCEPTION("Image error: stb_image failed to decode buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len = width * height * channels;
|
||||
image.pixels.resize( len );
|
||||
memcpy( &image.pixels[0], stbi_pixels, len );
|
||||
memcpy( image.pixels.data(), stbi_pixels, len );
|
||||
|
||||
stbi_image_free(stbi_pixels);
|
||||
}
|
||||
|
||||
image.size.x = width;
|
||||
@ -205,27 +224,29 @@ void uf::image::load( pod::Image& image, const pod::Image::container_t& containe
|
||||
if ( flip ) uf::image::flip( image );
|
||||
}
|
||||
bool uf::image::save( const pod::Image& image, const uf::stl::string& filename, bool flip ) {
|
||||
if ( image.pixels.empty() ) return false;
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
uf::image::save( image, buffer, flip );
|
||||
if ( buffer.empty() ) return false;
|
||||
return uf::vfs::write( filename, buffer.data(), buffer.size() ) > 0;
|
||||
}
|
||||
void uf::image::save( const pod::Image& image, uf::stl::vector<uint8_t>& buffer, bool flip ) {
|
||||
buffer.clear();
|
||||
|
||||
if ( image.pixels.empty() ) return;
|
||||
|
||||
uint w = image.size.x;
|
||||
uint h = image.size.y;
|
||||
auto* pixels = &image.pixels[0];
|
||||
uf::stl::string extension = uf::io::extension(filename);
|
||||
uf::stl::string extension = image.filename.empty() ? "png" : uf::io::extension( image.filename );
|
||||
stbi_flip_vertically_on_write(flip);
|
||||
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
|
||||
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); // 90 is quality
|
||||
stbi_write_jpg_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, pixels, 90); // quality
|
||||
} else {
|
||||
UF_MSG_ERROR("Unsupported image save format: {}", extension);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( buffer.empty() ) return false;
|
||||
return uf::vfs::write( filename, buffer.data(), buffer.size() ) > 0;
|
||||
}
|
||||
void uf::image::save( const pod::Image& image, std::ostream& stream ) {
|
||||
|
||||
@ -464,6 +485,9 @@ void uf::Image::setFilename( const uf::stl::string& filename ) {
|
||||
bool uf::Image::open( const uf::stl::string& filename, bool flip ) {
|
||||
return uf::image::open( *this, filename, flip );
|
||||
}
|
||||
bool uf::Image::open( const uf::stl::vector<uint8_t>& buffer, const uf::stl::string& formatHint, bool flip ) {
|
||||
return uf::image::open( *this, buffer, formatHint, flip );
|
||||
}
|
||||
void uf::Image::loadFromBuffer( const pod::Image::pixel_t::type_t* pointer, const pod::Vector2ui& size, size_t bpp, size_t channels, bool flip ) {
|
||||
return uf::image::load( *this, pointer, size, bpp, channels, flip );
|
||||
}
|
||||
@ -558,6 +582,11 @@ pod::Image::pixel_t uf::Image::at( const pod::Vector2ui& at ) {
|
||||
bool uf::Image::save( const uf::stl::string& filename, bool flip ) const {
|
||||
return uf::image::save( *this, filename, flip );
|
||||
}
|
||||
// to buffer
|
||||
bool uf::Image::save( uf::stl::vector<uint8_t>& buffer, bool flip ) const {
|
||||
uf::image::save( *this, buffer, flip );
|
||||
return true;
|
||||
}
|
||||
// to stream
|
||||
void uf::Image::save( std::ostream& stream ) const {
|
||||
return uf::image::save( *this, stream );
|
||||
|
||||
Loading…
Reference in New Issue
Block a user