|
#define WIN32_LEAN_AND_MEAN |
|
#define NOMINMAX |
|
#include <windows.h> |
|
|
|
#include <cstdio> |
|
#include <vector> |
|
#include <map> |
|
#include <array> |
|
#include <tuple> |
|
#include <string> |
|
#include <cmath> |
|
#include <assert.h> |
|
|
|
#include "BLI_path_utils.hh" |
|
#include "BLI_fileops.h" |
|
#include "BLI_math_vector.h" |
|
#include "BLI_math_matrix.h" |
|
#include "BLI_math_rotation.h" |
|
#include "BLI_string.h" |
|
#include "BLI_listbase.h" |
|
#include "BLI_map.hh" |
|
#include "BLI_bounds_types.hh" |
|
#include "BLI_bounds.hh" |
|
#include "BLI_time.h" |
|
|
|
#include "BKE_context.hh" |
|
#include "BKE_main.hh" |
|
#include "BKE_scene.hh" |
|
#include "BKE_object.hh" |
|
#include "BKE_mesh.hh" |
|
#include "BKE_material.hh" |
|
#include "BKE_image.hh" |
|
#include "BKE_node.hh" |
|
#include "BKE_customdata.hh" |
|
#include "BKE_idprop.hh" |
|
#include "BKE_lib_id.hh" |
|
#include "BKE_main.hh" |
|
#include "BKE_attribute.hh" |
|
#include "BKE_deform.hh" |
|
#include "BKE_mesh_tangent.hh" |
|
#include "BKE_attribute.hh" |
|
#include "BKE_node_legacy_types.hh" |
|
#include "BKE_image_save.hh" |
|
#include "BKE_image_format.hh" |
|
#include "BKE_editmesh.hh" |
|
#include "BKE_modifier.hh" |
|
#include "BKE_mesh_wrapper.hh" |
|
|
|
#include "RNA_access.hh" |
|
#include "RNA_define.hh" |
|
|
|
#include "WM_api.hh" |
|
#include "WM_types.hh" |
|
|
|
#include "DEG_depsgraph.hh" |
|
#include "DEG_depsgraph_query.hh" |
|
|
|
#include "DNA_object_types.h" |
|
#include "DNA_meshdata_types.h" |
|
#include "DNA_modifier_types.h" |
|
#include "DNA_mesh_types.h" |
|
#include "DNA_material_types.h" |
|
#include "DNA_scene_types.h" |
|
#include "DNA_image_types.h" |
|
|
|
#include "bmesh.hh" |
|
#include "bmesh_tools.hh" |
|
|
|
#include "meow_hash_x64_aesni.h" |
|
|
|
// What? |
|
using blender::StringRefNull; |
|
using namespace blender; |
|
|
|
///////////////////////////////////////////////////////////////////////////////// |
|
|
|
typedef uint32_t u32; |
|
typedef uint64_t u64; |
|
typedef char u8; |
|
|
|
#define int int32_t |
|
|
|
typedef struct Filedata { |
|
|
|
u32 size; |
|
char *data; |
|
|
|
} Filedata; |
|
|
|
typedef struct t_string { |
|
const char *data; |
|
u32 length; |
|
} t_string; |
|
|
|
#define T_STR(a) {a, sizeof(a) - 1} |
|
#define T_STRING(a) (t_string) T_STR(a) |
|
|
|
#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0]) |
|
#define ARRAY(type, name) \ |
|
struct { \ |
|
type *data; \ |
|
uint count; \ |
|
} name \ |
|
|
|
#define ARRAY_ADD(array, element) \ |
|
(array.data[array.count++] = element, array.count - 1) \ |
|
|
|
// @Note: It's better to have the vertex data separated and not interleaved! |
|
struct Vertex { |
|
float pos[3]; |
|
float norm[3]; |
|
float uv[2]; |
|
float tan[4]; |
|
}; |
|
|
|
struct TextureData { |
|
int type; |
|
t_string name; |
|
}; |
|
|
|
struct TextureHashItem { |
|
TextureData data; |
|
u32 hash; |
|
}; |
|
|
|
typedef enum MaterialTextureIds { |
|
|
|
TEXTURE_DIFFUSE = 0, |
|
TEXTURE_NORMALMAP, |
|
|
|
MAX_TEXTURES_COUNT, |
|
|
|
} MaterialTextureIds; |
|
|
|
typedef struct vec3 { |
|
float x, y, z; |
|
} vec3; |
|
|
|
struct PrimitiveMaterial { |
|
t_string name; |
|
float color[3]; |
|
t_string shader; |
|
bool tri_planar; |
|
ARRAY(TextureData, textures); |
|
int flags; |
|
}; |
|
|
|
struct SceneMaterial { |
|
PrimitiveMaterial pMat; |
|
Material *mat; |
|
}; |
|
|
|
struct Primitive { |
|
ARRAY(Vertex, vertices); |
|
ARRAY(u32, indices); |
|
u32 materialIdx; |
|
}; |
|
|
|
struct Entity { |
|
t_string name; |
|
ARRAY(u32, children); |
|
|
|
float translation[3]; |
|
float rotation[4]; |
|
float scale[3]; |
|
int type; |
|
int flags; |
|
t_string asset; |
|
ARRAY(Primitive, primitives); |
|
vec3 boundingBoxVertices[8]; |
|
float breaking_force; |
|
|
|
float fog_falloff; |
|
float fog_color[3]; |
|
float fade_begin; |
|
float fade_end; |
|
t_string sky_shader; |
|
float sun_intensity; |
|
float sun_color[3]; |
|
float ambient_intensity; |
|
float ambient_color[3]; |
|
int light_enabled; |
|
float light_color[3]; |
|
float light_intensity; |
|
float light_falloff; |
|
t_string portal_level; |
|
t_string portal_target_entity; |
|
int gravityswitch_enabled; |
|
int platform_target_entity; |
|
int platform_target_goal; |
|
int platform_looping; |
|
float platform_timer; |
|
int diamond_difficulty; |
|
ARRAY(u32, button_target_entities); |
|
float cable_target_color[3]; |
|
int movementSwitch_mode; |
|
}; |
|
|
|
typedef enum EntityFlags { |
|
|
|
ENTITY_FLAG_NONE = 0, |
|
ENTITY_INVISIBLE = (1 << 0), |
|
ENTITY_NO_COLLISION = (1 << 1), |
|
ENTITY_TRIGGER = (1 << 2), |
|
ENTITY_DEV = (1 << 3), |
|
ENTITY_MATERIAL_OVERRIDE = (1 << 4), |
|
ENTITY_BREAKABLE = (1 << 6), |
|
|
|
} EntityFlags; |
|
|
|
t_string entity_type_strings[] = { |
|
T_STR("None"), |
|
T_STR("Sun"), |
|
T_STR("Light"), |
|
T_STR("Portal"), |
|
T_STR("Cloud"), |
|
T_STR("GravitySwitch"), |
|
T_STR("Platform"), |
|
T_STR("Button"), |
|
T_STR("PlayerStart"), |
|
T_STR("Diamond"), |
|
T_STR("LampPost"), |
|
T_STR("Cable"), |
|
T_STR("MovementSwitch"), |
|
}; |
|
|
|
typedef enum EntityType { |
|
|
|
ENTITY_NONE = 0, |
|
ENTITY_SUN, |
|
ENTITY_LIGHT, |
|
ENTITY_PORTAL, |
|
ENTITY_CLOUD, |
|
ENTITY_GRAVITYSWITCH, |
|
ENTITY_PLATFORM, |
|
ENTITY_BUTTON, |
|
ENTITY_PLAYERSTART, |
|
ENTITY_DIAMOND, |
|
ENTITY_LAMPPOST, |
|
ENTITY_CABLE, |
|
ENTITY_MOVEMENTSWITCH, |
|
|
|
ENTITY_TYPES_COUNT, |
|
|
|
} EntityType; |
|
|
|
|
|
void _serialize(char *fileData, u32 *fileDataSize, void *data, u32 size, u32 count) { |
|
memcpy(fileData + *fileDataSize, data, size * count); |
|
*fileDataSize = *fileDataSize + size * count; |
|
} |
|
|
|
#define serialize(data, size, count) _serialize(fileData, &fileDataSize, &data, size, count) |
|
#define serialize_int(data, count) _serialize(fileData, &fileDataSize, &data, sizeof(int), count) |
|
#define serialize_float(data, count) _serialize(fileData, &fileDataSize, &data, sizeof(float), count) |
|
#define serialize_string(_data) \ |
|
_serialize(fileData, &fileDataSize, &_data.length, sizeof(u32), 1); \ |
|
_serialize(fileData, &fileDataSize, (char *) _data.data, _data.length, 1) \ |
|
|
|
|
|
|
|
#define Bytes(n) (n) |
|
#define Kilobytes(n) (n << 10) |
|
#define Megabytes(n) (n << 20) |
|
#define Gigabytes(n) (((u64)n) << 30) |
|
|
|
typedef struct Arena { |
|
|
|
u8 *data; |
|
size_t capacity; |
|
size_t size; |
|
|
|
} Arena; |
|
|
|
Arena arena = {}; |
|
|
|
static Arena memory_createArena (size_t size) { |
|
|
|
Arena arena = {0}; |
|
arena.data = (u8 *) MEM_mallocN(size, "Arena"); |
|
arena.capacity = size; |
|
memset(arena.data, 0, size); |
|
|
|
return arena; |
|
} |
|
|
|
#define memory_allocate(arena, type, count) (type *) memory_allocateAligned(arena, sizeof(type) * count, alignof(type)) |
|
|
|
static void *memory_allocateAligned (Arena *arena, size_t allocationSize, size_t alignment) { |
|
|
|
if (alignment == 0) alignment = 1; |
|
|
|
// Check that alignment is a power of two: |
|
assert((alignment & (alignment - 1)) == 0); |
|
|
|
// When aligning, we will align by at most `alignment - 1` bytes: |
|
size_t max_align_inc = alignment - 1; |
|
// Since `alignment` is a power of two, we want to have zero bits |
|
// whenever we have a one bit in `alignment - 1`. Thus we need to |
|
// mask off the bits in |
|
size_t align_mask = ~(alignment - 1); |
|
|
|
char *current = arena->data + arena->size; |
|
char *current_plus_max_alignment = current + max_align_inc; |
|
|
|
uintptr_t current_plus_max_alignment_as_uint = (uintptr_t)current_plus_max_alignment; |
|
uintptr_t aligned_current_as_uint = current_plus_max_alignment_as_uint & align_mask; |
|
|
|
void *result = (void*)aligned_current_as_uint; |
|
size_t alignedSize = (u8*)result - arena->data; |
|
size_t newSize = alignedSize + allocationSize; |
|
|
|
assert(newSize < arena->capacity); |
|
|
|
arena->size = newSize; |
|
return result; |
|
} |
|
|
|
static t_string t_stringCreate (Arena *arena, char *src, u32 length) { |
|
|
|
t_string result = {}; |
|
result.data = memory_allocate(arena, char, length); |
|
result.length = length; |
|
strncpy((char *) result.data, src, length); |
|
|
|
return result; |
|
} |
|
|
|
static int t_stringCompare (t_string str1, t_string str2) { |
|
|
|
uint i; |
|
|
|
if (str1.length != str2.length) |
|
return 0; |
|
for (i = 0; i < str1.length; i++) { |
|
|
|
if (str1.data[i] != str2.data[i]) |
|
return 0; |
|
} |
|
|
|
return 1; // Strings are equal |
|
} |
|
|
|
u64 getFileLastWriteTime (char *filename) { |
|
|
|
FILETIME time = {0}; |
|
u64 result = 0; |
|
WIN32_FILE_ATTRIBUTE_DATA data; |
|
|
|
if (GetFileAttributesExA(filename, GetFileExInfoStandard, &data)) { |
|
|
|
time = data.ftLastWriteTime; |
|
result = ((u64)time.dwHighDateTime << 32) | time.dwLowDateTime; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
Filedata readEntireFile (Arena *arena, char *filename) { |
|
|
|
Filedata result = {0}; |
|
|
|
HANDLE hFile = CreateFileA( |
|
filename, |
|
GENERIC_READ, |
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
|
NULL, |
|
OPEN_EXISTING, |
|
0, |
|
NULL |
|
); |
|
if (hFile != INVALID_HANDLE_VALUE) { |
|
|
|
result.size = GetFileSize(hFile, NULL); |
|
result.data = memory_allocate(arena, char, result.size); |
|
|
|
DWORD bytesRead; |
|
ReadFile(hFile, result.data, result.size, &bytesRead, NULL); |
|
CloseHandle(hFile); |
|
|
|
assert(bytesRead == result.size); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
int areFilesDifferent (Arena *arena, char *filepath1, char *filepath2) { |
|
|
|
int result = 1; |
|
|
|
size_t marker = arena->size; |
|
HANDLE hFile1 = CreateFileA(filepath1, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); |
|
HANDLE hFile2 = CreateFileA(filepath2, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); |
|
|
|
if (hFile1 != INVALID_HANDLE_VALUE && hFile2 != INVALID_HANDLE_VALUE) { |
|
|
|
Filedata data1 = {}; |
|
Filedata data2 = {}; |
|
data1.size = GetFileSize(hFile1, NULL); |
|
data2.size = GetFileSize(hFile2, NULL); |
|
if (data1.size == data2.size) { |
|
|
|
data1.data = memory_allocate(arena, char, data1.size); |
|
data2.data = memory_allocate(arena, char, data2.size); |
|
|
|
DWORD bytesRead1, bytesRead2; |
|
ReadFile(hFile1, data1.data, data1.size, &bytesRead1, NULL); |
|
ReadFile(hFile2, data2.data, data2.size, &bytesRead2, NULL); |
|
|
|
__m128i hash1 = MeowHash(MeowDefaultSeed, data1.size, data1.data); |
|
__m128i hash2 = MeowHash(MeowDefaultSeed, data2.size, data2.data); |
|
|
|
if (MeowHashesAreEqual(hash1, hash2)) { |
|
result = 0; |
|
} |
|
CloseHandle(hFile1); |
|
CloseHandle(hFile2); |
|
} |
|
} |
|
arena->size = marker; |
|
return result; |
|
} |
|
|
|
static u32 t_strhash (char *str) { |
|
|
|
u32 hash = 5381; |
|
int c; |
|
while ((c = *str++)) { |
|
|
|
hash = ((hash << 5) + hash) + c; |
|
} |
|
|
|
return hash; |
|
} |
|
|
|
////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
void get_custom_float (const ID* id, float *target, const char* name) { |
|
|
|
if (id->properties) { |
|
IDProperty *prop = IDP_GetPropertyFromGroup(id->properties, name); |
|
if (prop) { |
|
*target = IDP_Float(prop); |
|
} |
|
} |
|
} |
|
|
|
void get_custom_float3 (const ID* id, float *data, const char* name) { |
|
|
|
if (id->properties) { |
|
|
|
IDProperty* prop = IDP_GetPropertyFromGroup(id->properties, name); |
|
if (prop && prop->type == IDP_ARRAY && prop->subtype == IDP_FLOAT) { |
|
|
|
float *value = (float *) IDP_Array(prop); |
|
memcpy(data, value, sizeof(float) * 3); |
|
} |
|
} |
|
} |
|
|
|
void get_custom_int (const ID* id, int *target, const char* name) { |
|
|
|
if (id->properties) { |
|
IDProperty *prop = IDP_GetPropertyFromGroup(id->properties, name); |
|
if (prop) { |
|
*target = IDP_Int(prop); |
|
} |
|
} |
|
} |
|
|
|
void get_custom_bool (const ID* id, bool *target, const char* name) { |
|
|
|
if (id->properties) { |
|
IDProperty *prop = IDP_GetPropertyFromGroup(id->properties, name); |
|
if (prop) { |
|
*target = IDP_Bool(prop); |
|
} |
|
} |
|
} |
|
|
|
t_string get_custom_string (Arena *arena, const ID *id, const char *name) { |
|
|
|
t_string result = {}; |
|
if (id->properties) { |
|
|
|
IDProperty *prop = IDP_GetPropertyFromGroup(id->properties, name); |
|
if (prop) { |
|
|
|
char *str = IDP_String(prop); |
|
if (str) { |
|
|
|
u32 length = strlen(str); |
|
result = t_stringCreate(arena, str, length); |
|
} |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
Object *get_custom_object (const ID *id, const char *name) { |
|
|
|
Object *result = nullptr; |
|
|
|
if (id->properties) { |
|
IDProperty *prop = IDP_GetPropertyFromGroup(id->properties, name); |
|
if (prop && prop->type == IDP_ID) { |
|
ID *target_id = (ID *) prop->data.pointer; |
|
if (target_id && target_id->name[0]) { |
|
result = (Object *) target_id; |
|
} |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
u32 findEntityIndex (Object *object, Object **objectsArray, u32 objectsArrayCount) { |
|
|
|
u32 result = 0; |
|
for (u32 i = 0; i < objectsArrayCount; i++) { |
|
|
|
if (object == objectsArray[i]) { |
|
result = i + 1; // +1 Because of the root entity |
|
break; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
////////////////////////////////////////////////////////////////////////////////////// |
|
void rift_process_object (Arena *arena, Depsgraph *depsgraph, Object **allObjects, u32 allObjectsCount, Object *obj, Entity *target_entity, SceneMaterial *sceneMaterials, u32 sceneMaterialsCount); |
|
void rift_save_png (Arena *arena, char *source, char *dest); |
|
void rift_export_image (Main *bmain, Scene *scene, Image *image, char *source, char *dest); |
|
////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
#define MAX_THREADS 16 |
|
#define MAX_WORK_ITEMS 4096 |
|
|
|
enum WorkType { |
|
WORK_process_object, |
|
WORK_export_image, |
|
WORK_copy_png, |
|
}; |
|
|
|
struct WorkItem { |
|
|
|
WorkType type; |
|
|
|
// Object |
|
Object *object; |
|
Entity *target_entity; |
|
|
|
// Image export |
|
char from_path[FILE_MAX]; |
|
char dest_path[FILE_MAX]; |
|
Image *image; |
|
}; |
|
|
|
struct WorkQueue { |
|
WorkItem items[MAX_WORK_ITEMS]; |
|
volatile LONG head; |
|
volatile LONG tail; |
|
volatile LONG remaining; |
|
}; |
|
|
|
struct ThreadData { |
|
int thread_id; |
|
HANDLE wake_event; |
|
Arena * arena; |
|
|
|
Depsgraph * depsgraph; |
|
Main * bmain; |
|
Scene * scene; |
|
Object ** allObjects; |
|
u32 allObjectsCount; |
|
SceneMaterial * sceneMaterials; |
|
u32 sceneMaterialsCount; |
|
Entity * entitiesArray; |
|
}; |
|
|
|
|
|
Arena thread_arenas[MAX_THREADS] = {}; |
|
HANDLE worker_threads[MAX_THREADS] = {}; |
|
ThreadData threads_data[MAX_THREADS] = {}; |
|
WorkQueue work_queue = {}; |
|
|
|
|
|
int work_queue_push (WorkItem item) { |
|
|
|
while (1) { |
|
|
|
LONG current_tail = work_queue.tail; |
|
LONG next_tail = (current_tail + 1) % MAX_WORK_ITEMS; |
|
LONG current_head = work_queue.head; |
|
|
|
if (next_tail == current_head) { |
|
return 0; |
|
} |
|
|
|
if (InterlockedCompareExchange(&work_queue.tail, next_tail, current_tail) == current_tail) { |
|
work_queue.items[current_tail] = item; |
|
InterlockedIncrement(&work_queue.remaining); |
|
return 1; |
|
} |
|
} |
|
} |
|
|
|
int work_queue_pop (WorkItem *item) { |
|
|
|
while (1) { |
|
|
|
LONG current_head = work_queue.head; |
|
LONG current_tail = work_queue.tail; |
|
|
|
if (current_head == current_tail) { |
|
return 0; |
|
} |
|
|
|
*item = work_queue.items[current_head]; |
|
LONG next_head = (current_head + 1) % MAX_WORK_ITEMS; |
|
if (InterlockedCompareExchange(&work_queue.head, next_head, current_head) == current_head) { |
|
return 1; |
|
} |
|
} |
|
} |
|
|
|
DWORD WINAPI rift_worker_thread (void *data) { |
|
|
|
ThreadData *thread_data = (ThreadData *) data; |
|
while (1) { |
|
|
|
// @Note: This will wait only for the first time the exporter in invoked. |
|
// I never put the thread back to sleep, which means it's always rapidly |
|
// checking for new jobs. This, in theory is a bad thing to do, but I don't |
|
// have to care or worry about it. And it seems the Windows is doing some |
|
// intelligent behind the scenes and is lot letting the CPU melt away. So |
|
// your milage may vary here, based on your CPU and OS setup. |
|
WaitForSingleObject(thread_data->wake_event, INFINITE); |
|
|
|
WorkItem work_item; |
|
while (work_queue_pop(&work_item)) { |
|
|
|
switch (work_item.type) { |
|
|
|
case WORK_process_object: { |
|
|
|
rift_process_object( |
|
thread_data->arena, |
|
thread_data->depsgraph, |
|
thread_data->allObjects, |
|
thread_data->allObjectsCount, |
|
work_item.object, |
|
work_item.target_entity, |
|
thread_data->sceneMaterials, |
|
thread_data->sceneMaterialsCount |
|
); |
|
} break; |
|
|
|
case WORK_copy_png: { |
|
|
|
rift_save_png( |
|
thread_data->arena, |
|
work_item.from_path, |
|
work_item.dest_path |
|
); |
|
} break; |
|
|
|
case WORK_export_image: { |
|
rift_export_image( |
|
thread_data->bmain, |
|
thread_data->scene, |
|
work_item.image, |
|
work_item.from_path, |
|
work_item.dest_path |
|
); |
|
} break; |
|
} |
|
|
|
InterlockedDecrement(&work_queue.remaining); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
void rift_export_image (Main *bmain, Scene *scene, Image *image, char *source, char *dest) { |
|
|
|
int save_image = 0; |
|
u64 source_time = getFileLastWriteTime(source); |
|
u64 dest_time = getFileLastWriteTime(dest); |
|
if (!BLI_exists(dest)) save_image = 1; |
|
else if (source_time > dest_time) save_image = 1; |
|
else if (BKE_image_is_dirty(image)) save_image = 1; |
|
|
|
if (save_image) { |
|
|
|
ImageSaveOptions opts; |
|
BKE_image_save_options_init(&opts, bmain, scene, image, NULL, false, true); |
|
opts.im_format.imtype = R_IMF_IMTYPE_PNG; |
|
opts.im_format.compress = 0; |
|
opts.im_format.depth = R_IMF_CHAN_DEPTH_8; |
|
BLI_strncpy(opts.filepath, dest, sizeof(opts.filepath)); |
|
|
|
BKE_image_save(NULL, bmain, image, NULL, &opts); |
|
BKE_image_save_options_free(&opts); |
|
} |
|
} |
|
|
|
void rift_save_png (Arena *arena, char *source, char *dest) { |
|
|
|
if (areFilesDifferent(arena, source, dest)) { |
|
CopyFileA(source, dest, FALSE); |
|
} |
|
} |
|
|
|
void rift_process_material (Arena *arena, Material *material, SceneMaterial *sceneMaterial, TextureHashItem *textureHashes, u32 *textureHashesCount, char *fileBlendDir, char *texturesDir) { |
|
|
|
char *material_name = material->id.name + 2; |
|
|
|
SceneMaterial sceneMat = {}; |
|
sceneMat.mat = material; |
|
|
|
sceneMat.pMat.color[0] = 1.0f; |
|
sceneMat.pMat.color[1] = 1.0f; |
|
sceneMat.pMat.color[2] = 1.0f; |
|
sceneMat.pMat.shader = T_STR("default"); |
|
sceneMat.pMat.tri_planar = true; |
|
sceneMat.pMat.textures.data = memory_allocate(arena, TextureData, MAX_TEXTURES_COUNT); |
|
sceneMat.pMat.name = t_stringCreate(arena, material_name, strlen(material_name)); |
|
|
|
if (material->use_nodes && material->nodetree) { |
|
|
|
LISTBASE_FOREACH(bNode *, node, &material->nodetree->nodes) { |
|
if (node->type_legacy == SH_NODE_BSDF_PRINCIPLED) { |
|
LISTBASE_FOREACH(bNodeSocket *, input, &node->inputs) { |
|
if (STREQ(input->name, "Base Color")) { |
|
bNodeSocketValueRGBA *color_val = (bNodeSocketValueRGBA *) input->default_value; |
|
if (color_val) { |
|
sceneMat.pMat.color[0] = color_val->value[0]; |
|
sceneMat.pMat.color[1] = color_val->value[1]; |
|
sceneMat.pMat.color[2] = color_val->value[2]; |
|
} |
|
break; |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
|
|
LISTBASE_FOREACH(bNode*, node, &material->nodetree->nodes) { |
|
if (node->type_legacy == SH_NODE_TEX_IMAGE) { |
|
Image *image = (Image *) node->id; |
|
if (!image) continue; |
|
|
|
int texture_type = 0; |
|
LISTBASE_FOREACH(bNodeLink*, link, &material->nodetree->links) { |
|
if (link->fromnode == node && link->tonode->type_legacy == SH_NODE_BSDF_PRINCIPLED) { |
|
if (STREQ(link->tosock->name, "Normal")) { |
|
texture_type = 1; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
char abs_path[FILE_MAX] = {}; |
|
char dest_path[FILE_MAX] = {}; |
|
|
|
if (strlen(image->filepath)) { |
|
|
|
if (BLI_path_is_rel(image->filepath)) { |
|
BLI_path_join(abs_path, FILE_MAX, fileBlendDir, image->filepath + 2); |
|
} |
|
else { |
|
BLI_strncpy(abs_path, image->filepath, FILE_MAX); |
|
} |
|
} |
|
else { |
|
BLI_strncpy(abs_path, image->id.name + 2, FILE_MAX); |
|
} |
|
const char *basename = BLI_path_basename(abs_path); |
|
const char *ext = BLI_path_extension(abs_path); |
|
|
|
u32 hash = t_strhash(abs_path); |
|
int found = 0; |
|
for (int t = 0; t < *textureHashesCount; t++) { |
|
|
|
TextureHashItem *item = &textureHashes[t]; |
|
if (item->hash == hash) { |
|
found = 1; |
|
TextureData tex_data = item->data; |
|
tex_data.type = texture_type; |
|
ARRAY_ADD(sceneMat.pMat.textures, tex_data); |
|
break; |
|
} |
|
} |
|
if (!found) { |
|
|
|
TextureHashItem item = {}; |
|
item.hash = hash; |
|
item.data.type = texture_type; |
|
|
|
// Copy the PNG, or export the image as PNG. |
|
if (ext && BLI_strcasecmp(ext, ".png") == 0) { |
|
|
|
item.data.name = t_stringCreate(arena, (char *) basename, strlen(basename)); |
|
BLI_path_join(dest_path, FILE_MAX, texturesDir, basename); |
|
|
|
WorkItem item = {}; |
|
item.type = WORK_copy_png; |
|
BLI_strncpy(item.from_path, abs_path, FILE_MAX); |
|
BLI_strncpy(item.dest_path, dest_path, FILE_MAX); |
|
work_queue_push(item); |
|
|
|
} |
|
else { |
|
|
|
char final_tex_name[FILE_MAX] = {}; |
|
char basename_copy[FILE_MAX] = {}; |
|
BLI_strncpy(basename_copy, basename, FILE_MAX); |
|
BLI_path_extension_strip((char *) basename_copy); |
|
BLI_snprintf(final_tex_name, FILE_MAX, "%s.png", basename_copy); |
|
|
|
item.data.name = t_stringCreate(arena, final_tex_name, strlen(final_tex_name)); |
|
BLI_path_join(dest_path, FILE_MAX, texturesDir, final_tex_name); |
|
|
|
WorkItem item = {}; |
|
item.type = WORK_export_image; |
|
item.image = image; |
|
BLI_strncpy(item.from_path, abs_path, FILE_MAX); |
|
BLI_strncpy(item.dest_path, dest_path, FILE_MAX); |
|
work_queue_push(item); |
|
} |
|
|
|
textureHashes[*textureHashesCount] = item; |
|
*textureHashesCount = *textureHashesCount + 1; |
|
|
|
ARRAY_ADD(sceneMat.pMat.textures, item.data); |
|
} |
|
} |
|
} |
|
|
|
if (material->id.properties) { |
|
IDProperty *prop = IDP_GetPropertyFromGroup(material->id.properties, "tri_planar"); |
|
if (prop) { |
|
sceneMat.pMat.tri_planar = IDP_Bool(prop); |
|
} |
|
} |
|
} |
|
|
|
*sceneMaterial = sceneMat; |
|
} |
|
|
|
|
|
void rift_process_object (Arena *arena, Depsgraph *depsgraph, Object **allObjects, u32 allObjectsCount, Object *obj, Entity *target_entity, SceneMaterial *sceneMaterials, u32 sceneMaterialsCount) { |
|
|
|
Entity ent = {}; |
|
|
|
char *name = obj->id.name + 2; |
|
ent.name = t_stringCreate(arena, name, strlen(name)); |
|
|
|
// Process the children indices |
|
size_t arena_marker = arena->size; |
|
ent.children.data = memory_allocate(arena, u32, allObjectsCount); |
|
for (int i = 0; i < allObjectsCount; i++) { |
|
Object *child = allObjects[i]; |
|
if (child->parent == obj) { |
|
u32 idx = i + 1; |
|
ARRAY_ADD(ent.children, idx); |
|
} |
|
} |
|
arena->size = arena_marker; |
|
ent.children.data = memory_allocate(arena, u32, ent.children.count); |
|
|
|
// Transform to engine coords |
|
float local_mat[4][4]; |
|
copy_m4_m4(local_mat, obj->object_to_world().ptr()); |
|
|
|
if (obj->parent) { |
|
float parent_inv[4][4]; |
|
invert_m4_m4(parent_inv, obj->parent->object_to_world().ptr()); |
|
mul_m4_m4m4(local_mat, parent_inv, obj->object_to_world().ptr()); |
|
} |
|
float translation[3], rotationMatrix[3][3], scale[3]; |
|
mat4_to_loc_rot_size(translation, rotationMatrix, scale, local_mat); |
|
float quat[4]; |
|
mat3_to_quat(quat, rotationMatrix); |
|
normalize_qt(quat); |
|
|
|
ent.translation[0] = translation[0]; |
|
ent.translation[1] = translation[2]; |
|
ent.translation[2] = -translation[1]; |
|
ent.rotation[0] = quat[1]; // x |
|
ent.rotation[1] = quat[3]; // z |
|
ent.rotation[2] = -quat[2]; // -y |
|
ent.rotation[3] = quat[0]; // w |
|
ent.scale[0] = scale[0]; |
|
ent.scale[1] = scale[2]; |
|
ent.scale[2] = scale[1]; |
|
|
|
// Types and flags |
|
t_string type_name = get_custom_string(arena, &obj->id, "entity_type"); |
|
if (type_name.length) { |
|
for (int i = 0; i < ARRAY_SIZE(entity_type_strings); i++) { |
|
if (t_stringCompare(type_name, entity_type_strings[i])) { |
|
ent.type = i; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
bool invisible = 0, no_collision = 0, trigger_zone = 0, developer = 0, breakable = 0; |
|
get_custom_bool(&obj->id, &invisible, "invisible"); |
|
get_custom_bool(&obj->id, &no_collision, "no_collision"); |
|
get_custom_bool(&obj->id, &trigger_zone, "trigger_zone"); |
|
get_custom_bool(&obj->id, &developer, "developer"); |
|
get_custom_bool(&obj->id, &breakable, "breakable"); |
|
if (invisible) ent.flags |= ENTITY_INVISIBLE; |
|
if (no_collision) ent.flags |= ENTITY_NO_COLLISION; |
|
if (trigger_zone) ent.flags |= ENTITY_TRIGGER; |
|
if (developer) ent.flags |= ENTITY_DEV; |
|
if (breakable) ent.flags |= ENTITY_BREAKABLE; |
|
|
|
ent.asset = get_custom_string(arena, &obj->id, "asset_name"); |
|
|
|
get_custom_float(&obj->id, &ent.breaking_force, "breaking_force"); |
|
|
|
if (obj->type == OB_MESH) { |
|
|
|
Object *obj_eval = DEG_get_evaluated(depsgraph, obj); |
|
Mesh *mesh = BKE_object_get_evaluated_mesh(obj_eval); |
|
if (mesh) { // Kind of janky. |
|
BKE_mesh_wrapper_ensure_mdata(mesh); |
|
} |
|
if (mesh && mesh->verts_num) { |
|
|
|
float min[3] = {FLT_MAX, FLT_MAX, FLT_MAX}; |
|
float max[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX}; |
|
Span <float3> temp_vert_positions = mesh->vert_positions(); |
|
|
|
if (temp_vert_positions.data()) { |
|
|
|
// @Note: Redundant loop. Can be processed in the vertex processing loop. |
|
for (float3 pos : temp_vert_positions) { |
|
minmax_v3v3_v3(min, max, pos); |
|
} |
|
ent.boundingBoxVertices[0] = {min[0], min[2], -min[1]}; |
|
ent.boundingBoxVertices[1] = {min[0], max[2], -min[1]}; |
|
ent.boundingBoxVertices[2] = {min[0], max[2], -max[1]}; |
|
ent.boundingBoxVertices[3] = {min[0], min[2], -max[1]}; |
|
ent.boundingBoxVertices[4] = {max[0], min[2], -min[1]}; |
|
ent.boundingBoxVertices[5] = {max[0], max[2], -min[1]}; |
|
ent.boundingBoxVertices[6] = {max[0], max[2], -max[1]}; |
|
ent.boundingBoxVertices[7] = {max[0], min[2], -max[1]}; |
|
} |
|
|
|
bool needs_tri = false; |
|
OffsetIndices <int> faces = mesh->faces(); |
|
for (int i : faces.index_range()) { |
|
if (faces[i].size() != 3) { |
|
needs_tri = true; |
|
break; |
|
} |
|
} |
|
|
|
if (needs_tri) { |
|
BMeshCreateParams bm_create_params = {false}; |
|
BMeshFromMeshParams bm_convert_params = {}; |
|
bm_convert_params.calc_face_normal = true; |
|
bm_convert_params.calc_vert_normal = true; |
|
|
|
BMesh *bmesh = BKE_mesh_to_bmesh_ex(mesh, &bm_create_params, &bm_convert_params); |
|
|
|
BM_mesh_triangulate( |
|
bmesh, |
|
MOD_TRIANGULATE_NGON_BEAUTY, |
|
MOD_TRIANGULATE_QUAD_SHORTEDGE, |
|
4, |
|
false, |
|
nullptr, nullptr, nullptr |
|
); |
|
Mesh *triangulated = BKE_mesh_from_bmesh_for_eval_nomain(bmesh, nullptr, mesh); |
|
BM_mesh_free(bmesh); |
|
mesh = triangulated; |
|
} |
|
|
|
char uv_name[MAX_CUSTOMDATA_LAYER_NAME] = {}; |
|
if (CustomData_number_of_layers(&mesh->corner_data, CD_PROP_FLOAT2) > 0) { |
|
int active_uv = CustomData_get_active_layer_index(&mesh->corner_data, CD_PROP_FLOAT2); |
|
BLI_strncpy(uv_name, mesh->corner_data.layers[active_uv].name, MAX_CUSTOMDATA_LAYER_NAME); |
|
} |
|
else { |
|
CustomData_add_layer_named(&mesh->corner_data, CD_PROP_FLOAT2, CD_SET_DEFAULT, mesh->corners_num, "DefaultUVMap"); |
|
BLI_strncpy(uv_name, "DefaultUVMap", MAX_CUSTOMDATA_LAYER_NAME); |
|
} |
|
|
|
float (* uv_layer)[2] = (float (*)[2]) CustomData_get_layer_named(&mesh->corner_data, CD_PROP_FLOAT2, "DefaultUVMap"); |
|
if (!uv_layer) { |
|
uv_layer = (float (*)[2]) CustomData_get_layer(&mesh->corner_data, CD_PROP_FLOAT2); |
|
} |
|
|
|
float(*loop_tangents)[4]; |
|
|
|
if (CustomData_has_layer(&mesh->corner_data, CD_MLOOPTANGENT)) { |
|
loop_tangents = (float(*)[4]) CustomData_get_layer_for_write(&mesh->corner_data, CD_MLOOPTANGENT, mesh->corners_num); |
|
memset(loop_tangents, 0, sizeof(float[4]) * mesh->corners_num); |
|
} |
|
else { |
|
loop_tangents = (float(*)[4]) (CustomData_add_layer(&mesh->corner_data, CD_MLOOPTANGENT, CD_SET_DEFAULT, mesh->corners_num)); |
|
CustomData_set_layer_flag(&mesh->corner_data, CD_MLOOPTANGENT, CD_FLAG_TEMPORARY); |
|
} |
|
if (!loop_tangents) { |
|
__debugbreak(); |
|
} |
|
BKE_mesh_calc_loop_tangent_single(mesh, uv_name, loop_tangents, nullptr); |
|
|
|
Span <float3> vert_positions = mesh->vert_positions(); |
|
Span <int> corner_verts = mesh->corner_verts(); |
|
Span <float3> corner_normals = mesh->corner_normals(); |
|
Span <int3> corner_tris = mesh->corner_tris(); |
|
const int * corner_tri_faces = mesh->corner_tri_faces().data(); |
|
bke::AttributeAccessor attributes = mesh->attributes(); |
|
|
|
VArray <int> material_indices = *attributes.lookup_or_default <int> ("material_index", bke::AttrDomain::Face, 0); |
|
|
|
typedef struct MaterialStats { |
|
int tri_count; |
|
int estimated_vertex_count; |
|
} MaterialStats; |
|
|
|
ent.primitives.data = memory_allocate(arena, Primitive, obj_eval->totcol); |
|
|
|
MaterialStats *mat_stats = memory_allocate(arena, MaterialStats, obj_eval->totcol); |
|
memset(mat_stats, 0, sizeof(MaterialStats) * obj_eval->totcol); |
|
|
|
// Single pass to count |
|
for (int tri_idx = 0; tri_idx < corner_tris.size(); tri_idx++) { |
|
int face_idx = corner_tri_faces[tri_idx]; |
|
int mat_idx = material_indices[face_idx]; |
|
if (mat_idx >= 0 && mat_idx < obj_eval->totcol) { |
|
mat_stats[mat_idx].tri_count++; |
|
mat_stats[mat_idx].estimated_vertex_count += 3; // Conservative estimate |
|
} |
|
} |
|
|
|
// @Note: The following loop will create duplicate vertices. So a cube that can be |
|
// represented in 24 vertices and 36 indices will end up having 36 vertices. I didn't |
|
// bother to de-duplicate the vertices, but you would very likely want to actually do it! |
|
|
|
// @Note: There is likely a much better way of doing the loop of sad here... proceed |
|
// with caution! |
|
|
|
for (int mat_idx = 0; mat_idx < obj_eval->totcol; mat_idx++) { |
|
|
|
Material *material = BKE_object_material_get(obj_eval, mat_idx + 1); |
|
if (!material) continue; |
|
|
|
// Skip materials with no triangles |
|
if (mat_stats[mat_idx].tri_count == 0) continue; |
|
|
|
Primitive primitive = {}; |
|
|
|
int index_capacity = mat_stats[mat_idx].tri_count * 3; |
|
int vertex_capacity = mat_stats[mat_idx].estimated_vertex_count; |
|
|
|
primitive.vertices.data = memory_allocate(arena, Vertex, vertex_capacity); |
|
primitive.indices.data = memory_allocate(arena, u32, index_capacity); |
|
|
|
for (int tri_idx = 0; tri_idx < corner_tris.size(); tri_idx++) { |
|
int3 tri = corner_tris[tri_idx]; |
|
int face_idx = corner_tri_faces[tri_idx]; |
|
|
|
if (material_indices[face_idx] != mat_idx) continue; |
|
|
|
for (int j = 0; j < 3; j++) { |
|
int loop_idx = tri[j]; |
|
int vert_idx = corner_verts[loop_idx]; |
|
|
|
float pos_engine[3] = { |
|
vert_positions[vert_idx].x, |
|
vert_positions[vert_idx].z, |
|
-vert_positions[vert_idx].y |
|
}; |
|
|
|
float norm_engine[3] = { |
|
corner_normals[loop_idx].x, |
|
corner_normals[loop_idx].z, |
|
-corner_normals[loop_idx].y |
|
}; |
|
normalize_v3(norm_engine); |
|
|
|
float uv[2] = {0.0f, 0.0f}; |
|
if (uv_layer) { |
|
uv[0] = uv_layer[loop_idx][0]; |
|
uv[1] = 1.0f - uv_layer[loop_idx][1]; |
|
} |
|
|
|
float4 tan = loop_tangents[loop_idx]; |
|
float tan_engine[3] = {tan.x, tan.z, -tan.y}; |
|
float bitan_engine[3]; |
|
cross_v3_v3v3(bitan_engine, norm_engine, tan_engine); |
|
float tan_w[3] = {tan.w, tan.w, tan.w}; |
|
float w = (dot_v3v3(bitan_engine, tan_w) > 0.0f) ? 1.0f : -1.0f; |
|
|
|
int found_vertex_idx = -1; |
|
|
|
if (found_vertex_idx == -1) { |
|
// Should never happen |
|
if (primitive.vertices.count >= vertex_capacity) { |
|
__debugbreak(); |
|
} |
|
|
|
Vertex *vertex = &primitive.vertices.data[primitive.vertices.count]; |
|
copy_v3_v3(vertex->pos, pos_engine); |
|
copy_v3_v3(vertex->norm, norm_engine); |
|
copy_v2_v2(vertex->uv, uv); |
|
copy_v3_v3(vertex->tan, tan_engine); |
|
vertex->tan[3] = w; |
|
|
|
found_vertex_idx = primitive.vertices.count; |
|
primitive.vertices.count++; |
|
} |
|
|
|
if (primitive.indices.count >= index_capacity) { |
|
// Should never happen |
|
__debugbreak(); |
|
} |
|
|
|
ARRAY_ADD(primitive.indices, found_vertex_idx); |
|
} |
|
} |
|
|
|
if (primitive.vertices.count == 0 || primitive.indices.count == 0) { |
|
// This should never be true now |
|
__debugbreak(); |
|
} |
|
|
|
char *material_name = material->id.name + 2; |
|
t_string material_name_str = t_stringCreate(arena, material_name, strlen(material_name)); |
|
|
|
for (int m = 0; m < sceneMaterialsCount; m++) { |
|
|
|
SceneMaterial *sceneMat = &sceneMaterials[m]; |
|
if (t_stringCompare(material_name_str, sceneMat->pMat.name)) { |
|
primitive.materialIdx = m; |
|
} |
|
} |
|
|
|
ARRAY_ADD(ent.primitives, primitive); |
|
} |
|
|
|
if (needs_tri) { |
|
BKE_id_free(NULL, mesh); |
|
} |
|
} |
|
|
|
} |
|
|
|
switch (ent.type) { |
|
|
|
case ENTITY_SUN: { |
|
// Default Values, because IDP, doesn't retrieve any values if on default value |
|
ent.sun_color[0] = 1.0f; |
|
ent.sun_color[1] = 1.0f; |
|
ent.sun_color[2] = 1.0f; |
|
ent.sun_intensity = 1.0f; |
|
ent.ambient_color[0] = 1.0f; |
|
ent.ambient_color[1] = 1.0f; |
|
ent.ambient_color[2] = 1.0f; |
|
ent.ambient_intensity = 0.6f; |
|
|
|
get_custom_float(&obj->id, &ent.fog_falloff, "fog_falloff"); |
|
get_custom_float3(&obj->id, ent.fog_color, "fog_color"); |
|
get_custom_float(&obj->id, &ent.fade_begin, "fade_begin"); |
|
get_custom_float(&obj->id, &ent.fade_end, "fade_end"); |
|
get_custom_float(&obj->id, &ent.sun_intensity, "sun_intensity"); |
|
get_custom_float3(&obj->id, ent.sun_color, "sun_color"); |
|
get_custom_float(&obj->id, &ent.ambient_intensity, "ambient_intensity"); |
|
get_custom_float3(&obj->id, ent.ambient_color, "ambient_color"); |
|
ent.sky_shader = get_custom_string(arena, &obj->id, "sky_shader"); |
|
} break; |
|
|
|
case ENTITY_LIGHT: { |
|
get_custom_int(&obj->id, &ent.light_enabled , "light_enabled"); |
|
get_custom_float3(&obj->id, ent.light_color, "light_color"); |
|
get_custom_float(&obj->id, &ent.light_intensity, "light_intensity"); |
|
get_custom_float(&obj->id, &ent.light_falloff, "light_falloff"); |
|
} break; |
|
|
|
case ENTITY_PORTAL: { |
|
ent.portal_level = get_custom_string(arena, &obj->id, "portal_level"); |
|
ent.portal_target_entity = get_custom_string(arena, &obj->id, "portal_target_entity"); |
|
} break; |
|
|
|
case ENTITY_GRAVITYSWITCH : { |
|
get_custom_int(&obj->id, &ent.gravityswitch_enabled, "gravityswitch_enabled"); |
|
} break; |
|
|
|
case ENTITY_MOVEMENTSWITCH: { |
|
get_custom_int(&obj->id, &ent.movementSwitch_mode, "movementSwitch_mode"); |
|
} break; |
|
|
|
case ENTITY_PLATFORM: { |
|
Object *target_object = get_custom_object(&obj->id, "platform_target_entity"); |
|
Object *target_goal = get_custom_object(&obj->id, "platform_target_goal"); |
|
if (target_object) { |
|
ent.platform_target_entity = findEntityIndex(target_object, allObjects, allObjectsCount); |
|
} |
|
if (target_goal) { |
|
ent.platform_target_goal = findEntityIndex(target_goal, allObjects, allObjectsCount); |
|
} |
|
get_custom_int(&obj->id, &ent.platform_looping, "platform_looping"); |
|
get_custom_float(&obj->id, &ent.platform_timer, "platform_timer"); |
|
} break; |
|
|
|
case ENTITY_DIAMOND: { |
|
get_custom_int(&obj->id, &ent.diamond_difficulty, "diamond_difficulty"); |
|
} break; |
|
|
|
case ENTITY_BUTTON: { |
|
|
|
size_t arena_size = arena->size; |
|
ent.button_target_entities.data = memory_allocate(arena, u32, 64); |
|
|
|
PropertyRNA *collection_prop; |
|
PropertyRNA *obj_prop; |
|
CollectionPropertyIterator iter; |
|
|
|
PointerRNA obj_ptr = RNA_id_pointer_create(&obj->id); |
|
collection_prop = RNA_struct_find_property(&obj_ptr, "button_target_entities"); |
|
|
|
if (collection_prop) { |
|
RNA_PROP_BEGIN(&obj_ptr, itemptr, collection_prop) { |
|
|
|
obj_prop = RNA_struct_find_property(&itemptr, "obj"); |
|
if (obj_prop) { |
|
|
|
PointerRNA target_ptr = RNA_property_pointer_get(&itemptr, obj_prop); |
|
Object *target_obj = (Object *) target_ptr.data; |
|
if (target_obj) { |
|
|
|
u32 idx = findEntityIndex(target_obj, allObjects, allObjectsCount); |
|
ARRAY_ADD(ent.button_target_entities, idx); |
|
} |
|
} |
|
} RNA_PROP_END; |
|
} |
|
arena->size = arena_size; |
|
ent.button_target_entities.data = memory_allocate(arena, u32, ent.button_target_entities.count); |
|
} break; |
|
|
|
case ENTITY_CABLE: { |
|
// Default Values, because IDP, doesn't retrieve any values if on default value |
|
ent.cable_target_color[0] = 1.0f; |
|
ent.cable_target_color[1] = 1.0f; |
|
ent.cable_target_color[2] = 1.0f; |
|
|
|
get_custom_float3(&obj->id, ent.cable_target_color, "cable_target_color"); |
|
} break; |
|
} |
|
|
|
*target_entity = ent; |
|
} |
|
|
|
// |
|
// Exporter Main |
|
// |
|
static void rift_exporter_main(bContext *C, const char *filepath, const char *texturesDir) { |
|
|
|
arena.size = 0; |
|
work_queue.head = 0; |
|
work_queue.tail = 0; |
|
work_queue.remaining = 0; |
|
|
|
Scene *scene = CTX_data_scene(C); |
|
Main *bmain = CTX_data_main(C); |
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); |
|
|
|
// This makes it so that blender doesn't change the colorspace of images when exporting them |
|
STRNCPY(scene->view_settings.view_transform, "Standard"); |
|
|
|
// Collect the scene objects |
|
// @Waste, this does an extra loop over all the objects! |
|
u32 all_objects_capacity = BLI_listbase_count(&bmain->objects) + 1; |
|
ARRAY(Object *, all_objects); |
|
all_objects.data = memory_allocate(&arena, Object *, all_objects_capacity); |
|
all_objects.count = 0; |
|
|
|
LISTBASE_FOREACH(Object *, obj, &bmain->objects) { |
|
ARRAY_ADD(all_objects, obj); |
|
} |
|
|
|
// |
|
// Process the materials upfront |
|
// |
|
ARRAY(SceneMaterial, sceneMaterials); |
|
ARRAY(TextureHashItem, texture_hashes); |
|
sceneMaterials.data = memory_allocate(&arena, SceneMaterial, 128); |
|
sceneMaterials.count = 0; |
|
texture_hashes.data = memory_allocate(&arena, TextureHashItem, 128); |
|
texture_hashes.count = 0; |
|
|
|
// Pre-calculate blend file directory once |
|
char blend_dir[FILE_MAX] = {}; |
|
if (strlen(bmain->filepath) > 0) { |
|
BLI_path_split_dir_part(bmain->filepath, blend_dir, FILE_MAX); |
|
} |
|
|
|
LISTBASE_FOREACH(Material *, material, &bmain->materials) { |
|
|
|
// @Note: This adds jobs to the job queue system |
|
rift_process_material( |
|
&arena, |
|
material, |
|
&sceneMaterials.data[sceneMaterials.count], |
|
texture_hashes.data, |
|
&texture_hashes.count, |
|
blend_dir, |
|
(char *) texturesDir |
|
); |
|
sceneMaterials.count++; |
|
} |
|
|
|
// Prepare entities |
|
ARRAY(Entity, entities); |
|
entities.data = memory_allocate(&arena, Entity, (all_objects.count + 1)); |
|
entities.count = 0; |
|
|
|
Entity root = {}; |
|
root.name = T_STR("Root"); |
|
root.rotation[3] = 1.0f; // w = 1.0 |
|
root.scale[0] = 1.0f; |
|
root.scale[1] = 1.0f; |
|
root.scale[2] = 1.0f; |
|
|
|
size_t arena_marker = arena.size; |
|
root.children.data = memory_allocate(&arena, u32, all_objects.count); |
|
u32 idx = 0; |
|
LISTBASE_FOREACH(Object *, obj, &bmain->objects) { |
|
if (!obj->parent) { |
|
ARRAY_ADD(root.children, idx + 1); |
|
} |
|
idx++; |
|
} |
|
arena.size = arena_marker; |
|
root.children.data = memory_allocate(&arena, u32, root.children.count); |
|
|
|
ARRAY_ADD(entities, root); |
|
|
|
entities.count = all_objects.count + 1; |
|
|
|
// Setup the worker threads ... and go! |
|
for (int i = 0; i < MAX_THREADS; i++) { |
|
threads_data[i].arena->size = 0; |
|
threads_data[i].bmain = bmain; |
|
threads_data[i].scene = scene; |
|
threads_data[i].depsgraph = depsgraph; |
|
threads_data[i].allObjects = all_objects.data; |
|
threads_data[i].allObjectsCount = all_objects.count; |
|
threads_data[i].sceneMaterials = sceneMaterials.data; |
|
threads_data[i].sceneMaterialsCount = sceneMaterials.count; |
|
} |
|
|
|
for (int i = 0; i < all_objects.count; i++) { |
|
|
|
WorkItem item = {}; |
|
item.type = WORK_process_object; |
|
item.object = all_objects.data[i]; |
|
item.target_entity = &entities.data[i + 1]; |
|
|
|
work_queue_push(item); |
|
} |
|
|
|
for (int i = 0; i < MAX_THREADS; i++) { |
|
SetEvent(threads_data[i].wake_event); |
|
} |
|
|
|
// Wait till all threads are donezo |
|
while (1) { |
|
if (work_queue.remaining == 0) break; |
|
} |
|
|
|
// Single threaded version: |
|
// for (int i = 0; i < all_objects.count; i++) { |
|
// Object *obj = all_objects.data[i]; |
|
// Entity *entity = &entities.data[i + 1]; |
|
// rift_process_object(&arena, depsgraph, all_objects.data, all_objects.count, obj, entity, sceneMaterials.data, sceneMaterials.count); |
|
// } |
|
|
|
// |
|
// Serialize to disk |
|
// |
|
char *fileData = memory_allocate(&arena, char, Megabytes(500)); |
|
u32 fileDataSize = 0; |
|
|
|
HANDLE hFile = CreateFileA(filepath, GENERIC_WRITE, 0 , NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); |
|
if (hFile != INVALID_HANDLE_VALUE) { |
|
int lisa = 0x115A; |
|
serialize_int(lisa, 1); |
|
|
|
// |
|
// Materials |
|
// |
|
serialize_int(sceneMaterials.count, 1); |
|
for (int i = 0; i < sceneMaterials.count; i++) { |
|
|
|
PrimitiveMaterial *pMat = &sceneMaterials.data[i].pMat; |
|
|
|
serialize_string(pMat->name); |
|
serialize_float(pMat->color[0], 3); |
|
serialize_int(pMat->tri_planar, 1); |
|
serialize_string(pMat->shader); |
|
|
|
serialize_int(pMat->textures.count, 1); |
|
for (int t = 0; t < pMat->textures.count; t++) { |
|
|
|
TextureData *tex_data = &pMat->textures.data[t]; |
|
serialize_string(tex_data->name); |
|
serialize_int(tex_data->type, 1); |
|
} |
|
serialize_int(pMat->flags, 1); |
|
} |
|
|
|
// |
|
// Entities |
|
// |
|
serialize_int(entities.count, 1); |
|
for (int i = 0; i < entities.count; i++) { |
|
|
|
Entity *ent = &entities.data[i]; |
|
serialize_string(ent->name); |
|
|
|
serialize_int(ent->children.count, 1); |
|
serialize_int(ent->children.data[0], ent->children.count); |
|
|
|
serialize_float(ent->translation[0], 3); |
|
serialize_float(ent->rotation[0], 4); |
|
serialize_float(ent->scale[0], 3); |
|
|
|
serialize_int(ent->type, 1); |
|
serialize_int(ent->flags, 1); |
|
|
|
serialize_float(ent->breaking_force, 1); |
|
|
|
switch (ent->type) { |
|
case ENTITY_SUN: { |
|
serialize_float(ent->fog_falloff, 1); |
|
serialize_float(ent->fog_color[0], 3); |
|
serialize_float(ent->fade_begin, 1); |
|
serialize_float(ent->fade_end, 1); |
|
serialize_string(ent->sky_shader); |
|
serialize_float(ent->sun_intensity, 1); |
|
serialize_float(ent->sun_color[0], 3); |
|
serialize_float(ent->ambient_intensity, 1); |
|
serialize_float(ent->ambient_color[0], 3); |
|
} break; |
|
|
|
case ENTITY_LIGHT: { |
|
serialize_int(ent->light_enabled, 1); |
|
serialize_float(ent->light_color, 3); |
|
serialize_float(ent->light_intensity, 1); |
|
serialize_float(ent->light_falloff, 1); |
|
} break; |
|
|
|
case ENTITY_PORTAL: { |
|
serialize_string(ent->portal_level); |
|
serialize_string(ent->portal_target_entity); |
|
} break; |
|
|
|
case ENTITY_GRAVITYSWITCH: { |
|
serialize_int(ent->gravityswitch_enabled, 1); |
|
} break; |
|
|
|
case ENTITY_MOVEMENTSWITCH: { |
|
serialize_int(ent->movementSwitch_mode, 1); |
|
} break; |
|
|
|
case ENTITY_PLATFORM: { |
|
serialize_int(ent->platform_target_entity, 1); |
|
serialize_int(ent->platform_target_goal, 1); |
|
serialize_int(ent->platform_looping, 1); |
|
serialize_float(ent->platform_timer, 1); |
|
} break; |
|
|
|
case ENTITY_DIAMOND: { |
|
serialize_int(ent->diamond_difficulty, 1); |
|
} break; |
|
|
|
case ENTITY_BUTTON: { |
|
serialize_int(ent->button_target_entities.count, 1); |
|
serialize_int(ent->button_target_entities.data[0], ent->button_target_entities.count); |
|
} break; |
|
|
|
case ENTITY_CABLE: { |
|
serialize_float(ent->cable_target_color[0], 3); |
|
} break; |
|
} |
|
|
|
serialize_string(ent->asset); |
|
|
|
serialize(ent->boundingBoxVertices[0], sizeof(vec3), 8); |
|
|
|
serialize_int(ent->primitives.count, 1); |
|
for (int j = 0; j < ent->primitives.count; j++) { |
|
|
|
Primitive *prim = &ent->primitives.data[j]; |
|
serialize_int(prim->vertices.count, 1); |
|
|
|
// @Note: It's better to have the vertex data separated and not interleaved! |
|
for (int v = 0; v < prim->vertices.count; v++) { |
|
Vertex *vert = &prim->vertices.data[v]; |
|
serialize_float(vert->pos[0], 3); |
|
serialize_float(vert->norm[0], 3); |
|
serialize_float(vert->uv[0], 2); |
|
serialize_float(vert->tan[0], 4); |
|
} |
|
|
|
serialize_int(prim->indices.count, 1); |
|
serialize_int(prim->indices.data[0], prim->indices.count); |
|
serialize_int(prim->materialIdx, 1); |
|
} |
|
} |
|
} |
|
|
|
DWORD bytesWritten; |
|
WriteFile(hFile, fileData, fileDataSize, &bytesWritten, NULL); |
|
CloseHandle(hFile); |
|
} |
|
|
|
// |
|
// Exporter function, triggered from python |
|
// |
|
static wmOperatorStatus rift_export_exec(bContext *C, wmOperator *op) { |
|
|
|
char filepath[FILE_MAX]; |
|
char texturesDir[FILE_MAX]; |
|
RNA_string_get(op->ptr, "filepath", filepath); |
|
RNA_string_get(op->ptr, "texturesDir", texturesDir); |
|
|
|
rift_exporter_main(C, filepath, texturesDir); |
|
|
|
return OPERATOR_FINISHED; |
|
} |
|
|
|
static wmOperatorStatus rift_export_invoke(bContext *C, wmOperator *op, const wmEvent *) { |
|
|
|
WM_event_add_fileselect(C, op); |
|
return OPERATOR_RUNNING_MODAL; |
|
} |
|
|
|
// |
|
// Init function, registered on startup |
|
// |
|
extern "C" void EXPORT_SCENE_OT_rift(wmOperatorType * ot) { |
|
|
|
arena = memory_createArena(Gigabytes(2)); |
|
|
|
work_queue.head = 0; |
|
work_queue.tail = 0; |
|
|
|
LPCWSTR thread_names[] = { |
|
L"RiftWorker1", L"RiftWorker2", L"RiftWorker3", L"RiftWorker4", |
|
L"RiftWorker5", L"RiftWorker6", L"RiftWorker7", L"RiftWorker8", |
|
L"RiftWorker9", L"RiftWorker10", L"RiftWorker11", L"RiftWorker12", |
|
L"RiftWorker13", L"RiftWorker14", L"RiftWorker15", L"RiftWorker16", |
|
L"RiftWorker17", L"RiftWorker18", L"RiftWorker19", L"RiftWorker20", |
|
L"RiftWorker21", L"RiftWorker22", L"RiftWorker23", L"RiftWorker24", |
|
}; |
|
|
|
for (int i = 0; i < MAX_THREADS; i++) { |
|
|
|
thread_arenas[i] = memory_createArena(Megabytes(200)); |
|
|
|
threads_data[i].thread_id = i; |
|
threads_data[i].arena = &thread_arenas[i]; |
|
threads_data[i].wake_event = CreateEvent(NULL, FALSE, FALSE, NULL); |
|
|
|
worker_threads[i] = CreateThread(0, 0, rift_worker_thread, &threads_data[i], 0, 0); |
|
SetThreadDescription(worker_threads[i], thread_names[i]); |
|
} |
|
|
|
|
|
ot->name = "Export to Rift"; |
|
ot->idname = "EXPORT_SCENE_OT_rift"; |
|
ot->description = "Export scene to .rift format"; |
|
|
|
ot->invoke = rift_export_invoke; |
|
ot->exec = rift_export_exec; |
|
ot->poll = WM_operator_winactive; |
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; |
|
|
|
RNA_def_string_file_path(ot->srna, "filepath", nullptr, FILE_MAX, "File Path", "Destination .rift file"); |
|
RNA_def_string_dir_path(ot->srna, "texturesDir", nullptr, FILE_MAX, "Textures Directory", "Folder to which textures will exported to"); |
|
} |
|
|
|
|