dlib/assets.d

471 lines
9.6 KiB
D

module dlib.assets;
import includes;
import dlib.aliases;
import dlib.util;
import dlib.alloc;
import std.file;
import std.path;
import std.format;
import std.stdio;
import std.exception;
__gshared ImageData DEFAULT_IMAGE = GenerateDefaultTexture(32, 32);
__gshared Material DEFAULT_MATERIAL = GenerateDefaultMaterial();
// TODO:
// Alignment?
/*************************
****** FILE PACKER ******
*************************/
/*************************
****** FILE PACKER ******
*************************/
/************************
*** ASSET STRUCTURES ***
************************/
struct ImageData
{
u8[] data;
u32 w;
u32 h;
u32 ch;
}
struct ModelData
{
Model model;
ImageData[] tex;
}
/************************
*** ASSET STRUCTURES ***
************************/
/**************************
***** GFX STRUCTURES *****
**************************/
enum MaterialMapIndex
{
Albedo,
Normal,
Metallic,
Roughness,
Occlusion,
Emission,
Height,
Cubemap,
Irradiance,
Prefilter,
BRDF,
Max,
}
alias MMI = MaterialMapIndex;
struct MaterialMap
{
u32 tex_id;
Vec4 col;
f32 value;
}
struct Material
{
u32 pipeline_id;
MaterialMap[MMI.max] maps;
Vec4 params;
}
struct Vertex
{
Vec4 color;
Vec4 tangent;
Vec3 pos;
Vec3 normal;
Vec2 uv;
Vec2 uv2;
}
struct Mesh
{
Vertex[] vtx;
u32 idx;
u32 mat_id;
}
struct Model
{
Mat4 transform;
Mesh[] meshes;
Material[] mats;
// TODO: bones/animations
}
/**************************
***** GFX STRUCTURES *****
**************************/
__gshared string g_BASE_ASSETS_DIR;
static u32
MagicValue(string str)
{
assert(str.length == 4, "Magic value must 4 characters");
return cast(u32)(cast(u32)(str[0] << 24) | cast(u32)(str[1] << 16) | cast(u32)(str[2] << 8) | cast(u32)(str[3] << 0));
}
void
SetBaseAssetsDir(string dir)
{
if(!isDir(dir))
{
assert(false, "Unable to find assets directory");
}
g_BASE_ASSETS_DIR = dir;
}
void
U32ColToVec4(u32 col, Vec4* vec)
{
if(!col)
{
vec.rgb = 0.0;
vec.a = 1.0;
}
else
{
vec.r = cast(f32)((col >> 0)&0xFF) / 255;
vec.g = cast(f32)((col >> 8)&0xFF) / 255;
vec.b = cast(f32)((col >> 16)&0xFF) / 255;
vec.a = cast(f32)((col >> 24)&0xFF) / 255;
}
}
// TODO: make better
u8[]
OpenFile(string file_name)
{
File f;
u8[] data;
try
{
f = File(file_name, "rb");
data = Alloc!(u8)(f.size());
f.rawRead(data);
}
catch(Exception e)
{
data = null;
}
f.close();
return data;
}
extern (C) cgltf_result
GLTFLoadCallback(cgltf_memory_options* memory_opts, cgltf_file_options* file_opts, const(char)* path, cgltf_size* size, void** data)
{
u8[] file_data = OpenFile(ConvToStr(path[0 .. strlen(path)]));
if(file_data == null) return cgltf_result_io_error;
*size = cast(cgltf_size)file_data.length;
*data = file_data.ptr;
return cgltf_result_success;
}
extern (C) void
GLTFFreeCallback(cgltf_memory_options* memory_opts, cgltf_file_options* file_opts, void* data, cgltf_size size)
{
Free(data);
}
ImageData
LoadImage(void* data, i32 size)
{
ImageData image;
i32 w, h, has_ch;
u8* image_bytes = stbi_load_from_memory(cast(const(u8)*)data, size, &w, &h, &has_ch, 4);
if(w <= 0 || h <= 0 || has_ch <= 0)
{
Logf("Failed to load image data");
}
else
{
image.data = image_bytes[0 .. w*h*has_ch];
image.w = w;
image.h = h;
image.ch = has_ch;
}
return image;
}
void
UnloadImage(ImageData* image)
{
stbi_image_free(image.data.ptr);
}
ImageData
LoadImage(cgltf_image* asset_image, string tex_path)
{
ImageData image;
if(asset_image)
{
if(asset_image.uri != null)
{
if(strlen(asset_image.uri) > 5 && ConvToStr(asset_image.uri[0 .. 5]) == "data:")
{
u32 i;
for(; asset_image.uri[i] != ',' && asset_image.uri[i] != 0; i += 1) {}
if(asset_image.uri[i] == 0)
{
Logf("gltf data URI for is not a valid image");
}
else
{
u64 base_64_size = strlen(asset_image.uri + i + 1);
for(; asset_image.uri[i + base_64_size] == '='; base_64_size -= 1) {}
u64 bit_count = base_64_size*6 - (base_64_size*6) % 8;
i32 out_size = cast(i32)(bit_count/8);
void* data;
cgltf_options options;
options.file.read = &GLTFLoadCallback;
options.file.release = &GLTFFreeCallback;
cgltf_result result = cgltf_load_buffer_base64(&options, out_size, asset_image.uri + i + 1, &data);
if(result == cgltf_result_success)
{
image = LoadImage(data, out_size);
cgltf_free(cast(cgltf_data*)data);
}
}
}
else
{
char[512] buf;
char[] file_path = buf.sformat("%s%s%s", tex_path, dirSeparator, cast(char*)asset_image.uri);
u8[] image_file = OpenFile(ConvToStr(file_path));
image = LoadImage(image_file.ptr, cast(i32)image_file.length);
Free(image_file);
}
}
else if(asset_image.buffer_view != null && asset_image.buffer_view.buffer.data != null)
{
u8[] data = Alloc!(u8)(asset_image.buffer_view.size);
i32 offset = cast(i32)(asset_image.buffer_view.offset);
i32 stride = cast(i32)(asset_image.buffer_view.stride ? asset_image.buffer_view.stride : 1);
u64 len = asset_image.buffer_view.size - (asset_image.buffer_view.size % stride);
data[0 .. len] = (cast(u8*)(asset_image.buffer_view.buffer.data))[offset .. offset+len];
string mime_type = ConvToStr(asset_image.mime_type[0 .. strlen(asset_image.mime_type)]);
const string[] accepted_types = ["image\\/png", "image/png", "image\\/jpeg", "image/jpeg", "image\\/tga", "image/tga", "image\\/bmp", "image/bmp"];
bool accepted;
static foreach(type; accepted_types)
{
if(type == mime_type)
{
accepted = true;
goto PostTypeCheck;
}
}
PostTypeCheck:
u8[] image_data;
if(accepted)
{
image = LoadImage(data.ptr, cast(i32)data.length);
}
else
{
Logf("Unknown image type [%s], unable to load", mime_type);
}
Free(data);
}
}
if(image.data == null)
{
Logf("Failed to load GLTF image data/file");
image = DEFAULT_IMAGE;
}
return image;
}
ModelData
LoadGLTF(Arena* arena, string file_name)
{
ModelData m_data;
Model* model = &m_data.model;
u8[] file_data = OpenFile(file_name);
assert(file_data != null);
cgltf_options opts;
cgltf_data* data;
opts.file.read = &GLTFLoadCallback;
opts.file.release = &GLTFFreeCallback;
cgltf_result result = cgltf_parse(&opts, file_data.ptr, file_data.length, &data);
if(result == cgltf_result_success)
{
result = cgltf_load_buffers(&opts, data, file_name.ptr);
if(result != cgltf_result_success)
{
Logf("%s Failure: Unable to load buffers", __FUNCTION__);
}
u64 primitive_count;
for(u64 i = 0; i < data.nodes_count; i += 1)
{
cgltf_node* node = &data.nodes[i];
if(node.mesh == null) continue;
for(u64 j = 0; j < node.mesh.primitives_count; j += 1)
{
if(node.mesh.primitives[j].type == cgltf_primitive_type_triangles)
{
primitive_count += 1;
}
}
}
model.meshes = Alloc!(Mesh)(primitive_count);
model.mats = Alloc!(Material)(data.materials_count+1);
m_data.tex = Alloc!(ImageData)(data.textures_count+1);
model.mats[0] = DEFAULT_MATERIAL;
u32 tex_idx;
string file_path = GetFilePath(file_name);
for(u64 i = 0; i < data.materials_count; i += 1)
{
u64 mat_idx = i+1;
model.mats[mat_idx] = DEFAULT_MATERIAL;
if(data.materials[i].has_pbr_metallic_roughness)
{
auto pbr_mr = &data.materials[i].pbr_metallic_roughness;
if(pbr_mr.base_color_texture.texture)
{
m_data.tex[tex_idx] = LoadImage(pbr_mr.base_color_texture.texture.image, file_path);
model.mats[mat_idx].maps[MMI.Albedo].tex_id = tex_idx++;
}
model.mats[mat_idx].maps[MMI.Albedo].col.v = pbr_mr.base_color_factor;
if(pbr_mr.metallic_roughness_texture.texture)
{
m_data.tex[tex_idx] = LoadImage(pbr_mr.base_color_texture.texture.image, file_path);
model.mats[mat_idx].maps[MMI.Metallic].tex_id = tex_idx;
model.mats[mat_idx].maps[MMI.Roughness].tex_id = tex_idx;
tex_idx += 1;
model.mats[mat_idx].maps[MMI.Metallic].value = pbr_mr.metallic_factor;
model.mats[mat_idx].maps[MMI.Roughness].value = pbr_mr.roughness_factor;
}
if(data.materials[i].normal_texture.texture)
{
m_data.tex[tex_idx] = LoadImage(data.materials[i].normal_texture.texture.image, file_path);
model.mats[mat_idx].maps[MMI.Normal].tex_id = tex_idx++;
}
if(data.materials[i].occlusion_texture.texture)
{
m_data.tex[tex_idx] = LoadImage(data.materials[i].occlusion_texture.texture.image, file_path);
model.mats[mat_idx].maps[MMI.Occlusion].tex_id = tex_idx++;
}
if(data.materials[i].emissive_texture.texture)
{
m_data.tex[tex_idx] = LoadImage(data.materials[i].emissive_texture.texture.image, file_path);
model.mats[mat_idx].maps[MMI.Emission].tex_id = tex_idx++;
model.mats[mat_idx].maps[MMI.Emission].col.v[0 .. 3] = data.materials[i].emissive_factor;
model.mats[mat_idx].maps[MMI.Emission].col.v.a = 1.0;
}
}
}
}
return m_data;
}
static ImageData
GenerateDefaultTexture(u32 x, u32 y)
{
ImageData data = {
w: x,
h: y,
ch: 4,
};
u64 tex_size = x*y*4;
u8[] placeholder_tex = new u8[tex_size];
u8[4] magenta = [255, 0, 255, 255];
u8[4] black = [0, 0, 0, 255];
u64 half = tex_size/2;
for(u64 i = 0; i < tex_size; i += 32)
{
bool swap = i <= half;
for(u64 j = 0; j < 16; j += 4)
{
placeholder_tex[i+j .. i+j+4] = !swap ? magenta[0 .. $] : black[0 .. $];
placeholder_tex[i+j+16 .. i+j+16+4] = !swap ? black[0 .. $] : magenta[0 .. $];
}
}
data.data = placeholder_tex;
return data;
}
static Material
GenerateDefaultMaterial()
{
Material mat;
mat.maps[MMI.Albedo].col = Vec4(1.0);
mat.maps[MMI.Metallic].col = Vec4(1.0);
return mat;
}