mirror of https://github.com/bjornbytes/lovr.git
Start parsing glTF;
This commit is contained in:
parent
2eb23864b6
commit
6b323e3476
|
@ -396,6 +396,7 @@ if(LOVR_ENABLE_DATA)
|
|||
src/lib/stb/stb_image_write.c
|
||||
src/lib/stb/stb_truetype.c
|
||||
src/lib/stb/stb_vorbis.c
|
||||
src/lib/jsmn/jsmn.c
|
||||
)
|
||||
|
||||
if (LOVR_USE_ASSIMP)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "data/soundData.h"
|
||||
#include "data/textureData.h"
|
||||
#include "data/vertexData.h"
|
||||
#include "filesystem/filesystem.h"
|
||||
|
||||
static int l_lovrDataNewBlob(lua_State* L) {
|
||||
size_t size;
|
||||
|
@ -44,9 +45,11 @@ static int l_lovrDataNewAudioStream(lua_State* L) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static ModelDataIO modelDataIO = { lovrFilesystemRead };
|
||||
|
||||
static int l_lovrDataNewModelData(lua_State* L) {
|
||||
Blob* blob = luax_readblob(L, 1, "Model");
|
||||
ModelData* modelData = lovrModelDataCreate(blob);
|
||||
ModelData* modelData = lovrModelDataCreate(blob, modelDataIO);
|
||||
luax_pushobject(L, modelData);
|
||||
lovrRelease(blob);
|
||||
lovrRelease(modelData);
|
||||
|
@ -147,7 +150,7 @@ static int l_lovrDataNewVertexData(lua_State* L) {
|
|||
VertexData* vertexData = lovrVertexDataCreate(count, hasFormat ? &format : NULL);
|
||||
|
||||
if (dataIndex) {
|
||||
luax_loadvertices(L, dataIndex, &vertexData->format, (VertexPointer) { .raw = vertexData->blob.data });
|
||||
luax_loadvertices(L, dataIndex, &vertexData->format, (AttributePointer) { .raw = vertexData->blob.data });
|
||||
}
|
||||
luax_pushobject(L, vertexData);
|
||||
lovrRelease(vertexData);
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
#include "data/vertexData.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
int luax_loadvertices(lua_State* L, int index, VertexFormat* format, VertexPointer vertices);
|
||||
int luax_loadvertices(lua_State* L, int index, VertexFormat* format, AttributePointer vertices);
|
||||
bool luax_checkvertexformat(lua_State* L, int index, VertexFormat* format);
|
||||
int luax_pushvertexformat(lua_State* L, VertexFormat* format);
|
||||
int luax_pushvertexattribute(lua_State* L, VertexPointer* vertex, Attribute attribute);
|
||||
int luax_pushvertex(lua_State* L, VertexPointer* vertex, VertexFormat* format);
|
||||
void luax_setvertexattribute(lua_State* L, int index, VertexPointer* vertex, Attribute attribute);
|
||||
void luax_setvertex(lua_State* L, int index, VertexPointer* vertex, VertexFormat* format);
|
||||
int luax_pushvertexattribute(lua_State* L, AttributePointer* vertex, Attribute attribute);
|
||||
int luax_pushvertex(lua_State* L, AttributePointer* vertex, VertexFormat* format);
|
||||
void luax_setvertexattribute(lua_State* L, int index, AttributePointer* vertex, Attribute attribute);
|
||||
void luax_setvertex(lua_State* L, int index, AttributePointer* vertex, VertexFormat* format);
|
||||
Blob* luax_readblob(lua_State* L, int index, const char* debug);
|
||||
|
|
|
@ -26,9 +26,13 @@ const char* ArcModes[] = {
|
|||
};
|
||||
|
||||
const char* AttributeTypes[] = {
|
||||
[ATTR_FLOAT] = "float",
|
||||
[ATTR_BYTE] = "byte",
|
||||
[ATTR_INT] = "int",
|
||||
[I8] = "i8",
|
||||
[U8] = "u8",
|
||||
[I16] = "i16",
|
||||
[U16] = "u16",
|
||||
[I32] = "i32",
|
||||
[U32] = "u32",
|
||||
[F32] = "f32",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -899,7 +903,7 @@ static int l_lovrGraphicsCompute(lua_State* L) {
|
|||
|
||||
static int l_lovrGraphicsNewAnimator(lua_State* L) {
|
||||
Model* model = luax_checktype(L, 1, Model);
|
||||
Animator* animator = lovrAnimatorCreate(model->modelData);
|
||||
Animator* animator = lovrAnimatorCreate(model->data);
|
||||
luax_pushobject(L, animator);
|
||||
lovrRelease(animator);
|
||||
return 1;
|
||||
|
@ -1154,9 +1158,9 @@ static int l_lovrGraphicsNewMesh(lua_State* L) {
|
|||
}
|
||||
|
||||
if (!hasFormat) {
|
||||
vertexFormatAppend(&format, "lovrPosition", ATTR_FLOAT, 3);
|
||||
vertexFormatAppend(&format, "lovrNormal", ATTR_FLOAT, 3);
|
||||
vertexFormatAppend(&format, "lovrTexCoord", ATTR_FLOAT, 2);
|
||||
vertexFormatAppend(&format, "lovrPosition", F32, 3);
|
||||
vertexFormatAppend(&format, "lovrNormal", F32, 3);
|
||||
vertexFormatAppend(&format, "lovrTexCoord", F32, 2);
|
||||
}
|
||||
|
||||
DrawMode mode = luaL_checkoption(L, drawModeIndex, "fan", DrawModes);
|
||||
|
@ -1168,7 +1172,7 @@ static int l_lovrGraphicsNewMesh(lua_State* L) {
|
|||
|
||||
lovrMeshAttachAttribute(mesh, "lovrDrawID", &(MeshAttribute) {
|
||||
.buffer = lovrGraphicsGetIdentityBuffer(),
|
||||
.type = ATTR_BYTE,
|
||||
.type = U8,
|
||||
.components = 1,
|
||||
.divisor = 1,
|
||||
.integer = true,
|
||||
|
@ -1176,8 +1180,8 @@ static int l_lovrGraphicsNewMesh(lua_State* L) {
|
|||
});
|
||||
|
||||
if (dataIndex) {
|
||||
VertexPointer vertices = { .raw = lovrBufferMap(vertexBuffer, 0) };
|
||||
luax_loadvertices(L, dataIndex, &format, vertices);
|
||||
AttributePointer vertices = { .raw = lovrBufferMap(vertexBuffer, 0) };
|
||||
luax_loadvertices(L, dataIndex, lovrMeshGetVertexFormat(mesh), vertices);
|
||||
} else if (vertexData) {
|
||||
void* vertices = lovrBufferMap(vertexBuffer, 0);
|
||||
memcpy(vertices, vertexData->blob.data, vertexData->count * vertexData->format.stride);
|
||||
|
@ -1196,30 +1200,13 @@ static int l_lovrGraphicsNewModel(lua_State* L) {
|
|||
|
||||
if (!modelData) {
|
||||
Blob* blob = luax_readblob(L, 1, "Model");
|
||||
modelData = lovrModelDataCreate(blob);
|
||||
static ModelDataIO io = { lovrFilesystemRead };
|
||||
modelData = lovrModelDataCreate(blob, io);
|
||||
lovrRelease(blob);
|
||||
}
|
||||
|
||||
Model* model = lovrModelCreate(modelData);
|
||||
|
||||
if (lua_gettop(L) >= 2) {
|
||||
if (lua_type(L, 2) == LUA_TSTRING) {
|
||||
Blob* blob = luax_readblob(L, 2, "Texture");
|
||||
TextureData* textureData = lovrTextureDataCreateFromBlob(blob, true);
|
||||
Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, true, 0);
|
||||
Material* material = lovrMaterialCreate();
|
||||
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, texture);
|
||||
lovrModelSetMaterial(model, material);
|
||||
lovrRelease(blob);
|
||||
lovrRelease(texture);
|
||||
lovrRelease(material);
|
||||
} else {
|
||||
lovrModelSetMaterial(model, luax_checktype(L, 2, Material));
|
||||
}
|
||||
}
|
||||
|
||||
luax_pushobject(L, model);
|
||||
lovrRelease(modelData);
|
||||
lovrRelease(model);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ int l_lovrMeshGetVertex(lua_State* L) {
|
|||
Buffer* buffer = lovrMeshGetVertexBuffer(mesh);
|
||||
lovrAssert(lovrBufferIsReadable(buffer), "Mesh:getVertex can only be used if the Mesh was created with the readable flag");
|
||||
VertexFormat* format = lovrMeshGetVertexFormat(mesh);
|
||||
VertexPointer vertex = { .raw = lovrBufferMap(buffer, index * format->stride) };
|
||||
AttributePointer vertex = { .raw = lovrBufferMap(buffer, index * format->stride) };
|
||||
return luax_pushvertex(L, &vertex, format);
|
||||
}
|
||||
|
||||
|
@ -137,7 +137,7 @@ int l_lovrMeshSetVertex(lua_State* L) {
|
|||
lovrAssert(index >= 0 && index < lovrMeshGetVertexCount(mesh), "Invalid mesh vertex index: %d", index + 1);
|
||||
Buffer* buffer = lovrMeshGetVertexBuffer(mesh);
|
||||
VertexFormat* format = lovrMeshGetVertexFormat(mesh);
|
||||
VertexPointer vertex = { .raw = lovrBufferMap(buffer, index * format->stride) };
|
||||
AttributePointer vertex = { .raw = lovrBufferMap(buffer, index * format->stride) };
|
||||
luax_setvertex(L, 3, &vertex, format);
|
||||
lovrBufferMarkRange(buffer, index * format->stride, (index + 1) * format->stride);
|
||||
return 0;
|
||||
|
@ -153,7 +153,7 @@ int l_lovrMeshGetVertexAttribute(lua_State* L) {
|
|||
lovrAssert(vertexIndex >= 0 && vertexIndex < lovrMeshGetVertexCount(mesh), "Invalid mesh vertex: %d", vertexIndex + 1);
|
||||
lovrAssert(attributeIndex >= 0 && attributeIndex < format->count, "Invalid mesh attribute: %d", attributeIndex + 1);
|
||||
Attribute attribute = format->attributes[attributeIndex];
|
||||
VertexPointer vertex = { .raw = lovrBufferMap(buffer, vertexIndex * format->stride + attribute.offset) };
|
||||
AttributePointer vertex = { .raw = lovrBufferMap(buffer, vertexIndex * format->stride + attribute.offset) };
|
||||
return luax_pushvertexattribute(L, &vertex, attribute);
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ int l_lovrMeshSetVertexAttribute(lua_State* L) {
|
|||
lovrAssert(attributeIndex >= 0 && attributeIndex < format->count, "Invalid mesh attribute: %d", attributeIndex + 1);
|
||||
Attribute attribute = format->attributes[attributeIndex];
|
||||
Buffer* buffer = lovrMeshGetVertexBuffer(mesh);
|
||||
VertexPointer vertex = { .raw = lovrBufferMap(buffer, vertexIndex * format->stride + attribute.offset) };
|
||||
AttributePointer vertex = { .raw = lovrBufferMap(buffer, vertexIndex * format->stride + attribute.offset) };
|
||||
luax_setvertexattribute(L, 4, &vertex, attribute);
|
||||
lovrBufferMarkRange(buffer, vertexIndex * format->stride + attribute.offset, vertexIndex * format->stride + attribute.offset + attribute.size);
|
||||
return 0;
|
||||
|
@ -194,7 +194,7 @@ int l_lovrMeshSetVertices(lua_State* L) {
|
|||
lovrAssert(count <= sourceSize, "Cannot set %d vertices on Mesh: source only has %d vertices", count, sourceSize);
|
||||
|
||||
Buffer* buffer = lovrMeshGetVertexBuffer(mesh);
|
||||
VertexPointer vertices = { .raw = lovrBufferMap(buffer, start * format->stride) };
|
||||
AttributePointer vertices = { .raw = lovrBufferMap(buffer, start * format->stride) };
|
||||
|
||||
if (vertexData) {
|
||||
memcpy(vertices.raw, vertexData->blob.data, count * format->stride);
|
||||
|
|
|
@ -11,76 +11,7 @@ int l_lovrModelDraw(lua_State* L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrModelGetAABB(lua_State* L) {
|
||||
Model* model = luax_checktype(L, 1, Model);
|
||||
const float* aabb = lovrModelGetAABB(model);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
lua_pushnumber(L, aabb[i]);
|
||||
}
|
||||
return 6;
|
||||
}
|
||||
|
||||
int l_lovrModelGetAnimator(lua_State* L) {
|
||||
Model* model = luax_checktype(L, 1, Model);
|
||||
Animator* animator = lovrModelGetAnimator(model);
|
||||
luax_pushobject(L, animator);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelSetAnimator(lua_State* L) {
|
||||
Model* model = luax_checktype(L, 1, Model);
|
||||
if (lua_isnil(L, 2)) {
|
||||
lovrModelSetAnimator(model, NULL);
|
||||
} else {
|
||||
Animator* animator = luax_checktype(L, 2, Animator);
|
||||
lovrModelSetAnimator(model, animator);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrModelGetAnimationCount(lua_State* L) {
|
||||
Model* model = luax_checktype(L, 1, Model);
|
||||
lua_pushinteger(L, lovrModelGetAnimationCount(model));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelGetMaterial(lua_State* L) {
|
||||
Model* model = luax_checktype(L, 1, Model);
|
||||
Material* material = lovrModelGetMaterial(model);
|
||||
if (material) {
|
||||
luax_pushobject(L, material);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelSetMaterial(lua_State* L) {
|
||||
Model* model = luax_checktype(L, 1, Model);
|
||||
if (lua_isnoneornil(L, 2)) {
|
||||
lovrModelSetMaterial(model, NULL);
|
||||
} else {
|
||||
Material* material = luax_checktype(L, 2, Material);
|
||||
lovrModelSetMaterial(model, material);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrModelGetMesh(lua_State* L) {
|
||||
Model* model = luax_checktype(L, 1, Model);
|
||||
Mesh* mesh = lovrModelGetMesh(model);
|
||||
luax_pushobject(L, mesh);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg lovrModel[] = {
|
||||
{ "draw", l_lovrModelDraw },
|
||||
{ "getAABB", l_lovrModelGetAABB },
|
||||
{ "getAnimator", l_lovrModelGetAnimator },
|
||||
{ "setAnimator", l_lovrModelSetAnimator },
|
||||
{ "getAnimationCount", l_lovrModelGetAnimationCount },
|
||||
{ "getMaterial", l_lovrModelGetMaterial },
|
||||
{ "setMaterial", l_lovrModelSetMaterial },
|
||||
{ "getMesh", l_lovrModelGetMesh },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
|
|
@ -1,260 +1,6 @@
|
|||
#include "api.h"
|
||||
#include "data/modelData.h"
|
||||
#include "lib/math.h"
|
||||
|
||||
int l_lovrModelDataGetVertexData(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
luax_pushobject(L, modelData->vertexData);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetTriangleCount(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
lua_pushinteger(L, modelData->indexCount / 3);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetTriangle(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
int index = luaL_checkinteger(L, 2);
|
||||
if (modelData->indexSize == sizeof(uint16_t)) {
|
||||
lua_pushinteger(L, modelData->indices.shorts[index]);
|
||||
lua_pushinteger(L, modelData->indices.shorts[index]);
|
||||
lua_pushinteger(L, modelData->indices.shorts[index]);
|
||||
} else {
|
||||
lua_pushinteger(L, modelData->indices.ints[index]);
|
||||
lua_pushinteger(L, modelData->indices.ints[index]);
|
||||
lua_pushinteger(L, modelData->indices.ints[index]);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetNodeCount(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
lua_pushinteger(L, modelData->nodeCount);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetNodeName(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
int index = luaL_checkint(L, 2) - 1;
|
||||
lovrAssert(index >= 0 && index < modelData->nodeCount, "Invalid node index: %d", index);
|
||||
ModelNode* node = &modelData->nodes[index];
|
||||
lua_pushstring(L, node->name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luax_writenodetransform(lua_State* L, mat4 m, int transformIndex) {
|
||||
float x, y, z, sx, sy, sz, angle, ax, ay, az;
|
||||
mat4_getTransform(m, &x, &y, &z, &sx, &sy, &sz, &angle, &ax, &ay, &az);
|
||||
lua_pushnumber(L, x);
|
||||
lua_pushnumber(L, y);
|
||||
lua_pushnumber(L, z);
|
||||
lua_pushnumber(L, sx);
|
||||
lua_pushnumber(L, sy);
|
||||
lua_pushnumber(L, sz);
|
||||
lua_pushnumber(L, angle);
|
||||
lua_pushnumber(L, ax);
|
||||
lua_pushnumber(L, ay);
|
||||
lua_pushnumber(L, az);
|
||||
return 10;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetLocalNodeTransform(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
int index = luaL_checkint(L, 2) - 1;
|
||||
lovrAssert(index >= 0 && index < modelData->nodeCount, "Invalid node index: %d", index);
|
||||
ModelNode* node = &modelData->nodes[index];
|
||||
return luax_writenodetransform(L, node->transform, 3);
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetGlobalNodeTransform(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
int index = luaL_checkint(L, 2) - 1;
|
||||
lovrAssert(index >= 0 && index < modelData->nodeCount, "Invalid node index: %d", index);
|
||||
ModelNode* node = &modelData->nodes[index];
|
||||
return luax_writenodetransform(L, node->globalTransform, 3);
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetNodeParent(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
int index = luaL_checkint(L, 2) - 1;
|
||||
lovrAssert(index >= 0 && index < modelData->nodeCount, "Invalid node index: %d", index);
|
||||
ModelNode* node = &modelData->nodes[index];
|
||||
if (node->parent == -1) {
|
||||
lua_pushnil(L);
|
||||
} else {
|
||||
lua_pushinteger(L, node->parent);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetNodeChildren(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
int index = luaL_checkint(L, 2) - 1;
|
||||
lovrAssert(index >= 0 && index < modelData->nodeCount, "Invalid node index: %d", index);
|
||||
ModelNode* node = &modelData->nodes[index];
|
||||
|
||||
if (lua_istable(L, 3)) {
|
||||
lua_settop(L, 3);
|
||||
} else {
|
||||
lua_settop(L, 2);
|
||||
lua_createtable(L, node->children.length, 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < node->children.length; i++) {
|
||||
lua_pushinteger(L, node->children.data[i]);
|
||||
lua_rawseti(L, 3, i + 1);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetNodeComponentCount(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
int index = luaL_checkint(L, 2) - 1;
|
||||
lovrAssert(index >= 0 && index < modelData->nodeCount, "Invalid node index: %d", index);
|
||||
ModelNode* node = &modelData->nodes[index];
|
||||
lua_pushinteger(L, node->primitives.length);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetNodeComponent(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
int nodeIndex = luaL_checkint(L, 2) - 1;
|
||||
int primitiveIndex = luaL_checkint(L, 3) - 1;
|
||||
lovrAssert(nodeIndex >= 0 && nodeIndex < modelData->nodeCount, "Invalid node index: %d", nodeIndex + 1);
|
||||
ModelNode* node = &modelData->nodes[nodeIndex];
|
||||
lovrAssert(primitiveIndex >= 0 && primitiveIndex < node->primitives.length, "Invalid component index: %d", primitiveIndex + 1);
|
||||
ModelPrimitive* primitive = &modelData->primitives[node->primitives.data[primitiveIndex]];
|
||||
lua_pushinteger(L, primitive->drawStart);
|
||||
lua_pushinteger(L, primitive->drawCount);
|
||||
lua_pushinteger(L, primitive->material);
|
||||
return 3;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetAnimationCount(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
lua_pushinteger(L, modelData->animationCount);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetMaterialCount(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
lua_pushinteger(L, modelData->materialCount);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static ModelMaterial* luax_checkmodelmaterial(lua_State* L, int index) {
|
||||
ModelData* modelData = luax_checktype(L, index, ModelData);
|
||||
int materialIndex = luaL_checkint(L, index + 1) - 1;
|
||||
lovrAssert(materialIndex >= 0 && materialIndex < modelData->materialCount, "Invalid material index: %d", materialIndex + 1);
|
||||
return &modelData->materials[materialIndex];
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetMetalness(lua_State* L) {
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
lua_pushnumber(L, material->metalness);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetRoughness(lua_State* L) {
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
lua_pushnumber(L, material->roughness);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetDiffuseColor(lua_State* L) {
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
Color color = material->diffuseColor;
|
||||
lua_pushnumber(L, color.r);
|
||||
lua_pushnumber(L, color.g);
|
||||
lua_pushnumber(L, color.b);
|
||||
lua_pushnumber(L, color.a);
|
||||
return 4;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetEmissiveColor(lua_State* L) {
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
Color color = material->emissiveColor;
|
||||
lua_pushnumber(L, color.r);
|
||||
lua_pushnumber(L, color.g);
|
||||
lua_pushnumber(L, color.b);
|
||||
lua_pushnumber(L, color.a);
|
||||
return 4;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetDiffuseTexture(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
TextureData* textureData = modelData->textures.data[material->diffuseTexture];
|
||||
luax_pushobject(L, textureData);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetEmissiveTexture(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
TextureData* textureData = modelData->textures.data[material->emissiveTexture];
|
||||
luax_pushobject(L, textureData);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetMetalnessTexture(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
TextureData* textureData = modelData->textures.data[material->metalnessTexture];
|
||||
luax_pushobject(L, textureData);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetRoughnessTexture(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
TextureData* textureData = modelData->textures.data[material->roughnessTexture];
|
||||
luax_pushobject(L, textureData);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetOcclusionTexture(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
TextureData* textureData = modelData->textures.data[material->occlusionTexture];
|
||||
luax_pushobject(L, textureData);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrModelDataGetNormalTexture(lua_State* L) {
|
||||
ModelData* modelData = luax_checktype(L, 1, ModelData);
|
||||
ModelMaterial* material = luax_checkmodelmaterial(L, 1);
|
||||
TextureData* textureData = modelData->textures.data[material->normalTexture];
|
||||
luax_pushobject(L, textureData);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg lovrModelData[] = {
|
||||
{ "getVertexData", l_lovrModelDataGetVertexData },
|
||||
{ "getTriangleCount", l_lovrModelDataGetTriangleCount },
|
||||
{ "getTriangle", l_lovrModelDataGetTriangle },
|
||||
{ "getNodeCount", l_lovrModelDataGetNodeCount },
|
||||
{ "getNodeName", l_lovrModelDataGetNodeName },
|
||||
{ "getLocalNodeTransform", l_lovrModelDataGetLocalNodeTransform },
|
||||
{ "getGlobalNodeTransform", l_lovrModelDataGetGlobalNodeTransform },
|
||||
{ "getNodeParent", l_lovrModelDataGetNodeParent },
|
||||
{ "getNodeChildren", l_lovrModelDataGetNodeChildren },
|
||||
{ "getNodeComponentCount", l_lovrModelDataGetNodeComponentCount },
|
||||
{ "getNodeComponent", l_lovrModelDataGetNodeComponent },
|
||||
{ "getAnimationCount", l_lovrModelDataGetAnimationCount },
|
||||
{ "getMaterialCount", l_lovrModelDataGetMaterialCount },
|
||||
{ "getMetalness", l_lovrModelDataGetMetalness },
|
||||
{ "getRoughness", l_lovrModelDataGetRoughness },
|
||||
{ "getDiffuseColor", l_lovrModelDataGetDiffuseColor },
|
||||
{ "getEmissiveColor", l_lovrModelDataGetEmissiveColor },
|
||||
{ "getDiffuseTexture", l_lovrModelDataGetDiffuseTexture },
|
||||
{ "getEmissiveTexture", l_lovrModelDataGetEmissiveTexture },
|
||||
{ "getMetalnessTexture", l_lovrModelDataGetMetalnessTexture },
|
||||
{ "getRoughnessTexture", l_lovrModelDataGetRoughnessTexture },
|
||||
{ "getOcclusionTexture", l_lovrModelDataGetOcclusionTexture },
|
||||
{ "getNormalTexture", l_lovrModelDataGetNormalTexture },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "api.h"
|
||||
#include "api/data.h"
|
||||
|
||||
int luax_loadvertices(lua_State* L, int index, VertexFormat* format, VertexPointer vertices) {
|
||||
int luax_loadvertices(lua_State* L, int index, VertexFormat* format, AttributePointer vertices) {
|
||||
uint32_t count = lua_objlen(L, index);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
|
@ -16,9 +16,13 @@ int luax_loadvertices(lua_State* L, int index, VertexFormat* format, VertexPoint
|
|||
for (int k = 0; k < attribute.count; k++) {
|
||||
lua_rawgeti(L, -1, ++component);
|
||||
switch (attribute.type) {
|
||||
case ATTR_FLOAT: *vertices.floats++ = luax_optfloat(L, -1, 0.f); break;
|
||||
case ATTR_BYTE: *vertices.bytes++ = luaL_optint(L, -1, 255); break;
|
||||
case ATTR_INT: *vertices.ints++ = luaL_optint(L, -1, 0); break;
|
||||
case I8: *vertices.i8++ = luaL_optinteger(L, -1, 0); break;
|
||||
case U8: *vertices.u8++ = luaL_optinteger(L, -1, 0); break;
|
||||
case I16: *vertices.i16++ = luaL_optinteger(L, -1, 0); break;
|
||||
case U16: *vertices.u16++ = luaL_optinteger(L, -1, 0); break;
|
||||
case I32: *vertices.i32++ = luaL_optinteger(L, -1, 0); break;
|
||||
case U32: *vertices.u32++ = luaL_optinteger(L, -1, 0); break;
|
||||
case F32: *vertices.u32++ = luaL_optnumber(L, -1, 0); break;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
@ -83,18 +87,22 @@ int luax_pushvertexformat(lua_State* L, VertexFormat* format) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
int luax_pushvertexattribute(lua_State* L, VertexPointer* vertex, Attribute attribute) {
|
||||
int luax_pushvertexattribute(lua_State* L, AttributePointer* vertex, Attribute attribute) {
|
||||
for (int i = 0; i < attribute.count; i++) {
|
||||
switch (attribute.type) {
|
||||
case ATTR_FLOAT: lua_pushnumber(L, *vertex->floats++); break;
|
||||
case ATTR_BYTE: lua_pushnumber(L, *vertex->bytes++); break;
|
||||
case ATTR_INT: lua_pushinteger(L, *vertex->ints++); break;
|
||||
case I8: lua_pushinteger(L, *vertex->i8++); break;
|
||||
case U8: lua_pushinteger(L, *vertex->u8++); break;
|
||||
case I16: lua_pushinteger(L, *vertex->i16++); break;
|
||||
case U16: lua_pushinteger(L, *vertex->u16++); break;
|
||||
case I32: lua_pushinteger(L, *vertex->i32++); break;
|
||||
case U32: lua_pushinteger(L, *vertex->u32++); break;
|
||||
case F32: lua_pushnumber(L, *vertex->f32++); break;
|
||||
}
|
||||
}
|
||||
return attribute.count;
|
||||
}
|
||||
|
||||
int luax_pushvertex(lua_State* L, VertexPointer* vertex, VertexFormat* format) {
|
||||
int luax_pushvertex(lua_State* L, AttributePointer* vertex, VertexFormat* format) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < format->count; i++) {
|
||||
count += luax_pushvertexattribute(L, vertex, format->attributes[i]);
|
||||
|
@ -102,17 +110,21 @@ int luax_pushvertex(lua_State* L, VertexPointer* vertex, VertexFormat* format) {
|
|||
return count;
|
||||
}
|
||||
|
||||
void luax_setvertexattribute(lua_State* L, int index, VertexPointer* vertex, Attribute attribute) {
|
||||
void luax_setvertexattribute(lua_State* L, int index, AttributePointer* vertex, Attribute attribute) {
|
||||
for (int i = 0; i < attribute.count; i++) {
|
||||
switch (attribute.type) {
|
||||
case ATTR_FLOAT: *vertex->floats++ = luax_optfloat(L, index++, 0.f); break;
|
||||
case ATTR_BYTE: *vertex->bytes++ = luaL_optint(L, index++, 255); break;
|
||||
case ATTR_INT: *vertex->ints++ = luaL_optint(L, index++, 0); break;
|
||||
case I8: *vertex->i8++ = luaL_optinteger(L, index++, 0); break;
|
||||
case U8: *vertex->u8++ = luaL_optinteger(L, index++, 0); break;
|
||||
case I16: *vertex->i16++ = luaL_optinteger(L, index++, 0); break;
|
||||
case U16: *vertex->u16++ = luaL_optinteger(L, index++, 0); break;
|
||||
case I32: *vertex->i32++ = luaL_optinteger(L, index++, 0); break;
|
||||
case U32: *vertex->u32++ = luaL_optinteger(L, index++, 0); break;
|
||||
case F32: *vertex->f32++ = luaL_optnumber(L, index++, 0.); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void luax_setvertex(lua_State* L, int index, VertexPointer* vertex, VertexFormat* format) {
|
||||
void luax_setvertex(lua_State* L, int index, AttributePointer* vertex, VertexFormat* format) {
|
||||
if (lua_istable(L, index)) {
|
||||
int component = 0;
|
||||
for (int i = 0; i < format->count; i++) {
|
||||
|
@ -120,9 +132,13 @@ void luax_setvertex(lua_State* L, int index, VertexPointer* vertex, VertexFormat
|
|||
for (int j = 0; j < attribute.count; j++) {
|
||||
lua_rawgeti(L, index, ++component);
|
||||
switch (attribute.type) {
|
||||
case ATTR_FLOAT: *vertex->floats++ = luax_optfloat(L, -1, 0.f); break;
|
||||
case ATTR_BYTE: *vertex->bytes++ = luaL_optint(L, -1, 255); break;
|
||||
case ATTR_INT: *vertex->ints++ = luaL_optint(L, -1, 0); break;
|
||||
case I8: *vertex->i8++ = luaL_optinteger(L, -1, 0); break;
|
||||
case U8: *vertex->u8++ = luaL_optinteger(L, -1, 0); break;
|
||||
case I16: *vertex->i16++ = luaL_optinteger(L, -1, 0); break;
|
||||
case U16: *vertex->u16++ = luaL_optinteger(L, -1, 0); break;
|
||||
case I32: *vertex->i32++ = luaL_optinteger(L, -1, 0); break;
|
||||
case U32: *vertex->u32++ = luaL_optinteger(L, -1, 0); break;
|
||||
case F32: *vertex->f32++ = luaL_optnumber(L, -1, 0.); break;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
@ -152,7 +168,7 @@ int l_lovrVertexDataGetFormat(lua_State* L) {
|
|||
int l_lovrVertexDataGetVertex(lua_State* L) {
|
||||
VertexData* vertexData = luax_checktype(L, 1, VertexData);
|
||||
uint32_t index = (uint32_t) luaL_checkint(L, 2) - 1;
|
||||
VertexPointer vertex = { .raw = (uint8_t*) vertexData->blob.data + index * vertexData->format.stride };
|
||||
AttributePointer vertex = { .raw = (uint8_t*) vertexData->blob.data + index * vertexData->format.stride };
|
||||
return luax_pushvertex(L, &vertex, &vertexData->format);
|
||||
}
|
||||
|
||||
|
@ -161,8 +177,8 @@ int l_lovrVertexDataSetVertex(lua_State* L) {
|
|||
uint32_t index = (uint32_t) luaL_checkint(L, 2) - 1;
|
||||
lovrAssert(index < vertexData->count, "Invalid vertex index: %d", index + 1);
|
||||
VertexFormat* format = &vertexData->format;
|
||||
VertexPointer vertex = { .raw = vertexData->blob.data };
|
||||
vertex.bytes += index * format->stride;
|
||||
AttributePointer vertex = { .raw = vertexData->blob.data };
|
||||
vertex.u8 += index * format->stride;
|
||||
luax_setvertex(L, 3, &vertex, format);
|
||||
return 0;
|
||||
}
|
||||
|
@ -175,8 +191,8 @@ int l_lovrVertexDataGetVertexAttribute(lua_State* L) {
|
|||
lovrAssert(vertexIndex < vertexData->count, "Invalid vertex index: %d", vertexIndex + 1);
|
||||
lovrAssert(attributeIndex >= 0 && attributeIndex < format->count, "Invalid attribute index: %d", attributeIndex + 1);
|
||||
Attribute attribute = format->attributes[attributeIndex];
|
||||
VertexPointer vertex = { .raw = vertexData->blob.data };
|
||||
vertex.bytes += vertexIndex * format->stride + attribute.offset;
|
||||
AttributePointer vertex = { .raw = vertexData->blob.data };
|
||||
vertex.u8 += vertexIndex * format->stride + attribute.offset;
|
||||
return luax_pushvertexattribute(L, &vertex, attribute);
|
||||
}
|
||||
|
||||
|
@ -188,8 +204,8 @@ int l_lovrVertexDataSetVertexAttribute(lua_State* L) {
|
|||
lovrAssert(vertexIndex < vertexData->count, "Invalid vertex index: %d", vertexIndex + 1);
|
||||
lovrAssert(attributeIndex >= 0 && attributeIndex < format->count, "Invalid attribute index: %d", attributeIndex + 1);
|
||||
Attribute attribute = format->attributes[attributeIndex];
|
||||
VertexPointer vertex = { .raw = vertexData->blob.data };
|
||||
vertex.bytes += vertexIndex * format->stride + attribute.offset;
|
||||
AttributePointer vertex = { .raw = vertexData->blob.data };
|
||||
vertex.u8 += vertexIndex * format->stride + attribute.offset;
|
||||
luax_setvertexattribute(L, 4, &vertex, attribute);
|
||||
return 0;
|
||||
}
|
||||
|
@ -201,8 +217,8 @@ int l_lovrVertexDataSetVertices(lua_State* L) {
|
|||
uint32_t vertexCount = lua_objlen(L, 2);
|
||||
int start = luaL_optinteger(L, 3, 1) - 1;
|
||||
lovrAssert(start + vertexCount <= vertexData->count, "VertexData can only hold %d vertices", vertexData->count);
|
||||
VertexPointer vertices = { .raw = vertexData->blob.data };
|
||||
vertices.bytes += start * format->stride;
|
||||
AttributePointer vertices = { .raw = vertexData->blob.data };
|
||||
vertices.u8 += start * format->stride;
|
||||
|
||||
for (uint32_t i = 0; i < vertexCount; i++) {
|
||||
lua_rawgeti(L, 2, i + 1);
|
||||
|
|
|
@ -1,600 +1,432 @@
|
|||
#include "data/modelData.h"
|
||||
#include "filesystem/filesystem.h"
|
||||
#include "filesystem/file.h"
|
||||
#include "lib/math.h"
|
||||
#include <float.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "lib/jsmn/jsmn.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef LOVR_USE_ASSIMP
|
||||
#include <assimp/cfileio.h>
|
||||
#include <assimp/cimport.h>
|
||||
#include <assimp/config.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/mesh.h>
|
||||
#include <assimp/matrix4x4.h>
|
||||
#include <assimp/vector3.h>
|
||||
#include <assimp/postprocess.h>
|
||||
// Notes:
|
||||
// - We parse in two passes.
|
||||
// - In the first pass we figure out how much memory we need to allocate.
|
||||
// - Then we allocate the memory and do a second pass to fill everything in.
|
||||
// - Plan is to make this parser destructive, so it mutates the input Blob in order to avoid doing
|
||||
// work in some situations, for speed. May make sense to provide a non-destructive option.
|
||||
// - Currently this is most useful for reusing string memory by changing quotes to \0's.
|
||||
// - Caveats:
|
||||
// - The IO callback must not hang onto the filenames that are passed into it.
|
||||
|
||||
static void normalizePath(char* path, char* dst, size_t size) {
|
||||
char* slash = path;
|
||||
while ((slash = strchr(path, '\\')) != NULL) { *slash++ = '/'; }
|
||||
#define MAGIC_glTF 0x46546c67
|
||||
#define MAGIC_JSON 0x4e4f534a
|
||||
#define MAGIC_BIN 0x004e4942
|
||||
|
||||
if (path[0] == '/') {
|
||||
strncpy(dst, path, size);
|
||||
return;
|
||||
#define KEY_EQ(k, s) !strncmp(k.data, s, k.length)
|
||||
#define TOK_INT(j, t) strtol(j + t->start, NULL, 10)
|
||||
#define TOK_BOOL(j, t) (*(j + t->start) == 't')
|
||||
#define TOK_FLOAT(j, t) strtof(j + t->start, NULL)
|
||||
|
||||
typedef struct {
|
||||
struct { int count; jsmntok_t* token; } accessors;
|
||||
struct { int count; jsmntok_t* token; } blobs;
|
||||
struct { int count; jsmntok_t* token; } views;
|
||||
struct { int count; jsmntok_t* token; } nodes;
|
||||
struct { int count; jsmntok_t* token; } meshes;
|
||||
int childCount;
|
||||
int primitiveCount;
|
||||
} gltfInfo;
|
||||
|
||||
static int nomString(const char* data, jsmntok_t* token, gltfString* string) {
|
||||
lovrAssert(token->type == JSMN_STRING, "Expected string");
|
||||
string->data = (char*) data + token->start;
|
||||
string->length = token->end - token->start;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nomValue(const char* data, jsmntok_t* token, int count, int sum) {
|
||||
if (count == 0) { return sum; }
|
||||
switch (token->type) {
|
||||
case JSMN_OBJECT: return nomValue(data, token + 1, count - 1 + 2 * token->size, sum + 1);
|
||||
case JSMN_ARRAY: return nomValue(data, token + 1, count - 1 + token->size, sum + 1);
|
||||
default: return nomValue(data, token + 1, count - 1, sum + 1);
|
||||
}
|
||||
}
|
||||
|
||||
memset(dst, 0, size);
|
||||
|
||||
while (*path != '\0') {
|
||||
if (*path == '/') {
|
||||
path++;
|
||||
continue;
|
||||
}
|
||||
if (*path == '.') {
|
||||
if (path[1] == '\0' || path[1] == '/') {
|
||||
path++;
|
||||
continue;
|
||||
}
|
||||
if (path[1] == '.' && (path[2] == '\0' || path[2] == '/')) {
|
||||
path += 2;
|
||||
while ((--dst)[-1] != '/');
|
||||
continue;
|
||||
// Kinda like sum(map(arr, obj => #obj[key]))
|
||||
static jsmntok_t* aggregate(const char* json, jsmntok_t* token, const char* target, int* total) {
|
||||
*total = 0;
|
||||
int size = (token++)->size;
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (token->size > 0) {
|
||||
int keys = (token++)->size;
|
||||
for (int k = 0; k < keys; k++) {
|
||||
gltfString key;
|
||||
token += nomString(json, token, &key);
|
||||
if (KEY_EQ(key, target)) {
|
||||
*total += token->size;
|
||||
}
|
||||
token += nomValue(json, token, 1, 0);
|
||||
}
|
||||
}
|
||||
while (*path != '\0' && *path != '/') {
|
||||
*dst++ = *path++;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
static void preparse(const char* json, jsmntok_t* tokens, int tokenCount, gltfInfo* info, size_t* dataSize) {
|
||||
for (jsmntok_t* token = tokens + 1; token < tokens + tokenCount;) { // +1 to skip root object
|
||||
gltfString key;
|
||||
token += nomString(json, token, &key);
|
||||
|
||||
if (KEY_EQ(key, "accessors")) {
|
||||
info->accessors.token = token;
|
||||
info->accessors.count = token->size;
|
||||
*dataSize += info->accessors.count * sizeof(ModelAccessor);
|
||||
token += nomValue(json, token, 1, 0);
|
||||
} else if (KEY_EQ(key, "buffers")) {
|
||||
info->blobs.token = token;
|
||||
info->blobs.count = token->size;
|
||||
*dataSize += info->blobs.count * sizeof(ModelBlob);
|
||||
token += nomValue(json, token, 1, 0);
|
||||
} else if (KEY_EQ(key, "bufferViews")) {
|
||||
info->views.token = token;
|
||||
info->views.count = token->size;
|
||||
*dataSize += info->views.count * sizeof(ModelView);
|
||||
token += nomValue(json, token, 1, 0);
|
||||
} else if (KEY_EQ(key, "nodes")) {
|
||||
info->nodes.token = token;
|
||||
info->nodes.count = token->size;
|
||||
*dataSize += info->nodes.count * sizeof(ModelNode);
|
||||
token = aggregate(json, token, "children", &info->childCount);
|
||||
} else if (KEY_EQ(key, "meshes")) {
|
||||
info->meshes.token = token;
|
||||
info->meshes.count = token->size;
|
||||
*dataSize += info->meshes.count * sizeof(ModelMesh);
|
||||
token = aggregate(json, token, "primitives", &info->primitiveCount);
|
||||
*dataSize += info->primitiveCount * sizeof(ModelPrimitive);
|
||||
} else {
|
||||
token += nomValue(json, token, 1, 0); // Skip
|
||||
}
|
||||
*dst++ = '/';
|
||||
}
|
||||
|
||||
*--dst = '\0';
|
||||
}
|
||||
|
||||
static void assimpSumChildren(struct aiNode* assimpNode, int* totalChildren) {
|
||||
(*totalChildren)++;
|
||||
for (unsigned int i = 0; i < assimpNode->mNumChildren; i++) {
|
||||
assimpSumChildren(assimpNode->mChildren[i], totalChildren);
|
||||
}
|
||||
}
|
||||
|
||||
static void assimpNodeTraversal(ModelData* modelData, struct aiNode* assimpNode, int* nodeId) {
|
||||
int currentIndex = *nodeId;
|
||||
ModelNode* node = &modelData->nodes[currentIndex];
|
||||
node->name = strdup(assimpNode->mName.data);
|
||||
map_set(&modelData->nodeMap, node->name, currentIndex);
|
||||
static void parseAccessors(const char* json, jsmntok_t* token, ModelData* model) {
|
||||
if (!token) return;
|
||||
|
||||
// Transform
|
||||
struct aiMatrix4x4 m = assimpNode->mTransformation;
|
||||
aiTransposeMatrix4(&m);
|
||||
mat4_set(node->transform, (float*) &m);
|
||||
if (node->parent == -1) {
|
||||
mat4_set(node->globalTransform, node->transform);
|
||||
} else {
|
||||
mat4_set(node->globalTransform, modelData->nodes[node->parent].globalTransform);
|
||||
mat4_multiply(node->globalTransform, node->transform);
|
||||
}
|
||||
int count = (token++)->size;
|
||||
for (int i = 0; i < count; i++) {
|
||||
ModelAccessor* accessor = &model->accessors[i];
|
||||
gltfString key;
|
||||
int keyCount = (token++)->size;
|
||||
|
||||
// Primitives
|
||||
vec_init(&node->primitives);
|
||||
vec_pusharr(&node->primitives, assimpNode->mMeshes, assimpNode->mNumMeshes);
|
||||
|
||||
// Children
|
||||
vec_init(&node->children);
|
||||
for (unsigned int n = 0; n < assimpNode->mNumChildren; n++) {
|
||||
(*nodeId)++;
|
||||
vec_push(&node->children, *nodeId);
|
||||
ModelNode* child = &modelData->nodes[*nodeId];
|
||||
child->parent = currentIndex;
|
||||
assimpNodeTraversal(modelData, assimpNode->mChildren[n], nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
static void aabbIterator(ModelData* modelData, ModelNode* node, float aabb[6]) {
|
||||
for (int i = 0; i < node->primitives.length; i++) {
|
||||
ModelPrimitive* primitive = &modelData->primitives[node->primitives.data[i]];
|
||||
for (int j = 0; j < primitive->drawCount; j++) {
|
||||
uint32_t index;
|
||||
if (modelData->indexSize == sizeof(uint16_t)) {
|
||||
index = modelData->indices.shorts[primitive->drawStart + j];
|
||||
for (int k = 0; k < keyCount; k++) {
|
||||
token += nomString(json, token, &key);
|
||||
if (KEY_EQ(key, "bufferView")) {
|
||||
accessor->view = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "count")) {
|
||||
accessor->count = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "byteOffset")) {
|
||||
accessor->offset = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "componentType")) {
|
||||
switch (TOK_INT(json, token)) {
|
||||
case 5120: accessor->type = I8; break;
|
||||
case 5121: accessor->type = U8; break;
|
||||
case 5122: accessor->type = I16; break;
|
||||
case 5123: accessor->type = U16; break;
|
||||
case 5125: accessor->type = U32; break;
|
||||
case 5126: accessor->type = F32; break;
|
||||
default: break;
|
||||
}
|
||||
token++;
|
||||
} else if (KEY_EQ(key, "type")) {
|
||||
gltfString type;
|
||||
token += nomString(json, token, &type);
|
||||
if (KEY_EQ(type, "SCALAR")) {
|
||||
accessor->components = 1;
|
||||
} else if (type.length == 4 && type.data[0] == 'V') {
|
||||
accessor->components = type.data[3] - '0';
|
||||
} else if (type.length == 4 && type.data[0] == 'M') {
|
||||
lovrThrow("Matrix accessors are not supported");
|
||||
} else {
|
||||
lovrThrow("Unknown attribute type");
|
||||
}
|
||||
} else if (KEY_EQ(key, "normalized")) {
|
||||
accessor->normalized = TOK_BOOL(json, token), token++;
|
||||
} else {
|
||||
index = modelData->indices.ints[primitive->drawStart + j];
|
||||
token += nomValue(json, token, 1, 0); // Skip
|
||||
}
|
||||
float vertex[3];
|
||||
VertexPointer vertices = { .raw = modelData->vertexData->blob.data };
|
||||
vec3_init(vertex, (float*) (vertices.bytes + index * modelData->vertexData->format.stride));
|
||||
mat4_transform(node->globalTransform, &vertex[0], &vertex[1], &vertex[2]);
|
||||
aabb[0] = MIN(aabb[0], vertex[0]);
|
||||
aabb[1] = MAX(aabb[1], vertex[0]);
|
||||
aabb[2] = MIN(aabb[2], vertex[1]);
|
||||
aabb[3] = MAX(aabb[3], vertex[1]);
|
||||
aabb[4] = MIN(aabb[4], vertex[2]);
|
||||
aabb[5] = MAX(aabb[5], vertex[2]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < node->children.length; i++) {
|
||||
ModelNode* child = &modelData->nodes[node->children.data[i]];
|
||||
aabbIterator(modelData, child, aabb);
|
||||
}
|
||||
}
|
||||
|
||||
static float readMaterialScalar(struct aiMaterial* assimpMaterial, const char* key, unsigned int type, unsigned int index) {
|
||||
float scalar;
|
||||
if (aiGetMaterialFloatArray(assimpMaterial, key, type, index, &scalar, NULL) == aiReturn_SUCCESS) {
|
||||
return scalar;
|
||||
} else {
|
||||
return 1.f;
|
||||
}
|
||||
}
|
||||
static void parseBlobs(const char* json, jsmntok_t* token, ModelData* model, ModelDataIO io, void* binData) {
|
||||
if (!token) return;
|
||||
|
||||
static Color readMaterialColor(struct aiMaterial* assimpMaterial, const char* key, unsigned int type, unsigned int index, Color fallback) {
|
||||
struct aiColor4D assimpColor;
|
||||
if (aiGetMaterialColor(assimpMaterial, key, type, index, &assimpColor) == aiReturn_SUCCESS) {
|
||||
return (Color) { .r = assimpColor.r, .g = assimpColor.g, .b = assimpColor.b, .a = assimpColor.a };
|
||||
} else {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
int count = (token++)->size;
|
||||
for (int i = 0; i < count; i++) {
|
||||
ModelBlob* blob = &model->blobs[i];
|
||||
gltfString key;
|
||||
int keyCount = (token++)->size;
|
||||
size_t bytesRead = 0;
|
||||
bool hasUri = false;
|
||||
|
||||
static int readMaterialTexture(struct aiMaterial* assimpMaterial, enum aiTextureType type, ModelData* modelData, map_int_t* textureCache, const char* dirname) {
|
||||
struct aiString str;
|
||||
|
||||
if (aiGetMaterialTexture(assimpMaterial, type, 0, &str, NULL, NULL, NULL, NULL, NULL, NULL) != aiReturn_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* path = str.data;
|
||||
|
||||
int* cachedTexture = map_get(textureCache, path);
|
||||
if (cachedTexture) {
|
||||
return *cachedTexture;
|
||||
}
|
||||
|
||||
char fullPath[LOVR_PATH_MAX];
|
||||
char normalizedPath[LOVR_PATH_MAX];
|
||||
strncpy(fullPath, dirname, LOVR_PATH_MAX);
|
||||
char* lastSlash = strrchr(fullPath, '/');
|
||||
if (lastSlash) lastSlash[1] = '\0';
|
||||
else fullPath[0] = '\0';
|
||||
strncat(fullPath, path, LOVR_PATH_MAX - 1);
|
||||
normalizePath(fullPath, normalizedPath, LOVR_PATH_MAX);
|
||||
|
||||
size_t size;
|
||||
void* data = lovrFilesystemRead(normalizedPath, &size);
|
||||
if (!data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Blob* blob = lovrBlobCreate(data, size, path);
|
||||
TextureData* textureData = lovrTextureDataCreateFromBlob(blob, true);
|
||||
lovrRelease(blob);
|
||||
int textureIndex = modelData->textures.length;
|
||||
vec_push(&modelData->textures, textureData);
|
||||
map_set(textureCache, path, textureIndex);
|
||||
return textureIndex;
|
||||
}
|
||||
|
||||
// Blob IO (to avoid reading data twice)
|
||||
static size_t assimpBlobRead(struct aiFile* assimpFile, char* buffer, size_t size, size_t count) {
|
||||
Blob* blob = (Blob*) assimpFile->UserData;
|
||||
char* data = blob->data;
|
||||
size_t bytes = MIN(count * size * sizeof(char), blob->size - blob->seek);
|
||||
memcpy(buffer, data + blob->seek, bytes);
|
||||
blob->seek += bytes;
|
||||
return bytes / size;
|
||||
}
|
||||
|
||||
static size_t assimpBlobGetSize(struct aiFile* assimpFile) {
|
||||
Blob* blob = (Blob*) assimpFile->UserData;
|
||||
return blob->size;
|
||||
}
|
||||
|
||||
static aiReturn assimpBlobSeek(struct aiFile* assimpFile, size_t position, enum aiOrigin origin) {
|
||||
Blob* blob = (Blob*) assimpFile->UserData;
|
||||
switch (origin) {
|
||||
case aiOrigin_SET: blob->seek = position; break;
|
||||
case aiOrigin_CUR: blob->seek += position; break;
|
||||
case aiOrigin_END: blob->seek = blob->size - position; break;
|
||||
default: return aiReturn_FAILURE;
|
||||
}
|
||||
return blob->seek < blob->size ? aiReturn_SUCCESS : aiReturn_FAILURE;
|
||||
}
|
||||
|
||||
static size_t assimpBlobTell(struct aiFile* assimpFile) {
|
||||
Blob* blob = (Blob*) assimpFile->UserData;
|
||||
return blob->seek;
|
||||
}
|
||||
|
||||
// File IO (for reading referenced materials/textures)
|
||||
static size_t assimpFileRead(struct aiFile* assimpFile, char* buffer, size_t size, size_t count) {
|
||||
File* file = (File*) assimpFile->UserData;
|
||||
unsigned long bytes = lovrFileRead(file, buffer, size * count);
|
||||
return bytes / size;
|
||||
}
|
||||
|
||||
static size_t assimpFileGetSize(struct aiFile* assimpFile) {
|
||||
File* file = (File*) assimpFile->UserData;
|
||||
return lovrFileGetSize(file);
|
||||
}
|
||||
|
||||
static aiReturn assimpFileSeek(struct aiFile* assimpFile, size_t position, enum aiOrigin origin) {
|
||||
File* file = (File*) assimpFile->UserData;
|
||||
return lovrFileSeek(file, position) ? aiReturn_FAILURE : aiReturn_SUCCESS;
|
||||
}
|
||||
|
||||
static size_t assimpFileTell(struct aiFile* assimpFile) {
|
||||
File* file = (File*) assimpFile->UserData;
|
||||
return lovrFileTell(file);
|
||||
}
|
||||
|
||||
static struct aiFile* assimpFileOpen(struct aiFileIO* io, const char* path, const char* mode) {
|
||||
struct aiFile* assimpFile = malloc(sizeof(struct aiFile));
|
||||
Blob* blob = (Blob*) io->UserData;
|
||||
if (!strcmp(blob->name, path)) {
|
||||
blob->seek = 0;
|
||||
assimpFile->ReadProc = assimpBlobRead;
|
||||
assimpFile->FileSizeProc = assimpBlobGetSize;
|
||||
assimpFile->SeekProc = assimpBlobSeek;
|
||||
assimpFile->TellProc = assimpBlobTell;
|
||||
assimpFile->UserData = (void*) blob;
|
||||
} else {
|
||||
char tempPath[LOVR_PATH_MAX];
|
||||
char normalizedPath[LOVR_PATH_MAX];
|
||||
strncpy(tempPath, path, LOVR_PATH_MAX);
|
||||
normalizePath(tempPath, normalizedPath, LOVR_PATH_MAX);
|
||||
|
||||
File* file = lovrFileCreate(normalizedPath);
|
||||
if (lovrFileOpen(file, OPEN_READ)) {
|
||||
lovrRelease(file);
|
||||
return NULL;
|
||||
for (int k = 0; k < keyCount; k++) {
|
||||
token += nomString(json, token, &key);
|
||||
if (KEY_EQ(key, "byteLength")) {
|
||||
blob->size = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "uri")) {
|
||||
hasUri = true;
|
||||
gltfString filename;
|
||||
token += nomString(json, token, &filename);
|
||||
filename.data[filename.length] = '\0'; // Change the quote into a terminator (I'll be b0k)
|
||||
blob->data = io.read(filename.data, &bytesRead);
|
||||
lovrAssert(blob->data, "Unable to read %s", filename.data);
|
||||
} else {
|
||||
token += nomValue(json, token, 1, 0); // Skip
|
||||
}
|
||||
}
|
||||
|
||||
assimpFile->ReadProc = assimpFileRead;
|
||||
assimpFile->FileSizeProc = assimpFileGetSize;
|
||||
assimpFile->SeekProc = assimpFileSeek;
|
||||
assimpFile->TellProc = assimpFileTell;
|
||||
assimpFile->UserData = (void*) file;
|
||||
if (hasUri) {
|
||||
lovrAssert(bytesRead == blob->size, "Couldn't read all of buffer data");
|
||||
} else {
|
||||
lovrAssert(binData && i == 0, "Buffer is missing URI");
|
||||
blob->data = binData;
|
||||
}
|
||||
}
|
||||
|
||||
return assimpFile;
|
||||
}
|
||||
|
||||
static void assimpFileClose(struct aiFileIO* io, struct aiFile* assimpFile) {
|
||||
void* blob = io->UserData;
|
||||
if (assimpFile->UserData != blob) {
|
||||
File* file = (File*) assimpFile->UserData;
|
||||
lovrFileClose(file);
|
||||
lovrRelease(file);
|
||||
static void parseViews(const char* json, jsmntok_t* token, ModelData* model) {
|
||||
if (!token) return;
|
||||
|
||||
int count = (token++)->size;
|
||||
for (int i = 0; i < count; i++) {
|
||||
ModelView* view = &model->views[i];
|
||||
gltfString key;
|
||||
int keyCount = (token++)->size;
|
||||
|
||||
for (int k = 0; k < keyCount; k++) {
|
||||
token += nomString(json, token, &key);
|
||||
if (KEY_EQ(key, "buffer")) {
|
||||
view->blob = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "byteOffset")) {
|
||||
view->offset = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "byteLength")) {
|
||||
view->length = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "byteStride")) {
|
||||
view->stride = TOK_INT(json, token), token++;
|
||||
} else {
|
||||
token += nomValue(json, token, 1, 0); // Skip
|
||||
}
|
||||
}
|
||||
}
|
||||
free(assimpFile);
|
||||
}
|
||||
|
||||
ModelData* lovrModelDataInit(ModelData* modelData, Blob* blob) {
|
||||
struct aiFileIO assimpIO;
|
||||
assimpIO.OpenProc = assimpFileOpen;
|
||||
assimpIO.CloseProc = assimpFileClose;
|
||||
assimpIO.UserData = (void*) blob;
|
||||
static void parseNodes(const char* json, jsmntok_t* token, ModelData* model) {
|
||||
if (!token) return;
|
||||
|
||||
struct aiPropertyStore* propertyStore = aiCreatePropertyStore();
|
||||
aiSetImportPropertyInteger(propertyStore, AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE);
|
||||
aiSetImportPropertyInteger(propertyStore, AI_CONFIG_PP_SBBC_MAX_BONES, 48);
|
||||
unsigned int flags = aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_OptimizeGraph | aiProcess_SplitByBoneCount;
|
||||
const struct aiScene* scene = aiImportFileExWithProperties(blob->name, flags, &assimpIO, propertyStore);
|
||||
aiReleasePropertyStore(propertyStore);
|
||||
lovrAssert(scene, "Unable to load model from '%s': %s", blob->name, aiGetErrorString());
|
||||
int childIndex = 0;
|
||||
int count = (token++)->size; // Enter array
|
||||
for (int i = 0; i < count; i++) {
|
||||
ModelNode* node = &model->nodes[i];
|
||||
float translation[3] = { 0, 0, 0 };
|
||||
float rotation[4] = { 0, 0, 0, 0 };
|
||||
float scale[3] = { 1, 1, 1 };
|
||||
bool matrix = false;
|
||||
|
||||
uint32_t vertexCount = 0;
|
||||
bool hasNormals = false;
|
||||
bool hasUVs = false;
|
||||
bool hasVertexColors = false;
|
||||
bool hasTangents = false;
|
||||
bool isSkinned = false;
|
||||
gltfString key;
|
||||
int keyCount = (token++)->size; // Enter object
|
||||
for (int k = 0; k < keyCount; k++) {
|
||||
token += nomString(json, token, &key);
|
||||
|
||||
for (unsigned int m = 0; m < scene->mNumMeshes; m++) {
|
||||
struct aiMesh* assimpMesh = scene->mMeshes[m];
|
||||
vertexCount += assimpMesh->mNumVertices;
|
||||
modelData->indexCount += assimpMesh->mNumFaces * 3;
|
||||
hasNormals |= assimpMesh->mNormals != NULL;
|
||||
hasUVs |= assimpMesh->mTextureCoords[0] != NULL;
|
||||
hasVertexColors |= assimpMesh->mColors[0] != NULL;
|
||||
hasTangents |= assimpMesh->mTangents != NULL;
|
||||
isSkinned |= assimpMesh->mNumBones > 0;
|
||||
if (KEY_EQ(key, "children")) {
|
||||
node->children = &model->childMap[childIndex];
|
||||
node->childCount = (token++)->size;
|
||||
for (uint32_t j = 0; j < node->childCount; j++) {
|
||||
model->childMap[childIndex++] = TOK_INT(json, token), token++;
|
||||
}
|
||||
} else if (KEY_EQ(key, "mesh")) {
|
||||
node->mesh = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "matrix")) {
|
||||
lovrAssert(token->size == 16, "Node matrix needs 16 elements");
|
||||
matrix = true;
|
||||
for (int j = 0; j < token->size; j++) {
|
||||
node->transform[j] = TOK_FLOAT(json, token), token++;
|
||||
}
|
||||
} else if (KEY_EQ(key, "translation")) {
|
||||
lovrAssert(token->size == 3, "Node translation needs 3 elements");
|
||||
translation[0] = TOK_FLOAT(json, token), token++;
|
||||
translation[1] = TOK_FLOAT(json, token), token++;
|
||||
translation[2] = TOK_FLOAT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "rotation")) {
|
||||
lovrAssert(token->size == 4, "Node rotation needs 4 elements");
|
||||
rotation[0] = TOK_FLOAT(json, token), token++;
|
||||
rotation[1] = TOK_FLOAT(json, token), token++;
|
||||
rotation[2] = TOK_FLOAT(json, token), token++;
|
||||
rotation[3] = TOK_FLOAT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "scale")) {
|
||||
lovrAssert(token->size == 3, "Node scale needs 3 elements");
|
||||
scale[0] = TOK_FLOAT(json, token), token++;
|
||||
scale[1] = TOK_FLOAT(json, token), token++;
|
||||
scale[2] = TOK_FLOAT(json, token), token++;
|
||||
} else {
|
||||
token += nomValue(json, token, 1, 0); // Skip
|
||||
}
|
||||
}
|
||||
|
||||
// Fix it in post
|
||||
if (!matrix) {
|
||||
mat4_identity(node->transform);
|
||||
mat4_translate(node->transform, translation[0], translation[1], translation[2]);
|
||||
mat4_rotateQuat(node->transform, rotation);
|
||||
mat4_scale(node->transform, scale[0], scale[1], scale[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VertexFormat format;
|
||||
vertexFormatInit(&format);
|
||||
vertexFormatAppend(&format, "lovrPosition", ATTR_FLOAT, 3);
|
||||
static jsmntok_t* parsePrimitive(const char* json, jsmntok_t* token, int index, ModelData* model) {
|
||||
gltfString key;
|
||||
ModelPrimitive* primitive = &model->primitives[index];
|
||||
int keyCount = (token++)->size; // Enter object
|
||||
memset(primitive->attributes, 0xff, sizeof(primitive->attributes));
|
||||
primitive->indices = -1;
|
||||
primitive->mode = DRAW_TRIANGLES;
|
||||
|
||||
if (hasNormals) vertexFormatAppend(&format, "lovrNormal", ATTR_FLOAT, 3);
|
||||
if (hasUVs) vertexFormatAppend(&format, "lovrTexCoord", ATTR_FLOAT, 2);
|
||||
if (hasVertexColors) vertexFormatAppend(&format, "lovrVertexColor", ATTR_BYTE, 4);
|
||||
if (hasTangents) vertexFormatAppend(&format, "lovrTangent", ATTR_FLOAT, 3);
|
||||
size_t boneByteOffset = format.stride;
|
||||
if (isSkinned) vertexFormatAppend(&format, "lovrBones", ATTR_INT, 4);
|
||||
if (isSkinned) vertexFormatAppend(&format, "lovrBoneWeights", ATTR_FLOAT, 4);
|
||||
for (int k = 0; k < keyCount; k++) {
|
||||
token += nomString(json, token, &key);
|
||||
|
||||
// Allocate
|
||||
modelData->vertexData = lovrVertexDataCreate(vertexCount, &format);
|
||||
modelData->indexSize = vertexCount > USHRT_MAX ? sizeof(uint32_t) : sizeof(uint16_t);
|
||||
modelData->indices.raw = malloc(modelData->indexCount * modelData->indexSize);
|
||||
modelData->primitiveCount = scene->mNumMeshes;
|
||||
modelData->primitives = malloc(modelData->primitiveCount * sizeof(ModelPrimitive));
|
||||
if (KEY_EQ(key, "material")) {
|
||||
primitive->material = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "indices")) {
|
||||
primitive->indices = TOK_INT(json, token), token++;
|
||||
} else if (KEY_EQ(key, "mode")) {
|
||||
switch (TOK_INT(json, token)) {
|
||||
case 0: primitive->mode = DRAW_POINTS; break;
|
||||
case 1: primitive->mode = DRAW_LINES; break;
|
||||
case 2: primitive->mode = DRAW_LINE_LOOP; break;
|
||||
case 3: primitive->mode = DRAW_LINE_STRIP; break;
|
||||
case 4: primitive->mode = DRAW_TRIANGLES; break;
|
||||
case 5: primitive->mode = DRAW_TRIANGLE_STRIP; break;
|
||||
case 6: primitive->mode = DRAW_TRIANGLE_FAN; break;
|
||||
default: lovrThrow("Unknown primitive mode");
|
||||
}
|
||||
token++;
|
||||
} else if (KEY_EQ(key, "attributes")) {
|
||||
int attributeCount = (token++)->size;
|
||||
for (int i = 0; i < attributeCount; i++) {
|
||||
gltfString name;
|
||||
token += nomString(json, token, &name);
|
||||
int accessor = TOK_INT(json, token);
|
||||
if (KEY_EQ(name, "POSITION")) {
|
||||
primitive->attributes[ATTR_POSITION] = accessor;
|
||||
} else if (KEY_EQ(name, "NORMAL")) {
|
||||
primitive->attributes[ATTR_NORMAL] = accessor;
|
||||
} else if (KEY_EQ(name, "TEXCOORD_0")) {
|
||||
primitive->attributes[ATTR_TEXCOORD] = accessor;
|
||||
} else if (KEY_EQ(name, "COLOR_0")) {
|
||||
primitive->attributes[ATTR_COLOR] = accessor;
|
||||
} else if (KEY_EQ(name, "TANGENT")) {
|
||||
primitive->attributes[ATTR_TANGENT] = accessor;
|
||||
} else if (KEY_EQ(name, "JOINTS_0")) {
|
||||
primitive->attributes[ATTR_BONES] = accessor;
|
||||
} else if (KEY_EQ(name, "WEIGHTS_0")) {
|
||||
primitive->attributes[ATTR_WEIGHTS] = accessor;
|
||||
}
|
||||
token++;
|
||||
}
|
||||
} else {
|
||||
token += nomValue(json, token, 1, 0); // Skip
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
// Load vertices
|
||||
IndexPointer indices = modelData->indices;
|
||||
uint32_t vertex = 0;
|
||||
uint32_t index = 0;
|
||||
for (unsigned int m = 0; m < scene->mNumMeshes; m++) {
|
||||
struct aiMesh* assimpMesh = scene->mMeshes[m];
|
||||
ModelPrimitive* primitive = &modelData->primitives[m];
|
||||
primitive->material = assimpMesh->mMaterialIndex;
|
||||
primitive->drawStart = index;
|
||||
primitive->drawCount = 0;
|
||||
uint32_t baseVertex = vertex;
|
||||
static void parseMeshes(const char* json, jsmntok_t* token, ModelData* model) {
|
||||
if (!token) return;
|
||||
|
||||
// Indices
|
||||
for (unsigned int f = 0; f < assimpMesh->mNumFaces; f++) {
|
||||
struct aiFace assimpFace = assimpMesh->mFaces[f];
|
||||
lovrAssert(assimpFace.mNumIndices == 3, "Only triangular faces are supported");
|
||||
int primitiveIndex = 0;
|
||||
int count = (token++)->size; // Enter array
|
||||
for (int i = 0; i < count; i++) {
|
||||
gltfString key;
|
||||
ModelMesh* mesh = &model->meshes[i];
|
||||
int keyCount = (token++)->size; // Enter object
|
||||
for (int k = 0; k < keyCount; k++) {
|
||||
token += nomString(json, token, &key);
|
||||
|
||||
primitive->drawCount += assimpFace.mNumIndices;
|
||||
|
||||
if (modelData->indexSize == sizeof(uint16_t)) {
|
||||
for (unsigned int i = 0; i < assimpFace.mNumIndices; i++) {
|
||||
indices.shorts[index++] = baseVertex + assimpFace.mIndices[i];
|
||||
if (KEY_EQ(key, "primitives")) {
|
||||
mesh->primitives = &model->primitives[primitiveIndex];
|
||||
mesh->primitiveCount = (token++)->size;
|
||||
for (uint32_t j = 0; j < mesh->primitiveCount; j++) {
|
||||
token = parsePrimitive(json, token, primitiveIndex++, model);
|
||||
}
|
||||
} else {
|
||||
for (unsigned int i = 0; i < assimpFace.mNumIndices; i++) {
|
||||
indices.ints[index++] = baseVertex + assimpFace.mIndices[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertices
|
||||
for (unsigned int v = 0; v < assimpMesh->mNumVertices; v++) {
|
||||
VertexPointer vertices = { .raw = modelData->vertexData->blob.data };
|
||||
vertices.bytes += vertex * modelData->vertexData->format.stride;
|
||||
|
||||
*vertices.floats++ = assimpMesh->mVertices[v].x;
|
||||
*vertices.floats++ = assimpMesh->mVertices[v].y;
|
||||
*vertices.floats++ = assimpMesh->mVertices[v].z;
|
||||
|
||||
if (hasNormals) {
|
||||
if (assimpMesh->mNormals) {
|
||||
*vertices.floats++ = assimpMesh->mNormals[v].x;
|
||||
*vertices.floats++ = assimpMesh->mNormals[v].y;
|
||||
*vertices.floats++ = assimpMesh->mNormals[v].z;
|
||||
} else {
|
||||
*vertices.floats++ = 0;
|
||||
*vertices.floats++ = 0;
|
||||
*vertices.floats++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUVs) {
|
||||
if (assimpMesh->mTextureCoords[0]) {
|
||||
*vertices.floats++ = assimpMesh->mTextureCoords[0][v].x;
|
||||
*vertices.floats++ = assimpMesh->mTextureCoords[0][v].y;
|
||||
} else {
|
||||
*vertices.floats++ = 0;
|
||||
*vertices.floats++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasVertexColors) {
|
||||
if (assimpMesh->mColors[0]) {
|
||||
*vertices.bytes++ = assimpMesh->mColors[0][v].r * 255;
|
||||
*vertices.bytes++ = assimpMesh->mColors[0][v].g * 255;
|
||||
*vertices.bytes++ = assimpMesh->mColors[0][v].b * 255;
|
||||
*vertices.bytes++ = assimpMesh->mColors[0][v].a * 255;
|
||||
} else {
|
||||
*vertices.bytes++ = 255;
|
||||
*vertices.bytes++ = 255;
|
||||
*vertices.bytes++ = 255;
|
||||
*vertices.bytes++ = 255;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTangents) {
|
||||
if (assimpMesh->mTangents) {
|
||||
*vertices.floats++ = assimpMesh->mTangents[v].x;
|
||||
*vertices.floats++ = assimpMesh->mTangents[v].y;
|
||||
*vertices.floats++ = assimpMesh->mTangents[v].z;
|
||||
} else {
|
||||
*vertices.floats++ = 0;
|
||||
*vertices.floats++ = 0;
|
||||
*vertices.floats++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
vertex++;
|
||||
}
|
||||
|
||||
// Bones
|
||||
primitive->boneCount = assimpMesh->mNumBones;
|
||||
map_init(&primitive->boneMap);
|
||||
for (unsigned int b = 0; b < assimpMesh->mNumBones; b++) {
|
||||
struct aiBone* assimpBone = assimpMesh->mBones[b];
|
||||
Bone* bone = &primitive->bones[b];
|
||||
|
||||
bone->name = strdup(assimpBone->mName.data);
|
||||
aiTransposeMatrix4(&assimpBone->mOffsetMatrix);
|
||||
mat4_set(bone->offset, (float*) &assimpBone->mOffsetMatrix);
|
||||
map_set(&primitive->boneMap, bone->name, b);
|
||||
|
||||
for (unsigned int w = 0; w < assimpBone->mNumWeights; w++) {
|
||||
uint32_t vertexIndex = baseVertex + assimpBone->mWeights[w].mVertexId;
|
||||
float weight = assimpBone->mWeights[w].mWeight;
|
||||
VertexPointer vertices = { .raw = modelData->vertexData->blob.data };
|
||||
vertices.bytes += vertexIndex * modelData->vertexData->format.stride;
|
||||
uint32_t* bones = (uint32_t*) (vertices.bytes + boneByteOffset);
|
||||
float* weights = (float*) (bones + MAX_BONES_PER_VERTEX);
|
||||
|
||||
int boneSlot = 0;
|
||||
while (weights[boneSlot] > 0) {
|
||||
boneSlot++;
|
||||
lovrAssert(boneSlot < MAX_BONES_PER_VERTEX, "Too many bones for vertex %d", vertexIndex);
|
||||
}
|
||||
|
||||
bones[boneSlot] = b;
|
||||
weights[boneSlot] = weight;
|
||||
token += nomValue(json, token, 1, 0); // Skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Materials
|
||||
map_int_t textureCache;
|
||||
map_init(&textureCache);
|
||||
vec_init(&modelData->textures);
|
||||
vec_push(&modelData->textures, NULL);
|
||||
modelData->materialCount = scene->mNumMaterials;
|
||||
modelData->materials = malloc(modelData->materialCount * sizeof(ModelMaterial));
|
||||
for (unsigned int m = 0; m < scene->mNumMaterials; m++) {
|
||||
ModelMaterial* material = &modelData->materials[m];
|
||||
struct aiMaterial* assimpMaterial = scene->mMaterials[m];
|
||||
|
||||
material->diffuseColor = readMaterialColor(assimpMaterial, AI_MATKEY_COLOR_DIFFUSE, (Color) { 1., 1., 1., 1. });
|
||||
material->emissiveColor = readMaterialColor(assimpMaterial, AI_MATKEY_COLOR_EMISSIVE, (Color) { 0., 0., 0., 0. });
|
||||
material->diffuseTexture = readMaterialTexture(assimpMaterial, aiTextureType_DIFFUSE, modelData, &textureCache, blob->name);
|
||||
material->emissiveTexture = readMaterialTexture(assimpMaterial, aiTextureType_EMISSIVE, modelData, &textureCache, blob->name);
|
||||
material->metalnessTexture = readMaterialTexture(assimpMaterial, aiTextureType_UNKNOWN, modelData, &textureCache, blob->name);
|
||||
material->roughnessTexture = material->metalnessTexture;
|
||||
material->occlusionTexture = readMaterialTexture(assimpMaterial, aiTextureType_LIGHTMAP, modelData, &textureCache, blob->name);
|
||||
material->normalTexture = readMaterialTexture(assimpMaterial, aiTextureType_NORMALS, modelData, &textureCache, blob->name);
|
||||
material->metalness = readMaterialScalar(assimpMaterial, "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0);
|
||||
material->roughness = readMaterialScalar(assimpMaterial, "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0);
|
||||
}
|
||||
map_deinit(&textureCache);
|
||||
|
||||
// Nodes
|
||||
modelData->nodeCount = 0;
|
||||
assimpSumChildren(scene->mRootNode, &modelData->nodeCount);
|
||||
modelData->nodes = malloc(modelData->nodeCount * sizeof(ModelNode));
|
||||
modelData->nodes[0].parent = -1;
|
||||
map_init(&modelData->nodeMap);
|
||||
int nodeIndex = 0;
|
||||
assimpNodeTraversal(modelData, scene->mRootNode, &nodeIndex);
|
||||
|
||||
// Animations
|
||||
modelData->animationCount = scene->mNumAnimations;
|
||||
modelData->animations = malloc(modelData->animationCount * sizeof(Animation));
|
||||
for (int i = 0; i < modelData->animationCount; i++) {
|
||||
struct aiAnimation* assimpAnimation = scene->mAnimations[i];
|
||||
float ticksPerSecond = assimpAnimation->mTicksPerSecond;
|
||||
|
||||
Animation* animation = &modelData->animations[i];
|
||||
animation->name = strdup(assimpAnimation->mName.data);
|
||||
animation->duration = assimpAnimation->mDuration / ticksPerSecond;
|
||||
animation->channelCount = assimpAnimation->mNumChannels;
|
||||
map_init(&animation->channels);
|
||||
|
||||
for (int j = 0; j < animation->channelCount; j++) {
|
||||
struct aiNodeAnim* assimpChannel = assimpAnimation->mChannels[j];
|
||||
AnimationChannel channel;
|
||||
|
||||
channel.node = strdup(assimpChannel->mNodeName.data);
|
||||
vec_init(&channel.positionKeyframes);
|
||||
vec_init(&channel.rotationKeyframes);
|
||||
vec_init(&channel.scaleKeyframes);
|
||||
|
||||
for (unsigned int k = 0; k < assimpChannel->mNumPositionKeys; k++) {
|
||||
struct aiVectorKey assimpKeyframe = assimpChannel->mPositionKeys[k];
|
||||
struct aiVector3D position = assimpKeyframe.mValue;
|
||||
vec_push(&channel.positionKeyframes, ((Keyframe) {
|
||||
.time = assimpKeyframe.mTime / ticksPerSecond,
|
||||
.data = { position.x, position.y, position.z }
|
||||
}));
|
||||
}
|
||||
|
||||
for (unsigned int k = 0; k < assimpChannel->mNumRotationKeys; k++) {
|
||||
struct aiQuatKey assimpKeyframe = assimpChannel->mRotationKeys[k];
|
||||
struct aiQuaternion quaternion = assimpKeyframe.mValue;
|
||||
vec_push(&channel.rotationKeyframes, ((Keyframe) {
|
||||
.time = assimpKeyframe.mTime / ticksPerSecond,
|
||||
.data = { quaternion.x, quaternion.y, quaternion.z, quaternion.w }
|
||||
}));
|
||||
}
|
||||
|
||||
for (unsigned int k = 0; k < assimpChannel->mNumScalingKeys; k++) {
|
||||
struct aiVectorKey assimpKeyframe = assimpChannel->mScalingKeys[k];
|
||||
struct aiVector3D scale = assimpKeyframe.mValue;
|
||||
vec_push(&channel.scaleKeyframes, ((Keyframe) {
|
||||
.time = assimpKeyframe.mTime / ticksPerSecond,
|
||||
.data = { scale.x, scale.y, scale.z }
|
||||
}));
|
||||
}
|
||||
|
||||
map_set(&animation->channels, channel.node, channel);
|
||||
}
|
||||
}
|
||||
|
||||
aiReleaseImport(scene);
|
||||
return modelData;
|
||||
}
|
||||
#else
|
||||
static void aabbIterator(ModelData* modelData, ModelNode* node, float aabb[6]) {}
|
||||
ModelData* lovrModelDataInit(ModelData* modelData, Blob* blob) {
|
||||
return NULL;
|
||||
|
||||
ModelData* lovrModelDataInit(ModelData* model, Blob* blob, ModelDataIO io) {
|
||||
uint8_t* data = blob->data;
|
||||
gltfHeader* header = (gltfHeader*) data;
|
||||
bool glb = header->magic == MAGIC_glTF;
|
||||
const char *jsonData, *binData;
|
||||
size_t jsonLength, binLength;
|
||||
|
||||
if (glb) {
|
||||
gltfChunkHeader* jsonHeader = (gltfChunkHeader*) &header[1];
|
||||
lovrAssert(jsonHeader->type == MAGIC_JSON, "Invalid JSON header");
|
||||
|
||||
jsonData = (char*) &jsonHeader[1];
|
||||
jsonLength = jsonHeader->length;
|
||||
|
||||
gltfChunkHeader* binHeader = (gltfChunkHeader*) &jsonData[jsonLength];
|
||||
lovrAssert(binHeader->type == MAGIC_BIN, "Invalid BIN header");
|
||||
|
||||
binData = (char*) &binHeader[1];
|
||||
binLength = binHeader->length;
|
||||
|
||||
// Hang onto the data since it's already here rather than make a copy of it
|
||||
lovrRetain(blob);
|
||||
} else {
|
||||
jsonData = (char*) data;
|
||||
jsonLength = blob->size;
|
||||
binData = NULL;
|
||||
binLength = 0;
|
||||
}
|
||||
|
||||
jsmn_parser parser;
|
||||
jsmn_init(&parser);
|
||||
|
||||
jsmntok_t tokens[1024]; // TODO malloc or token queue
|
||||
int tokenCount = jsmn_parse(&parser, jsonData, jsonLength, tokens, 1024);
|
||||
lovrAssert(tokenCount >= 0, "Invalid JSON");
|
||||
lovrAssert(tokens[0].type == JSMN_OBJECT, "No root object");
|
||||
|
||||
gltfInfo info = { 0 };
|
||||
size_t dataSize = 0;
|
||||
preparse(jsonData, tokens, tokenCount, &info, &dataSize);
|
||||
|
||||
size_t offset = 0;
|
||||
model->data = calloc(1, dataSize);
|
||||
model->glbBlob = glb ? blob : NULL;
|
||||
model->accessorCount = info.accessors.count;
|
||||
model->blobCount = info.blobs.count;
|
||||
model->viewCount = info.views.count;
|
||||
model->meshCount = info.meshes.count;
|
||||
model->nodeCount = info.nodes.count;
|
||||
model->primitiveCount = info.primitiveCount;
|
||||
model->accessors = (ModelAccessor*) (model->data + offset), offset += info.accessors.count * sizeof(ModelAccessor);
|
||||
model->blobs = (ModelBlob*) (model->data + offset), offset += info.blobs.count * sizeof(ModelBlob);
|
||||
model->views = (ModelView*) (model->data + offset), offset += info.views.count * sizeof(ModelView);
|
||||
model->meshes = (ModelMesh*) (model->data + offset), offset += info.meshes.count * sizeof(ModelMesh);
|
||||
model->nodes = (ModelNode*) (model->data + offset), offset += info.nodes.count * sizeof(ModelNode);
|
||||
model->primitives = (ModelPrimitive*) (model->data + offset), offset += info.primitiveCount * sizeof(ModelPrimitive);
|
||||
model->childMap = (uint32_t*) (model->data + offset), offset += info.childCount * sizeof(uint32_t);
|
||||
|
||||
parseAccessors(jsonData, info.accessors.token, model);
|
||||
parseBlobs(jsonData, info.blobs.token, model, io, (void*) binData);
|
||||
parseViews(jsonData, info.views.token, model);
|
||||
parseNodes(jsonData, info.nodes.token, model);
|
||||
parseMeshes(jsonData, info.meshes.token, model);
|
||||
|
||||
return model;
|
||||
}
|
||||
#endif
|
||||
|
||||
void lovrModelDataDestroy(void* ref) {
|
||||
ModelData* modelData = ref;
|
||||
|
||||
for (int i = 0; i < modelData->nodeCount; i++) {
|
||||
vec_deinit(&modelData->nodes[i].children);
|
||||
vec_deinit(&modelData->nodes[i].primitives);
|
||||
free((char*) modelData->nodes[i].name);
|
||||
}
|
||||
|
||||
for (int i = 0; i < modelData->primitiveCount; i++) {
|
||||
ModelPrimitive* primitive = &modelData->primitives[i];
|
||||
for (int j = 0; j < primitive->boneCount; j++) {
|
||||
free((char*) primitive->bones[j].name);
|
||||
}
|
||||
map_deinit(&primitive->boneMap);
|
||||
}
|
||||
|
||||
for (int i = 0; i < modelData->animationCount; i++) {
|
||||
Animation* animation = &modelData->animations[i];
|
||||
const char* key;
|
||||
map_iter_t iter = map_iter(&animation->channels);
|
||||
while ((key = map_next(&animation->channels, &iter)) != NULL) {
|
||||
AnimationChannel* channel = map_get(&animation->channels, key);
|
||||
vec_deinit(&channel->positionKeyframes);
|
||||
vec_deinit(&channel->rotationKeyframes);
|
||||
vec_deinit(&channel->scaleKeyframes);
|
||||
}
|
||||
map_deinit(&animation->channels);
|
||||
free((char*) animation->name);
|
||||
}
|
||||
|
||||
for (int i = 0; i < modelData->textures.length; i++) {
|
||||
lovrRelease(modelData->textures.data[i]);
|
||||
}
|
||||
|
||||
vec_deinit(&modelData->textures);
|
||||
map_deinit(&modelData->nodeMap);
|
||||
|
||||
lovrRelease(modelData->vertexData);
|
||||
|
||||
free(modelData->nodes);
|
||||
free(modelData->primitives);
|
||||
free(modelData->animations);
|
||||
free(modelData->materials);
|
||||
free(modelData->indices.raw);
|
||||
}
|
||||
|
||||
void lovrModelDataGetAABB(ModelData* modelData, float aabb[6]) {
|
||||
aabb[0] = FLT_MAX;
|
||||
aabb[1] = -FLT_MAX;
|
||||
aabb[2] = FLT_MAX;
|
||||
aabb[3] = -FLT_MAX;
|
||||
aabb[4] = FLT_MAX;
|
||||
aabb[5] = -FLT_MAX;
|
||||
aabbIterator(modelData, &modelData->nodes[0], aabb);
|
||||
//
|
||||
}
|
||||
|
|
|
@ -1,52 +1,90 @@
|
|||
#include "data/blob.h"
|
||||
#include "data/textureData.h"
|
||||
#include "data/vertexData.h"
|
||||
#include "util.h"
|
||||
#include "lib/map/map.h"
|
||||
#include "lib/vec/vec.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MAX_BONES_PER_VERTEX 4
|
||||
#define MAX_BONES 48
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
float offset[16];
|
||||
} Bone;
|
||||
typedef enum {
|
||||
ATTR_POSITION,
|
||||
ATTR_NORMAL,
|
||||
ATTR_TEXCOORD,
|
||||
ATTR_COLOR,
|
||||
ATTR_TANGENT,
|
||||
ATTR_BONES,
|
||||
ATTR_WEIGHTS,
|
||||
MAX_DEFAULT_ATTRIBUTES
|
||||
} DefaultAttribute;
|
||||
|
||||
typedef enum {
|
||||
DRAW_POINTS,
|
||||
DRAW_LINES,
|
||||
DRAW_LINE_LOOP,
|
||||
DRAW_LINE_STRIP,
|
||||
DRAW_TRIANGLES,
|
||||
DRAW_TRIANGLE_STRIP,
|
||||
DRAW_TRIANGLE_FAN
|
||||
} DrawMode;
|
||||
|
||||
typedef enum { I8, U8, I16, U16, I32, U32, F32 } AttributeType;
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t length;
|
||||
} gltfHeader;
|
||||
|
||||
typedef struct {
|
||||
uint32_t length;
|
||||
uint32_t type;
|
||||
} gltfChunkHeader;
|
||||
|
||||
typedef struct {
|
||||
char* data;
|
||||
size_t length;
|
||||
} gltfString;
|
||||
|
||||
typedef struct {
|
||||
int view;
|
||||
int count;
|
||||
int offset;
|
||||
AttributeType type;
|
||||
int components : 3;
|
||||
int normalized : 1;
|
||||
} ModelAccessor;
|
||||
|
||||
typedef struct {
|
||||
void* data;
|
||||
size_t size;
|
||||
} ModelBlob;
|
||||
|
||||
typedef struct {
|
||||
int blob;
|
||||
int offset;
|
||||
int length;
|
||||
int stride;
|
||||
} ModelView;
|
||||
|
||||
typedef struct {
|
||||
DrawMode mode;
|
||||
int attributes[MAX_DEFAULT_ATTRIBUTES];
|
||||
int indices;
|
||||
int material;
|
||||
int drawStart;
|
||||
int drawCount;
|
||||
Bone bones[MAX_BONES];
|
||||
map_int_t boneMap;
|
||||
int boneCount;
|
||||
} ModelPrimitive;
|
||||
|
||||
typedef vec_t(unsigned int) vec_uint_t;
|
||||
|
||||
typedef struct ModelNode {
|
||||
const char* name;
|
||||
float transform[16];
|
||||
float globalTransform[16];
|
||||
int parent;
|
||||
vec_uint_t children;
|
||||
vec_uint_t primitives;
|
||||
} ModelNode;
|
||||
typedef struct {
|
||||
ModelPrimitive* primitives;
|
||||
uint32_t primitiveCount;
|
||||
} ModelMesh;
|
||||
|
||||
typedef struct {
|
||||
Color diffuseColor;
|
||||
Color emissiveColor;
|
||||
int diffuseTexture;
|
||||
int emissiveTexture;
|
||||
int metalnessTexture;
|
||||
int roughnessTexture;
|
||||
int occlusionTexture;
|
||||
int normalTexture;
|
||||
float metalness;
|
||||
float roughness;
|
||||
} ModelMaterial;
|
||||
float transform[16];
|
||||
uint32_t* children;
|
||||
uint32_t childCount;
|
||||
int mesh;
|
||||
} ModelNode;
|
||||
|
||||
typedef struct {
|
||||
double time;
|
||||
|
@ -73,23 +111,27 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
Ref ref;
|
||||
VertexData* vertexData;
|
||||
IndexPointer indices;
|
||||
int indexCount;
|
||||
size_t indexSize;
|
||||
uint8_t* data;
|
||||
Blob* glbBlob;
|
||||
ModelAccessor* accessors;
|
||||
ModelBlob* blobs;
|
||||
ModelView* views;
|
||||
ModelMesh* meshes;
|
||||
ModelNode* nodes;
|
||||
map_int_t nodeMap;
|
||||
ModelPrimitive* primitives;
|
||||
Animation* animations;
|
||||
ModelMaterial* materials;
|
||||
vec_void_t textures;
|
||||
uint32_t* childMap;
|
||||
int accessorCount;
|
||||
int blobCount;
|
||||
int viewCount;
|
||||
int meshCount;
|
||||
int nodeCount;
|
||||
int primitiveCount;
|
||||
int animationCount;
|
||||
int materialCount;
|
||||
} ModelData;
|
||||
|
||||
ModelData* lovrModelDataInit(ModelData* modelData, Blob* blob);
|
||||
typedef struct {
|
||||
void* (*read)(const char* path, size_t* bytesRead);
|
||||
} ModelDataIO;
|
||||
|
||||
ModelData* lovrModelDataInit(ModelData* model, Blob* blob, ModelDataIO io);
|
||||
#define lovrModelDataCreate(...) lovrModelDataInit(lovrAlloc(ModelData), __VA_ARGS__)
|
||||
void lovrModelDataDestroy(void* ref);
|
||||
void lovrModelDataGetAABB(ModelData* modelData, float aabb[6]);
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
#include "data/vertexData.h"
|
||||
#include <string.h>
|
||||
|
||||
static const size_t attributeTypeSizes[3] = { 4, 1, 4 };
|
||||
static const size_t attributeTypeSizes[] = {
|
||||
[I8] = 1,
|
||||
[U8] = 1,
|
||||
[I16] = 2,
|
||||
[U16] = 2,
|
||||
[I32] = 4,
|
||||
[U32] = 4,
|
||||
[F32] = 4
|
||||
};
|
||||
|
||||
void vertexFormatInit(VertexFormat* format) {
|
||||
memset(format, 0, sizeof(*format));
|
||||
|
@ -20,10 +28,10 @@ VertexData* lovrVertexDataInit(VertexData* vertexData, uint32_t count, VertexFor
|
|||
} else {
|
||||
format = &vertexData->format;
|
||||
vertexFormatInit(&vertexData->format);
|
||||
vertexFormatAppend(&vertexData->format, "lovrPosition", ATTR_FLOAT, 3);
|
||||
vertexFormatAppend(&vertexData->format, "lovrNormal", ATTR_FLOAT, 3);
|
||||
vertexFormatAppend(&vertexData->format, "lovrTexCoord", ATTR_FLOAT, 2);
|
||||
vertexFormatAppend(&vertexData->format, "lovrVertexColor", ATTR_BYTE, 4);
|
||||
vertexFormatAppend(&vertexData->format, "lovrPosition", F32, 3);
|
||||
vertexFormatAppend(&vertexData->format, "lovrNormal", F32, 3);
|
||||
vertexFormatAppend(&vertexData->format, "lovrTexCoord", F32, 2);
|
||||
vertexFormatAppend(&vertexData->format, "lovrVertexColor", U8, 4);
|
||||
}
|
||||
|
||||
size_t size = format->stride * count;
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
#include "blob.h"
|
||||
#include "data/modelData.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
ATTR_FLOAT,
|
||||
ATTR_BYTE,
|
||||
ATTR_INT
|
||||
} AttributeType;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
AttributeType type;
|
||||
|
@ -26,10 +21,14 @@ typedef struct {
|
|||
|
||||
typedef union {
|
||||
void* raw;
|
||||
float* floats;
|
||||
uint8_t* bytes;
|
||||
int* ints;
|
||||
} VertexPointer;
|
||||
int8_t* i8;
|
||||
uint8_t* u8;
|
||||
int16_t* i16;
|
||||
uint16_t* u16;
|
||||
int32_t* i32;
|
||||
uint32_t* u32;
|
||||
float* f32;
|
||||
} AttributePointer;
|
||||
|
||||
typedef union {
|
||||
void* raw;
|
||||
|
|
|
@ -19,6 +19,7 @@ Animator* lovrAnimatorInit(Animator* animator, ModelData* modelData) {
|
|||
vec_init(&animator->trackList);
|
||||
animator->speed = 1;
|
||||
|
||||
/*
|
||||
for (int i = 0; i < modelData->animationCount; i++) {
|
||||
Animation* animation = &modelData->animations[i];
|
||||
|
||||
|
@ -35,6 +36,7 @@ Animator* lovrAnimatorInit(Animator* animator, ModelData* modelData) {
|
|||
map_set(&animator->trackMap, animation->name, track);
|
||||
vec_push(&animator->trackList, map_get(&animator->trackMap, animation->name));
|
||||
}
|
||||
*/
|
||||
|
||||
return animator;
|
||||
}
|
||||
|
@ -192,15 +194,17 @@ bool lovrAnimatorEvaluate(Animator* animator, const char* bone, mat4 transform)
|
|||
}
|
||||
|
||||
int lovrAnimatorGetAnimationCount(Animator* animator) {
|
||||
return animator->modelData->animationCount;
|
||||
//oreturn animator->modelData->animationCount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* lovrAnimatorGetAnimationName(Animator* animator, int index) {
|
||||
if (index < 0 || index >= animator->modelData->animationCount) {
|
||||
if (index < 0 || index >= 0/*animator->modelData->animationCount*/) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return animator->modelData->animations[index].name;
|
||||
//return animator->modelData->animations[index].name;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void lovrAnimatorPlay(Animator* animator, const char* animation) {
|
||||
|
|
|
@ -70,11 +70,11 @@ static void lovrGraphicsInitBuffers() {
|
|||
VertexFormat empty = { .count = 0 };
|
||||
Buffer* vertexBuffer = state.buffers[STREAM_VERTEX];
|
||||
size_t stride = BUFFER_STRIDES[STREAM_VERTEX];
|
||||
MeshAttribute position = { vertexBuffer, 0, stride, ATTR_FLOAT, 3, .enabled = true };
|
||||
MeshAttribute normal = { vertexBuffer, 12, stride, ATTR_FLOAT, 3, .enabled = true };
|
||||
MeshAttribute texCoord = { vertexBuffer, 24, stride, ATTR_FLOAT, 2, .enabled = true };
|
||||
MeshAttribute drawId = { state.buffers[STREAM_DRAW_ID], 0, 0, ATTR_BYTE, 1, .integer = true, .enabled = true };
|
||||
MeshAttribute identity = { state.identityBuffer, 0, 0, ATTR_BYTE, 1, .divisor = 1, .integer = true, .enabled = true };
|
||||
MeshAttribute position = { vertexBuffer, 0, stride, F32, 3, .enabled = true };
|
||||
MeshAttribute normal = { vertexBuffer, 12, stride, F32, 3, .enabled = true };
|
||||
MeshAttribute texCoord = { vertexBuffer, 24, stride, F32, 2, .enabled = true };
|
||||
MeshAttribute drawId = { state.buffers[STREAM_DRAW_ID], 0, 0, U8, 1, .integer = true, .enabled = true };
|
||||
MeshAttribute identity = { state.identityBuffer, 0, 0, U8, 1, .divisor = 1, .integer = true, .enabled = true };
|
||||
|
||||
state.mesh = lovrMeshCreate(DRAW_TRIANGLES, empty, NULL, 0);
|
||||
lovrMeshAttachAttribute(state.mesh, "lovrPosition", &position);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "data/modelData.h"
|
||||
#include "graphics/material.h"
|
||||
#include "graphics/shader.h"
|
||||
#include "graphics/opengl.h"
|
||||
|
@ -22,16 +23,6 @@ typedef struct {
|
|||
|
||||
typedef map_t(MeshAttribute) map_attribute_t;
|
||||
|
||||
typedef enum {
|
||||
DRAW_POINTS,
|
||||
DRAW_LINES,
|
||||
DRAW_LINE_STRIP,
|
||||
DRAW_LINE_LOOP,
|
||||
DRAW_TRIANGLE_STRIP,
|
||||
DRAW_TRIANGLES,
|
||||
DRAW_TRIANGLE_FAN
|
||||
} DrawMode;
|
||||
|
||||
typedef struct {
|
||||
Ref ref;
|
||||
DrawMode mode;
|
||||
|
@ -51,7 +42,9 @@ typedef struct {
|
|||
} Mesh;
|
||||
|
||||
Mesh* lovrMeshInit(Mesh* mesh, DrawMode mode, VertexFormat format, Buffer* vertexBuffer, uint32_t vertexCount);
|
||||
Mesh* lovrMeshInitEmpty(Mesh* mesh, DrawMode drawMode);
|
||||
#define lovrMeshCreate(...) lovrMeshInit(lovrAlloc(Mesh), __VA_ARGS__)
|
||||
#define lovrMeshCreateEmpty(...) lovrMeshInitEmpty(lovrAlloc(Mesh), __VA_ARGS__)
|
||||
void lovrMeshDestroy(void* ref);
|
||||
VertexFormat* lovrMeshGetVertexFormat(Mesh* mesh);
|
||||
Buffer* lovrMeshGetVertexBuffer(Mesh* mesh);
|
||||
|
|
|
@ -1,147 +1,60 @@
|
|||
#include "graphics/model.h"
|
||||
#include "graphics/graphics.h"
|
||||
#include "graphics/shader.h"
|
||||
#include "data/blob.h"
|
||||
#include "data/textureData.h"
|
||||
#include "data/vertexData.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "resources/shaders.h"
|
||||
|
||||
static void renderNode(Model* model, int nodeIndex, int instances) {
|
||||
ModelNode* node = &model->modelData->nodes[nodeIndex];
|
||||
Model* lovrModelInit(Model* model, ModelData* data) {
|
||||
model->data = data;
|
||||
lovrRetain(data);
|
||||
|
||||
if (node->primitives.length > 0) {
|
||||
float globalInverse[16];
|
||||
if (model->animator) {
|
||||
mat4_set(globalInverse, model->nodeTransforms[nodeIndex]);
|
||||
mat4_invert(globalInverse);
|
||||
}
|
||||
|
||||
for (int i = 0; i < node->primitives.length; i++) {
|
||||
ModelPrimitive* primitive = &model->modelData->primitives[node->primitives.data[i]];
|
||||
|
||||
if (model->animator) {
|
||||
for (int i = 0; i < primitive->boneCount; i++) {
|
||||
Bone* bone = &primitive->bones[i];
|
||||
int nodeIndex = *(int*) map_get(&model->modelData->nodeMap, bone->name);
|
||||
|
||||
mat4 bonePose = model->pose[i];
|
||||
mat4_identity(bonePose);
|
||||
mat4_set(bonePose, globalInverse);
|
||||
mat4_multiply(bonePose, model->nodeTransforms[nodeIndex]);
|
||||
mat4_multiply(bonePose, bone->offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (!model->material && model->materials) {
|
||||
lovrMeshSetMaterial(model->mesh, model->materials[primitive->material]);
|
||||
}
|
||||
|
||||
lovrGraphicsBatch(&(BatchRequest) {
|
||||
.type = BATCH_MESH,
|
||||
.params.mesh = {
|
||||
.object = model->mesh,
|
||||
.mode = DRAW_TRIANGLES,
|
||||
.rangeStart = primitive->drawStart,
|
||||
.rangeCount = primitive->drawCount,
|
||||
.pose = (float*) model->pose,
|
||||
.instances = instances
|
||||
},
|
||||
.transform = model->nodeTransforms[nodeIndex],
|
||||
.material = lovrMeshGetMaterial(model->mesh)
|
||||
});
|
||||
if (data->viewCount > 0) {
|
||||
model->buffers = calloc(data->viewCount, sizeof(Buffer*));
|
||||
for (int i = 0; i < data->viewCount; i++) {
|
||||
ModelView* view = &data->views[i];
|
||||
ModelBlob* blob = &data->blobs[view->blob];
|
||||
model->buffers[i] = lovrBufferCreate(view->length, (uint8_t*) blob->data + view->offset, BUFFER_GENERIC, USAGE_STATIC, false);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < node->children.length; i++) {
|
||||
renderNode(model, node->children.data[i], instances);
|
||||
}
|
||||
}
|
||||
if (data->primitiveCount > 0) {
|
||||
model->meshes = calloc(data->primitiveCount, sizeof(Mesh*));
|
||||
for (int i = 0; i < data->primitiveCount; i++) {
|
||||
ModelPrimitive* primitive = &data->primitives[i];
|
||||
model->meshes[i] = lovrMeshCreateEmpty(primitive->mode);
|
||||
|
||||
Model* lovrModelInit(Model* model, ModelData* modelData) {
|
||||
lovrRetain(modelData);
|
||||
model->modelData = modelData;
|
||||
model->aabbDirty = true;
|
||||
bool setDrawRange = false;
|
||||
for (int j = 0; j < MAX_DEFAULT_ATTRIBUTES; j++) {
|
||||
if (primitive->attributes[j] >= 0) {
|
||||
ModelAccessor* accessor = &data->accessors[primitive->attributes[j]];
|
||||
|
||||
VertexFormat* format = &modelData->vertexData->format;
|
||||
size_t vboSize = modelData->vertexData->count * format->stride;
|
||||
Buffer* vertexBuffer = lovrBufferCreate(vboSize, modelData->vertexData->blob.data, BUFFER_VERTEX, USAGE_STATIC, false);
|
||||
model->mesh = lovrMeshCreate(DRAW_TRIANGLES, *format, vertexBuffer, modelData->vertexData->count);
|
||||
lovrRelease(vertexBuffer);
|
||||
lovrMeshAttachAttribute(model->meshes[i], lovrShaderAttributeNames[j], &(MeshAttribute) {
|
||||
.buffer = model->buffers[accessor->view],
|
||||
.offset = accessor->offset,
|
||||
.stride = data->views[accessor->view].stride,
|
||||
.type = accessor->type,
|
||||
.components = accessor->components,
|
||||
.enabled = true
|
||||
});
|
||||
|
||||
size_t indexSize = modelData->indexSize;
|
||||
uint32_t indexCount = modelData->indexCount;
|
||||
Buffer* indexBuffer = lovrBufferCreate(indexCount * indexSize, modelData->indices.raw, BUFFER_INDEX, USAGE_STATIC, false);
|
||||
lovrMeshSetIndexBuffer(model->mesh, indexBuffer, indexCount, indexSize);
|
||||
lovrRelease(indexBuffer);
|
||||
|
||||
lovrMeshAttachAttribute(model->mesh, "lovrDrawID", &(MeshAttribute) {
|
||||
.buffer = lovrGraphicsGetIdentityBuffer(),
|
||||
.type = ATTR_BYTE,
|
||||
.components = 1,
|
||||
.divisor = 1,
|
||||
.integer = true,
|
||||
.enabled = true
|
||||
});
|
||||
|
||||
if (modelData->textures.length > 0) {
|
||||
model->textures = calloc(modelData->textures.length, sizeof(Texture*));
|
||||
lovrAssert(model->textures, "Out of memory");
|
||||
}
|
||||
|
||||
if (modelData->materialCount > 0) {
|
||||
model->materials = calloc(modelData->materialCount, sizeof(Material*));
|
||||
lovrAssert(model->materials, "Out of memory");
|
||||
for (int i = 0; i < modelData->materialCount; i++) {
|
||||
ModelMaterial* materialData = &modelData->materials[i];
|
||||
Material* material = lovrMaterialCreate();
|
||||
lovrMaterialSetScalar(material, SCALAR_METALNESS, materialData->metalness);
|
||||
lovrMaterialSetScalar(material, SCALAR_ROUGHNESS, materialData->roughness);
|
||||
lovrMaterialSetColor(material, COLOR_DIFFUSE, materialData->diffuseColor);
|
||||
lovrMaterialSetColor(material, COLOR_EMISSIVE, materialData->emissiveColor);
|
||||
for (MaterialTexture textureType = 0; textureType < MAX_MATERIAL_TEXTURES; textureType++) {
|
||||
int textureIndex = 0;
|
||||
|
||||
switch (textureType) {
|
||||
case TEXTURE_DIFFUSE: textureIndex = materialData->diffuseTexture; break;
|
||||
case TEXTURE_EMISSIVE: textureIndex = materialData->emissiveTexture; break;
|
||||
case TEXTURE_METALNESS: textureIndex = materialData->metalnessTexture; break;
|
||||
case TEXTURE_ROUGHNESS: textureIndex = materialData->roughnessTexture; break;
|
||||
case TEXTURE_OCCLUSION: textureIndex = materialData->occlusionTexture; break;
|
||||
case TEXTURE_NORMAL: textureIndex = materialData->normalTexture; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (textureIndex) {
|
||||
if (!model->textures[textureIndex]) {
|
||||
TextureData* textureData = modelData->textures.data[textureIndex];
|
||||
bool srgb = textureType == TEXTURE_DIFFUSE || textureType == TEXTURE_EMISSIVE;
|
||||
model->textures[textureIndex] = lovrTextureCreate(TEXTURE_2D, &textureData, 1, srgb, true, 0);
|
||||
if (!setDrawRange && primitive->indices == -1) {
|
||||
lovrMeshSetDrawRange(model->meshes[i], 0, accessor->count);
|
||||
setDrawRange = true;
|
||||
}
|
||||
|
||||
lovrMaterialSetTexture(material, textureType, model->textures[textureIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
model->materials[i] = material;
|
||||
}
|
||||
}
|
||||
lovrMeshAttachAttribute(model->meshes[i], "lovrDrawID", &(MeshAttribute) {
|
||||
.buffer = lovrGraphicsGetIdentityBuffer(),
|
||||
.type = U8,
|
||||
.components = 1,
|
||||
.divisor = 1,
|
||||
.integer = true,
|
||||
.enabled = true
|
||||
});
|
||||
|
||||
for (int i = 0; i < MAX_BONES; i++) {
|
||||
mat4_identity(model->pose[i]);
|
||||
}
|
||||
|
||||
model->nodeTransforms = malloc(16 * modelData->nodeCount * sizeof(float));
|
||||
lovrAssert(model->nodeTransforms, "Out of memory");
|
||||
for (int i = 0; i < modelData->nodeCount; i++) {
|
||||
ModelNode* node = &model->modelData->nodes[i];
|
||||
mat4 transform = model->nodeTransforms[i];
|
||||
|
||||
if (node->parent >= 0) {
|
||||
mat4_set(transform, model->nodeTransforms[node->parent]);
|
||||
mat4_multiply(transform, node->transform);
|
||||
} else {
|
||||
mat4_set(transform, node->transform);
|
||||
if (primitive->indices >= 0) {
|
||||
ModelAccessor* accessor = &data->accessors[primitive->indices];
|
||||
lovrMeshSetIndexBuffer(model->meshes[i], model->buffers[accessor->view], accessor->count, accessor->type == U16 ? 2 : 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,92 +63,16 @@ Model* lovrModelInit(Model* model, ModelData* modelData) {
|
|||
|
||||
void lovrModelDestroy(void* ref) {
|
||||
Model* model = ref;
|
||||
for (int i = 0; i < model->modelData->textures.length; i++) {
|
||||
lovrRelease(model->textures[i]);
|
||||
for (int i = 0; i < model->data->viewCount; i++) {
|
||||
lovrRelease(model->buffers[i]);
|
||||
}
|
||||
for (int i = 0; i < model->modelData->materialCount; i++) {
|
||||
lovrRelease(model->materials[i]);
|
||||
for (int i = 0; i < model->data->primitiveCount; i++) {
|
||||
lovrRelease(model->meshes[i]);
|
||||
}
|
||||
lovrRelease(model->animator);
|
||||
lovrRelease(model->material);
|
||||
free(model->textures);
|
||||
free(model->materials);
|
||||
lovrRelease(model->modelData);
|
||||
lovrRelease(model->mesh);
|
||||
free(model->nodeTransforms);
|
||||
lovrRelease(model->data);
|
||||
free(ref);
|
||||
}
|
||||
|
||||
void lovrModelDraw(Model* model, mat4 transform, int instances) {
|
||||
if (model->modelData->nodeCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model->animator) {
|
||||
for (int i = 0; i < model->modelData->nodeCount; i++) {
|
||||
ModelNode* node = &model->modelData->nodes[i];
|
||||
|
||||
float localTransform[16] = MAT4_IDENTITY;
|
||||
if (!lovrAnimatorEvaluate(model->animator, node->name, localTransform)) {
|
||||
mat4_set(localTransform, node->transform);
|
||||
}
|
||||
|
||||
mat4 globalTransform = model->nodeTransforms[i];
|
||||
if (node->parent >= 0) {
|
||||
mat4_set(globalTransform, model->nodeTransforms[node->parent]);
|
||||
mat4_multiply(globalTransform, localTransform);
|
||||
} else {
|
||||
mat4_set(globalTransform, localTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (model->material) {
|
||||
lovrMeshSetMaterial(model->mesh, model->material);
|
||||
}
|
||||
|
||||
lovrGraphicsPush();
|
||||
lovrGraphicsMatrixTransform(transform);
|
||||
renderNode(model, 0, instances);
|
||||
lovrGraphicsPop();
|
||||
}
|
||||
|
||||
Animator* lovrModelGetAnimator(Model* model) {
|
||||
return model->animator;
|
||||
}
|
||||
|
||||
void lovrModelSetAnimator(Model* model, Animator* animator) {
|
||||
if (model->animator != animator) {
|
||||
lovrRetain(animator);
|
||||
lovrRelease(model->animator);
|
||||
model->animator = animator;
|
||||
}
|
||||
}
|
||||
|
||||
int lovrModelGetAnimationCount(Model* model) {
|
||||
return model->modelData->animationCount;
|
||||
}
|
||||
|
||||
Material* lovrModelGetMaterial(Model* model) {
|
||||
return model->material;
|
||||
}
|
||||
|
||||
void lovrModelSetMaterial(Model* model, Material* material) {
|
||||
if (model->material != material) {
|
||||
lovrRetain(material);
|
||||
lovrRelease(model->material);
|
||||
model->material = material;
|
||||
}
|
||||
}
|
||||
|
||||
Mesh* lovrModelGetMesh(Model* model) {
|
||||
return model->mesh;
|
||||
}
|
||||
|
||||
const float* lovrModelGetAABB(Model* model) {
|
||||
if (model->aabbDirty) {
|
||||
lovrModelDataGetAABB(model->modelData, model->aabb);
|
||||
model->aabbDirty = false;
|
||||
}
|
||||
|
||||
return model->aabb;
|
||||
//
|
||||
}
|
||||
|
|
|
@ -1,36 +1,18 @@
|
|||
#include "data/modelData.h"
|
||||
#include "graphics/animator.h"
|
||||
#include "graphics/material.h"
|
||||
#include "graphics/mesh.h"
|
||||
#include "graphics/texture.h"
|
||||
#include "lib/math.h"
|
||||
#include "util.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct {
|
||||
Ref ref;
|
||||
ModelData* modelData;
|
||||
Texture** textures;
|
||||
Material** materials;
|
||||
Material* material;
|
||||
Animator* animator;
|
||||
Mesh* mesh;
|
||||
float pose[MAX_BONES][16];
|
||||
float (*nodeTransforms)[16];
|
||||
float aabb[6];
|
||||
bool aabbDirty;
|
||||
ModelData* data;
|
||||
Buffer** buffers;
|
||||
Mesh** meshes;
|
||||
} Model;
|
||||
|
||||
Model* lovrModelInit(Model* model, ModelData* modelData);
|
||||
Model* lovrModelInit(Model* model, ModelData* data);
|
||||
#define lovrModelCreate(...) lovrModelInit(lovrAlloc(Model), __VA_ARGS__)
|
||||
void lovrModelDestroy(void* ref);
|
||||
void lovrModelDraw(Model* model, mat4 transform, int instances);
|
||||
Animator* lovrModelGetAnimator(Model* model);
|
||||
void lovrModelSetAnimator(Model* model, Animator* animator);
|
||||
int lovrModelGetAnimationCount(Model* model);
|
||||
Material* lovrModelGetMaterial(Model* model);
|
||||
void lovrModelSetMaterial(Model* model, Material* material);
|
||||
Mesh* lovrModelGetMesh(Model* model);
|
||||
const float* lovrModelGetAABB(Model* model);
|
||||
|
|
|
@ -196,6 +196,19 @@ static bool isTextureFormatDepth(TextureFormat format) {
|
|||
}
|
||||
}
|
||||
|
||||
static GLenum convertAttributeType(AttributeType type) {
|
||||
switch (type) {
|
||||
case I8: return GL_BYTE;
|
||||
case U8: return GL_UNSIGNED_BYTE;
|
||||
case I16: return GL_SHORT;
|
||||
case U16: return GL_UNSIGNED_SHORT;
|
||||
case I32: return GL_INT;
|
||||
case U32: return GL_UNSIGNED_INT;
|
||||
case F32: return GL_FLOAT;
|
||||
default: lovrThrow("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
static GLenum convertBufferType(BufferType type) {
|
||||
switch (type) {
|
||||
case BUFFER_VERTEX: return GL_ARRAY_BUFFER;
|
||||
|
@ -537,20 +550,13 @@ static void lovrGpuBindMesh(Mesh* mesh, Shader* shader, int divisorMultiplier) {
|
|||
int count = current.components;
|
||||
int stride = current.stride;
|
||||
GLvoid* offset = (GLvoid*) current.offset;
|
||||
GLenum type = convertAttributeType(current.type);
|
||||
|
||||
// TODO
|
||||
if (current.integer) {
|
||||
switch (current.type) {
|
||||
case ATTR_BYTE: glVertexAttribIPointer(i, count, GL_UNSIGNED_BYTE, stride, offset); break;
|
||||
case ATTR_INT: glVertexAttribIPointer(i, count, GL_INT, stride, offset); break;
|
||||
default: lovrThrow("Cannot use float data for int attribute");
|
||||
}
|
||||
glVertexAttribIPointer(i, count, type, stride, offset);
|
||||
} else {
|
||||
switch (current.type) {
|
||||
case ATTR_FLOAT: glVertexAttribPointer(i, count, GL_FLOAT, GL_TRUE, stride, offset); break;
|
||||
case ATTR_BYTE: glVertexAttribPointer(i, count, GL_UNSIGNED_BYTE, GL_TRUE, stride, offset); break;
|
||||
case ATTR_INT: glVertexAttribPointer(i, count, GL_INT, GL_TRUE, stride, offset); break;
|
||||
}
|
||||
glVertexAttribPointer(i, count, type, GL_TRUE, stride, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1973,7 +1979,6 @@ Mesh* lovrMeshInit(Mesh* mesh, DrawMode mode, VertexFormat format, Buffer* verte
|
|||
.stride = format.stride,
|
||||
.type = format.attributes[i].type,
|
||||
.components = format.attributes[i].count,
|
||||
.integer = format.attributes[i].type == ATTR_INT,
|
||||
.enabled = true
|
||||
}));
|
||||
}
|
||||
|
@ -1981,6 +1986,13 @@ Mesh* lovrMeshInit(Mesh* mesh, DrawMode mode, VertexFormat format, Buffer* verte
|
|||
return mesh;
|
||||
}
|
||||
|
||||
Mesh* lovrMeshInitEmpty(Mesh* mesh, DrawMode mode) {
|
||||
mesh->mode = mode;
|
||||
glGenVertexArrays(1, &mesh->vao);
|
||||
map_init(&mesh->attributes);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void lovrMeshDestroy(void* ref) {
|
||||
Mesh* mesh = ref;
|
||||
lovrGraphicsFlushMesh(mesh);
|
||||
|
|
|
@ -401,78 +401,7 @@ static ModelData* openvrControllerNewModelData(Controller* controller) {
|
|||
|
||||
RenderModel_t* vrModel = state.deviceModels[id];
|
||||
|
||||
ModelData* modelData = lovrAlloc(ModelData);
|
||||
|
||||
VertexFormat format;
|
||||
vertexFormatInit(&format);
|
||||
vertexFormatAppend(&format, "lovrPosition", ATTR_FLOAT, 3);
|
||||
vertexFormatAppend(&format, "lovrNormal", ATTR_FLOAT, 3);
|
||||
vertexFormatAppend(&format, "lovrTexCoord", ATTR_FLOAT, 2);
|
||||
|
||||
modelData->vertexData = lovrVertexDataCreate(vrModel->unVertexCount, &format);
|
||||
|
||||
float* vertices = (float*) modelData->vertexData->blob.data;
|
||||
int vertex = 0;
|
||||
for (size_t i = 0; i < vrModel->unVertexCount; i++) {
|
||||
float* position = vrModel->rVertexData[i].vPosition.v;
|
||||
float* normal = vrModel->rVertexData[i].vNormal.v;
|
||||
float* texCoords = vrModel->rVertexData[i].rfTextureCoord;
|
||||
|
||||
vertices[vertex++] = position[0];
|
||||
vertices[vertex++] = position[1];
|
||||
vertices[vertex++] = position[2];
|
||||
|
||||
vertices[vertex++] = normal[0];
|
||||
vertices[vertex++] = normal[1];
|
||||
vertices[vertex++] = normal[2];
|
||||
|
||||
vertices[vertex++] = texCoords[0];
|
||||
vertices[vertex++] = texCoords[1];
|
||||
}
|
||||
|
||||
modelData->indexCount = vrModel->unTriangleCount * 3;
|
||||
modelData->indexSize = sizeof(uint16_t);
|
||||
modelData->indices.raw = malloc(modelData->indexCount * modelData->indexSize);
|
||||
memcpy(modelData->indices.raw, vrModel->rIndexData, modelData->indexCount * modelData->indexSize);
|
||||
|
||||
modelData->nodeCount = 1;
|
||||
modelData->primitiveCount = 1;
|
||||
modelData->animationCount = 0;
|
||||
modelData->materialCount = 1;
|
||||
|
||||
modelData->nodes = calloc(1, sizeof(ModelNode));
|
||||
modelData->primitives = calloc(1, sizeof(ModelPrimitive));
|
||||
modelData->materials = calloc(1, sizeof(ModelMaterial));
|
||||
|
||||
// Nodes
|
||||
ModelNode* root = &modelData->nodes[0];
|
||||
root->parent = -1;
|
||||
vec_init(&root->children);
|
||||
vec_init(&root->primitives);
|
||||
vec_push(&root->primitives, 0);
|
||||
mat4_identity(root->transform);
|
||||
mat4_identity(root->globalTransform);
|
||||
modelData->primitives[0].material = 0;
|
||||
modelData->primitives[0].drawStart = 0;
|
||||
modelData->primitives[0].drawCount = modelData->indexCount;
|
||||
map_init(&modelData->primitives[0].boneMap);
|
||||
map_init(&modelData->nodeMap);
|
||||
|
||||
// Material
|
||||
RenderModel_TextureMap_t* vrTexture = state.deviceTextures[id];
|
||||
TextureData* textureData = lovrTextureDataCreate(vrTexture->unWidth, vrTexture->unHeight, 0, FORMAT_RGBA);
|
||||
memcpy(textureData->blob.data, vrTexture->rubTextureMapData, vrTexture->unWidth * vrTexture->unHeight * 4);
|
||||
|
||||
vec_init(&modelData->textures);
|
||||
vec_push(&modelData->textures, NULL);
|
||||
vec_push(&modelData->textures, textureData);
|
||||
|
||||
ModelMaterial* material = &modelData->materials[0];
|
||||
material->diffuseColor = (Color) { 1, 1, 1, 1 };
|
||||
material->emissiveColor = (Color) { 0, 0, 0, 1 };
|
||||
material->diffuseTexture = 1;
|
||||
|
||||
return modelData;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void openvrRenderTo(void (*callback)(void*), void* userdata) {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2010 Serge A. Zaitsev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,313 @@
|
|||
#include "jsmn.h"
|
||||
|
||||
/**
|
||||
* Allocates a fresh unused token from the token pull.
|
||||
*/
|
||||
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
|
||||
jsmntok_t *tokens, size_t num_tokens) {
|
||||
jsmntok_t *tok;
|
||||
if (parser->toknext >= num_tokens) {
|
||||
return NULL;
|
||||
}
|
||||
tok = &tokens[parser->toknext++];
|
||||
tok->start = tok->end = -1;
|
||||
tok->size = 0;
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
tok->parent = -1;
|
||||
#endif
|
||||
return tok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills token type and boundaries.
|
||||
*/
|
||||
static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
|
||||
int start, int end) {
|
||||
token->type = type;
|
||||
token->start = start;
|
||||
token->end = end;
|
||||
token->size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills next available token with JSON primitive.
|
||||
*/
|
||||
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
|
||||
size_t len, jsmntok_t *tokens, size_t num_tokens) {
|
||||
jsmntok_t *token;
|
||||
int start;
|
||||
|
||||
start = parser->pos;
|
||||
|
||||
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
|
||||
switch (js[parser->pos]) {
|
||||
#ifndef JSMN_STRICT
|
||||
/* In strict mode primitive must be followed by "," or "}" or "]" */
|
||||
case ':':
|
||||
#endif
|
||||
case '\t' : case '\r' : case '\n' : case ' ' :
|
||||
case ',' : case ']' : case '}' :
|
||||
goto found;
|
||||
}
|
||||
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
}
|
||||
#ifdef JSMN_STRICT
|
||||
/* In strict mode primitive must be followed by a comma/object/array */
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_PART;
|
||||
#endif
|
||||
|
||||
found:
|
||||
if (tokens == NULL) {
|
||||
parser->pos--;
|
||||
return 0;
|
||||
}
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||
if (token == NULL) {
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_NOMEM;
|
||||
}
|
||||
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
token->parent = parser->toksuper;
|
||||
#endif
|
||||
parser->pos--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills next token with JSON string.
|
||||
*/
|
||||
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
|
||||
size_t len, jsmntok_t *tokens, size_t num_tokens) {
|
||||
jsmntok_t *token;
|
||||
|
||||
int start = parser->pos;
|
||||
|
||||
parser->pos++;
|
||||
|
||||
/* Skip starting quote */
|
||||
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
|
||||
char c = js[parser->pos];
|
||||
|
||||
/* Quote: end of string */
|
||||
if (c == '\"') {
|
||||
if (tokens == NULL) {
|
||||
return 0;
|
||||
}
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||
if (token == NULL) {
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_NOMEM;
|
||||
}
|
||||
jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
token->parent = parser->toksuper;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Backslash: Quoted symbol expected */
|
||||
if (c == '\\' && parser->pos + 1 < len) {
|
||||
int i;
|
||||
parser->pos++;
|
||||
switch (js[parser->pos]) {
|
||||
/* Allowed escaped symbols */
|
||||
case '\"': case '/' : case '\\' : case 'b' :
|
||||
case 'f' : case 'r' : case 'n' : case 't' :
|
||||
break;
|
||||
/* Allows escaped symbol \uXXXX */
|
||||
case 'u':
|
||||
parser->pos++;
|
||||
for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
|
||||
/* If it isn't a hex character we have an error */
|
||||
if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
|
||||
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
|
||||
(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
parser->pos++;
|
||||
}
|
||||
parser->pos--;
|
||||
break;
|
||||
/* Unexpected symbol */
|
||||
default:
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_PART;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse JSON string and fill tokens.
|
||||
*/
|
||||
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
|
||||
jsmntok_t *tokens, unsigned int num_tokens) {
|
||||
int r;
|
||||
int i;
|
||||
jsmntok_t *token;
|
||||
int count = parser->toknext;
|
||||
|
||||
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
|
||||
char c;
|
||||
jsmntype_t type;
|
||||
|
||||
c = js[parser->pos];
|
||||
switch (c) {
|
||||
case '{': case '[':
|
||||
count++;
|
||||
if (tokens == NULL) {
|
||||
break;
|
||||
}
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||
if (token == NULL)
|
||||
return JSMN_ERROR_NOMEM;
|
||||
if (parser->toksuper != -1) {
|
||||
tokens[parser->toksuper].size++;
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
token->parent = parser->toksuper;
|
||||
#endif
|
||||
}
|
||||
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
|
||||
token->start = parser->pos;
|
||||
parser->toksuper = parser->toknext - 1;
|
||||
break;
|
||||
case '}': case ']':
|
||||
if (tokens == NULL)
|
||||
break;
|
||||
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
if (parser->toknext < 1) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
token = &tokens[parser->toknext - 1];
|
||||
for (;;) {
|
||||
if (token->start != -1 && token->end == -1) {
|
||||
if (token->type != type) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
token->end = parser->pos + 1;
|
||||
parser->toksuper = token->parent;
|
||||
break;
|
||||
}
|
||||
if (token->parent == -1) {
|
||||
if(token->type != type || parser->toksuper == -1) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
token = &tokens[token->parent];
|
||||
}
|
||||
#else
|
||||
for (i = parser->toknext - 1; i >= 0; i--) {
|
||||
token = &tokens[i];
|
||||
if (token->start != -1 && token->end == -1) {
|
||||
if (token->type != type) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
parser->toksuper = -1;
|
||||
token->end = parser->pos + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Error if unmatched closing bracket */
|
||||
if (i == -1) return JSMN_ERROR_INVAL;
|
||||
for (; i >= 0; i--) {
|
||||
token = &tokens[i];
|
||||
if (token->start != -1 && token->end == -1) {
|
||||
parser->toksuper = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case '\"':
|
||||
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
|
||||
if (r < 0) return r;
|
||||
count++;
|
||||
if (parser->toksuper != -1 && tokens != NULL)
|
||||
tokens[parser->toksuper].size++;
|
||||
break;
|
||||
case '\t' : case '\r' : case '\n' : case ' ':
|
||||
break;
|
||||
case ':':
|
||||
parser->toksuper = parser->toknext - 1;
|
||||
break;
|
||||
case ',':
|
||||
if (tokens != NULL && parser->toksuper != -1 &&
|
||||
tokens[parser->toksuper].type != JSMN_ARRAY &&
|
||||
tokens[parser->toksuper].type != JSMN_OBJECT) {
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
parser->toksuper = tokens[parser->toksuper].parent;
|
||||
#else
|
||||
for (i = parser->toknext - 1; i >= 0; i--) {
|
||||
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
|
||||
if (tokens[i].start != -1 && tokens[i].end == -1) {
|
||||
parser->toksuper = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
#ifdef JSMN_STRICT
|
||||
/* In strict mode primitives are: numbers and booleans */
|
||||
case '-': case '0': case '1' : case '2': case '3' : case '4':
|
||||
case '5': case '6': case '7' : case '8': case '9':
|
||||
case 't': case 'f': case 'n' :
|
||||
/* And they must not be keys of the object */
|
||||
if (tokens != NULL && parser->toksuper != -1) {
|
||||
jsmntok_t *t = &tokens[parser->toksuper];
|
||||
if (t->type == JSMN_OBJECT ||
|
||||
(t->type == JSMN_STRING && t->size != 0)) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* In non-strict mode every unquoted value is a primitive */
|
||||
default:
|
||||
#endif
|
||||
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
|
||||
if (r < 0) return r;
|
||||
count++;
|
||||
if (parser->toksuper != -1 && tokens != NULL)
|
||||
tokens[parser->toksuper].size++;
|
||||
break;
|
||||
|
||||
#ifdef JSMN_STRICT
|
||||
/* Unexpected char in strict mode */
|
||||
default:
|
||||
return JSMN_ERROR_INVAL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (tokens != NULL) {
|
||||
for (i = parser->toknext - 1; i >= 0; i--) {
|
||||
/* Unmatched opened object or array */
|
||||
if (tokens[i].start != -1 && tokens[i].end == -1) {
|
||||
return JSMN_ERROR_PART;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new parser based over a given buffer with an array of tokens
|
||||
* available.
|
||||
*/
|
||||
void jsmn_init(jsmn_parser *parser) {
|
||||
parser->pos = 0;
|
||||
parser->toknext = 0;
|
||||
parser->toksuper = -1;
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef __JSMN_H_
|
||||
#define __JSMN_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* JSON type identifier. Basic types are:
|
||||
* o Object
|
||||
* o Array
|
||||
* o String
|
||||
* o Other primitive: number, boolean (true/false) or null
|
||||
*/
|
||||
typedef enum {
|
||||
JSMN_UNDEFINED = 0,
|
||||
JSMN_OBJECT = 1,
|
||||
JSMN_ARRAY = 2,
|
||||
JSMN_STRING = 3,
|
||||
JSMN_PRIMITIVE = 4
|
||||
} jsmntype_t;
|
||||
|
||||
enum jsmnerr {
|
||||
/* Not enough tokens were provided */
|
||||
JSMN_ERROR_NOMEM = -1,
|
||||
/* Invalid character inside JSON string */
|
||||
JSMN_ERROR_INVAL = -2,
|
||||
/* The string is not a full JSON packet, more bytes expected */
|
||||
JSMN_ERROR_PART = -3
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON token description.
|
||||
* type type (object, array, string etc.)
|
||||
* start start position in JSON data string
|
||||
* end end position in JSON data string
|
||||
*/
|
||||
typedef struct {
|
||||
jsmntype_t type;
|
||||
int start;
|
||||
int end;
|
||||
int size;
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
int parent;
|
||||
#endif
|
||||
} jsmntok_t;
|
||||
|
||||
/**
|
||||
* JSON parser. Contains an array of token blocks available. Also stores
|
||||
* the string being parsed now and current position in that string
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned int pos; /* offset in the JSON string */
|
||||
unsigned int toknext; /* next token to allocate */
|
||||
int toksuper; /* superior token node, e.g parent object or array */
|
||||
} jsmn_parser;
|
||||
|
||||
/**
|
||||
* Create JSON parser over an array of tokens
|
||||
*/
|
||||
void jsmn_init(jsmn_parser *parser);
|
||||
|
||||
/**
|
||||
* Run JSON parser. It parses a JSON data string into and array of tokens, each describing
|
||||
* a single JSON object.
|
||||
*/
|
||||
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
|
||||
jsmntok_t *tokens, unsigned int num_tokens);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __JSMN_H_ */
|
|
@ -173,3 +173,13 @@ const char* lovrShaderTextureUniforms[] = {
|
|||
"lovrNormalTexture",
|
||||
"lovrEnvironmentTexture"
|
||||
};
|
||||
|
||||
const char* lovrShaderAttributeNames[] = {
|
||||
"lovrPosition",
|
||||
"lovrNormal",
|
||||
"lovrTexCoord",
|
||||
"lovrVertexColor",
|
||||
"lovrTangent",
|
||||
"lovrBones",
|
||||
"lovrBoneWeights"
|
||||
};
|
||||
|
|
|
@ -17,3 +17,4 @@ extern const char* lovrFillVertexShader;
|
|||
extern const char* lovrShaderScalarUniforms[];
|
||||
extern const char* lovrShaderColorUniforms[];
|
||||
extern const char* lovrShaderTextureUniforms[];
|
||||
extern const char* lovrShaderAttributeNames[];
|
||||
|
|
Loading…
Reference in New Issue