From 5333a43018378c667f1e8e3e137a85b051450fbb Mon Sep 17 00:00:00 2001 From: Vasu Mahesh Date: Mon, 15 Oct 2018 11:25:42 -0400 Subject: [PATCH 1/6] format files --- src/CMakeLists.txt | 2 +- src/rasterize.cu | 1343 +++++++++++++++++++++++--------------------- 2 files changed, 691 insertions(+), 654 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a57f69f..ce4f1b6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,5 +6,5 @@ set(SOURCE_FILES cuda_add_library(src ${SOURCE_FILES} - OPTIONS -arch=sm_20 + OPTIONS -arch=sm_50 ) diff --git a/src/rasterize.cu b/src/rasterize.cu index 1262a09..68b65db 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -18,84 +18,88 @@ #include #include -namespace { - - typedef unsigned short VertexIndex; - typedef glm::vec3 VertexAttributePosition; - typedef glm::vec3 VertexAttributeNormal; - typedef glm::vec2 VertexAttributeTexcoord; - typedef unsigned char TextureData; - - typedef unsigned char BufferByte; - - enum PrimitiveType{ - Point = 1, - Line = 2, - Triangle = 3 - }; - - struct VertexOut { - glm::vec4 pos; - - // TODO: add new attributes to your VertexOut - // The attributes listed below might be useful, - // but always feel free to modify on your own - - glm::vec3 eyePos; // eye space position used for shading - glm::vec3 eyeNor; // eye space normal used for shading, cuz normal will go wrong after perspective transformation - // glm::vec3 col; - glm::vec2 texcoord0; - TextureData* dev_diffuseTex = NULL; - // int texWidth, texHeight; - // ... - }; - - struct Primitive { - PrimitiveType primitiveType = Triangle; // C++ 11 init - VertexOut v[3]; - }; - - struct Fragment { - glm::vec3 color; - - // TODO: add new attributes to your Fragment - // The attributes listed below might be useful, - // but always feel free to modify on your own - - // glm::vec3 eyePos; // eye space position used for shading - // glm::vec3 eyeNor; - // VertexAttributeTexcoord texcoord0; - // TextureData* dev_diffuseTex; - // ... - }; - - struct PrimitiveDevBufPointers { - int primitiveMode; //from tinygltfloader macro - PrimitiveType primitiveType; - int numPrimitives; - int numIndices; - int numVertices; - - // Vertex In, const after loaded - VertexIndex* dev_indices; - VertexAttributePosition* dev_position; - VertexAttributeNormal* dev_normal; - VertexAttributeTexcoord* dev_texcoord0; - - // Materials, add more attributes when needed - TextureData* dev_diffuseTex; - int diffuseTexWidth; - int diffuseTexHeight; - // TextureData* dev_specularTex; - // TextureData* dev_normalTex; - // ... - - // Vertex Out, vertex used for rasterization, this is changing every frame - VertexOut* dev_verticesOut; - - // TODO: add more attributes when needed - }; - +namespace +{ + typedef unsigned short VertexIndex; + typedef glm::vec3 VertexAttributePosition; + typedef glm::vec3 VertexAttributeNormal; + typedef glm::vec2 VertexAttributeTexcoord; + typedef unsigned char TextureData; + + typedef unsigned char BufferByte; + + enum PrimitiveType + { + Point = 1, + Line = 2, + Triangle = 3 + }; + + struct VertexOut + { + glm::vec4 pos; + + // TODO: add new attributes to your VertexOut + // The attributes listed below might be useful, + // but always feel free to modify on your own + + glm::vec3 eyePos; // eye space position used for shading + glm::vec3 eyeNor; // eye space normal used for shading, cuz normal will go wrong after perspective transformation + // glm::vec3 col; + glm::vec2 texcoord0; + TextureData* dev_diffuseTex = NULL; + // int texWidth, texHeight; + // ... + }; + + struct Primitive + { + PrimitiveType primitiveType = Triangle; // C++ 11 init + VertexOut v[3]; + }; + + struct Fragment + { + glm::vec3 color; + + // TODO: add new attributes to your Fragment + // The attributes listed below might be useful, + // but always feel free to modify on your own + + // glm::vec3 eyePos; // eye space position used for shading + // glm::vec3 eyeNor; + // VertexAttributeTexcoord texcoord0; + // TextureData* dev_diffuseTex; + // ... + }; + + struct PrimitiveDevBufPointers + { + int primitiveMode; //from tinygltfloader macro + PrimitiveType primitiveType; + int numPrimitives; + int numIndices; + int numVertices; + + // Vertex In, const after loaded + VertexIndex* dev_indices; + VertexAttributePosition* dev_position; + VertexAttributeNormal* dev_normal; + VertexAttributeTexcoord* dev_texcoord0; + + // Materials, add more attributes when needed + TextureData* dev_diffuseTex; + int diffuseTexWidth; + int diffuseTexHeight; + // TextureData* dev_specularTex; + // TextureData* dev_normalTex; + // ... + + // Vertex Out, vertex used for rasterization, this is changing every frame + VertexOut* dev_verticesOut; + + // TODO: add more attributes when needed + }; } static std::map> mesh2PrimitivesMap; @@ -105,81 +109,88 @@ static int width = 0; static int height = 0; static int totalNumPrimitives = 0; -static Primitive *dev_primitives = NULL; -static Fragment *dev_fragmentBuffer = NULL; -static glm::vec3 *dev_framebuffer = NULL; +static Primitive* dev_primitives = NULL; +static Fragment* dev_fragmentBuffer = NULL; +static glm::vec3* dev_framebuffer = NULL; -static int * dev_depth = NULL; // you might need this buffer when doing depth test +static int* dev_depth = NULL; // you might need this buffer when doing depth test /** * Kernel that writes the image to the OpenGL PBO directly. */ -__global__ -void sendImageToPBO(uchar4 *pbo, int w, int h, glm::vec3 *image) { - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; - int index = x + (y * w); +__global__ - if (x < w && y < h) { - glm::vec3 color; - color.x = glm::clamp(image[index].x, 0.0f, 1.0f) * 255.0; - color.y = glm::clamp(image[index].y, 0.0f, 1.0f) * 255.0; - color.z = glm::clamp(image[index].z, 0.0f, 1.0f) * 255.0; - // Each thread writes one pixel location in the texture (textel) - pbo[index].w = 0; - pbo[index].x = color.x; - pbo[index].y = color.y; - pbo[index].z = color.z; - } +void sendImageToPBO(uchar4* pbo, int w, int h, glm::vec3* image) +{ + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + int index = x + (y * w); + + if (x < w && y < h) + { + glm::vec3 color; + color.x = glm::clamp(image[index].x, 0.0f, 1.0f) * 255.0; + color.y = glm::clamp(image[index].y, 0.0f, 1.0f) * 255.0; + color.z = glm::clamp(image[index].z, 0.0f, 1.0f) * 255.0; + // Each thread writes one pixel location in the texture (textel) + pbo[index].w = 0; + pbo[index].x = color.x; + pbo[index].y = color.y; + pbo[index].z = color.z; + } } /** * Writes fragment colors to the framebuffer */ __global__ -void render(int w, int h, Fragment *fragmentBuffer, glm::vec3 *framebuffer) { - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; - int index = x + (y * w); - if (x < w && y < h) { - framebuffer[index] = fragmentBuffer[index].color; +void render(int w, int h, Fragment* fragmentBuffer, glm::vec3* framebuffer) +{ + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + int index = x + (y * w); - // TODO: add your fragment shader code here + if (x < w && y < h) + { + framebuffer[index] = fragmentBuffer[index].color; - } + // TODO: add your fragment shader code here + } } /** * Called once at the beginning of the program to allocate memory. */ -void rasterizeInit(int w, int h) { - width = w; - height = h; - cudaFree(dev_fragmentBuffer); - cudaMalloc(&dev_fragmentBuffer, width * height * sizeof(Fragment)); - cudaMemset(dev_fragmentBuffer, 0, width * height * sizeof(Fragment)); - cudaFree(dev_framebuffer); - cudaMalloc(&dev_framebuffer, width * height * sizeof(glm::vec3)); - cudaMemset(dev_framebuffer, 0, width * height * sizeof(glm::vec3)); - - cudaFree(dev_depth); - cudaMalloc(&dev_depth, width * height * sizeof(int)); - - checkCUDAError("rasterizeInit"); +void rasterizeInit(int w, int h) +{ + width = w; + height = h; + cudaFree(dev_fragmentBuffer); + cudaMalloc(&dev_fragmentBuffer, width * height * sizeof(Fragment)); + cudaMemset(dev_fragmentBuffer, 0, width * height * sizeof(Fragment)); + cudaFree(dev_framebuffer); + cudaMalloc(&dev_framebuffer, width * height * sizeof(glm::vec3)); + cudaMemset(dev_framebuffer, 0, width * height * sizeof(glm::vec3)); + + cudaFree(dev_depth); + cudaMalloc(&dev_depth, width * height * sizeof(int)); + + checkCUDAError("rasterizeInit"); } __global__ -void initDepth(int w, int h, int * depth) + +void initDepth(int w, int h, int* depth) { - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; - - if (x < w && y < h) - { - int index = x + (y * w); - depth[index] = INT_MAX; - } + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + + if (x < w && y < h) + { + int index = x + (y * w); + depth[index] = INT_MAX; + } } @@ -187,590 +198,616 @@ void initDepth(int w, int h, int * depth) * kern function with support for stride to sometimes replace cudaMemcpy * One thread is responsible for copying one component */ -__global__ -void _deviceBufferCopy(int N, BufferByte* dev_dst, const BufferByte* dev_src, int n, int byteStride, int byteOffset, int componentTypeByteSize) { - - // Attribute (vec3 position) - // component (3 * float) - // byte (4 * byte) - - // id of component - int i = (blockIdx.x * blockDim.x) + threadIdx.x; - - if (i < N) { - int count = i / n; - int offset = i - count * n; // which component of the attribute - - for (int j = 0; j < componentTypeByteSize; j++) { - - dev_dst[count * componentTypeByteSize * n - + offset * componentTypeByteSize - + j] - - = - - dev_src[byteOffset - + count * (byteStride == 0 ? componentTypeByteSize * n : byteStride) - + offset * componentTypeByteSize - + j]; - } - } - +__global__ +void _deviceBufferCopy(int N, BufferByte* dev_dst, const BufferByte* dev_src, int n, int byteStride, int byteOffset, + int componentTypeByteSize) +{ + // Attribute (vec3 position) + // component (3 * float) + // byte (4 * byte) + + // id of component + int i = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (i < N) + { + int count = i / n; + int offset = i - count * n; // which component of the attribute + + for (int j = 0; j < componentTypeByteSize; j++) + { + dev_dst[count * componentTypeByteSize * n + + offset * componentTypeByteSize + + j] + + = + + dev_src[byteOffset + + count * (byteStride == 0 ? componentTypeByteSize * n : byteStride) + + offset * componentTypeByteSize + + j]; + } + } } __global__ -void _nodeMatrixTransform( - int numVertices, - VertexAttributePosition* position, - VertexAttributeNormal* normal, - glm::mat4 MV, glm::mat3 MV_normal) { - - // vertex id - int vid = (blockIdx.x * blockDim.x) + threadIdx.x; - if (vid < numVertices) { - position[vid] = glm::vec3(MV * glm::vec4(position[vid], 1.0f)); - normal[vid] = glm::normalize(MV_normal * normal[vid]); - } -} - -glm::mat4 getMatrixFromNodeMatrixVector(const tinygltf::Node & n) { - - glm::mat4 curMatrix(1.0); - - const std::vector &m = n.matrix; - if (m.size() > 0) { - // matrix, copy it - - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - curMatrix[i][j] = (float)m.at(4 * i + j); - } - } - } else { - // no matrix, use rotation, scale, translation - - if (n.translation.size() > 0) { - curMatrix[3][0] = n.translation[0]; - curMatrix[3][1] = n.translation[1]; - curMatrix[3][2] = n.translation[2]; - } - - if (n.rotation.size() > 0) { - glm::mat4 R; - glm::quat q; - q[0] = n.rotation[0]; - q[1] = n.rotation[1]; - q[2] = n.rotation[2]; - - R = glm::mat4_cast(q); - curMatrix = curMatrix * R; - } - - if (n.scale.size() > 0) { - curMatrix = curMatrix * glm::scale(glm::vec3(n.scale[0], n.scale[1], n.scale[2])); - } - } - - return curMatrix; -} -void traverseNode ( - std::map & n2m, - const tinygltf::Scene & scene, - const std::string & nodeString, - const glm::mat4 & parentMatrix - ) +void _nodeMatrixTransform( + int numVertices, + VertexAttributePosition* position, + VertexAttributeNormal* normal, + glm::mat4 MV, glm::mat3 MV_normal) { - const tinygltf::Node & n = scene.nodes.at(nodeString); - glm::mat4 M = parentMatrix * getMatrixFromNodeMatrixVector(n); - n2m.insert(std::pair(nodeString, M)); - - auto it = n.children.begin(); - auto itEnd = n.children.end(); - - for (; it != itEnd; ++it) { - traverseNode(n2m, scene, *it, M); - } + // vertex id + int vid = (blockIdx.x * blockDim.x) + threadIdx.x; + if (vid < numVertices) + { + position[vid] = glm::vec3(MV * glm::vec4(position[vid], 1.0f)); + normal[vid] = glm::normalize(MV_normal * normal[vid]); + } } -void rasterizeSetBuffers(const tinygltf::Scene & scene) { +glm::mat4 getMatrixFromNodeMatrixVector(const tinygltf::Node& n) +{ + glm::mat4 curMatrix(1.0); + + const std::vector& m = n.matrix; + if (m.size() > 0) + { + // matrix, copy it + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + curMatrix[i][j] = (float)m.at(4 * i + j); + } + } + } + else + { + // no matrix, use rotation, scale, translation + + if (n.translation.size() > 0) + { + curMatrix[3][0] = n.translation[0]; + curMatrix[3][1] = n.translation[1]; + curMatrix[3][2] = n.translation[2]; + } - totalNumPrimitives = 0; + if (n.rotation.size() > 0) + { + glm::mat4 R; + glm::quat q; + q[0] = n.rotation[0]; + q[1] = n.rotation[1]; + q[2] = n.rotation[2]; - std::map bufferViewDevPointers; + R = glm::mat4_cast(q); + curMatrix = curMatrix * R; + } - // 1. copy all `bufferViews` to device memory - { - std::map::const_iterator it( - scene.bufferViews.begin()); - std::map::const_iterator itEnd( - scene.bufferViews.end()); + if (n.scale.size() > 0) + { + curMatrix = curMatrix * glm::scale(glm::vec3(n.scale[0], n.scale[1], n.scale[2])); + } + } - for (; it != itEnd; it++) { - const std::string key = it->first; - const tinygltf::BufferView &bufferView = it->second; - if (bufferView.target == 0) { - continue; // Unsupported bufferView. - } + return curMatrix; +} - const tinygltf::Buffer &buffer = scene.buffers.at(bufferView.buffer); +void traverseNode( + std::map& n2m, + const tinygltf::Scene& scene, + const std::string& nodeString, + const glm::mat4& parentMatrix +) +{ + const tinygltf::Node& n = scene.nodes.at(nodeString); + glm::mat4 M = parentMatrix * getMatrixFromNodeMatrixVector(n); + n2m.insert(std::pair(nodeString, M)); - BufferByte* dev_bufferView; - cudaMalloc(&dev_bufferView, bufferView.byteLength); - cudaMemcpy(dev_bufferView, &buffer.data.front() + bufferView.byteOffset, bufferView.byteLength, cudaMemcpyHostToDevice); + auto it = n.children.begin(); + auto itEnd = n.children.end(); - checkCUDAError("Set BufferView Device Mem"); + for (; it != itEnd; ++it) + { + traverseNode(n2m, scene, *it, M); + } +} - bufferViewDevPointers.insert(std::make_pair(key, dev_bufferView)); +void rasterizeSetBuffers(const tinygltf::Scene& scene) +{ + totalNumPrimitives = 0; - } - } + std::map bufferViewDevPointers; + // 1. copy all `bufferViews` to device memory + { + std::map::const_iterator it( + scene.bufferViews.begin()); + std::map::const_iterator itEnd( + scene.bufferViews.end()); + for (; it != itEnd; it++) + { + const std::string key = it->first; + const tinygltf::BufferView& bufferView = it->second; + if (bufferView.target == 0) + { + continue; // Unsupported bufferView. + } - // 2. for each mesh: - // for each primitive: - // build device buffer of indices, materail, and each attributes - // and store these pointers in a map - { + const tinygltf::Buffer& buffer = scene.buffers.at(bufferView.buffer); - std::map nodeString2Matrix; - auto rootNodeNamesList = scene.scenes.at(scene.defaultScene); - - { - auto it = rootNodeNamesList.begin(); - auto itEnd = rootNodeNamesList.end(); - for (; it != itEnd; ++it) { - traverseNode(nodeString2Matrix, scene, *it, glm::mat4(1.0f)); - } - } + BufferByte* dev_bufferView; + cudaMalloc(&dev_bufferView, bufferView.byteLength); + cudaMemcpy(dev_bufferView, &buffer.data.front() + bufferView.byteOffset, bufferView.byteLength, + cudaMemcpyHostToDevice); + checkCUDAError("Set BufferView Device Mem"); - // parse through node to access mesh + bufferViewDevPointers.insert(std::make_pair(key, dev_bufferView)); + } + } + + + // 2. for each mesh: + // for each primitive: + // build device buffer of indices, materail, and each attributes + // and store these pointers in a map + { + std::map nodeString2Matrix; + auto rootNodeNamesList = scene.scenes.at(scene.defaultScene); + + { + auto it = rootNodeNamesList.begin(); + auto itEnd = rootNodeNamesList.end(); + for (; it != itEnd; ++it) + { + traverseNode(nodeString2Matrix, scene, *it, glm::mat4(1.0f)); + } + } - auto itNode = nodeString2Matrix.begin(); - auto itEndNode = nodeString2Matrix.end(); - for (; itNode != itEndNode; ++itNode) { - - const tinygltf::Node & N = scene.nodes.at(itNode->first); - const glm::mat4 & matrix = itNode->second; - const glm::mat3 & matrixNormal = glm::transpose(glm::inverse(glm::mat3(matrix))); - - auto itMeshName = N.meshes.begin(); - auto itEndMeshName = N.meshes.end(); - - for (; itMeshName != itEndMeshName; ++itMeshName) { - - const tinygltf::Mesh & mesh = scene.meshes.at(*itMeshName); - - auto res = mesh2PrimitivesMap.insert(std::pair>(mesh.name, std::vector())); - std::vector & primitiveVector = (res.first)->second; - - // for each primitive - for (size_t i = 0; i < mesh.primitives.size(); i++) { - const tinygltf::Primitive &primitive = mesh.primitives[i]; - - if (primitive.indices.empty()) - return; - - // TODO: add new attributes for your PrimitiveDevBufPointers when you add new attributes - VertexIndex* dev_indices = NULL; - VertexAttributePosition* dev_position = NULL; - VertexAttributeNormal* dev_normal = NULL; - VertexAttributeTexcoord* dev_texcoord0 = NULL; - - // ----------Indices------------- - - const tinygltf::Accessor &indexAccessor = scene.accessors.at(primitive.indices); - const tinygltf::BufferView &bufferView = scene.bufferViews.at(indexAccessor.bufferView); - BufferByte* dev_bufferView = bufferViewDevPointers.at(indexAccessor.bufferView); - - // assume type is SCALAR for indices - int n = 1; - int numIndices = indexAccessor.count; - int componentTypeByteSize = sizeof(VertexIndex); - int byteLength = numIndices * n * componentTypeByteSize; - - dim3 numThreadsPerBlock(128); - dim3 numBlocks((numIndices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); - cudaMalloc(&dev_indices, byteLength); - _deviceBufferCopy << > > ( - numIndices, - (BufferByte*)dev_indices, - dev_bufferView, - n, - indexAccessor.byteStride, - indexAccessor.byteOffset, - componentTypeByteSize); - - - checkCUDAError("Set Index Buffer"); - - - // ---------Primitive Info------- - - // Warning: LINE_STRIP is not supported in tinygltfloader - int numPrimitives; - PrimitiveType primitiveType; - switch (primitive.mode) { - case TINYGLTF_MODE_TRIANGLES: - primitiveType = PrimitiveType::Triangle; - numPrimitives = numIndices / 3; - break; - case TINYGLTF_MODE_TRIANGLE_STRIP: - primitiveType = PrimitiveType::Triangle; - numPrimitives = numIndices - 2; - break; - case TINYGLTF_MODE_TRIANGLE_FAN: - primitiveType = PrimitiveType::Triangle; - numPrimitives = numIndices - 2; - break; - case TINYGLTF_MODE_LINE: - primitiveType = PrimitiveType::Line; - numPrimitives = numIndices / 2; - break; - case TINYGLTF_MODE_LINE_LOOP: - primitiveType = PrimitiveType::Line; - numPrimitives = numIndices + 1; - break; - case TINYGLTF_MODE_POINTS: - primitiveType = PrimitiveType::Point; - numPrimitives = numIndices; - break; - default: - // output error - break; - }; - - - // ----------Attributes------------- - - auto it(primitive.attributes.begin()); - auto itEnd(primitive.attributes.end()); - - int numVertices = 0; - // for each attribute - for (; it != itEnd; it++) { - const tinygltf::Accessor &accessor = scene.accessors.at(it->second); - const tinygltf::BufferView &bufferView = scene.bufferViews.at(accessor.bufferView); - - int n = 1; - if (accessor.type == TINYGLTF_TYPE_SCALAR) { - n = 1; - } - else if (accessor.type == TINYGLTF_TYPE_VEC2) { - n = 2; - } - else if (accessor.type == TINYGLTF_TYPE_VEC3) { - n = 3; - } - else if (accessor.type == TINYGLTF_TYPE_VEC4) { - n = 4; - } - - BufferByte * dev_bufferView = bufferViewDevPointers.at(accessor.bufferView); - BufferByte ** dev_attribute = NULL; - - numVertices = accessor.count; - int componentTypeByteSize; - - // Note: since the type of our attribute array (dev_position) is static (float32) - // We assume the glTF model attribute type are 5126(FLOAT) here - - if (it->first.compare("POSITION") == 0) { - componentTypeByteSize = sizeof(VertexAttributePosition) / n; - dev_attribute = (BufferByte**)&dev_position; - } - else if (it->first.compare("NORMAL") == 0) { - componentTypeByteSize = sizeof(VertexAttributeNormal) / n; - dev_attribute = (BufferByte**)&dev_normal; - } - else if (it->first.compare("TEXCOORD_0") == 0) { - componentTypeByteSize = sizeof(VertexAttributeTexcoord) / n; - dev_attribute = (BufferByte**)&dev_texcoord0; - } - - std::cout << accessor.bufferView << " - " << it->second << " - " << it->first << '\n'; - - dim3 numThreadsPerBlock(128); - dim3 numBlocks((n * numVertices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); - int byteLength = numVertices * n * componentTypeByteSize; - cudaMalloc(dev_attribute, byteLength); - - _deviceBufferCopy << > > ( - n * numVertices, - *dev_attribute, - dev_bufferView, - n, - accessor.byteStride, - accessor.byteOffset, - componentTypeByteSize); - - std::string msg = "Set Attribute Buffer: " + it->first; - checkCUDAError(msg.c_str()); - } - - // malloc for VertexOut - VertexOut* dev_vertexOut; - cudaMalloc(&dev_vertexOut, numVertices * sizeof(VertexOut)); - checkCUDAError("Malloc VertexOut Buffer"); - - // ----------Materials------------- - - // You can only worry about this part once you started to - // implement textures for your rasterizer - TextureData* dev_diffuseTex = NULL; - int diffuseTexWidth = 0; - int diffuseTexHeight = 0; - if (!primitive.material.empty()) { - const tinygltf::Material &mat = scene.materials.at(primitive.material); - printf("material.name = %s\n", mat.name.c_str()); - - if (mat.values.find("diffuse") != mat.values.end()) { - std::string diffuseTexName = mat.values.at("diffuse").string_value; - if (scene.textures.find(diffuseTexName) != scene.textures.end()) { - const tinygltf::Texture &tex = scene.textures.at(diffuseTexName); - if (scene.images.find(tex.source) != scene.images.end()) { - const tinygltf::Image &image = scene.images.at(tex.source); - - size_t s = image.image.size() * sizeof(TextureData); - cudaMalloc(&dev_diffuseTex, s); - cudaMemcpy(dev_diffuseTex, &image.image.at(0), s, cudaMemcpyHostToDevice); - - diffuseTexWidth = image.width; - diffuseTexHeight = image.height; - - checkCUDAError("Set Texture Image data"); - } - } - } - - // TODO: write your code for other materails - // You may have to take a look at tinygltfloader - // You can also use the above code loading diffuse material as a start point - } - - - // ---------Node hierarchy transform-------- - cudaDeviceSynchronize(); - - dim3 numBlocksNodeTransform((numVertices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); - _nodeMatrixTransform << > > ( - numVertices, - dev_position, - dev_normal, - matrix, - matrixNormal); - - checkCUDAError("Node hierarchy transformation"); - - // at the end of the for loop of primitive - // push dev pointers to map - primitiveVector.push_back(PrimitiveDevBufPointers{ - primitive.mode, - primitiveType, - numPrimitives, - numIndices, - numVertices, - - dev_indices, - dev_position, - dev_normal, - dev_texcoord0, - - dev_diffuseTex, - diffuseTexWidth, - diffuseTexHeight, - - dev_vertexOut //VertexOut - }); - - totalNumPrimitives += numPrimitives; - - } // for each primitive - - } // for each mesh - - } // for each node - - } - - - // 3. Malloc for dev_primitives - { - cudaMalloc(&dev_primitives, totalNumPrimitives * sizeof(Primitive)); - } - - - // Finally, cudaFree raw dev_bufferViews - { - - std::map::const_iterator it(bufferViewDevPointers.begin()); - std::map::const_iterator itEnd(bufferViewDevPointers.end()); - - //bufferViewDevPointers - - for (; it != itEnd; it++) { - cudaFree(it->second); - } - - checkCUDAError("Free BufferView Device Mem"); - } + // parse through node to access mesh + + auto itNode = nodeString2Matrix.begin(); + auto itEndNode = nodeString2Matrix.end(); + for (; itNode != itEndNode; ++itNode) + { + const tinygltf::Node& N = scene.nodes.at(itNode->first); + const glm::mat4& matrix = itNode->second; + const glm::mat3& matrixNormal = glm::transpose(glm::inverse(glm::mat3(matrix))); + + auto itMeshName = N.meshes.begin(); + auto itEndMeshName = N.meshes.end(); + + for (; itMeshName != itEndMeshName; ++itMeshName) + { + const tinygltf::Mesh& mesh = scene.meshes.at(*itMeshName); + + auto res = mesh2PrimitivesMap.insert( + std::pair>( + mesh.name, std::vector())); + std::vector& primitiveVector = (res.first)->second; + + // for each primitive + for (size_t i = 0; i < mesh.primitives.size(); i++) + { + const tinygltf::Primitive& primitive = mesh.primitives[i]; + + if (primitive.indices.empty()) + return; + + // TODO: add new attributes for your PrimitiveDevBufPointers when you add new attributes + VertexIndex* dev_indices = NULL; + VertexAttributePosition* dev_position = NULL; + VertexAttributeNormal* dev_normal = NULL; + VertexAttributeTexcoord* dev_texcoord0 = NULL; + + // ----------Indices------------- + + const tinygltf::Accessor& indexAccessor = scene.accessors.at(primitive.indices); + const tinygltf::BufferView& bufferView = scene.bufferViews.at(indexAccessor.bufferView); + BufferByte* dev_bufferView = bufferViewDevPointers.at(indexAccessor.bufferView); + + // assume type is SCALAR for indices + int n = 1; + int numIndices = indexAccessor.count; + int componentTypeByteSize = sizeof(VertexIndex); + int byteLength = numIndices * n * componentTypeByteSize; + + dim3 numThreadsPerBlock(128); + dim3 numBlocks((numIndices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); + cudaMalloc(&dev_indices, byteLength); + _deviceBufferCopy << > >( + numIndices, + (BufferByte*)dev_indices, + dev_bufferView, + n, + indexAccessor.byteStride, + indexAccessor.byteOffset, + componentTypeByteSize); + + + checkCUDAError("Set Index Buffer"); + + + // ---------Primitive Info------- + + // Warning: LINE_STRIP is not supported in tinygltfloader + int numPrimitives; + PrimitiveType primitiveType; + switch (primitive.mode) + { + case TINYGLTF_MODE_TRIANGLES: + primitiveType = PrimitiveType::Triangle; + numPrimitives = numIndices / 3; + break; + case TINYGLTF_MODE_TRIANGLE_STRIP: + primitiveType = PrimitiveType::Triangle; + numPrimitives = numIndices - 2; + break; + case TINYGLTF_MODE_TRIANGLE_FAN: + primitiveType = PrimitiveType::Triangle; + numPrimitives = numIndices - 2; + break; + case TINYGLTF_MODE_LINE: + primitiveType = PrimitiveType::Line; + numPrimitives = numIndices / 2; + break; + case TINYGLTF_MODE_LINE_LOOP: + primitiveType = PrimitiveType::Line; + numPrimitives = numIndices + 1; + break; + case TINYGLTF_MODE_POINTS: + primitiveType = PrimitiveType::Point; + numPrimitives = numIndices; + break; + default: + // output error + break; + }; + + + // ----------Attributes------------- + + auto it(primitive.attributes.begin()); + auto itEnd(primitive.attributes.end()); + + int numVertices = 0; + // for each attribute + for (; it != itEnd; it++) + { + const tinygltf::Accessor& accessor = scene.accessors.at(it->second); + const tinygltf::BufferView& bufferView = scene.bufferViews.at(accessor.bufferView); + + int n = 1; + if (accessor.type == TINYGLTF_TYPE_SCALAR) + { + n = 1; + } + else if (accessor.type == TINYGLTF_TYPE_VEC2) + { + n = 2; + } + else if (accessor.type == TINYGLTF_TYPE_VEC3) + { + n = 3; + } + else if (accessor.type == TINYGLTF_TYPE_VEC4) + { + n = 4; + } + + BufferByte* dev_bufferView = bufferViewDevPointers.at(accessor.bufferView); + BufferByte** dev_attribute = NULL; + + numVertices = accessor.count; + int componentTypeByteSize; + + // Note: since the type of our attribute array (dev_position) is static (float32) + // We assume the glTF model attribute type are 5126(FLOAT) here + + if (it->first.compare("POSITION") == 0) + { + componentTypeByteSize = sizeof(VertexAttributePosition) / n; + dev_attribute = (BufferByte**)&dev_position; + } + else if (it->first.compare("NORMAL") == 0) + { + componentTypeByteSize = sizeof(VertexAttributeNormal) / n; + dev_attribute = (BufferByte**)&dev_normal; + } + else if (it->first.compare("TEXCOORD_0") == 0) + { + componentTypeByteSize = sizeof(VertexAttributeTexcoord) / n; + dev_attribute = (BufferByte**)&dev_texcoord0; + } + + std::cout << accessor.bufferView << " - " << it->second << " - " << it->first << '\n'; + + dim3 numThreadsPerBlock(128); + dim3 numBlocks((n * numVertices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); + int byteLength = numVertices * n * componentTypeByteSize; + cudaMalloc(dev_attribute, byteLength); + + _deviceBufferCopy << > >( + n * numVertices, + *dev_attribute, + dev_bufferView, + n, + accessor.byteStride, + accessor.byteOffset, + componentTypeByteSize); + + std::string msg = "Set Attribute Buffer: " + it->first; + checkCUDAError(msg.c_str()); + } + + // malloc for VertexOut + VertexOut* dev_vertexOut; + cudaMalloc(&dev_vertexOut, numVertices * sizeof(VertexOut)); + checkCUDAError("Malloc VertexOut Buffer"); + + // ----------Materials------------- + + // You can only worry about this part once you started to + // implement textures for your rasterizer + TextureData* dev_diffuseTex = NULL; + int diffuseTexWidth = 0; + int diffuseTexHeight = 0; + if (!primitive.material.empty()) + { + const tinygltf::Material& mat = scene.materials.at(primitive.material); + printf("material.name = %s\n", mat.name.c_str()); + + if (mat.values.find("diffuse") != mat.values.end()) + { + std::string diffuseTexName = mat.values.at("diffuse").string_value; + if (scene.textures.find(diffuseTexName) != scene.textures.end()) + { + const tinygltf::Texture& tex = scene.textures.at(diffuseTexName); + if (scene.images.find(tex.source) != scene.images.end()) + { + const tinygltf::Image& image = scene.images.at(tex.source); + + size_t s = image.image.size() * sizeof(TextureData); + cudaMalloc(&dev_diffuseTex, s); + cudaMemcpy(dev_diffuseTex, &image.image.at(0), s, cudaMemcpyHostToDevice); + + diffuseTexWidth = image.width; + diffuseTexHeight = image.height; + + checkCUDAError("Set Texture Image data"); + } + } + } + + // TODO: write your code for other materails + // You may have to take a look at tinygltfloader + // You can also use the above code loading diffuse material as a start point + } + + + // ---------Node hierarchy transform-------- + cudaDeviceSynchronize(); + + dim3 numBlocksNodeTransform((numVertices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); + _nodeMatrixTransform << > >( + numVertices, + dev_position, + dev_normal, + matrix, + matrixNormal); + + checkCUDAError("Node hierarchy transformation"); + + // at the end of the for loop of primitive + // push dev pointers to map + primitiveVector.push_back(PrimitiveDevBufPointers{ + primitive.mode, + primitiveType, + numPrimitives, + numIndices, + numVertices, + + dev_indices, + dev_position, + dev_normal, + dev_texcoord0, + + dev_diffuseTex, + diffuseTexWidth, + diffuseTexHeight, + + dev_vertexOut //VertexOut + }); + + totalNumPrimitives += numPrimitives; + } // for each primitive + } // for each mesh + } // for each node + } + + + // 3. Malloc for dev_primitives + { + cudaMalloc(&dev_primitives, totalNumPrimitives * sizeof(Primitive)); + } + + + // Finally, cudaFree raw dev_bufferViews + { + std::map::const_iterator it(bufferViewDevPointers.begin()); + std::map::const_iterator itEnd(bufferViewDevPointers.end()); + + //bufferViewDevPointers + + for (; it != itEnd; it++) + { + cudaFree(it->second); + } + checkCUDAError("Free BufferView Device Mem"); + } } +__global__ -__global__ void _vertexTransformAndAssembly( - int numVertices, - PrimitiveDevBufPointers primitive, - glm::mat4 MVP, glm::mat4 MV, glm::mat3 MV_normal, - int width, int height) { - - // vertex id - int vid = (blockIdx.x * blockDim.x) + threadIdx.x; - if (vid < numVertices) { - - // TODO: Apply vertex transformation here - // Multiply the MVP matrix for each vertex position, this will transform everything into clipping space - // Then divide the pos by its w element to transform into NDC space - // Finally transform x and y to viewport space - - // TODO: Apply vertex assembly here - // Assemble all attribute arraies into the primitive array - - } + int numVertices, + PrimitiveDevBufPointers primitive, + glm::mat4 MVP, glm::mat4 MV, glm::mat3 MV_normal, + int width, int height) +{ + // vertex id + int vid = (blockIdx.x * blockDim.x) + threadIdx.x; + if (vid < numVertices) + { + // TODO: Apply vertex transformation here + // Multiply the MVP matrix for each vertex position, this will transform everything into clipping space + // Then divide the pos by its w element to transform into NDC space + // Finally transform x and y to viewport space + + // TODO: Apply vertex assembly here + // Assemble all attribute arraies into the primitive array + } } - static int curPrimitiveBeginId = 0; -__global__ -void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Primitive* dev_primitives, PrimitiveDevBufPointers primitive) { - - // index id - int iid = (blockIdx.x * blockDim.x) + threadIdx.x; +__global__ - if (iid < numIndices) { +void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Primitive* dev_primitives, + PrimitiveDevBufPointers primitive) +{ + // index id + int iid = (blockIdx.x * blockDim.x) + threadIdx.x; - // TODO: uncomment the following code for a start - // This is primitive assembly for triangles + if (iid < numIndices) + { + // TODO: uncomment the following code for a start + // This is primitive assembly for triangles - //int pid; // id for cur primitives vector - //if (primitive.primitiveMode == TINYGLTF_MODE_TRIANGLES) { - // pid = iid / (int)primitive.primitiveType; - // dev_primitives[pid + curPrimitiveBeginId].v[iid % (int)primitive.primitiveType] - // = primitive.dev_verticesOut[primitive.dev_indices[iid]]; - //} + //int pid; // id for cur primitives vector + //if (primitive.primitiveMode == TINYGLTF_MODE_TRIANGLES) { + // pid = iid / (int)primitive.primitiveType; + // dev_primitives[pid + curPrimitiveBeginId].v[iid % (int)primitive.primitiveType] + // = primitive.dev_verticesOut[primitive.dev_indices[iid]]; + //} - // TODO: other primitive types (point, line) - } - + // TODO: other primitive types (point, line) + } } - /** * Perform rasterization. */ -void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const glm::mat3 MV_normal) { - int sideLength2d = 8; - dim3 blockSize2d(sideLength2d, sideLength2d); - dim3 blockCount2d((width - 1) / blockSize2d.x + 1, - (height - 1) / blockSize2d.y + 1); - - // Execute your rasterization pipeline here - // (See README for rasterization pipeline outline.) - - // Vertex Process & primitive assembly - { - curPrimitiveBeginId = 0; - dim3 numThreadsPerBlock(128); - - auto it = mesh2PrimitivesMap.begin(); - auto itEnd = mesh2PrimitivesMap.end(); - - for (; it != itEnd; ++it) { - auto p = (it->second).begin(); // each primitive - auto pEnd = (it->second).end(); - for (; p != pEnd; ++p) { - dim3 numBlocksForVertices((p->numVertices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); - dim3 numBlocksForIndices((p->numIndices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); - - _vertexTransformAndAssembly << < numBlocksForVertices, numThreadsPerBlock >> >(p->numVertices, *p, MVP, MV, MV_normal, width, height); - checkCUDAError("Vertex Processing"); - cudaDeviceSynchronize(); - _primitiveAssembly << < numBlocksForIndices, numThreadsPerBlock >> > - (p->numIndices, - curPrimitiveBeginId, - dev_primitives, - *p); - checkCUDAError("Primitive Assembly"); - - curPrimitiveBeginId += p->numPrimitives; - } - } - - checkCUDAError("Vertex Processing and Primitive Assembly"); - } - - cudaMemset(dev_fragmentBuffer, 0, width * height * sizeof(Fragment)); - initDepth << > >(width, height, dev_depth); - - // TODO: rasterize - - - - // Copy depthbuffer colors into framebuffer - render << > >(width, height, dev_fragmentBuffer, dev_framebuffer); - checkCUDAError("fragment shader"); - // Copy framebuffer into OpenGL buffer for OpenGL previewing - sendImageToPBO<<>>(pbo, width, height, dev_framebuffer); - checkCUDAError("copy render result to pbo"); +void rasterize(uchar4* pbo, const glm::mat4& MVP, const glm::mat4& MV, const glm::mat3 MV_normal) +{ + int sideLength2d = 8; + dim3 blockSize2d(sideLength2d, sideLength2d); + dim3 blockCount2d((width - 1) / blockSize2d.x + 1, + (height - 1) / blockSize2d.y + 1); + + // Execute your rasterization pipeline here + // (See README for rasterization pipeline outline.) + + // Vertex Process & primitive assembly + { + curPrimitiveBeginId = 0; + dim3 numThreadsPerBlock(128); + + auto it = mesh2PrimitivesMap.begin(); + auto itEnd = mesh2PrimitivesMap.end(); + + for (; it != itEnd; ++it) + { + auto p = (it->second).begin(); // each primitive + auto pEnd = (it->second).end(); + for (; p != pEnd; ++p) + { + dim3 numBlocksForVertices((p->numVertices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); + dim3 numBlocksForIndices((p->numIndices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); + + _vertexTransformAndAssembly << < numBlocksForVertices, numThreadsPerBlock >> >( + p->numVertices, *p, MVP, MV, MV_normal, width, height); + checkCUDAError("Vertex Processing"); + cudaDeviceSynchronize(); + _primitiveAssembly << < numBlocksForIndices, numThreadsPerBlock >> > + (p->numIndices, + curPrimitiveBeginId, + dev_primitives, + *p); + checkCUDAError("Primitive Assembly"); + + curPrimitiveBeginId += p->numPrimitives; + } + } + + checkCUDAError("Vertex Processing and Primitive Assembly"); + } + + cudaMemset(dev_fragmentBuffer, 0, width * height * sizeof(Fragment)); + initDepth << > >(width, height, dev_depth); + + // TODO: rasterize + + + // Copy depthbuffer colors into framebuffer + render << > >(width, height, dev_fragmentBuffer, dev_framebuffer); + checkCUDAError("fragment shader"); + // Copy framebuffer into OpenGL buffer for OpenGL previewing + sendImageToPBO<<>>(pbo, width, height, dev_framebuffer); + checkCUDAError("copy render result to pbo"); } /** * Called once at the end of the program to free CUDA memory. */ -void rasterizeFree() { +void rasterizeFree() +{ + // deconstruct primitives attribute/indices device buffer - // deconstruct primitives attribute/indices device buffer + auto it(mesh2PrimitivesMap.begin()); + auto itEnd(mesh2PrimitivesMap.end()); + for (; it != itEnd; ++it) + { + for (auto p = it->second.begin(); p != it->second.end(); ++p) + { + cudaFree(p->dev_indices); + cudaFree(p->dev_position); + cudaFree(p->dev_normal); + cudaFree(p->dev_texcoord0); + cudaFree(p->dev_diffuseTex); - auto it(mesh2PrimitivesMap.begin()); - auto itEnd(mesh2PrimitivesMap.end()); - for (; it != itEnd; ++it) { - for (auto p = it->second.begin(); p != it->second.end(); ++p) { - cudaFree(p->dev_indices); - cudaFree(p->dev_position); - cudaFree(p->dev_normal); - cudaFree(p->dev_texcoord0); - cudaFree(p->dev_diffuseTex); + cudaFree(p->dev_verticesOut); - cudaFree(p->dev_verticesOut); - - //TODO: release other attributes and materials - } - } + //TODO: release other attributes and materials + } + } - //////////// + //////////// - cudaFree(dev_primitives); - dev_primitives = NULL; + cudaFree(dev_primitives); + dev_primitives = NULL; - cudaFree(dev_fragmentBuffer); - dev_fragmentBuffer = NULL; + cudaFree(dev_fragmentBuffer); + dev_fragmentBuffer = NULL; - cudaFree(dev_framebuffer); - dev_framebuffer = NULL; + cudaFree(dev_framebuffer); + dev_framebuffer = NULL; - cudaFree(dev_depth); - dev_depth = NULL; + cudaFree(dev_depth); + dev_depth = NULL; - checkCUDAError("rasterize Free"); + checkCUDAError("rasterize Free"); } From 2a9272dd46a46674e927e02234582bb49037c989 Mon Sep 17 00:00:00 2001 From: Vasu Mahesh Date: Mon, 15 Oct 2018 15:01:05 -0400 Subject: [PATCH 2/6] get rasterizer working --- src/rasterize.cu | 357 ++++++++++++++++++++++++++++++++++++++----- src/rasterizeTools.h | 52 ++++++- 2 files changed, 363 insertions(+), 46 deletions(-) diff --git a/src/rasterize.cu b/src/rasterize.cu index 68b65db..6d7b037 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -673,6 +673,16 @@ void _vertexTransformAndAssembly( // Then divide the pos by its w element to transform into NDC space // Finally transform x and y to viewport space + const glm::vec3 devicePosition = primitive.dev_position[vid]; + glm::vec4 screenPosition = MVP * glm::vec4(devicePosition, 1.0f); // CLIP SPACE + screenPosition /= screenPosition.w; // NDC SPACE + screenPosition.x = 0.5f * width * (1.0f + screenPosition.x); // VIEWPORT SPACE + screenPosition.y = 0.5f * height * (1.0f - screenPosition.y); + + primitive.dev_verticesOut[vid].pos = screenPosition; + primitive.dev_verticesOut[vid].eyePos = glm::vec3(MV * glm::vec4(devicePosition, 1.0f)); + primitive.dev_verticesOut[vid].eyeNor = MV_normal * primitive.dev_normal[vid]; + // TODO: Apply vertex assembly here // Assemble all attribute arraies into the primitive array } @@ -681,9 +691,7 @@ void _vertexTransformAndAssembly( static int curPrimitiveBeginId = 0; -__global__ - -void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Primitive* dev_primitives, +__global__ void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Primitive* dev_primitives, PrimitiveDevBufPointers primitive) { // index id @@ -694,18 +702,287 @@ void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Primitive* dev_ // TODO: uncomment the following code for a start // This is primitive assembly for triangles - //int pid; // id for cur primitives vector - //if (primitive.primitiveMode == TINYGLTF_MODE_TRIANGLES) { - // pid = iid / (int)primitive.primitiveType; - // dev_primitives[pid + curPrimitiveBeginId].v[iid % (int)primitive.primitiveType] - // = primitive.dev_verticesOut[primitive.dev_indices[iid]]; - //} + int pid; // id for cur primitives vector + if (primitive.primitiveMode == TINYGLTF_MODE_TRIANGLES) { + pid = iid / (int)primitive.primitiveType; + dev_primitives[pid + curPrimitiveBeginId].v[iid % (int)primitive.primitiveType] + = primitive.dev_verticesOut[primitive.dev_indices[iid]]; + } // TODO: other primitive types (point, line) } } +__device__ void ClampRange(float& actualStart, float& actualEnd, float targetStart, float targetEnd) { + if (actualStart < targetStart) { + actualStart = targetStart; + } + + if (actualEnd > targetEnd) { + actualEnd = targetEnd; + } +} + +__device__ void ClampRangeInt(int& actualStart, int& actualEnd, int targetStart, int targetEnd) { + if (actualStart < targetStart) { + actualStart = targetStart; + } + + if (actualEnd > targetEnd) { + actualEnd = targetEnd; + } +} + +__device__ bool CheckLineSegmentIntersect(glm::vec2 startPoint, glm::vec2 endPoint, int c, float slope, float* xCoord) { + /*---------- Slope 0 Check ----------*/ + if (slope > -EPSILON && slope < EPSILON) { + return false; + } + + float yIntercept = static_cast(c); + + // Incoming Line: y = c + float y1 = startPoint.y; + float y2 = endPoint.y; + + float maxY = y1 > y2 ? y1 : y2; + float minY = y1 > y2 ? y2 : y1; + + if (yIntercept <= minY) { + return false; + } + + if (yIntercept > maxY) { + return false; + } + + if (slope == INFINITY) { + (*xCoord) = startPoint.x; + return true; + } + + // y = m(x - p1.x) + p1.y + // Solve for y = c + float x = ((yIntercept - startPoint.y) / slope) + startPoint.x; + (*xCoord) = x; + return true; +} + +__device__ float GetLineSegmentSlope(const glm::vec2& startPoint, const glm::vec2& endPoint) +{ + // x2 - x1 + const float denom = endPoint[0] - startPoint[0]; + + // y2 - y1 + const float num = endPoint[1] - startPoint[1]; + + if (denom > -EPSILON && denom < EPSILON) { + return INFINITY; + } + + const float slope = num / denom; + return slope; +} + +__device__ bool BoundingBoxContains(const BoundingBox& box, float x, float y) { + if (x < box.min.x - EPSILON || x > box.max.x + EPSILON) { + return false; + } + + if (y < box.min.y - EPSILON || y > box.max.y + EPSILON) { + return false; + } + + return true; +} + + +__device__ bool CalculateIntersection(const glm::vec2& p0, + const glm::vec2& p1, + const glm::vec2& p2, + float slope0, + float slope1, + float slope2, + const BoundingBox& box, + float& startX, + float& endX, + int yIntercept + ) { + + float xResult1 = 0.0f; + float xResult2 = 0.0f; + + float x1 = 0.0f; + float x2 = 0.0f; + float x3 = 0.0f; + + const bool result1 = CheckLineSegmentIntersect(p0, p1, yIntercept, slope0, &x1); + const bool result2 = CheckLineSegmentIntersect(p1, p2, yIntercept, slope1, &x2); + const bool result3 = CheckLineSegmentIntersect(p2, p0, yIntercept, slope2, &x3); + + int pointsCount = 0; + + if (result1 && BoundingBoxContains(box, x1, yIntercept)) { + pointsCount++; + xResult1 = x1; + } + + if (result2 && BoundingBoxContains(box, x2, yIntercept)) { + pointsCount++; + + if (pointsCount == 2) { + xResult2 = x2; + } else { + xResult1 = x2; + } + } + + if (result3 && BoundingBoxContains(box, x3, yIntercept)) { + pointsCount++; + xResult2 = x3; + } + + if (pointsCount == 2) { + startX = xResult1 > xResult2 ? xResult2 : xResult1; + endX = xResult1 > xResult2 ? xResult1 : xResult2; + + startX = ceil(startX); + endX = floor(endX); + + return true; + } + + return false; +} + +__device__ void TryStoreFragment(float xCoord, int yCoord, const VertexOut& v1, const VertexOut& v2, const VertexOut& v3, const glm::vec3& baryCoordinates, int pixelIndex, int* depth, Fragment* fragmentBuffer) { + const float ratio1 = baryCoordinates.x; + const float ratio2 = baryCoordinates.y; + const float ratio3 = baryCoordinates.z; + + // pos[2] holds NDC Z [0,1] + const float fragmentDepth = 1.0f / ((ratio1 * (1.0f / v1.pos[2])) + (ratio2 * (1.0f / v2.pos[2])) +(ratio3 * (1.0f / v3.pos[2]))); + const int fragmentIntegerDepth = fragmentDepth * INT_MAX; + + //const glm::vec3 vertexOutColor = fragmentDepth * ((ratio1 * (colorP1 / z1)) + (ratio2 * (colorP2 / z2)) + + // (ratio3 * (colorP3 / z3)));; + + + //color = Triangle::BaryInterpolateColor(v1, v2, v3, targetPoint); + + //color = AddLighting(color, v1, v2, v3, targetPoint); + + //color = color * 255.0f; + + //int idx = ToLinearCoords(xCoord, yCoord); + //Fragment frag = m_fragments[idx]; + + Fragment targetFragment; + targetFragment.color = glm::vec3(1,0,0); + + const int minDepth = atomicMin(&depth[pixelIndex], fragmentIntegerDepth); + + if (minDepth != fragmentIntegerDepth) { + depth[pixelIndex] = fragmentIntegerDepth; + fragmentBuffer[pixelIndex] = targetFragment; + } + // + // if (frag.m_isActive && frag.m_depth > fragmentDepth) { + // m_fragments[idx] = Fragment(fragmentDepth, color); + // } else if (!frag.m_isActive) { + // m_fragments[idx] = Fragment(fragmentDepth, color); + // } + + // Old Draw Commands + // QRgb value = qRgb(color[0], color[1], color[2]); + // m_renderTarget.setPixel(ceil(k), ceil(rowY), value); +} + +// #define USE_LINE_SEGMENT_CHECK +#define USE_BARY_CHECK + +__global__ void _rasterizer(int numPrimitives, Primitive* dev_primitives, int screenWidth, int screenHeight, int* depth, Fragment* fragmentBuffer) +{ + // primitive id + int primtiveId = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (primtiveId >= numPrimitives) + { + + return; + } + + const Primitive& target = dev_primitives[primtiveId]; + + // if (target.primitiveType == Triangle) + // { + const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); + const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); + const glm::vec2 p2 = glm::vec2(target.v[2].pos[0], target.v[2].pos[1]); + + const BoundingBox boundingBox = getBoundingBoxForTriangle(p0, p1, p2); + +#ifdef USE_LINE_SEGMENT_CHECK + int rasterStartY = floor(boundingBox.min.y); + int rasterEndY = ceil(boundingBox.max.y); + ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); + + const float slope0 = GetLineSegmentSlope(p0, p1); + const float slope1 = GetLineSegmentSlope(p1, p2); + const float slope2 = GetLineSegmentSlope(p2, p0); + + for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { + + + float rasterStartX = 0; + float rasterEndX = 0; + + const bool result = CalculateIntersection(p0, p1, p2, slope0, slope1, slope2, boundingBox, rasterStartX, rasterEndX, yValue); + + if (!result) { + continue; + } + + ClampRange(rasterStartX, rasterEndX, 0, screenWidth - 1); + + for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) { + const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragment(xValue, yValue, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); + } + } +#endif + +#ifdef USE_BARY_CHECK + int rasterStartX = floor(boundingBox.min.x); + int rasterEndX = ceil(boundingBox.max.x); + + ClampRangeInt(rasterStartX, rasterEndX, 0, screenWidth - 1); + + for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) { + + int rasterStartY = floor(boundingBox.min.y); + int rasterEndY = ceil(boundingBox.max.y); + ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); + + for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { + + const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); + + if (!isBarycentricCoordInBounds(baryCoordinates)) + { + continue; + } + + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragment(xValue, yValue, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); + } + } +#endif +// } +} + /** * Perform rasterization. @@ -721,48 +998,48 @@ void rasterize(uchar4* pbo, const glm::mat4& MVP, const glm::mat4& MV, const glm // (See README for rasterization pipeline outline.) // Vertex Process & primitive assembly - { - curPrimitiveBeginId = 0; - dim3 numThreadsPerBlock(128); + curPrimitiveBeginId = 0; + dim3 numThreadsPerBlock(128); - auto it = mesh2PrimitivesMap.begin(); - auto itEnd = mesh2PrimitivesMap.end(); + auto it = mesh2PrimitivesMap.begin(); + auto itEnd = mesh2PrimitivesMap.end(); - for (; it != itEnd; ++it) + for (; it != itEnd; ++it) + { + auto p = (it->second).begin(); // each primitive + auto pEnd = (it->second).end(); + for (; p != pEnd; ++p) { - auto p = (it->second).begin(); // each primitive - auto pEnd = (it->second).end(); - for (; p != pEnd; ++p) - { - dim3 numBlocksForVertices((p->numVertices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); - dim3 numBlocksForIndices((p->numIndices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); - - _vertexTransformAndAssembly << < numBlocksForVertices, numThreadsPerBlock >> >( - p->numVertices, *p, MVP, MV, MV_normal, width, height); - checkCUDAError("Vertex Processing"); - cudaDeviceSynchronize(); - _primitiveAssembly << < numBlocksForIndices, numThreadsPerBlock >> > - (p->numIndices, - curPrimitiveBeginId, - dev_primitives, - *p); - checkCUDAError("Primitive Assembly"); - - curPrimitiveBeginId += p->numPrimitives; - } + dim3 numBlocksForVertices((p->numVertices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); + dim3 numBlocksForIndices((p->numIndices + numThreadsPerBlock.x - 1) / numThreadsPerBlock.x); + + _vertexTransformAndAssembly <<< numBlocksForVertices, numThreadsPerBlock >>>( + p->numVertices, *p, MVP, MV, MV_normal, width, height); + checkCUDAError("Vertex Processing"); + cudaDeviceSynchronize(); + _primitiveAssembly <<< numBlocksForIndices, numThreadsPerBlock >>> + (p->numIndices, + curPrimitiveBeginId, + dev_primitives, + *p); + checkCUDAError("Primitive Assembly"); + + curPrimitiveBeginId += p->numPrimitives; } - - checkCUDAError("Vertex Processing and Primitive Assembly"); } + checkCUDAError("Vertex Processing and Primitive Assembly"); + cudaMemset(dev_fragmentBuffer, 0, width * height * sizeof(Fragment)); - initDepth << > >(width, height, dev_depth); + initDepth <<>>(width, height, dev_depth); // TODO: rasterize - + const int blockSize1d = 512; + dim3 numRasterizeBlocks = (curPrimitiveBeginId + blockSize1d - 1) / blockSize1d; + _rasterizer <<< numRasterizeBlocks, blockSize1d >>> (curPrimitiveBeginId, dev_primitives, width, height, dev_depth, dev_fragmentBuffer); // Copy depthbuffer colors into framebuffer - render << > >(width, height, dev_fragmentBuffer, dev_framebuffer); + render <<< blockCount2d, blockSize2d >>>(width, height, dev_fragmentBuffer, dev_framebuffer); checkCUDAError("fragment shader"); // Copy framebuffer into OpenGL buffer for OpenGL previewing sendImageToPBO<<>>(pbo, width, height, dev_framebuffer); diff --git a/src/rasterizeTools.h b/src/rasterizeTools.h index 46c701e..1f61117 100644 --- a/src/rasterizeTools.h +++ b/src/rasterizeTools.h @@ -17,6 +17,11 @@ struct AABB { glm::vec3 max; }; +struct BoundingBox { + glm::vec2 min; + glm::vec2 max; +}; + /** * Multiplies a glm::mat4 matrix and a vec4. */ @@ -33,16 +38,28 @@ __host__ __device__ static AABB getAABBForTriangle(const glm::vec3 tri[3]) { AABB aabb; aabb.min = glm::vec3( - min(min(tri[0].x, tri[1].x), tri[2].x), - min(min(tri[0].y, tri[1].y), tri[2].y), - min(min(tri[0].z, tri[1].z), tri[2].z)); + glm::min(glm::min(tri[0].x, tri[1].x), tri[2].x), + glm::min(glm::min(tri[0].y, tri[1].y), tri[2].y), + glm::min(glm::min(tri[0].z, tri[1].z), tri[2].z)); aabb.max = glm::vec3( - max(max(tri[0].x, tri[1].x), tri[2].x), - max(max(tri[0].y, tri[1].y), tri[2].y), - max(max(tri[0].z, tri[1].z), tri[2].z)); + glm::max(glm::max(tri[0].x, tri[1].x), tri[2].x), + glm::max(glm::max(tri[0].y, tri[1].y), tri[2].y), + glm::max(glm::max(tri[0].z, tri[1].z), tri[2].z)); return aabb; } +__host__ __device__ static +BoundingBox getBoundingBoxForTriangle(const glm::vec2 p0, const glm::vec2 p1, const glm::vec2 p2) { + BoundingBox aabb; + aabb.min = glm::vec2( + glm::min(glm::min(p0.x, p1.x), p2.x), + glm::min(glm::min(p0.y, p1.y), p2.y)); + aabb.max = glm::vec2( + glm::max(glm::max(p0.x, p1.x), p2.x), + glm::max(glm::max(p0.y, p1.y), p2.y)); + return aabb; +} + // CHECKITOUT /** * Calculate the signed area of a given triangle. @@ -52,6 +69,11 @@ float calculateSignedArea(const glm::vec3 tri[3]) { return 0.5 * ((tri[2].x - tri[0].x) * (tri[1].y - tri[0].y) - (tri[1].x - tri[0].x) * (tri[2].y - tri[0].y)); } +__host__ __device__ static +float calculateSignedArea(const glm::vec2 p0, const glm::vec2 p1, const glm::vec2 p2) { + return 0.5 * ((p2.x - p0.x) * (p1.y - p0.y) - (p1.x - p0.x) * (p2.y - p0.y)); +} + // CHECKITOUT /** * Helper function for calculating barycentric coordinates. @@ -65,6 +87,14 @@ float calculateBarycentricCoordinateValue(glm::vec2 a, glm::vec2 b, glm::vec2 c, return calculateSignedArea(baryTri) / calculateSignedArea(tri); } +__host__ __device__ static float calculateBarycentricCoordinateValue(glm::vec2 a, glm::vec2 b, glm::vec2 c, float totalSignedArea) { + glm::vec3 baryTri[3]; + baryTri[0] = glm::vec3(a, 0); + baryTri[1] = glm::vec3(b, 0); + baryTri[2] = glm::vec3(c, 0); + return calculateSignedArea(baryTri) / totalSignedArea; +} + // CHECKITOUT /** * Calculate barycentric coordinates. @@ -77,6 +107,16 @@ glm::vec3 calculateBarycentricCoordinate(const glm::vec3 tri[3], glm::vec2 point return glm::vec3(alpha, beta, gamma); } +__host__ __device__ static +glm::vec3 calculateBarycentricCoordinate(const glm::vec2 p0, const glm::vec2 p1, const glm::vec2 p2, glm::vec2 point) { + const float totalArea = calculateSignedArea(p0, p1, p2); + + const float beta = calculateBarycentricCoordinateValue(glm::vec2(p0.x, p0.y), point, glm::vec2(p2.x, p2.y), totalArea); + const float gamma = calculateBarycentricCoordinateValue(glm::vec2(p0.x, p0.y), glm::vec2(p1.x, p1.y), point, totalArea); + const float alpha = 1.0 - beta - gamma; + return glm::vec3(alpha, beta, gamma); +} + // CHECKITOUT /** * Check if a barycentric coordinate is within the boundaries of a triangle. From ba9b85ec4e3037135c67d4a03d164c23ac0db042 Mon Sep 17 00:00:00 2001 From: Vasu Mahesh Date: Mon, 15 Oct 2018 15:15:18 -0400 Subject: [PATCH 3/6] add: lambertian diffuse --- src/rasterize.cu | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/rasterize.cu b/src/rasterize.cu index 6d7b037..e649851 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -66,8 +66,11 @@ namespace // The attributes listed below might be useful, // but always feel free to modify on your own - // glm::vec3 eyePos; // eye space position used for shading - // glm::vec3 eyeNor; + glm::vec3 pos; + glm::vec3 normal; + glm::vec2 uv; + + // VertexAttributeTexcoord texcoord0; // TextureData* dev_diffuseTex; // ... @@ -153,9 +156,22 @@ void render(int w, int h, Fragment* fragmentBuffer, glm::vec3* framebuffer) if (x < w && y < h) { - framebuffer[index] = fragmentBuffer[index].color; + const Fragment& frag = fragmentBuffer[index]; + + float ambientTerm = 0.2f; + + glm::vec3 fragColor = frag.color; + + glm::vec3 lightVector = glm::normalize(glm::vec3(glm::vec3(5, 5, 0) - frag.pos)); + + float diffuseTerm = glm::dot(lightVector, glm::normalize(frag.normal)); + diffuseTerm = glm::clamp(diffuseTerm, 0.0f, 1.0f); + + framebuffer[index] = (ambientTerm + diffuseTerm) * fragColor; // TODO: add your fragment shader code here + + } } @@ -865,9 +881,11 @@ __device__ void TryStoreFragment(float xCoord, int yCoord, const VertexOut& v1, const float fragmentDepth = 1.0f / ((ratio1 * (1.0f / v1.pos[2])) + (ratio2 * (1.0f / v2.pos[2])) +(ratio3 * (1.0f / v3.pos[2]))); const int fragmentIntegerDepth = fragmentDepth * INT_MAX; - //const glm::vec3 vertexOutColor = fragmentDepth * ((ratio1 * (colorP1 / z1)) + (ratio2 * (colorP2 / z2)) + - // (ratio3 * (colorP3 / z3)));; + // const glm::vec3 vertexOutColor = fragmentDepth * ((ratio1 * (colorP1 / v1.pos[2])) + (ratio2 * (colorP2 / v2.pos[2])) + (ratio3 * (colorP3 / v3.pos[2])));; + const glm::vec2 interpolatedUV = fragmentDepth * ((ratio1 * (v1.texcoord0 / v1.pos[2])) + (ratio2 * (v2.texcoord0 / v2.pos[2])) + (ratio3 * (v3.texcoord0 / v3.pos[2]))); + const glm::vec3 interpolatedEyeNormal = fragmentDepth * ((ratio1 * (v1.eyeNor / v1.pos[2])) + (ratio2 * (v2.eyeNor / v2.pos[2])) + (ratio3 * (v3.eyeNor / v3.pos[2]))); + const glm::vec3 interpolatedEyePos = fragmentDepth * ((ratio1 * (v1.eyePos / v1.pos[2])) + (ratio2 * (v2.eyePos / v2.pos[2])) + (ratio3 * (v3.eyePos / v3.pos[2]))); //color = Triangle::BaryInterpolateColor(v1, v2, v3, targetPoint); @@ -880,10 +898,13 @@ __device__ void TryStoreFragment(float xCoord, int yCoord, const VertexOut& v1, Fragment targetFragment; targetFragment.color = glm::vec3(1,0,0); + targetFragment.uv = interpolatedUV; + targetFragment.normal = interpolatedEyeNormal; + targetFragment.pos = interpolatedEyePos; const int minDepth = atomicMin(&depth[pixelIndex], fragmentIntegerDepth); - if (minDepth != fragmentIntegerDepth) { + if (minDepth > fragmentIntegerDepth) { depth[pixelIndex] = fragmentIntegerDepth; fragmentBuffer[pixelIndex] = targetFragment; } From 11da8d91225fa0b30cfde29ede82dcac9e650815 Mon Sep 17 00:00:00 2001 From: Vasu Mahesh Date: Mon, 15 Oct 2018 15:45:06 -0400 Subject: [PATCH 4/6] add: texture support --- src/rasterize.cu | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/rasterize.cu b/src/rasterize.cu index e649851..1f482ef 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -48,8 +48,8 @@ namespace // glm::vec3 col; glm::vec2 texcoord0; TextureData* dev_diffuseTex = NULL; - // int texWidth, texHeight; - // ... + int diffuseTexWidth; + int diffuseTexHeight; }; struct Primitive @@ -72,8 +72,9 @@ namespace // VertexAttributeTexcoord texcoord0; - // TextureData* dev_diffuseTex; - // ... + TextureData* dev_diffuseTex; + int diffuseTexWidth; + int diffuseTexHeight; }; struct PrimitiveDevBufPointers @@ -161,6 +162,18 @@ void render(int w, int h, Fragment* fragmentBuffer, glm::vec3* framebuffer) float ambientTerm = 0.2f; glm::vec3 fragColor = frag.color; + if (frag.dev_diffuseTex) { + const int pixelX = glm::floor(frag.uv.x * frag.diffuseTexWidth); + const int pixelY = glm::floor(frag.uv.y * frag.diffuseTexHeight); + const int linearCoordinate = pixelX + (frag.diffuseTexWidth * pixelY); + + const int strideFormat = 3; + const uint8_t red = *((uint8_t*)&frag.dev_diffuseTex[strideFormat * linearCoordinate]); + const uint8_t green = *((uint8_t*)&frag.dev_diffuseTex[strideFormat * linearCoordinate + 1]); + const uint8_t blue = *((uint8_t*)&frag.dev_diffuseTex[strideFormat * linearCoordinate + 2]); + + fragColor = glm::vec3(red / 255.0f, green / 255.0f, blue / 255.0f); + } glm::vec3 lightVector = glm::normalize(glm::vec3(glm::vec3(5, 5, 0) - frag.pos)); @@ -698,6 +711,10 @@ void _vertexTransformAndAssembly( primitive.dev_verticesOut[vid].pos = screenPosition; primitive.dev_verticesOut[vid].eyePos = glm::vec3(MV * glm::vec4(devicePosition, 1.0f)); primitive.dev_verticesOut[vid].eyeNor = MV_normal * primitive.dev_normal[vid]; + primitive.dev_verticesOut[vid].dev_diffuseTex = primitive.dev_diffuseTex; + primitive.dev_verticesOut[vid].texcoord0 = primitive.dev_texcoord0[vid]; + primitive.dev_verticesOut[vid].diffuseTexWidth = primitive.diffuseTexWidth; + primitive.dev_verticesOut[vid].diffuseTexHeight = primitive.diffuseTexHeight; // TODO: Apply vertex assembly here // Assemble all attribute arraies into the primitive array @@ -872,7 +889,7 @@ __device__ bool CalculateIntersection(const glm::vec2& p0, return false; } -__device__ void TryStoreFragment(float xCoord, int yCoord, const VertexOut& v1, const VertexOut& v2, const VertexOut& v3, const glm::vec3& baryCoordinates, int pixelIndex, int* depth, Fragment* fragmentBuffer) { +__device__ void TryStoreFragment(const Primitive& target, float xCoord, int yCoord, int screenWidth, int screenHeight, const VertexOut& v1, const VertexOut& v2, const VertexOut& v3, const glm::vec3& baryCoordinates, int pixelIndex, int* depth, Fragment* fragmentBuffer) { const float ratio1 = baryCoordinates.x; const float ratio2 = baryCoordinates.y; const float ratio3 = baryCoordinates.z; @@ -897,10 +914,13 @@ __device__ void TryStoreFragment(float xCoord, int yCoord, const VertexOut& v1, //Fragment frag = m_fragments[idx]; Fragment targetFragment; - targetFragment.color = glm::vec3(1,0,0); + targetFragment.color = glm::vec3(1, 0, 0); targetFragment.uv = interpolatedUV; targetFragment.normal = interpolatedEyeNormal; targetFragment.pos = interpolatedEyePos; + targetFragment.dev_diffuseTex = v1.dev_diffuseTex; + targetFragment.diffuseTexWidth = v1.diffuseTexWidth; + targetFragment.diffuseTexHeight = v1.diffuseTexHeight; const int minDepth = atomicMin(&depth[pixelIndex], fragmentIntegerDepth); @@ -970,7 +990,7 @@ __global__ void _rasterizer(int numPrimitives, Primitive* dev_primitives, int sc for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) { const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); const int pixelIndex = xValue + (yValue * screenWidth); - TryStoreFragment(xValue, yValue, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); + TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); } } #endif @@ -997,7 +1017,7 @@ __global__ void _rasterizer(int numPrimitives, Primitive* dev_primitives, int sc } const int pixelIndex = xValue + (yValue * screenWidth); - TryStoreFragment(xValue, yValue, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); + TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); } } #endif From e1349fe192d9a06a9a8704f8ced948b652f48115 Mon Sep 17 00:00:00 2001 From: Vasu Mahesh Date: Tue, 16 Oct 2018 14:06:17 -0400 Subject: [PATCH 5/6] add : SSAA --- gltfs/triangle/triangle_lines.gltf | 263 ++ gltfs/triangle/triangle_points.gltf | 263 ++ src/rasterize.cu | 468 ++- src/rasterizeTools.h | 12 + util/stb_image_write.h | 1831 ++++++++++ util/tiny_gltf.h | 4791 +++++++++++++++++++++++++++ 6 files changed, 7519 insertions(+), 109 deletions(-) create mode 100644 gltfs/triangle/triangle_lines.gltf create mode 100644 gltfs/triangle/triangle_points.gltf create mode 100644 util/stb_image_write.h create mode 100644 util/tiny_gltf.h diff --git a/gltfs/triangle/triangle_lines.gltf b/gltfs/triangle/triangle_lines.gltf new file mode 100644 index 0000000..c32fe4d --- /dev/null +++ b/gltfs/triangle/triangle_lines.gltf @@ -0,0 +1,263 @@ +{ + "accessors": { + "accessor_index_0": { + "bufferView": "bufferView_1", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5123, + "count": 3, + "type": "SCALAR", + "min": [ + 0 + ], + "max": [ + 2 + ] + }, + "accessor_position": { + "bufferView": "bufferView_0", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5126, + "count": 3, + "min": [ + 0, + 0, + 0 + ], + "max": [ + 0.5, + 1, + 0 + ], + "type": "VEC3" + }, + "accessor_normal": { + "bufferView": "bufferView_0", + "byteOffset": 36, + "byteStride": 0, + "componentType": 5126, + "count": 3, + "type": "VEC3", + "min": [ + 0, + 0, + 1 + ], + "max": [ + 0, + 0, + 1 + ] + } + }, + "asset": { + "generator": "OBJ2GLTF", + "premultipliedAlpha": true, + "profile": { + "api": "WebGL", + "version": "1.0" + }, + "version": "1.0" + }, + "buffers": { + "buffer_0": { + "type": "arraybuffer", + "byteLength": 78, + "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAABAAIA" + } + }, + "bufferViews": { + "bufferView_0": { + "buffer": "buffer_0", + "byteLength": 72, + "byteOffset": 0, + "target": 34962 + }, + "bufferView_1": { + "buffer": "buffer_0", + "byteLength": 6, + "byteOffset": 72, + "target": 34963 + } + }, + "images": {}, + "materials": { + "material_czmDefaultMat": { + "name": "czmDefaultMat", + "extensions": {}, + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0, + 0, + 0, + 1 + ], + "shininess": 0, + "transparency": 1 + }, + "technique": "technique0" + } + }, + "meshes": { + "mesh_input": { + "name": "input", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_position", + "NORMAL": "accessor_normal" + }, + "indices": "accessor_index_0", + "material": "material_czmDefaultMat", + "mode": 1 + } + ] + } + }, + "nodes": { + "rootNode": { + "children": [], + "meshes": [ + "mesh_input" + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + } + }, + "samplers": {}, + "scene": "scene_input", + "scenes": { + "scene_input": { + "nodes": [ + "rootNode" + ] + } + }, + "textures": {}, + "extensionsUsed": [], + "animations": {}, + "cameras": {}, + "techniques": { + "technique0": { + "attributes": { + "a_position": "position", + "a_normal": "normal" + }, + "parameters": { + "modelViewMatrix": { + "semantic": "MODELVIEW", + "type": 35676 + }, + "projectionMatrix": { + "semantic": "PROJECTION", + "type": 35676 + }, + "normalMatrix": { + "semantic": "MODELVIEWINVERSETRANSPOSE", + "type": 35675 + }, + "ambient": { + "type": 35666 + }, + "diffuse": { + "type": 35666 + }, + "emission": { + "type": 35666 + }, + "specular": { + "type": 35666 + }, + "shininess": { + "type": 5126 + }, + "transparency": { + "type": 5126 + }, + "position": { + "semantic": "POSITION", + "type": 35665 + }, + "normal": { + "semantic": "NORMAL", + "type": 35665 + } + }, + "program": "program0", + "states": { + "enable": [ + 2884, + 2929 + ] + }, + "uniforms": { + "u_modelViewMatrix": "modelViewMatrix", + "u_projectionMatrix": "projectionMatrix", + "u_normalMatrix": "normalMatrix", + "u_ambient": "ambient", + "u_diffuse": "diffuse", + "u_emission": "emission", + "u_specular": "specular", + "u_shininess": "shininess", + "u_transparency": "transparency" + } + } + }, + "programs": { + "program0": { + "attributes": [ + "a_position", + "a_normal" + ], + "fragmentShader": "fragmentShader0", + "vertexShader": "vertexShader0" + } + }, + "shaders": { + "vertexShader0": { + "type": 35633, + "uri": "data:text/plain;base64,cHJlY2lzaW9uIGhpZ2hwIGZsb2F0Owp1bmlmb3JtIG1hdDQgdV9tb2RlbFZpZXdNYXRyaXg7CnVuaWZvcm0gbWF0NCB1X3Byb2plY3Rpb25NYXRyaXg7CnVuaWZvcm0gbWF0MyB1X25vcm1hbE1hdHJpeDsKYXR0cmlidXRlIHZlYzMgYV9wb3NpdGlvbjsKdmFyeWluZyB2ZWMzIHZfcG9zaXRpb25FQzsKYXR0cmlidXRlIHZlYzMgYV9ub3JtYWw7CnZhcnlpbmcgdmVjMyB2X25vcm1hbDsKdm9pZCBtYWluKHZvaWQpIHsKICB2ZWM0IHBvcyA9IHVfbW9kZWxWaWV3TWF0cml4ICogdmVjNChhX3Bvc2l0aW9uLDEuMCk7CiAgdl9wb3NpdGlvbkVDID0gcG9zLnh5ejsKICBnbF9Qb3NpdGlvbiA9IHVfcHJvamVjdGlvbk1hdHJpeCAqIHBvczsKICB2X25vcm1hbCA9IHVfbm9ybWFsTWF0cml4ICogYV9ub3JtYWw7Cn0K" + }, + "fragmentShader0": { + "type": 35632, + "uri": "data:text/plain;base64,cHJlY2lzaW9uIGhpZ2hwIGZsb2F0Owp1bmlmb3JtIHZlYzQgdV9hbWJpZW50Owp1bmlmb3JtIHZlYzQgdV9kaWZmdXNlOwp1bmlmb3JtIHZlYzQgdV9lbWlzc2lvbjsKdW5pZm9ybSB2ZWM0IHVfc3BlY3VsYXI7CnVuaWZvcm0gZmxvYXQgdV9zaGluaW5lc3M7CnVuaWZvcm0gZmxvYXQgdV90cmFuc3BhcmVuY3k7CnZhcnlpbmcgdmVjMyB2X3Bvc2l0aW9uRUM7CnZhcnlpbmcgdmVjMyB2X25vcm1hbDsKdm9pZCBtYWluKHZvaWQpIHsKICB2ZWMzIG5vcm1hbCA9IG5vcm1hbGl6ZSh2X25vcm1hbCk7CiAgdmVjNCBkaWZmdXNlID0gdV9kaWZmdXNlOwogIHZlYzMgZGlmZnVzZUxpZ2h0ID0gdmVjMygwLjAsIDAuMCwgMC4wKTsKICB2ZWMzIHNwZWN1bGFyID0gdV9zcGVjdWxhci5yZ2I7CiAgdmVjMyBzcGVjdWxhckxpZ2h0ID0gdmVjMygwLjAsIDAuMCwgMC4wKTsKICB2ZWMzIGVtaXNzaW9uID0gdV9lbWlzc2lvbi5yZ2I7CiAgdmVjMyBhbWJpZW50ID0gdV9hbWJpZW50LnJnYjsKICB2ZWMzIHZpZXdEaXIgPSAtbm9ybWFsaXplKHZfcG9zaXRpb25FQyk7CiAgdmVjMyBhbWJpZW50TGlnaHQgPSB2ZWMzKDAuMCwgMC4wLCAwLjApOwogIGFtYmllbnRMaWdodCArPSB2ZWMzKDAuMiwgMC4yLCAwLjIpOwogIHZlYzMgbCA9IHZlYzMoMC4wLCAwLjAsIDEuMCk7CiAgZGlmZnVzZUxpZ2h0ICs9IHZlYzMoMS4wLCAxLjAsIDEuMCkgKiBtYXgoZG90KG5vcm1hbCxsKSwgMC4pOwogIHZlYzMgaCA9IG5vcm1hbGl6ZShsICsgdmlld0Rpcik7CiAgZmxvYXQgc3BlY3VsYXJJbnRlbnNpdHkgPSBtYXgoMC4sIHBvdyhtYXgoZG90KG5vcm1hbCwgaCksIDAuKSwgdV9zaGluaW5lc3MpKTsKICBzcGVjdWxhckxpZ2h0ICs9IHZlYzMoMS4wLCAxLjAsIDEuMCkgKiBzcGVjdWxhckludGVuc2l0eTsKICB2ZWMzIGNvbG9yID0gdmVjMygwLjAsIDAuMCwgMC4wKTsKICBjb2xvciArPSBkaWZmdXNlLnJnYiAqIGRpZmZ1c2VMaWdodDsKICBjb2xvciArPSBzcGVjdWxhciAqIHNwZWN1bGFyTGlnaHQ7CiAgY29sb3IgKz0gZW1pc3Npb247CiAgY29sb3IgKz0gYW1iaWVudCAqIGFtYmllbnRMaWdodDsKICBnbF9GcmFnQ29sb3IgPSB2ZWM0KGNvbG9yICogZGlmZnVzZS5hLCBkaWZmdXNlLmEgKiB1X3RyYW5zcGFyZW5jeSk7Cn0K" + } + }, + "skins": {}, + "extensions": {} +} diff --git a/gltfs/triangle/triangle_points.gltf b/gltfs/triangle/triangle_points.gltf new file mode 100644 index 0000000..38a7e50 --- /dev/null +++ b/gltfs/triangle/triangle_points.gltf @@ -0,0 +1,263 @@ +{ + "accessors": { + "accessor_index_0": { + "bufferView": "bufferView_1", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5123, + "count": 3, + "type": "SCALAR", + "min": [ + 0 + ], + "max": [ + 2 + ] + }, + "accessor_position": { + "bufferView": "bufferView_0", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5126, + "count": 3, + "min": [ + 0, + 0, + 0 + ], + "max": [ + 0.5, + 1, + 0 + ], + "type": "VEC3" + }, + "accessor_normal": { + "bufferView": "bufferView_0", + "byteOffset": 36, + "byteStride": 0, + "componentType": 5126, + "count": 3, + "type": "VEC3", + "min": [ + 0, + 0, + 1 + ], + "max": [ + 0, + 0, + 1 + ] + } + }, + "asset": { + "generator": "OBJ2GLTF", + "premultipliedAlpha": true, + "profile": { + "api": "WebGL", + "version": "1.0" + }, + "version": "1.0" + }, + "buffers": { + "buffer_0": { + "type": "arraybuffer", + "byteLength": 78, + "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAABAAIA" + } + }, + "bufferViews": { + "bufferView_0": { + "buffer": "buffer_0", + "byteLength": 72, + "byteOffset": 0, + "target": 34962 + }, + "bufferView_1": { + "buffer": "buffer_0", + "byteLength": 6, + "byteOffset": 72, + "target": 34963 + } + }, + "images": {}, + "materials": { + "material_czmDefaultMat": { + "name": "czmDefaultMat", + "extensions": {}, + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0, + 0, + 0, + 1 + ], + "shininess": 0, + "transparency": 1 + }, + "technique": "technique0" + } + }, + "meshes": { + "mesh_input": { + "name": "input", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_position", + "NORMAL": "accessor_normal" + }, + "indices": "accessor_index_0", + "material": "material_czmDefaultMat", + "mode": 0 + } + ] + } + }, + "nodes": { + "rootNode": { + "children": [], + "meshes": [ + "mesh_input" + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + } + }, + "samplers": {}, + "scene": "scene_input", + "scenes": { + "scene_input": { + "nodes": [ + "rootNode" + ] + } + }, + "textures": {}, + "extensionsUsed": [], + "animations": {}, + "cameras": {}, + "techniques": { + "technique0": { + "attributes": { + "a_position": "position", + "a_normal": "normal" + }, + "parameters": { + "modelViewMatrix": { + "semantic": "MODELVIEW", + "type": 35676 + }, + "projectionMatrix": { + "semantic": "PROJECTION", + "type": 35676 + }, + "normalMatrix": { + "semantic": "MODELVIEWINVERSETRANSPOSE", + "type": 35675 + }, + "ambient": { + "type": 35666 + }, + "diffuse": { + "type": 35666 + }, + "emission": { + "type": 35666 + }, + "specular": { + "type": 35666 + }, + "shininess": { + "type": 5126 + }, + "transparency": { + "type": 5126 + }, + "position": { + "semantic": "POSITION", + "type": 35665 + }, + "normal": { + "semantic": "NORMAL", + "type": 35665 + } + }, + "program": "program0", + "states": { + "enable": [ + 2884, + 2929 + ] + }, + "uniforms": { + "u_modelViewMatrix": "modelViewMatrix", + "u_projectionMatrix": "projectionMatrix", + "u_normalMatrix": "normalMatrix", + "u_ambient": "ambient", + "u_diffuse": "diffuse", + "u_emission": "emission", + "u_specular": "specular", + "u_shininess": "shininess", + "u_transparency": "transparency" + } + } + }, + "programs": { + "program0": { + "attributes": [ + "a_position", + "a_normal" + ], + "fragmentShader": "fragmentShader0", + "vertexShader": "vertexShader0" + } + }, + "shaders": { + "vertexShader0": { + "type": 35633, + "uri": "data:text/plain;base64,cHJlY2lzaW9uIGhpZ2hwIGZsb2F0Owp1bmlmb3JtIG1hdDQgdV9tb2RlbFZpZXdNYXRyaXg7CnVuaWZvcm0gbWF0NCB1X3Byb2plY3Rpb25NYXRyaXg7CnVuaWZvcm0gbWF0MyB1X25vcm1hbE1hdHJpeDsKYXR0cmlidXRlIHZlYzMgYV9wb3NpdGlvbjsKdmFyeWluZyB2ZWMzIHZfcG9zaXRpb25FQzsKYXR0cmlidXRlIHZlYzMgYV9ub3JtYWw7CnZhcnlpbmcgdmVjMyB2X25vcm1hbDsKdm9pZCBtYWluKHZvaWQpIHsKICB2ZWM0IHBvcyA9IHVfbW9kZWxWaWV3TWF0cml4ICogdmVjNChhX3Bvc2l0aW9uLDEuMCk7CiAgdl9wb3NpdGlvbkVDID0gcG9zLnh5ejsKICBnbF9Qb3NpdGlvbiA9IHVfcHJvamVjdGlvbk1hdHJpeCAqIHBvczsKICB2X25vcm1hbCA9IHVfbm9ybWFsTWF0cml4ICogYV9ub3JtYWw7Cn0K" + }, + "fragmentShader0": { + "type": 35632, + "uri": "data:text/plain;base64,cHJlY2lzaW9uIGhpZ2hwIGZsb2F0Owp1bmlmb3JtIHZlYzQgdV9hbWJpZW50Owp1bmlmb3JtIHZlYzQgdV9kaWZmdXNlOwp1bmlmb3JtIHZlYzQgdV9lbWlzc2lvbjsKdW5pZm9ybSB2ZWM0IHVfc3BlY3VsYXI7CnVuaWZvcm0gZmxvYXQgdV9zaGluaW5lc3M7CnVuaWZvcm0gZmxvYXQgdV90cmFuc3BhcmVuY3k7CnZhcnlpbmcgdmVjMyB2X3Bvc2l0aW9uRUM7CnZhcnlpbmcgdmVjMyB2X25vcm1hbDsKdm9pZCBtYWluKHZvaWQpIHsKICB2ZWMzIG5vcm1hbCA9IG5vcm1hbGl6ZSh2X25vcm1hbCk7CiAgdmVjNCBkaWZmdXNlID0gdV9kaWZmdXNlOwogIHZlYzMgZGlmZnVzZUxpZ2h0ID0gdmVjMygwLjAsIDAuMCwgMC4wKTsKICB2ZWMzIHNwZWN1bGFyID0gdV9zcGVjdWxhci5yZ2I7CiAgdmVjMyBzcGVjdWxhckxpZ2h0ID0gdmVjMygwLjAsIDAuMCwgMC4wKTsKICB2ZWMzIGVtaXNzaW9uID0gdV9lbWlzc2lvbi5yZ2I7CiAgdmVjMyBhbWJpZW50ID0gdV9hbWJpZW50LnJnYjsKICB2ZWMzIHZpZXdEaXIgPSAtbm9ybWFsaXplKHZfcG9zaXRpb25FQyk7CiAgdmVjMyBhbWJpZW50TGlnaHQgPSB2ZWMzKDAuMCwgMC4wLCAwLjApOwogIGFtYmllbnRMaWdodCArPSB2ZWMzKDAuMiwgMC4yLCAwLjIpOwogIHZlYzMgbCA9IHZlYzMoMC4wLCAwLjAsIDEuMCk7CiAgZGlmZnVzZUxpZ2h0ICs9IHZlYzMoMS4wLCAxLjAsIDEuMCkgKiBtYXgoZG90KG5vcm1hbCxsKSwgMC4pOwogIHZlYzMgaCA9IG5vcm1hbGl6ZShsICsgdmlld0Rpcik7CiAgZmxvYXQgc3BlY3VsYXJJbnRlbnNpdHkgPSBtYXgoMC4sIHBvdyhtYXgoZG90KG5vcm1hbCwgaCksIDAuKSwgdV9zaGluaW5lc3MpKTsKICBzcGVjdWxhckxpZ2h0ICs9IHZlYzMoMS4wLCAxLjAsIDEuMCkgKiBzcGVjdWxhckludGVuc2l0eTsKICB2ZWMzIGNvbG9yID0gdmVjMygwLjAsIDAuMCwgMC4wKTsKICBjb2xvciArPSBkaWZmdXNlLnJnYiAqIGRpZmZ1c2VMaWdodDsKICBjb2xvciArPSBzcGVjdWxhciAqIHNwZWN1bGFyTGlnaHQ7CiAgY29sb3IgKz0gZW1pc3Npb247CiAgY29sb3IgKz0gYW1iaWVudCAqIGFtYmllbnRMaWdodDsKICBnbF9GcmFnQ29sb3IgPSB2ZWM0KGNvbG9yICogZGlmZnVzZS5hLCBkaWZmdXNlLmEgKiB1X3RyYW5zcGFyZW5jeSk7Cn0K" + } + }, + "skins": {}, + "extensions": {} +} diff --git a/src/rasterize.cu b/src/rasterize.cu index 1f482ef..d10842f 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -45,7 +45,7 @@ namespace glm::vec3 eyePos; // eye space position used for shading glm::vec3 eyeNor; // eye space normal used for shading, cuz normal will go wrong after perspective transformation - // glm::vec3 col; + glm::vec3 col; glm::vec2 texcoord0; TextureData* dev_diffuseTex = NULL; int diffuseTexWidth; @@ -108,10 +108,17 @@ namespace static std::map> mesh2PrimitivesMap; +static PrimitiveType GLOBAL_DRAW_MODE = PrimitiveType::Triangle; static int width = 0; static int height = 0; +static int baseWidth = 0; +static int baseHeight = 0; + +static const int ALIASING_VALUE = 2; +static const glm::mat3 ALIASING_SCALE = glm::mat3(glm::scale(glm::mat4(), glm::vec3(ALIASING_VALUE))); + static int totalNumPrimitives = 0; static Primitive* dev_primitives = NULL; static Fragment* dev_fragmentBuffer = NULL; @@ -124,18 +131,40 @@ static int* dev_depth = NULL; // you might need this buffer when doing depth tes */ __global__ -void sendImageToPBO(uchar4* pbo, int w, int h, glm::vec3* image) +void sendImageToPBO(uchar4* pbo, int baseW, int baseH, int alias, glm::vec3* image) { - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; - int index = x + (y * w); + const int row = (blockIdx.x * blockDim.x) + threadIdx.x; + const int col = (blockIdx.y * blockDim.y) + threadIdx.y; + const int index = row + (col * baseW); - if (x < w && y < h) + if (row < baseW && col < baseH) { - glm::vec3 color; - color.x = glm::clamp(image[index].x, 0.0f, 1.0f) * 255.0; - color.y = glm::clamp(image[index].y, 0.0f, 1.0f) * 255.0; - color.z = glm::clamp(image[index].z, 0.0f, 1.0f) * 255.0; + const int startX = (row * alias); + const int startY = (col * alias); + + const int totalPartSize = alias * alias; + + const int screenWidth = baseW * alias; + const int screenHeight = baseH * alias; + + glm::vec3 color = glm::vec3(); + + for (int p = 0; p < alias; p++) { + int x = startX + p; + + for (int q = 0; q < alias; q++) { + int y = startY + q; + int idx = x + (screenWidth * y); + color += image[idx]; + } + } + + color = color / (float)totalPartSize; + + color.x = glm::clamp(color.x, 0.0f, 1.0f) * 255.0; + color.y = glm::clamp(color.y, 0.0f, 1.0f) * 255.0; + color.z = glm::clamp(color.z, 0.0f, 1.0f) * 255.0; + // Each thread writes one pixel location in the texture (textel) pbo[index].w = 0; pbo[index].x = color.x; @@ -162,7 +191,8 @@ void render(int w, int h, Fragment* fragmentBuffer, glm::vec3* framebuffer) float ambientTerm = 0.2f; glm::vec3 fragColor = frag.color; - if (frag.dev_diffuseTex) { + if (frag.dev_diffuseTex) + { const int pixelX = glm::floor(frag.uv.x * frag.diffuseTexWidth); const int pixelY = glm::floor(frag.uv.y * frag.diffuseTexHeight); const int linearCoordinate = pixelX + (frag.diffuseTexWidth * pixelY); @@ -183,8 +213,6 @@ void render(int w, int h, Fragment* fragmentBuffer, glm::vec3* framebuffer) framebuffer[index] = (ambientTerm + diffuseTerm) * fragColor; // TODO: add your fragment shader code here - - } } @@ -193,8 +221,12 @@ void render(int w, int h, Fragment* fragmentBuffer, glm::vec3* framebuffer) */ void rasterizeInit(int w, int h) { - width = w; - height = h; + width = w * ALIASING_VALUE; + height = h * ALIASING_VALUE; + + baseWidth = w; + baseHeight = h; + cudaFree(dev_fragmentBuffer); cudaMalloc(&dev_fragmentBuffer, width * height * sizeof(Fragment)); cudaMemset(dev_fragmentBuffer, 0, width * height * sizeof(Fragment)); @@ -500,6 +532,7 @@ void rasterizeSetBuffers(const tinygltf::Scene& scene) break; }; + // GLOBAL_DRAW_MODE = primitiveType; // ----------Attributes------------- @@ -703,16 +736,21 @@ void _vertexTransformAndAssembly( // Finally transform x and y to viewport space const glm::vec3 devicePosition = primitive.dev_position[vid]; - glm::vec4 screenPosition = MVP * glm::vec4(devicePosition, 1.0f); // CLIP SPACE - screenPosition /= screenPosition.w; // NDC SPACE - screenPosition.x = 0.5f * width * (1.0f + screenPosition.x); // VIEWPORT SPACE + glm::vec4 screenPosition = MVP * glm::vec4(devicePosition, 1.0f); // CLIP SPACE + screenPosition /= screenPosition.w; // NDC SPACE + screenPosition.x = 0.5f * width * (1.0f + screenPosition.x); // VIEWPORT SPACE screenPosition.y = 0.5f * height * (1.0f - screenPosition.y); primitive.dev_verticesOut[vid].pos = screenPosition; + primitive.dev_verticesOut[vid].col = glm::vec3(1, 0, 0); // TODO: red primitive.dev_verticesOut[vid].eyePos = glm::vec3(MV * glm::vec4(devicePosition, 1.0f)); primitive.dev_verticesOut[vid].eyeNor = MV_normal * primitive.dev_normal[vid]; primitive.dev_verticesOut[vid].dev_diffuseTex = primitive.dev_diffuseTex; - primitive.dev_verticesOut[vid].texcoord0 = primitive.dev_texcoord0[vid]; + if (primitive.dev_texcoord0) + { + primitive.dev_verticesOut[vid].texcoord0 = primitive.dev_texcoord0[vid]; + } + primitive.dev_verticesOut[vid].diffuseTexWidth = primitive.diffuseTexWidth; primitive.dev_verticesOut[vid].diffuseTexHeight = primitive.diffuseTexHeight; @@ -725,7 +763,7 @@ void _vertexTransformAndAssembly( static int curPrimitiveBeginId = 0; __global__ void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Primitive* dev_primitives, - PrimitiveDevBufPointers primitive) + PrimitiveDevBufPointers primitive, PrimitiveType drawMode) { // index id int iid = (blockIdx.x * blockDim.x) + threadIdx.x; @@ -735,41 +773,50 @@ __global__ void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Prim // TODO: uncomment the following code for a start // This is primitive assembly for triangles - int pid; // id for cur primitives vector - if (primitive.primitiveMode == TINYGLTF_MODE_TRIANGLES) { - pid = iid / (int)primitive.primitiveType; - dev_primitives[pid + curPrimitiveBeginId].v[iid % (int)primitive.primitiveType] - = primitive.dev_verticesOut[primitive.dev_indices[iid]]; - } + int pid; // id for cur primitives vector + // if (drawMode == TINYGLTF_MODE_TRIANGLES) + // { + pid = iid / (int)primitive.primitiveType; + dev_primitives[pid + curPrimitiveBeginId].primitiveType = drawMode; + dev_primitives[pid + curPrimitiveBeginId].v[iid % (int)primitive.primitiveType] = primitive.dev_verticesOut[primitive.dev_indices[iid]]; + // } // TODO: other primitive types (point, line) } } -__device__ void ClampRange(float& actualStart, float& actualEnd, float targetStart, float targetEnd) { - if (actualStart < targetStart) { +__device__ void ClampRange(float& actualStart, float& actualEnd, float targetStart, float targetEnd) +{ + if (actualStart < targetStart) + { actualStart = targetStart; } - if (actualEnd > targetEnd) { + if (actualEnd > targetEnd) + { actualEnd = targetEnd; } } -__device__ void ClampRangeInt(int& actualStart, int& actualEnd, int targetStart, int targetEnd) { - if (actualStart < targetStart) { +__device__ void ClampRangeInt(int& actualStart, int& actualEnd, int targetStart, int targetEnd) +{ + if (actualStart < targetStart) + { actualStart = targetStart; } - if (actualEnd > targetEnd) { + if (actualEnd > targetEnd) + { actualEnd = targetEnd; } } -__device__ bool CheckLineSegmentIntersect(glm::vec2 startPoint, glm::vec2 endPoint, int c, float slope, float* xCoord) { +__device__ bool CheckLineSegmentIntersect(glm::vec2 startPoint, glm::vec2 endPoint, int c, float slope, float* xCoord) +{ /*---------- Slope 0 Check ----------*/ - if (slope > -EPSILON && slope < EPSILON) { + if (slope > -EPSILON && slope < EPSILON) + { return false; } @@ -782,15 +829,18 @@ __device__ bool CheckLineSegmentIntersect(glm::vec2 startPoint, glm::vec2 endPoi float maxY = y1 > y2 ? y1 : y2; float minY = y1 > y2 ? y2 : y1; - if (yIntercept <= minY) { + if (yIntercept <= minY) + { return false; } - if (yIntercept > maxY) { + if (yIntercept > maxY) + { return false; } - if (slope == INFINITY) { + if (slope == INFINITY) + { (*xCoord) = startPoint.x; return true; } @@ -810,7 +860,8 @@ __device__ float GetLineSegmentSlope(const glm::vec2& startPoint, const glm::vec // y2 - y1 const float num = endPoint[1] - startPoint[1]; - if (denom > -EPSILON && denom < EPSILON) { + if (denom > -EPSILON && denom < EPSILON) + { return INFINITY; } @@ -818,12 +869,15 @@ __device__ float GetLineSegmentSlope(const glm::vec2& startPoint, const glm::vec return slope; } -__device__ bool BoundingBoxContains(const BoundingBox& box, float x, float y) { - if (x < box.min.x - EPSILON || x > box.max.x + EPSILON) { +__device__ bool BoundingBoxContains(const BoundingBox& box, float x, float y) +{ + if (x < box.min.x - EPSILON || x > box.max.x + EPSILON) + { return false; } - if (y < box.min.y - EPSILON || y > box.max.y + EPSILON) { + if (y < box.min.y - EPSILON || y > box.max.y + EPSILON) + { return false; } @@ -832,17 +886,17 @@ __device__ bool BoundingBoxContains(const BoundingBox& box, float x, float y) { __device__ bool CalculateIntersection(const glm::vec2& p0, - const glm::vec2& p1, - const glm::vec2& p2, - float slope0, - float slope1, - float slope2, - const BoundingBox& box, - float& startX, - float& endX, - int yIntercept - ) { - + const glm::vec2& p1, + const glm::vec2& p2, + float slope0, + float slope1, + float slope2, + const BoundingBox& box, + float& startX, + float& endX, + int yIntercept +) +{ float xResult1 = 0.0f; float xResult2 = 0.0f; @@ -856,27 +910,34 @@ __device__ bool CalculateIntersection(const glm::vec2& p0, int pointsCount = 0; - if (result1 && BoundingBoxContains(box, x1, yIntercept)) { + if (result1 && BoundingBoxContains(box, x1, yIntercept)) + { pointsCount++; xResult1 = x1; } - if (result2 && BoundingBoxContains(box, x2, yIntercept)) { + if (result2 && BoundingBoxContains(box, x2, yIntercept)) + { pointsCount++; - if (pointsCount == 2) { + if (pointsCount == 2) + { xResult2 = x2; - } else { + } + else + { xResult1 = x2; } } - if (result3 && BoundingBoxContains(box, x3, yIntercept)) { + if (result3 && BoundingBoxContains(box, x3, yIntercept)) + { pointsCount++; xResult2 = x3; } - if (pointsCount == 2) { + if (pointsCount == 2) + { startX = xResult1 > xResult2 ? xResult2 : xResult1; endX = xResult1 > xResult2 ? xResult1 : xResult2; @@ -889,80 +950,133 @@ __device__ bool CalculateIntersection(const glm::vec2& p0, return false; } -__device__ void TryStoreFragment(const Primitive& target, float xCoord, int yCoord, int screenWidth, int screenHeight, const VertexOut& v1, const VertexOut& v2, const VertexOut& v3, const glm::vec3& baryCoordinates, int pixelIndex, int* depth, Fragment* fragmentBuffer) { +__device__ void TryStoreFragment(const Primitive& target, float xCoord, int yCoord, int screenWidth, int screenHeight, + const VertexOut& v1, const VertexOut& v2, const VertexOut& v3, + const glm::vec3& baryCoordinates, int pixelIndex, int* depth, Fragment* fragmentBuffer) +{ const float ratio1 = baryCoordinates.x; const float ratio2 = baryCoordinates.y; const float ratio3 = baryCoordinates.z; // pos[2] holds NDC Z [0,1] - const float fragmentDepth = 1.0f / ((ratio1 * (1.0f / v1.pos[2])) + (ratio2 * (1.0f / v2.pos[2])) +(ratio3 * (1.0f / v3.pos[2]))); + const float fragmentDepth = 1.0f / ((ratio1 * (1.0f / v1.pos[2])) + (ratio2 * (1.0f / v2.pos[2])) + (ratio3 * (1.0f / + v3.pos[2]))); const int fragmentIntegerDepth = fragmentDepth * INT_MAX; - // const glm::vec3 vertexOutColor = fragmentDepth * ((ratio1 * (colorP1 / v1.pos[2])) + (ratio2 * (colorP2 / v2.pos[2])) + (ratio3 * (colorP3 / v3.pos[2])));; + const glm::vec2 interpolatedUV = fragmentDepth * ((ratio1 * (v1.texcoord0 / v1.pos[2])) + (ratio2 * (v2.texcoord0 / v2 + .pos[2])) + (ratio3 * (v3.texcoord0 / v3.pos[2]))); + const glm::vec3 interpolatedEyeNormal = fragmentDepth * ((ratio1 * (v1.eyeNor / v1.pos[2])) + (ratio2 * (v2.eyeNor / + v2.pos[2])) + (ratio3 * (v3.eyeNor / v3.pos[2]))); + const glm::vec3 interpolatedEyePos = fragmentDepth * ((ratio1 * (v1.eyePos / v1.pos[2])) + (ratio2 * (v2.eyePos / v2. + pos[2])) + (ratio3 * (v3.eyePos / v3.pos[2]))); - const glm::vec2 interpolatedUV = fragmentDepth * ((ratio1 * (v1.texcoord0 / v1.pos[2])) + (ratio2 * (v2.texcoord0 / v2.pos[2])) + (ratio3 * (v3.texcoord0 / v3.pos[2]))); - const glm::vec3 interpolatedEyeNormal = fragmentDepth * ((ratio1 * (v1.eyeNor / v1.pos[2])) + (ratio2 * (v2.eyeNor / v2.pos[2])) + (ratio3 * (v3.eyeNor / v3.pos[2]))); - const glm::vec3 interpolatedEyePos = fragmentDepth * ((ratio1 * (v1.eyePos / v1.pos[2])) + (ratio2 * (v2.eyePos / v2.pos[2])) + (ratio3 * (v3.eyePos / v3.pos[2]))); - - //color = Triangle::BaryInterpolateColor(v1, v2, v3, targetPoint); + const glm::vec3 interpolatedColor = fragmentDepth * ((ratio1 * (v1.col / v1.pos[2])) + (ratio2 * (v2.col / v2. + pos[2])) + (ratio3 * (v3.col / v3.pos[2]))); - //color = AddLighting(color, v1, v2, v3, targetPoint); + Fragment targetFragment; + targetFragment.color = interpolatedColor; + targetFragment.uv = interpolatedUV; + targetFragment.normal = interpolatedEyeNormal; + targetFragment.pos = interpolatedEyePos; + targetFragment.dev_diffuseTex = v1.dev_diffuseTex; + targetFragment.diffuseTexWidth = v1.diffuseTexWidth; + targetFragment.diffuseTexHeight = v1.diffuseTexHeight; - //color = color * 255.0f; + const int minDepth = atomicMin(&depth[pixelIndex], fragmentIntegerDepth); - //int idx = ToLinearCoords(xCoord, yCoord); - //Fragment frag = m_fragments[idx]; + if (minDepth > fragmentIntegerDepth) + { + depth[pixelIndex] = fragmentIntegerDepth; + fragmentBuffer[pixelIndex] = targetFragment; + } +} + +__device__ void TryStoreFragmentLine(const Primitive& target, float xCoord, int yCoord, int screenWidth, int screenHeight, + const VertexOut& v1, const VertexOut& v2, int pixelIndex, int* depth, Fragment* fragmentBuffer) +{ + // // pos[2] holds NDC Z [0,1] + // const float fragmentDepth = 1.0f / ((ratio1 * (1.0f / v1.pos[2])) + (ratio2 * (1.0f / v2.pos[2])) + (ratio3 * (1.0f / + // v3.pos[2]))); + // const int fragmentIntegerDepth = fragmentDepth * INT_MAX; + // + // const glm::vec2 interpolatedUV = fragmentDepth * ((ratio1 * (v1.texcoord0 / v1.pos[2])) + (ratio2 * (v2.texcoord0 / v2 + // .pos[2])) + (ratio3 * (v3.texcoord0 / v3.pos[2]))); + // const glm::vec3 interpolatedEyeNormal = fragmentDepth * ((ratio1 * (v1.eyeNor / v1.pos[2])) + (ratio2 * (v2.eyeNor / + // v2.pos[2])) + (ratio3 * (v3.eyeNor / v3.pos[2]))); + // const glm::vec3 interpolatedEyePos = fragmentDepth * ((ratio1 * (v1.eyePos / v1.pos[2])) + (ratio2 * (v2.eyePos / v2. + // pos[2])) + (ratio3 * (v3.eyePos / v3.pos[2]))); + // + // Fragment targetFragment; + // targetFragment.color = glm::vec3(1, 0, 0); + // targetFragment.uv = interpolatedUV; + // targetFragment.normal = interpolatedEyeNormal; + // targetFragment.pos = interpolatedEyePos; + // targetFragment.dev_diffuseTex = v1.dev_diffuseTex; + // targetFragment.diffuseTexWidth = v1.diffuseTexWidth; + // targetFragment.diffuseTexHeight = v1.diffuseTexHeight; + // + // const int minDepth = atomicMin(&depth[pixelIndex], fragmentIntegerDepth); + // + // if (minDepth > fragmentIntegerDepth) + // { + // depth[pixelIndex] = fragmentIntegerDepth; + // fragmentBuffer[pixelIndex] = targetFragment; + // } +} +__device__ void TryStoreFragmentPoint(const Primitive& target, float xCoord, int yCoord, int screenWidth, int screenHeight, + const VertexOut& v1, int pixelIndex, int* depth, Fragment* fragmentBuffer) +{ + // pos[2] holds NDC Z [0,1] + const float fragmentDepth = v1.pos[2]; + const int fragmentIntegerDepth = fragmentDepth * INT_MAX; + + const glm::vec2 interpolatedUV = v1.texcoord0; + const glm::vec3 interpolatedEyeNormal = v1.eyeNor; + const glm::vec3 interpolatedEyePos = v1.eyePos; + const glm::vec3 interpolatedColor = v1.col; + Fragment targetFragment; - targetFragment.color = glm::vec3(1, 0, 0); + targetFragment.color = interpolatedColor; targetFragment.uv = interpolatedUV; targetFragment.normal = interpolatedEyeNormal; targetFragment.pos = interpolatedEyePos; targetFragment.dev_diffuseTex = v1.dev_diffuseTex; targetFragment.diffuseTexWidth = v1.diffuseTexWidth; targetFragment.diffuseTexHeight = v1.diffuseTexHeight; - + const int minDepth = atomicMin(&depth[pixelIndex], fragmentIntegerDepth); - - if (minDepth > fragmentIntegerDepth) { + + if (minDepth > fragmentIntegerDepth) + { depth[pixelIndex] = fragmentIntegerDepth; fragmentBuffer[pixelIndex] = targetFragment; } - // - // if (frag.m_isActive && frag.m_depth > fragmentDepth) { - // m_fragments[idx] = Fragment(fragmentDepth, color); - // } else if (!frag.m_isActive) { - // m_fragments[idx] = Fragment(fragmentDepth, color); - // } - - // Old Draw Commands - // QRgb value = qRgb(color[0], color[1], color[2]); - // m_renderTarget.setPixel(ceil(k), ceil(rowY), value); } // #define USE_LINE_SEGMENT_CHECK #define USE_BARY_CHECK -__global__ void _rasterizer(int numPrimitives, Primitive* dev_primitives, int screenWidth, int screenHeight, int* depth, Fragment* fragmentBuffer) +__global__ void _rasterizeTriangles(int numPrimitives, Primitive* dev_primitives, int screenWidth, int screenHeight, int* depth, + Fragment* fragmentBuffer) { // primitive id int primtiveId = (blockIdx.x * blockDim.x) + threadIdx.x; if (primtiveId >= numPrimitives) { - return; } const Primitive& target = dev_primitives[primtiveId]; - // if (target.primitiveType == Triangle) - // { - const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); - const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); - const glm::vec2 p2 = glm::vec2(target.v[2].pos[0], target.v[2].pos[1]); + if (target.primitiveType == Triangle) + { + const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); + const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); + const glm::vec2 p2 = glm::vec2(target.v[2].pos[0], target.v[2].pos[1]); - const BoundingBox boundingBox = getBoundingBoxForTriangle(p0, p1, p2); + const BoundingBox boundingBox = getBoundingBoxForTriangle(p0, p1, p2); #ifdef USE_LINE_SEGMENT_CHECK int rasterStartY = floor(boundingBox.min.y); @@ -981,49 +1095,172 @@ __global__ void _rasterizer(int numPrimitives, Primitive* dev_primitives, int sc const bool result = CalculateIntersection(p0, p1, p2, slope0, slope1, slope2, boundingBox, rasterStartX, rasterEndX, yValue); - if (!result) { - continue; + if (!result) { + continue; } ClampRange(rasterStartX, rasterEndX, 0, screenWidth - 1); for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) { const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); - const int pixelIndex = xValue + (yValue * screenWidth); - TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); } } #endif #ifdef USE_BARY_CHECK - int rasterStartX = floor(boundingBox.min.x); - int rasterEndX = ceil(boundingBox.max.x); + int rasterStartX = floor(boundingBox.min.x); + int rasterEndX = ceil(boundingBox.max.x); + + ClampRangeInt(rasterStartX, rasterEndX, 0, screenWidth - 1); + + for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) + { + int rasterStartY = floor(boundingBox.min.y); + int rasterEndY = ceil(boundingBox.max.y); + ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); - ClampRangeInt(rasterStartX, rasterEndX, 0, screenWidth - 1); + for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) + { + const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); - for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) { + if (!isBarycentricCoordInBounds(baryCoordinates)) + { + continue; + } + + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], + baryCoordinates, pixelIndex, depth, fragmentBuffer); + } + } +#endif + } + + else if (target.primitiveType == Line) + { + const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); + const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); + + const BoundingBox boundingBox = getBoundingBoxForLine(p0, p1); int rasterStartY = floor(boundingBox.min.y); int rasterEndY = ceil(boundingBox.max.y); ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); + const float slope0 = GetLineSegmentSlope(p0, p1); + for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { + float xIntercept; - const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); + const bool doesIntersect = CheckLineSegmentIntersect(p0, p1, yValue, slope0, &xIntercept); - if (!isBarycentricCoordInBounds(baryCoordinates)) + if (!doesIntersect) { continue; } - const int pixelIndex = xValue + (yValue * screenWidth); - TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); + int xValue = (int)glm::clamp(xIntercept, 0.0f, float(screenWidth - 1)); + + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragmentLine(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], pixelIndex, depth, fragmentBuffer); + } + } + + else if (target.primitiveType == Point) + { + const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); + const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); + + const BoundingBox boundingBox = getBoundingBoxForLine(p0, p1); + + int rasterStartY = floor(boundingBox.min.y); + int rasterEndY = ceil(boundingBox.max.y); + ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); + + const float slope0 = GetLineSegmentSlope(p0, p1); + + for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { + float xIntercept; + + const bool doesIntersect = CheckLineSegmentIntersect(p0, p1, yValue, slope0, &xIntercept); + + if (!doesIntersect) + { + continue; + } + + int xValue = (int)glm::clamp(xIntercept, 0.0f, float(screenWidth - 1)); + + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragmentLine(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], pixelIndex, depth, fragmentBuffer); } } -#endif -// } } +__global__ void _rasterizeLines(int numPrimitives, Primitive* dev_primitives, int screenWidth, int screenHeight, int* depth, + Fragment* fragmentBuffer) +{ + // primitive id + int primtiveId = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (primtiveId >= numPrimitives) + { + return; + } + + const Primitive& target = dev_primitives[primtiveId]; + + const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); + const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); + + const BoundingBox boundingBox = getBoundingBoxForLine(p0, p1); + + int rasterStartY = floor(boundingBox.min.y); + int rasterEndY = ceil(boundingBox.max.y); + ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); + + const float slope0 = GetLineSegmentSlope(p0, p1); + + for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { + float xIntercept; + + const bool doesIntersect = CheckLineSegmentIntersect(p0, p1, yValue, slope0, &xIntercept); + + if (!doesIntersect) + { + continue; + } + + const int xValue = (int)glm::clamp(xIntercept, 0.0f, float(screenWidth - 1)); + + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragmentLine(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], pixelIndex, depth, fragmentBuffer); + } +} + +__global__ void _rasterizePoints(int numPrimitives, Primitive* dev_primitives, int screenWidth, int screenHeight, int* depth, + Fragment* fragmentBuffer) +{ + // primitive id + int primtiveId = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (primtiveId >= numPrimitives) + { + return; + } + + const Primitive& target = dev_primitives[primtiveId]; + + const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); + + const int xValue = glm::clamp((int)glm::round(p0.x), 0, screenWidth - 1); + const int yValue = glm::clamp((int)glm::round(p0.y), 0, screenHeight - 1); + + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragmentPoint(target, xValue, yValue, screenWidth, screenHeight, target.v[0], pixelIndex, depth, fragmentBuffer); +} /** * Perform rasterization. @@ -1062,7 +1299,8 @@ void rasterize(uchar4* pbo, const glm::mat4& MVP, const glm::mat4& MV, const glm (p->numIndices, curPrimitiveBeginId, dev_primitives, - *p); + *p, + GLOBAL_DRAW_MODE); checkCUDAError("Primitive Assembly"); curPrimitiveBeginId += p->numPrimitives; @@ -1077,13 +1315,25 @@ void rasterize(uchar4* pbo, const glm::mat4& MVP, const glm::mat4& MV, const glm // TODO: rasterize const int blockSize1d = 512; dim3 numRasterizeBlocks = (curPrimitiveBeginId + blockSize1d - 1) / blockSize1d; - _rasterizer <<< numRasterizeBlocks, blockSize1d >>> (curPrimitiveBeginId, dev_primitives, width, height, dev_depth, dev_fragmentBuffer); + + if (GLOBAL_DRAW_MODE == Triangle) { + _rasterizeTriangles <<< numRasterizeBlocks, blockSize1d >>> (curPrimitiveBeginId, dev_primitives, width, height, dev_depth, + dev_fragmentBuffer); + } + else if (GLOBAL_DRAW_MODE == Line) { + _rasterizeLines <<< numRasterizeBlocks, blockSize1d >>> (curPrimitiveBeginId, dev_primitives, width, height, dev_depth, + dev_fragmentBuffer); + } + else if (GLOBAL_DRAW_MODE == Point) { + _rasterizePoints <<< numRasterizeBlocks, blockSize1d >>> (curPrimitiveBeginId, dev_primitives, width, height, dev_depth, + dev_fragmentBuffer); + } // Copy depthbuffer colors into framebuffer render <<< blockCount2d, blockSize2d >>>(width, height, dev_fragmentBuffer, dev_framebuffer); checkCUDAError("fragment shader"); // Copy framebuffer into OpenGL buffer for OpenGL previewing - sendImageToPBO<<>>(pbo, width, height, dev_framebuffer); + sendImageToPBO<<>>(pbo, baseWidth, baseHeight, ALIASING_VALUE, dev_framebuffer); checkCUDAError("copy render result to pbo"); } diff --git a/src/rasterizeTools.h b/src/rasterizeTools.h index 1f61117..94b16fe 100644 --- a/src/rasterizeTools.h +++ b/src/rasterizeTools.h @@ -60,6 +60,18 @@ BoundingBox getBoundingBoxForTriangle(const glm::vec2 p0, const glm::vec2 p1, co return aabb; } +__host__ __device__ static +BoundingBox getBoundingBoxForLine(const glm::vec2 p0, const glm::vec2 p1) { + BoundingBox aabb; + aabb.min = glm::vec2( + glm::min(p0.x, p1.x), + glm::min(p0.y, p1.y)); + aabb.max = glm::vec2( + glm::max(p0.x, p1.x), + glm::max(p0.y, p1.y)); + return aabb; +} + // CHECKITOUT /** * Calculate the signed area of a given triangle. diff --git a/util/stb_image_write.h b/util/stb_image_write.h new file mode 100644 index 0000000..17c68a2 --- /dev/null +++ b/util/stb_image_write.h @@ -0,0 +1,1831 @@ +/* stb_image_write - v1.09 - public domain - +http://nothings.org/stb/stb_image_write.h writes out PNG/BMP/TGA/JPEG/HDR images +to C stdio - Sean Barrett 2010-2015 no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + + If using a modern Microsoft Compiler, non-safe versions of CRT calls may +cause compilation warnings or even errors. To avoid this, also before +#including, + + #define STBI_MSC_SECURE_CRT + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress +function for PNG compression (instead of the builtin one), it must have the +following signature: unsigned char * my_compress(unsigned char *data, int +data_len, int *out_len, int quality); The returned data will be freed with +STBIW_FREE() (free() by default), so it must be heap allocated with +STBIW_MALLOC() (malloc() by default), + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void +*data, int stride_in_bytes); int stbi_write_bmp(char const *filename, int w, int +h, int comp, const void *data); int stbi_write_tga(char const *filename, int w, +int h, int comp, const void *data); int stbi_write_jpg(char const *filename, int +w, int h, int comp, const void *data, int quality); int stbi_write_hdr(char +const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip +data vertically + + There are also five equivalent functions that use an arbitrary write +function. You are expected to open/close your file-equivalent before and after +calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int +h, int comp, const void *data, int stride_in_bytes); int +stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int +comp, const void *data); int stbi_write_tga_to_func(stbi_write_func *func, void +*context, int w, int h, int comp, const void *data); int +stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int +comp, const float *data); int stbi_write_jpg_to_func(stbi_write_func *func, void +*context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to +disable RLE int stbi_write_png_compression_level; // defaults to 8; set to +higher for more compression int stbi_write_force_png_filter; // defaults +to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' +// or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +extern int stbi_write_tga_with_rle; +extern int stbi_write_png_compression_level; +extern int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, + const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, + const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, + const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, + const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, + const void *data, int quality); +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, + int h, int comp, const void *data, + int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, + int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, + int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, + int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, + int y, int comp, const void *data, + int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif // INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && \ + (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && \ + !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error \ + "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p, newsz) realloc(p, newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p, oldsz, newsz) STBIW_REALLOC(p, newsz) +#endif + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a, b, sz) memmove(a, b, sz) +#endif + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char)((x)&0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi__flip_vertically_on_write = 0; +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi__flip_vertically_on_write = 0; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +STBIWDEF void stbi_flip_vertically_on_write(int flag) { + stbi__flip_vertically_on_write = flag; +} + +typedef struct { + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, + stbi_write_func *c, void *context) { + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) { + fwrite(data, 1, size, (FILE *)context); +} + +static int stbi__start_write_file(stbi__write_context *s, + const char *filename) { + FILE *f; +#ifdef STBI_MSC_SECURE_CRT + if (fopen_s(&f, filename, "wb")) f = NULL; +#else + f = fopen(filename, "wb"); +#endif + stbi__start_write_callbacks(s, stbi__stdio_write, (void *)f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) { + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32) == 4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) { + while (*fmt) { + switch (*fmt++) { + case ' ': + break; + case '1': { + unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context, &x, 1); + break; + } + case '2': { + int x = va_arg(v, int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x >> 8); + s->func(s->context, b, 2); + break; + } + case '4': { + stbiw_uint32 x = va_arg(v, int); + unsigned char b[4]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x >> 8); + b[2] = STBIW_UCHAR(x >> 16); + b[3] = STBIW_UCHAR(x >> 24); + s->func(s->context, b, 4); + break; + } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) { + s->func(s->context, &c, 1); +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, + unsigned char b, unsigned char c) { + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + s->func(s->context, arr, 3); +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, + int write_alpha, int expand_mono, + unsigned char *d) { + unsigned char bg[3] = {255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) s->func(s->context, &d[comp - 1], 1); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as + // 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + s->func(s->context, d, 1); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) s->func(s->context, &d[comp - 1], 1); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, + int x, int y, int comp, void *data, + int write_alpha, int scanline_pad, + int expand_mono) { + stbiw_uint32 zero = 0; + int i, j, j_end; + + if (y <= 0) return; + + if (stbi__flip_vertically_on_write) vdir *= -1; + + if (vdir < 0) + j_end = -1, j = y - 1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i = 0; i < x; ++i) { + unsigned char *d = (unsigned char *)data + (j * x + i) * comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, + int y, int comp, int expand_mono, void *data, + int alpha, int pad, const char *fmt, ...) { + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s, rgb_dir, vdir, x, y, comp, data, alpha, pad, + expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, + const void *data) { + int pad = (-x * 3) & 3; + return stbiw__outfile(s, -1, -1, x, y, comp, 1, (void *)data, 0, pad, + "11 4 22 4" + "4 44 22 444444", + 'B', 'M', 14 + 40 + (x * 3 + pad) * y, 0, 0, + 14 + 40, // file header + 40, x, y, 1, 24, 0, 0, 0, 0, 0, 0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, + int y, int comp, const void *data) { + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, + const void *data) { + stbi__write_context s; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //! STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, + void *data) { + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp - 1 : comp; + int format = + colorbytes < 2 + ? 3 + : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *)data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, + (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i, j, k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0, 0, format + 8, 0, 0, 0, 0, 0, x, y, + (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y - 1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *)data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + s->func(s->context, &header, 1); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + s->func(s->context, &header, 1); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, + int y, int comp, const void *data) { + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *)data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, + const void *data) { + stbi__write_context s; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *)data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) { + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float)frexp(maxcomp, &exponent) * 256.0f / maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +void stbiw__write_run_data(stbi__write_context *s, int length, + unsigned char databyte) { + unsigned char lengthbyte = STBIW_UCHAR(length + 128); + STBIW_ASSERT(length + 128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +void stbiw__write_dump_data(stbi__write_context *s, int length, + unsigned char *data) { + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT( + length <= + 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, + unsigned char *scratch, float *scanline) { + unsigned char scanlineheader[4] = {2, 2, 0, 0}; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width & 0xff00) >> 8; + scanlineheader[3] = (width & 0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x = 0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: + linear[2] = scanline[x * ncomp + 2]; + linear[1] = scanline[x * ncomp + 1]; + linear[0] = scanline[x * ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x * ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c, r; + /* encode into scratch buffer */ + for (x = 0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: + linear[2] = scanline[x * ncomp + 2]; + linear[1] = scanline[x * ncomp + 1]; + linear[0] = scanline[x * ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x * ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width * 0] = rgbe[0]; + scratch[x + width * 1] = rgbe[1]; + scratch[x + width * 2] = rgbe[2]; + scratch[x + width * 3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c = 0; c < 4; c++) { + unsigned char *comp = &scratch[width * c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r + 2 < width) { + if (comp[r] == comp[r + 1] && comp[r] == comp[r + 2]) break; + ++r; + } + if (r + 2 >= width) r = width; + // dump up to first run + while (x < r) { + int len = r - x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r + 2 < width) { // same test as what we break out of in search + // loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) ++r; + // output run up to r + while (x < r) { + int len = r - x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, + float *data) { + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full + // output scanline. + unsigned char *scratch = (unsigned char *)STBIW_MALLOC(x * 4); + int i, len; + char buffer[128]; + char header[] = + "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header) - 1); + +#ifdef STBI_MSC_SECURE_CRT + len = sprintf_s( + buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", + y, x); +#endif + s->func(s->context, buffer, len); + + for (i = 0; i < y; i++) + stbiw__write_hdr_scanline( + s, x, comp, scratch, + data + + comp * x * (stbi__flip_vertically_on_write ? y - 1 - i : i) * x); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, + int y, int comp, const float *data) { + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *)data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, + const float *data) { + stbi__write_context s; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *)data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() +// == vector<>::size() +#define stbiw__sbraw(a) ((int *)(a)-2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a, n) ((a) == 0 || stbiw__sbn(a) + n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a, n) \ + (stbiw__sbneedgrow(a, (n)) ? stbiw__sbgrow(a, n) : 0) +#define stbiw__sbgrow(a, n) stbiw__sbgrowf((void **)&(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) \ + (stbiw__sbmaybegrow(a, 1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)), 0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) { + int m = *arr ? 2 * stbiw__sbm(*arr) + increment : increment + 1; + void *p = STBIW_REALLOC_SIZED( + *arr ? stbiw__sbraw(*arr) : 0, + *arr ? (stbiw__sbm(*arr) * itemsize + sizeof(int) * 2) : 0, + itemsize * m + sizeof(int) * 2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *)p)[1] = 0; + *arr = (void *)((int *)p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, + unsigned int *bitbuffer, + int *bitcount) { + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) { + int res = 0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, + int limit) { + int i; + for (i = 0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) { + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code, codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b, c) stbiw__zlib_add(stbiw__zlib_bitrev(b, c), c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256, 7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280, 8) +#define stbiw__zlib_huff(n) \ + ((n) <= 143 ? stbiw__zlib_huff1(n) \ + : (n) <= 255 ? stbiw__zlib_huff2(n) \ + : (n) <= 279 ? stbiw__zlib_huff3(n) \ + : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) \ + ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, + int *out_len, int quality) { +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, + 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 259}; + static unsigned char lengtheb[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static unsigned short distc[] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, + 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, + 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 32768}; + static unsigned char disteb[] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + unsigned int bitbuf = 0; + int i, j, bitcount = 0; + unsigned char *out = NULL; + unsigned char ***hash_table = + (unsigned char ***)STBIW_MALLOC(stbiw__ZHASH * sizeof(char **)); + if (hash_table == NULL) return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1, 1); // BFINAL = 1 + stbiw__zlib_add(1, 2); // BTYPE = 1 -- fixed huffman + + for (i = 0; i < stbiw__ZHASH; ++i) hash_table[i] = NULL; + + i = 0; + while (i < data_len - 3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data + i) & (stbiw__ZHASH - 1), best = 3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j = 0; j < n; ++j) { + if (hlist[j] - data > i - 32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data + i, data_len - i); + if (d >= best) best = d, bestloc = hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2 * quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h] + quality, + sizeof(hash_table[h][0]) * quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h], data + i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do + // cur byte as literal + h = stbiw__zhash(data + i + 1) & (stbiw__ZHASH - 1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j = 0; j < n; ++j) { + if (hlist[j] - data > i - 32767) { + int e = stbiw__zlib_countm(hlist[j], data + i + 1, data_len - i - 1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int)(data + i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j = 0; best > lengthc[j + 1] - 1; ++j) + ; + stbiw__zlib_huff(j + 257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j = 0; d > distc[j + 1] - 1; ++j) + ; + stbiw__zlib_add(stbiw__zlib_bitrev(j, 5), 5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (; i < data_len; ++i) stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) stbiw__zlib_add(0, 1); + + for (i = 0; i < stbiw__ZHASH; ++i) (void)stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1 = 1, s2 = 0; + int blocklen = (int)(data_len % 5552); + j = 0; + while (j < data_len) { + for (i = 0; i < blocklen; ++i) s1 += data[j + i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *)stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) { + static unsigned int crc_table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D}; + + unsigned int crc = ~0u; + int i; + for (i = 0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +#define stbiw__wpng4(o, a, b, c, d) \ + ((o)[0] = STBIW_UCHAR(a), (o)[1] = STBIW_UCHAR(b), (o)[2] = STBIW_UCHAR(c), \ + (o)[3] = STBIW_UCHAR(d), (o) += 4) +#define stbiw__wp32(data, v) \ + stbiw__wpng4(data, (v) >> 24, (v) >> 16, (v) >> 8, (v)); +#define stbiw__wptag(data, s) stbiw__wpng4(data, s[0], s[1], s[2], s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) { + unsigned int crc = stbiw__crc32(*data - len - 4, len + 4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) { + int p = a + b - c, pa = abs(p - a), pb = abs(p - b), pc = abs(p - c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, + int width, int height, int y, int n, + int filter_type, signed char *line_buffer) { + static int mapping[] = {0, 1, 2, 3, 4}; + static int firstmap[] = {0, 1, 0, 5, 6}; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = + pixels + + stride_bytes * (stbi__flip_vertically_on_write ? height - 1 - y : y); + int signed_stride = + stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + for (i = 0; i < n; ++i) { + switch (type) { + case 0: + line_buffer[i] = z[i]; + break; + case 1: + line_buffer[i] = z[i]; + break; + case 2: + line_buffer[i] = z[i] - z[i - signed_stride]; + break; + case 3: + line_buffer[i] = z[i] - (z[i - signed_stride] >> 1); + break; + case 4: + line_buffer[i] = + (signed char)(z[i] - stbiw__paeth(0, z[i - signed_stride], 0)); + break; + case 5: + line_buffer[i] = z[i]; + break; + case 6: + line_buffer[i] = z[i]; + break; + } + } + for (i = n; i < width * n; ++i) { + switch (type) { + case 0: + line_buffer[i] = z[i]; + break; + case 1: + line_buffer[i] = z[i] - z[i - n]; + break; + case 2: + line_buffer[i] = z[i] - z[i - signed_stride]; + break; + case 3: + line_buffer[i] = z[i] - ((z[i - n] + z[i - signed_stride]) >> 1); + break; + case 4: + line_buffer[i] = z[i] - stbiw__paeth(z[i - n], z[i - signed_stride], + z[i - signed_stride - n]); + break; + case 5: + line_buffer[i] = z[i] - (z[i - n] >> 1); + break; + case 6: + line_buffer[i] = z[i] - stbiw__paeth(z[i - n], 0, 0); + break; + } + } +} + +unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, + int x, int y, int n, int *out_len) { + int force_filter = stbi_write_force_png_filter; + int ctype[5] = {-1, 0, 4, 2, 6}; + unsigned char sig[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + unsigned char *out, *o, *filt, *zlib; + signed char *line_buffer; + int j, zlen; + + if (stride_bytes == 0) stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *)STBIW_MALLOC((x * n + 1) * y); + if (!filt) return 0; + line_buffer = (signed char *)STBIW_MALLOC(x * n); + if (!line_buffer) { + STBIW_FREE(filt); + return 0; + } + for (j = 0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line(pixels, stride_bytes, x, y, j, n, force_filter, + line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line(pixels, stride_bytes, x, y, j, n, filter_type, + line_buffer); + + // Estimate the entropy of the line using this filter; the less, the + // better. + est = 0; + for (i = 0; i < x * n; ++i) { + est += abs((signed char)line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us + // the best filter, don't redo it + stbiw__encode_png_line(pixels, stride_bytes, x, y, j, n, best_filter, + line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer + // contains the data + filt[j * (x * n + 1)] = (unsigned char)filter_type; + STBIW_MEMMOVE(filt + j * (x * n + 1) + 1, line_buffer, x * n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y * (x * n + 1), &zlen, + stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *)STBIW_MALLOC(8 + 12 + 13 + 12 + zlen + 12); + if (!out) return 0; + *out_len = 8 + 12 + 13 + 12 + zlen + 12; + + o = out; + STBIW_MEMMOVE(o, sig, 8); + o += 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o, 13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o, 0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o, 0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, + const void *data, int stride_bytes) { + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *)data, + stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; +#ifdef STBI_MSC_SECURE_CRT + if (fopen_s(&f, filename, "wb")) f = NULL; +#else + f = fopen(filename, "wb"); +#endif + if (!f) { + STBIW_FREE(png); + return 0; + } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, + int y, int comp, const void *data, + int stride_bytes) { + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *)data, + stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - + * http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { + 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63}; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, + int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while (bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if (c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, + float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, + d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; + *d2p = d2; + *d4p = d4; + *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val - 1 : val; + bits[1] = 1; + while (tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1 << bits[1]) - 1); +} + +static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, + int *bitCnt, float *CDU, float *fdtbl, int DC, + const unsigned short HTDC[256][2], + const unsigned short HTAC[256][2]) { + const unsigned short EOB[2] = {HTAC[0x00][0], HTAC[0x00][1]}; + const unsigned short M16zeroes[2] = {HTAC[0xF0][0], HTAC[0xF0][1]}; + int dataOff, i, diff, end0pos; + int DU[64]; + + // DCT rows + for (dataOff = 0; dataOff < 64; dataOff += 8) { + stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff + 1], &CDU[dataOff + 2], + &CDU[dataOff + 3], &CDU[dataOff + 4], &CDU[dataOff + 5], + &CDU[dataOff + 6], &CDU[dataOff + 7]); + } + // DCT columns + for (dataOff = 0; dataOff < 8; ++dataOff) { + stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff + 8], &CDU[dataOff + 16], + &CDU[dataOff + 24], &CDU[dataOff + 32], &CDU[dataOff + 40], + &CDU[dataOff + 48], &CDU[dataOff + 56]); + } + // Quantize/descale/zigzag the coefficients + for (i = 0; i < 64; ++i) { + float v = CDU[i] * fdtbl[i]; + // DU[stbiw__jpg_ZigZag[i]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + + // 0.5f)); ceilf() and floorf() are C99, not C89, but I /think/ they're not + // needed here anyway? + DU[stbiw__jpg_ZigZag[i]] = (int)(v < 0 ? v - 0.5f : v + 0.5f); + } + + // Encode DC + diff = DU[0] - DC; + if (diff == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[0]); + } else { + unsigned short bits[2]; + stbiw__jpg_calcBits(diff, bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + // Encode ACs + end0pos = 63; + for (; (end0pos > 0) && (DU[end0pos] == 0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if (end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for (i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i] == 0 && i <= end0pos; ++i) { + } + nrzeroes = i - startpos; + if (nrzeroes >= 16) { + int lng = nrzeroes >> 4; + int nrmarker; + for (nrmarker = 1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes << 4) + bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if (end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, + int comp, const void *data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = { + 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}; + static const unsigned char std_dc_luminance_values[] = {0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11}; + static const unsigned char std_ac_luminance_nrcodes[] = { + 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, + 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa}; + static const unsigned char std_dc_chrominance_nrcodes[] = { + 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}; + static const unsigned char std_dc_chrominance_values[] = {0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11}; + static const unsigned char std_ac_chrominance_nrcodes[] = { + 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, + 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa}; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { + {0, 2}, {2, 3}, {3, 3}, {4, 3}, {5, 3}, {6, 3}, + {14, 4}, {30, 5}, {62, 6}, {126, 7}, {254, 8}, {510, 9}}; + static const unsigned short UVDC_HT[256][2] = { + {0, 2}, {1, 2}, {2, 2}, {6, 3}, {14, 4}, {30, 5}, + {62, 6}, {126, 7}, {254, 8}, {510, 9}, {1022, 10}, {2046, 11}}; + static const unsigned short YAC_HT[256][2] = { + {10, 4}, {0, 2}, {1, 2}, {4, 3}, {11, 4}, + {26, 5}, {120, 7}, {248, 8}, {1014, 10}, {65410, 16}, + {65411, 16}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {12, 4}, {27, 5}, {121, 7}, + {502, 9}, {2038, 11}, {65412, 16}, {65413, 16}, {65414, 16}, + {65415, 16}, {65416, 16}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {28, 5}, {249, 8}, + {1015, 10}, {4084, 12}, {65417, 16}, {65418, 16}, {65419, 16}, + {65420, 16}, {65421, 16}, {65422, 16}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {58, 6}, + {503, 9}, {4085, 12}, {65423, 16}, {65424, 16}, {65425, 16}, + {65426, 16}, {65427, 16}, {65428, 16}, {65429, 16}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {59, 6}, {1016, 10}, {65430, 16}, {65431, 16}, {65432, 16}, + {65433, 16}, {65434, 16}, {65435, 16}, {65436, 16}, {65437, 16}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {122, 7}, {2039, 11}, {65438, 16}, {65439, 16}, + {65440, 16}, {65441, 16}, {65442, 16}, {65443, 16}, {65444, 16}, + {65445, 16}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {123, 7}, {4086, 12}, {65446, 16}, + {65447, 16}, {65448, 16}, {65449, 16}, {65450, 16}, {65451, 16}, + {65452, 16}, {65453, 16}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {250, 8}, {4087, 12}, + {65454, 16}, {65455, 16}, {65456, 16}, {65457, 16}, {65458, 16}, + {65459, 16}, {65460, 16}, {65461, 16}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {504, 9}, + {32704, 15}, {65462, 16}, {65463, 16}, {65464, 16}, {65465, 16}, + {65466, 16}, {65467, 16}, {65468, 16}, {65469, 16}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {505, 9}, {65470, 16}, {65471, 16}, {65472, 16}, {65473, 16}, + {65474, 16}, {65475, 16}, {65476, 16}, {65477, 16}, {65478, 16}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {506, 9}, {65479, 16}, {65480, 16}, {65481, 16}, + {65482, 16}, {65483, 16}, {65484, 16}, {65485, 16}, {65486, 16}, + {65487, 16}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {1017, 10}, {65488, 16}, {65489, 16}, + {65490, 16}, {65491, 16}, {65492, 16}, {65493, 16}, {65494, 16}, + {65495, 16}, {65496, 16}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {1018, 10}, {65497, 16}, + {65498, 16}, {65499, 16}, {65500, 16}, {65501, 16}, {65502, 16}, + {65503, 16}, {65504, 16}, {65505, 16}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {2040, 11}, + {65506, 16}, {65507, 16}, {65508, 16}, {65509, 16}, {65510, 16}, + {65511, 16}, {65512, 16}, {65513, 16}, {65514, 16}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {65515, 16}, {65516, 16}, {65517, 16}, {65518, 16}, {65519, 16}, + {65520, 16}, {65521, 16}, {65522, 16}, {65523, 16}, {65524, 16}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {2041, 11}, {65525, 16}, {65526, 16}, {65527, 16}, {65528, 16}, + {65529, 16}, {65530, 16}, {65531, 16}, {65532, 16}, {65533, 16}, + {65534, 16}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}}; + static const unsigned short UVAC_HT[256][2] = { + {0, 2}, {1, 2}, {4, 3}, {10, 4}, {24, 5}, + {25, 5}, {56, 6}, {120, 7}, {500, 9}, {1014, 10}, + {4084, 12}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {11, 4}, {57, 6}, {246, 8}, + {501, 9}, {2038, 11}, {4085, 12}, {65416, 16}, {65417, 16}, + {65418, 16}, {65419, 16}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {26, 5}, {247, 8}, + {1015, 10}, {4086, 12}, {32706, 15}, {65420, 16}, {65421, 16}, + {65422, 16}, {65423, 16}, {65424, 16}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {27, 5}, + {248, 8}, {1016, 10}, {4087, 12}, {65425, 16}, {65426, 16}, + {65427, 16}, {65428, 16}, {65429, 16}, {65430, 16}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {58, 6}, {502, 9}, {65431, 16}, {65432, 16}, {65433, 16}, + {65434, 16}, {65435, 16}, {65436, 16}, {65437, 16}, {65438, 16}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {59, 6}, {1017, 10}, {65439, 16}, {65440, 16}, + {65441, 16}, {65442, 16}, {65443, 16}, {65444, 16}, {65445, 16}, + {65446, 16}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {121, 7}, {2039, 11}, {65447, 16}, + {65448, 16}, {65449, 16}, {65450, 16}, {65451, 16}, {65452, 16}, + {65453, 16}, {65454, 16}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {122, 7}, {2040, 11}, + {65455, 16}, {65456, 16}, {65457, 16}, {65458, 16}, {65459, 16}, + {65460, 16}, {65461, 16}, {65462, 16}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {249, 8}, + {65463, 16}, {65464, 16}, {65465, 16}, {65466, 16}, {65467, 16}, + {65468, 16}, {65469, 16}, {65470, 16}, {65471, 16}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {503, 9}, {65472, 16}, {65473, 16}, {65474, 16}, {65475, 16}, + {65476, 16}, {65477, 16}, {65478, 16}, {65479, 16}, {65480, 16}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {504, 9}, {65481, 16}, {65482, 16}, {65483, 16}, + {65484, 16}, {65485, 16}, {65486, 16}, {65487, 16}, {65488, 16}, + {65489, 16}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {505, 9}, {65490, 16}, {65491, 16}, + {65492, 16}, {65493, 16}, {65494, 16}, {65495, 16}, {65496, 16}, + {65497, 16}, {65498, 16}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {506, 9}, {65499, 16}, + {65500, 16}, {65501, 16}, {65502, 16}, {65503, 16}, {65504, 16}, + {65505, 16}, {65506, 16}, {65507, 16}, {0, 0}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {2041, 11}, + {65508, 16}, {65509, 16}, {65510, 16}, {65511, 16}, {65512, 16}, + {65513, 16}, {65514, 16}, {65515, 16}, {65516, 16}, {0, 0}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {16352, 14}, {65517, 16}, {65518, 16}, {65519, 16}, {65520, 16}, + {65521, 16}, {65522, 16}, {65523, 16}, {65524, 16}, {65525, 16}, + {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {1018, 10}, {32707, 15}, {65526, 16}, {65527, 16}, {65528, 16}, + {65529, 16}, {65530, 16}, {65531, 16}, {65532, 16}, {65533, 16}, + {65534, 16}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, + {0, 0}}; + static const int YQT[] = { + 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99}; + static const int UVQT[] = {17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, + 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}; + static const float aasf[] = { + 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, + 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, + 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f}; + + int row, col, i, k; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if (!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for (i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i] * quality + 50) / 100; + YTable[stbiw__jpg_ZigZag[i]] = + (unsigned char)(yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i] * quality + 50) / 100; + UVTable[stbiw__jpg_ZigZag[i]] = + (unsigned char)(uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for (row = 0, k = 0; row < 8; ++row) { + for (col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { + 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 'J', 'F', 'I', 'F', 0, 1, 1, + 0, 0, 1, 0, 1, 0, 0, 0xFF, 0xDB, 0, 0x84, 0}; + static const unsigned char head2[] = {0xFF, 0xDA, 0, 0xC, 3, 1, 0, + 2, 0x11, 3, 0x11, 0, 0x3F, 0}; + const unsigned char head1[] = {0xFF, + 0xC0, + 0, + 0x11, + 8, + (unsigned char)(height >> 8), + STBIW_UCHAR(height), + (unsigned char)(width >> 8), + STBIW_UCHAR(width), + 3, + 1, + 0x11, + 0, + 2, + 0x11, + 1, + 3, + 0x11, + 1, + 0xFF, + 0xC4, + 0x01, + 0xA2, + 0}; + s->func(s->context, (void *)head0, sizeof(head0)); + s->func(s->context, (void *)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void *)head1, sizeof(head1)); + s->func(s->context, (void *)(std_dc_luminance_nrcodes + 1), + sizeof(std_dc_luminance_nrcodes) - 1); + s->func(s->context, (void *)std_dc_luminance_values, + sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void *)(std_ac_luminance_nrcodes + 1), + sizeof(std_ac_luminance_nrcodes) - 1); + s->func(s->context, (void *)std_ac_luminance_values, + sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void *)(std_dc_chrominance_nrcodes + 1), + sizeof(std_dc_chrominance_nrcodes) - 1); + s->func(s->context, (void *)std_dc_chrominance_values, + sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void *)(std_ac_chrominance_nrcodes + 1), + sizeof(std_ac_chrominance_nrcodes) - 1); + s->func(s->context, (void *)std_ac_chrominance_values, + sizeof(std_ac_chrominance_values)); + s->func(s->context, (void *)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + const unsigned char *imageData = (const unsigned char *)data; + int DCY = 0, DCU = 0, DCV = 0; + int bitBuf = 0, bitCnt = 0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + int x, y, pos; + for (y = 0; y < height; y += 8) { + for (x = 0; x < width; x += 8) { + float YDU[64], UDU[64], VDU[64]; + for (row = y, pos = 0; row < y + 8; ++row) { + for (col = x; col < x + 8; ++col, ++pos) { + int p = (stbi__flip_vertically_on_write ? height - 1 - row : row) * + width * comp + + col * comp; + float r, g, b; + if (row >= height) { + p -= width * comp * (row + 1 - height); + } + if (col >= width) { + p -= comp * (col + 1 - width); + } + + r = imageData[p + 0]; + g = imageData[p + ofsG]; + b = imageData[p + ofsB]; + YDU[pos] = +0.29900f * r + 0.58700f * g + 0.11400f * b - 128; + UDU[pos] = -0.16874f * r - 0.33126f * g + 0.50000f * b; + VDU[pos] = +0.50000f * r - 0.41869f * g - 0.08131f * b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, + YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, + UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, + UVDC_HT, UVAC_HT); + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, + int y, int comp, const void *data, + int quality) { + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *)data, quality); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, + const void *data, int quality) { + stbi__write_context s; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, + choose PNG filter 1.07 (2017-07-24) doc fix 1.06 (2017-07-23) writing JPEG + (using Jon Olick's code) 1.05 ??? 1.04 (2017-03-03) monochrome BMP + expansion 1.03 ??? 1.02 (2016-04-02) avoid allocating large structures on + the stack 1.01 (2016-01-16) STBIW_REALLOC_SIZED: support allocators with no + realloc support avoid race-condition in crc initialization minor compile + issues 1.00 (2015-09-14) installable file IO function 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/util/tiny_gltf.h b/util/tiny_gltf.h new file mode 100644 index 0000000..2a9b3a6 --- /dev/null +++ b/util/tiny_gltf.h @@ -0,0 +1,4791 @@ +// +// Header-only tiny glTF 2.0 loader and serializer. +// +// +// The MIT License (MIT) +// +// Copyright (c) 2015 - 2018 Syoyo Fujita, Aurélien Chatelain and many +// contributors. +// +// 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. + +// Version: +// - v2.0.1 Add comparsion feature(Thanks to @Selmar). +// - v2.0.0 glTF 2.0!. +// +// Tiny glTF loader is using following third party libraries: +// +// - jsonhpp: C++ JSON library. +// - base64: base64 decode/encode library. +// - stb_image: Image loading library. +// +#ifndef TINY_GLTF_H_ +#define TINY_GLTF_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tinygltf { + +#define TINYGLTF_MODE_POINTS (0) +#define TINYGLTF_MODE_LINE (1) +#define TINYGLTF_MODE_LINE_LOOP (2) +#define TINYGLTF_MODE_TRIANGLES (4) +#define TINYGLTF_MODE_TRIANGLE_STRIP (5) +#define TINYGLTF_MODE_TRIANGLE_FAN (6) + +#define TINYGLTF_COMPONENT_TYPE_BYTE (5120) +#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE (5121) +#define TINYGLTF_COMPONENT_TYPE_SHORT (5122) +#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT (5123) +#define TINYGLTF_COMPONENT_TYPE_INT (5124) +#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125) +#define TINYGLTF_COMPONENT_TYPE_FLOAT (5126) +#define TINYGLTF_COMPONENT_TYPE_DOUBLE (5130) + +#define TINYGLTF_TEXTURE_FILTER_NEAREST (9728) +#define TINYGLTF_TEXTURE_FILTER_LINEAR (9729) +#define TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST (9984) +#define TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST (9985) +#define TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR (9986) +#define TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR (9987) + +#define TINYGLTF_TEXTURE_WRAP_REPEAT (10497) +#define TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE (33071) +#define TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT (33648) + +// Redeclarations of the above for technique.parameters. +#define TINYGLTF_PARAMETER_TYPE_BYTE (5120) +#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE (5121) +#define TINYGLTF_PARAMETER_TYPE_SHORT (5122) +#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT (5123) +#define TINYGLTF_PARAMETER_TYPE_INT (5124) +#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT (5125) +#define TINYGLTF_PARAMETER_TYPE_FLOAT (5126) + +#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC2 (35664) +#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 (35665) +#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC4 (35666) + +#define TINYGLTF_PARAMETER_TYPE_INT_VEC2 (35667) +#define TINYGLTF_PARAMETER_TYPE_INT_VEC3 (35668) +#define TINYGLTF_PARAMETER_TYPE_INT_VEC4 (35669) + +#define TINYGLTF_PARAMETER_TYPE_BOOL (35670) +#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC2 (35671) +#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC3 (35672) +#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC4 (35673) + +#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT2 (35674) +#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT3 (35675) +#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT4 (35676) + +#define TINYGLTF_PARAMETER_TYPE_SAMPLER_2D (35678) + +// End parameter types + +#define TINYGLTF_TYPE_VEC2 (2) +#define TINYGLTF_TYPE_VEC3 (3) +#define TINYGLTF_TYPE_VEC4 (4) +#define TINYGLTF_TYPE_MAT2 (32 + 2) +#define TINYGLTF_TYPE_MAT3 (32 + 3) +#define TINYGLTF_TYPE_MAT4 (32 + 4) +#define TINYGLTF_TYPE_SCALAR (64 + 1) +#define TINYGLTF_TYPE_VECTOR (64 + 4) +#define TINYGLTF_TYPE_MATRIX (64 + 16) + +#define TINYGLTF_IMAGE_FORMAT_JPEG (0) +#define TINYGLTF_IMAGE_FORMAT_PNG (1) +#define TINYGLTF_IMAGE_FORMAT_BMP (2) +#define TINYGLTF_IMAGE_FORMAT_GIF (3) + +#define TINYGLTF_TEXTURE_FORMAT_ALPHA (6406) +#define TINYGLTF_TEXTURE_FORMAT_RGB (6407) +#define TINYGLTF_TEXTURE_FORMAT_RGBA (6408) +#define TINYGLTF_TEXTURE_FORMAT_LUMINANCE (6409) +#define TINYGLTF_TEXTURE_FORMAT_LUMINANCE_ALPHA (6410) + +#define TINYGLTF_TEXTURE_TARGET_TEXTURE2D (3553) +#define TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE (5121) + +#define TINYGLTF_TARGET_ARRAY_BUFFER (34962) +#define TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER (34963) + +#define TINYGLTF_SHADER_TYPE_VERTEX_SHADER (35633) +#define TINYGLTF_SHADER_TYPE_FRAGMENT_SHADER (35632) + +#define TINYGLTF_DOUBLE_EPS (1.e-12) +#define TINYGLTF_DOUBLE_EQUAL(a, b) (std::fabs((b) - (a)) < TINYGLTF_DOUBLE_EPS) + +typedef enum { + NULL_TYPE = 0, + NUMBER_TYPE = 1, + INT_TYPE = 2, + BOOL_TYPE = 3, + STRING_TYPE = 4, + ARRAY_TYPE = 5, + BINARY_TYPE = 6, + OBJECT_TYPE = 7 +} Type; + +static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { + if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { + return 1; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + return 1; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { + return 2; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + return 2; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { + return 8; + } else { + // Unknown componenty type + return -1; + } +} + +static inline int32_t GetTypeSizeInBytes(uint32_t ty) { + if (ty == TINYGLTF_TYPE_SCALAR) { + return 1; + } else if (ty == TINYGLTF_TYPE_VEC2) { + return 2; + } else if (ty == TINYGLTF_TYPE_VEC3) { + return 3; + } else if (ty == TINYGLTF_TYPE_VEC4) { + return 4; + } else if (ty == TINYGLTF_TYPE_MAT2) { + return 4; + } else if (ty == TINYGLTF_TYPE_MAT3) { + return 9; + } else if (ty == TINYGLTF_TYPE_MAT4) { + return 16; + } else { + // Unknown componenty type + return -1; + } +} + +bool IsDataURI(const std::string &in); +bool DecodeDataURI(std::vector *out, std::string &mime_type, + const std::string &in, size_t reqBytes, bool checkSize); + +#ifdef __clang__ +#pragma clang diagnostic push +// Suppress warning for : static Value null_value +// https://stackoverflow.com/questions/15708411/how-to-deal-with-global-constructor-warning-in-clang +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// Simple class to represent JSON object +class Value { + public: + typedef std::vector Array; + typedef std::map Object; + + Value() : type_(NULL_TYPE) {} + + explicit Value(bool b) : type_(BOOL_TYPE) { boolean_value_ = b; } + explicit Value(int i) : type_(INT_TYPE) { int_value_ = i; } + explicit Value(double n) : type_(NUMBER_TYPE) { number_value_ = n; } + explicit Value(const std::string &s) : type_(STRING_TYPE) { + string_value_ = s; + } + explicit Value(const unsigned char *p, size_t n) : type_(BINARY_TYPE) { + binary_value_.resize(n); + memcpy(binary_value_.data(), p, n); + } + explicit Value(const Array &a) : type_(ARRAY_TYPE) { + array_value_ = Array(a); + } + explicit Value(const Object &o) : type_(OBJECT_TYPE) { + object_value_ = Object(o); + } + + char Type() const { return static_cast(type_); } + + bool IsBool() const { return (type_ == BOOL_TYPE); } + + bool IsInt() const { return (type_ == INT_TYPE); } + + bool IsNumber() const { return (type_ == NUMBER_TYPE); } + + bool IsString() const { return (type_ == STRING_TYPE); } + + bool IsBinary() const { return (type_ == BINARY_TYPE); } + + bool IsArray() const { return (type_ == ARRAY_TYPE); } + + bool IsObject() const { return (type_ == OBJECT_TYPE); } + + // Accessor + template + const T &Get() const; + template + T &Get(); + + // Lookup value from an array + const Value &Get(int idx) const { + static Value null_value; + assert(IsArray()); + assert(idx >= 0); + return (static_cast(idx) < array_value_.size()) + ? array_value_[static_cast(idx)] + : null_value; + } + + // Lookup value from a key-value pair + const Value &Get(const std::string &key) const { + static Value null_value; + assert(IsObject()); + Object::const_iterator it = object_value_.find(key); + return (it != object_value_.end()) ? it->second : null_value; + } + + size_t ArrayLen() const { + if (!IsArray()) return 0; + return array_value_.size(); + } + + // Valid only for object type. + bool Has(const std::string &key) const { + if (!IsObject()) return false; + Object::const_iterator it = object_value_.find(key); + return (it != object_value_.end()) ? true : false; + } + + // List keys + std::vector Keys() const { + std::vector keys; + if (!IsObject()) return keys; // empty + + for (Object::const_iterator it = object_value_.begin(); + it != object_value_.end(); ++it) { + keys.push_back(it->first); + } + + return keys; + } + + size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); } + + bool operator==(const tinygltf::Value &other) const; + + protected: + int type_; + + int int_value_; + double number_value_; + std::string string_value_; + std::vector binary_value_; + Array array_value_; + Object object_value_; + bool boolean_value_; +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#define TINYGLTF_VALUE_GET(ctype, var) \ + template <> \ + inline const ctype &Value::Get() const { \ + return var; \ + } \ + template <> \ + inline ctype &Value::Get() { \ + return var; \ + } +TINYGLTF_VALUE_GET(bool, boolean_value_) +TINYGLTF_VALUE_GET(double, number_value_) +TINYGLTF_VALUE_GET(int, int_value_) +TINYGLTF_VALUE_GET(std::string, string_value_) +TINYGLTF_VALUE_GET(std::vector, binary_value_) +TINYGLTF_VALUE_GET(Value::Array, array_value_) +TINYGLTF_VALUE_GET(Value::Object, object_value_) +#undef TINYGLTF_VALUE_GET + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat" +#pragma clang diagnostic ignored "-Wpadded" +#endif + +/// Agregate object for representing a color +using ColorValue = std::array; + +struct Parameter { + bool bool_value = false; + bool has_number_value = false; + std::string string_value; + std::vector number_array; + std::map json_double_value; + double number_value = 0.0; + // context sensitive methods. depending the type of the Parameter you are + // accessing, these are either valid or not + // If this parameter represent a texture map in a material, will return the + // texture index + + /// Return the index of a texture if this Parameter is a texture map. + /// Returned value is only valid if the parameter represent a texture from a + /// material + int TextureIndex() const { + const auto it = json_double_value.find("index"); + if (it != std::end(json_double_value)) { + return int(it->second); + } + return -1; + } + + /// Material factor, like the roughness or metalness of a material + /// Returned value is only valid if the parameter represent a texture from a + /// material + double Factor() const { return number_value; } + + /// Return the color of a material + /// Returned value is only valid if the parameter represent a texture from a + /// material + ColorValue ColorFactor() const { + return { + {// this agregate intialize the std::array object, and uses C++11 RVO. + number_array[0], number_array[1], number_array[2], + (number_array.size() > 3 ? number_array[3] : 1.0)}}; + } + + bool operator==(const Parameter &) const; +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +typedef std::map ParameterMap; +typedef std::map ExtensionMap; + +struct AnimationChannel { + int sampler; // required + int target_node; // required (index of the node to target) + std::string target_path; // required in ["translation", "rotation", "scale", + // "weights"] + Value extras; + + AnimationChannel() : sampler(-1), target_node(-1) {} + bool operator==(const AnimationChannel &) const; +}; + +struct AnimationSampler { + int input; // required + int output; // required + std::string interpolation; // in ["LINEAR", "STEP", "CATMULLROMSPLINE", + // "CUBICSPLINE"], default "LINEAR" + Value extras; + + AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {} + bool operator==(const AnimationSampler &) const; +}; + +struct Animation { + std::string name; + std::vector channels; + std::vector samplers; + Value extras; + + bool operator==(const Animation &) const; +}; + +struct Skin { + std::string name; + int inverseBindMatrices; // required here but not in the spec + int skeleton; // The index of the node used as a skeleton root + std::vector joints; // Indices of skeleton nodes + + Skin() { + inverseBindMatrices = -1; + skeleton = -1; + } + bool operator==(const Skin &) const; +}; + +struct Sampler { + std::string name; + int minFilter; // ["NEAREST", "LINEAR", "NEAREST_MIPMAP_LINEAR", + // "LINEAR_MIPMAP_NEAREST", "NEAREST_MIPMAP_LINEAR", + // "LINEAR_MIPMAP_LINEAR"] + int magFilter; // ["NEAREST", "LINEAR"] + int wrapS; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default + // "REPEAT" + int wrapT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default + // "REPEAT" + int wrapR; // TinyGLTF extension + Value extras; + + Sampler() + : wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT), + wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT) {} + bool operator==(const Sampler &) const; +}; + +struct Image { + std::string name; + int width; + int height; + int component; + std::vector image; + int bufferView; // (required if no uri) + std::string mimeType; // (required if no uri) ["image/jpeg", "image/png", + // "image/bmp", "image/gif"] + std::string uri; // (required if no mimeType) + Value extras; + ExtensionMap extensions; + + // When this flag is true, data is stored to `image` in as-is format(e.g. jpeg + // compressed for "image/jpeg" mime) This feature is good if you use custom + // image loader function. (e.g. delayed decoding of images for faster glTF + // parsing) Default parser for Image does not provide as-is loading feature at + // the moment. (You can manipulate this by providing your own LoadImageData + // function) + bool as_is; + + Image() : as_is(false) { + bufferView = -1; + width = -1; + height = -1; + component = -1; + } + bool operator==(const Image &) const; +}; + +struct Texture { + std::string name; + + int sampler; + int source; + Value extras; + ExtensionMap extensions; + + Texture() : sampler(-1), source(-1) {} + bool operator==(const Texture &) const; +}; + +// Each extension should be stored in a ParameterMap. +// members not in the values could be included in the ParameterMap +// to keep a single material model +struct Material { + std::string name; + + ParameterMap values; // PBR metal/roughness workflow + ParameterMap additionalValues; // normal/occlusion/emissive values + + ExtensionMap extensions; + Value extras; + + bool operator==(const Material &) const; +}; + +struct BufferView { + std::string name; + int buffer; // Required + size_t byteOffset; // minimum 0, default 0 + size_t byteLength; // required, minimum 1 + size_t byteStride; // minimum 4, maximum 252 (multiple of 4), default 0 = + // understood to be tightly packed + int target; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] + Value extras; + + BufferView() : byteOffset(0), byteStride(0) {} + bool operator==(const BufferView &) const; +}; + +struct Accessor { + int bufferView; // optional in spec but required here since sparse accessor + // are not supported + std::string name; + size_t byteOffset; + bool normalized; // optinal. + int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_*** + size_t count; // required + int type; // (required) One of TINYGLTF_TYPE_*** .. + Value extras; + + std::vector minValues; // optional + std::vector maxValues; // optional + + // TODO(syoyo): "sparse" + + /// + /// Utility function to compute byteStride for a given bufferView object. + /// Returns -1 upon invalid glTF value or parameter configuration. + /// + int ByteStride(const BufferView &bufferViewObject) const { + if (bufferViewObject.byteStride == 0) { + // Assume data is tightly packed. + int componentSizeInBytes = + GetComponentSizeInBytes(static_cast(componentType)); + if (componentSizeInBytes <= 0) { + return -1; + } + + int typeSizeInBytes = GetTypeSizeInBytes(static_cast(type)); + if (typeSizeInBytes <= 0) { + return -1; + } + + return componentSizeInBytes * typeSizeInBytes; + } else { + // Check if byteStride is a mulple of the size of the accessor's component + // type. + int componentSizeInBytes = + GetComponentSizeInBytes(static_cast(componentType)); + if (componentSizeInBytes <= 0) { + return -1; + } + + if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) { + return -1; + } + return static_cast(bufferViewObject.byteStride); + } + + return 0; + } + + Accessor() { bufferView = -1; } + bool operator==(const tinygltf::Accessor &) const; +}; + +struct PerspectiveCamera { + double aspectRatio; // min > 0 + double yfov; // required. min > 0 + double zfar; // min > 0 + double znear; // required. min > 0 + + PerspectiveCamera() + : aspectRatio(0.0), + yfov(0.0), + zfar(0.0) // 0 = use infinite projecton matrix + , + znear(0.0) {} + bool operator==(const PerspectiveCamera &) const; + + ExtensionMap extensions; + Value extras; +}; + +struct OrthographicCamera { + double xmag; // required. must not be zero. + double ymag; // required. must not be zero. + double zfar; // required. `zfar` must be greater than `znear`. + double znear; // required + + OrthographicCamera() : xmag(0.0), ymag(0.0), zfar(0.0), znear(0.0) {} + bool operator==(const OrthographicCamera &) const; + + ExtensionMap extensions; + Value extras; +}; + +struct Camera { + std::string type; // required. "perspective" or "orthographic" + std::string name; + + PerspectiveCamera perspective; + OrthographicCamera orthographic; + + Camera() {} + bool operator==(const Camera &) const; + + ExtensionMap extensions; + Value extras; +}; + +struct Primitive { + std::map attributes; // (required) A dictionary object of + // integer, where each integer + // is the index of the accessor + // containing an attribute. + int material; // The index of the material to apply to this primitive + // when rendering. + int indices; // The index of the accessor that contains the indices. + int mode; // one of TINYGLTF_MODE_*** + std::vector > targets; // array of morph targets, + // where each target is a dict with attribues in ["POSITION, "NORMAL", + // "TANGENT"] pointing + // to their corresponding accessors + Value extras; + + Primitive() { + material = -1; + indices = -1; + } + bool operator==(const Primitive &) const; +}; + +struct Mesh { + std::string name; + std::vector primitives; + std::vector weights; // weights to be applied to the Morph Targets + std::vector > targets; + ExtensionMap extensions; + Value extras; + + bool operator==(const Mesh &) const; +}; + +class Node { + public: + Node() : camera(-1), skin(-1), mesh(-1) {} + + Node(const Node &rhs) { + camera = rhs.camera; + + name = rhs.name; + skin = rhs.skin; + mesh = rhs.mesh; + children = rhs.children; + rotation = rhs.rotation; + scale = rhs.scale; + translation = rhs.translation; + matrix = rhs.matrix; + weights = rhs.weights; + + extensions = rhs.extensions; + extras = rhs.extras; + } + ~Node() {} + bool operator==(const Node &) const; + + int camera; // the index of the camera referenced by this node + + std::string name; + int skin; + int mesh; + std::vector children; + std::vector rotation; // length must be 0 or 4 + std::vector scale; // length must be 0 or 3 + std::vector translation; // length must be 0 or 3 + std::vector matrix; // length must be 0 or 16 + std::vector weights; // The weights of the instantiated Morph Target + + ExtensionMap extensions; + Value extras; +}; + +struct Buffer { + std::string name; + std::vector data; + std::string + uri; // considered as required here but not in the spec (need to clarify) + Value extras; + + bool operator==(const Buffer &) const; +}; + +struct Asset { + std::string version; // required + std::string generator; + std::string minVersion; + std::string copyright; + ExtensionMap extensions; + Value extras; + + bool operator==(const Asset &) const; +}; + +struct Scene { + std::string name; + std::vector nodes; + + ExtensionMap extensions; + Value extras; + + bool operator==(const Scene &) const; +}; + +struct Light { + std::string name; + std::vector color; + std::string type; + + bool operator==(const Light &) const; +}; + +class Model { + public: + Model() {} + ~Model() {} + bool operator==(const Model &) const; + + std::vector accessors; + std::vector animations; + std::vector buffers; + std::vector bufferViews; + std::vector materials; + std::vector meshes; + std::vector nodes; + std::vector textures; + std::vector images; + std::vector skins; + std::vector samplers; + std::vector cameras; + std::vector scenes; + std::vector lights; + ExtensionMap extensions; + + int defaultScene; + std::vector extensionsUsed; + std::vector extensionsRequired; + + Asset asset; + + Value extras; +}; + +enum SectionCheck { + NO_REQUIRE = 0x00, + REQUIRE_SCENE = 0x01, + REQUIRE_SCENES = 0x02, + REQUIRE_NODES = 0x04, + REQUIRE_ACCESSORS = 0x08, + REQUIRE_BUFFERS = 0x10, + REQUIRE_BUFFER_VIEWS = 0x20, + REQUIRE_ALL = 0x3f +}; + +/// +/// LoadImageDataFunction type. Signature for custom image loading callbacks. +/// +typedef bool (*LoadImageDataFunction)(Image *, std::string *, std::string *, + int, int, const unsigned char *, int, + void *); + +/// +/// WriteImageDataFunction type. Signature for custom image writing callbacks. +/// +typedef bool (*WriteImageDataFunction)(const std::string *, const std::string *, + Image *, bool, void *); + +#ifndef TINYGLTF_NO_STB_IMAGE +// Declaration of default image loader callback +bool LoadImageData(Image *image, std::string *err, std::string *warn, + int req_width, int req_height, const unsigned char *bytes, + int size, void *); +#endif + +#ifndef TINYGLTF_NO_STB_IMAGE_WRITE +// Declaration of default image writer callback +bool WriteImageData(const std::string *basepath, const std::string *filename, + Image *image, bool embedImages, void *); +#endif + +/// +/// FilExistsFunction type. Signature for custom filesystem callbacks. +/// +typedef bool (*FileExistsFunction)(const std::string &abs_filename, void *); + +/// +/// ExpandFilePathFunction type. Signature for custom filesystem callbacks. +/// +typedef std::string (*ExpandFilePathFunction)(const std::string &, void *); + +/// +/// ReadWholeFileFunction type. Signature for custom filesystem callbacks. +/// +typedef bool (*ReadWholeFileFunction)(std::vector *, + std::string *, const std::string &, + void *); + +/// +/// WriteWholeFileFunction type. Signature for custom filesystem callbacks. +/// +typedef bool (*WriteWholeFileFunction)(std::string *, const std::string &, + const std::vector &, + void *); + +/// +/// A structure containing all required filesystem callbacks and a pointer to +/// their user data. +/// +struct FsCallbacks { + FileExistsFunction FileExists; + ExpandFilePathFunction ExpandFilePath; + ReadWholeFileFunction ReadWholeFile; + WriteWholeFileFunction WriteWholeFile; + + void *user_data; // An argument that is passed to all fs callbacks +}; + +#ifndef TINYGLTF_NO_FS +// Declaration of default filesystem callbacks + +bool FileExists(const std::string &abs_filename, void *); + +std::string ExpandFilePath(const std::string &filepath, void *); + +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *); + +bool WriteWholeFile(std::string *err, const std::string &filepath, + const std::vector &contents, void *); +#endif + +class TinyGLTF { + public: +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat" +#endif + + TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + ~TinyGLTF() {} + + /// + /// Loads glTF ASCII asset from a file. + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadASCIIFromFile(Model *model, std::string *err, std::string *warn, + const std::string &filename, + unsigned int check_sections = REQUIRE_ALL); + + /// + /// Loads glTF ASCII asset from string(memory). + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadASCIIFromString(Model *model, std::string *err, std::string *warn, + const char *str, const unsigned int length, + const std::string &base_dir, + unsigned int check_sections = REQUIRE_ALL); + + /// + /// Loads glTF binary asset from a file. + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadBinaryFromFile(Model *model, std::string *err, std::string *warn, + const std::string &filename, + unsigned int check_sections = REQUIRE_ALL); + + /// + /// Loads glTF binary asset from memory. + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn, + const unsigned char *bytes, + const unsigned int length, + const std::string &base_dir = "", + unsigned int check_sections = REQUIRE_ALL); + + /// + /// Write glTF to file. + /// + bool WriteGltfSceneToFile(Model *model, const std::string &filename, + bool embedImages, + bool embedBuffers, + bool prettyPrint /*, bool writeBinary*/); + + /// + /// Set callback to use for loading image data + /// + void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data); + + /// + /// Set callback to use for writing image data + /// + void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data); + + /// + /// Set callbacks to use for filesystem (fs) access and their user data + /// + void SetFsCallbacks(FsCallbacks callbacks); + + private: + /// + /// Loads glTF asset from string(memory). + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadFromString(Model *model, std::string *err, std::string *warn, + const char *str, const unsigned int length, + const std::string &base_dir, unsigned int check_sections); + + const unsigned char *bin_data_; + size_t bin_size_; + bool is_binary_; + + FsCallbacks fs = { +#ifndef TINYGLTF_NO_FS + &tinygltf::FileExists, &tinygltf::ExpandFilePath, + &tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile, + + nullptr // Fs callback user data +#else + nullptr, nullptr, nullptr, nullptr, + + nullptr // Fs callback user data +#endif + }; + + LoadImageDataFunction LoadImageData = +#ifndef TINYGLTF_NO_STB_IMAGE + &tinygltf::LoadImageData; +#else + nullptr; +#endif + void *load_image_user_data_ = reinterpret_cast(&fs); + + WriteImageDataFunction WriteImageData = +#ifndef TINYGLTF_NO_STB_IMAGE_WRITE + &tinygltf::WriteImageData; +#else + nullptr; +#endif + void *write_image_user_data_ = reinterpret_cast(&fs); +}; + +#ifdef __clang__ +#pragma clang diagnostic pop // -Wpadded +#endif + +} // namespace tinygltf + +#endif // TINY_GLTF_H_ + +#if defined(TINYGLTF_IMPLEMENTATION) || defined(__INTELLISENSE__) +#include +//#include +#ifndef TINYGLTF_NO_FS +#include +#endif +#include + +#ifdef __clang__ +// Disable some warnings for external files. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wglobal-constructors" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#pragma clang diagnostic ignored "-Wpadded" +#pragma clang diagnostic ignored "-Wc++98-compat" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#pragma clang diagnostic ignored "-Wswitch-enum" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" +#endif +#if __has_warning("-Wcomma") +#pragma clang diagnostic ignored "-Wcomma" +#endif +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#if __has_warning("-Wcast-qual") +#pragma clang diagnostic ignored "-Wcast-qual" +#endif +#if __has_warning("-Wmissing-variable-declarations") +#pragma clang diagnostic ignored "-Wmissing-variable-declarations" +#endif +#if __has_warning("-Wmissing-prototypes") +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#endif +#if __has_warning("-Wcast-align") +#pragma clang diagnostic ignored "-Wcast-align" +#endif +#if __has_warning("-Wnewline-eof") +#pragma clang diagnostic ignored "-Wnewline-eof" +#endif +#endif + +#include "./json.hpp" + +#ifndef TINYGLTF_NO_STB_IMAGE +#include "./stb_image.h" +#endif + +#ifndef TINYGLTF_NO_STB_IMAGE_WRITE +#include "./stb_image_write.h" +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _WIN32 +#include +#elif !defined(__ANDROID__) +#include +#endif + +#if defined(__sparcv9) +// Big endian +#else +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +#define TINYGLTF_LITTLE_ENDIAN 1 +#endif +#endif + +using nlohmann::json; + +#ifdef __APPLE__ +#include "TargetConditionals.h" +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat" +#endif + +namespace tinygltf { + +// Equals function for Value, for recursivity +static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) { + if (one.Type() != other.Type()) return false; + + switch (one.Type()) { + case NULL_TYPE: + return true; + case BOOL_TYPE: + return one.Get() == other.Get(); + case NUMBER_TYPE: + return TINYGLTF_DOUBLE_EQUAL(one.Get(), other.Get()); + case INT_TYPE: + return one.Get() == other.Get(); + case OBJECT_TYPE: { + auto oneObj = one.Get(); + auto otherObj = other.Get(); + if (oneObj.size() != otherObj.size()) return false; + for (auto &it : oneObj) { + auto otherIt = otherObj.find(it.first); + if (otherIt == otherObj.end()) return false; + + if (!Equals(it.second, otherIt->second)) return false; + } + return true; + } + case ARRAY_TYPE: { + if (one.Size() != other.Size()) return false; + for (int i = 0; i < int(one.Size()); ++i) + if (Equals(one.Get(i), other.Get(i))) return false; + return true; + } + case STRING_TYPE: + return one.Get() == other.Get(); + case BINARY_TYPE: + return one.Get >() == + other.Get >(); + default: { + // unhandled type + return false; + } + } + + return false; +} + +// Equals function for std::vector using TINYGLTF_DOUBLE_EPSILON +static bool Equals(const std::vector &one, + const std::vector &other) { + if (one.size() != other.size()) return false; + for (int i = 0; i < int(one.size()); ++i) { + if (!TINYGLTF_DOUBLE_EQUAL(one[size_t(i)], other[size_t(i)])) return false; + } + return true; +} + +bool Accessor::operator==(const Accessor &other) const { + return this->bufferView == other.bufferView && + this->byteOffset == other.byteOffset && + this->componentType == other.componentType && + this->count == other.count && this->extras == other.extras && + Equals(this->maxValues, other.maxValues) && + Equals(this->minValues, other.minValues) && this->name == other.name && + this->normalized == other.normalized && this->type == other.type; +} +bool Animation::operator==(const Animation &other) const { + return this->channels == other.channels && this->extras == other.extras && + this->name == other.name && this->samplers == other.samplers; +} +bool AnimationChannel::operator==(const AnimationChannel &other) const { + return this->extras == other.extras && + this->target_node == other.target_node && + this->target_path == other.target_path && + this->sampler == other.sampler; +} +bool AnimationSampler::operator==(const AnimationSampler &other) const { + return this->extras == other.extras && this->input == other.input && + this->interpolation == other.interpolation && + this->output == other.output; +} +bool Asset::operator==(const Asset &other) const { + return this->copyright == other.copyright && + this->extensions == other.extensions && this->extras == other.extras && + this->generator == other.generator && + this->minVersion == other.minVersion && this->version == other.version; +} +bool Buffer::operator==(const Buffer &other) const { + return this->data == other.data && this->extras == other.extras && + this->name == other.name && this->uri == other.uri; +} +bool BufferView::operator==(const BufferView &other) const { + return this->buffer == other.buffer && this->byteLength == other.byteLength && + this->byteOffset == other.byteOffset && + this->byteStride == other.byteStride && this->name == other.name && + this->target == other.target && this->extras == other.extras; +} +bool Camera::operator==(const Camera &other) const { + return this->name == other.name && this->extensions == other.extensions && + this->extras == other.extras && + this->orthographic == other.orthographic && + this->perspective == other.perspective && this->type == other.type; +} +bool Image::operator==(const Image &other) const { + return this->bufferView == other.bufferView && + this->component == other.component && this->extras == other.extras && + this->height == other.height && this->image == other.image && + this->mimeType == other.mimeType && this->name == other.name && + this->uri == other.uri && this->width == other.width; +} +bool Light::operator==(const Light &other) const { + return Equals(this->color, other.color) && this->name == other.name && + this->type == other.type; +} +bool Material::operator==(const Material &other) const { + return this->additionalValues == other.additionalValues && + this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->values == other.values; +} +bool Mesh::operator==(const Mesh &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->primitives == other.primitives && + this->targets == other.targets && Equals(this->weights, other.weights); +} +bool Model::operator==(const Model &other) const { + return this->accessors == other.accessors && + this->animations == other.animations && this->asset == other.asset && + this->buffers == other.buffers && + this->bufferViews == other.bufferViews && + this->cameras == other.cameras && + this->defaultScene == other.defaultScene && + this->extensions == other.extensions && + this->extensionsRequired == other.extensionsRequired && + this->extensionsUsed == other.extensionsUsed && + this->extras == other.extras && this->images == other.images && + this->lights == other.lights && this->materials == other.materials && + this->meshes == other.meshes && this->nodes == other.nodes && + this->samplers == other.samplers && this->scenes == other.scenes && + this->skins == other.skins && this->textures == other.textures; +} +bool Node::operator==(const Node &other) const { + return this->camera == other.camera && this->children == other.children && + this->extensions == other.extensions && this->extras == other.extras && + Equals(this->matrix, other.matrix) && this->mesh == other.mesh && + this->name == other.name && Equals(this->rotation, other.rotation) && + Equals(this->scale, other.scale) && this->skin == other.skin && + Equals(this->translation, other.translation) && + Equals(this->weights, other.weights); +} +bool OrthographicCamera::operator==(const OrthographicCamera &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->xmag, other.xmag) && + TINYGLTF_DOUBLE_EQUAL(this->ymag, other.ymag) && + TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && + TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); +} +bool Parameter::operator==(const Parameter &other) const { + if (this->bool_value != other.bool_value || + this->has_number_value != other.has_number_value) + return false; + + if (!TINYGLTF_DOUBLE_EQUAL(this->number_value, other.number_value)) + return false; + + if (this->json_double_value.size() != other.json_double_value.size()) + return false; + for (auto &it : this->json_double_value) { + auto otherIt = other.json_double_value.find(it.first); + if (otherIt == other.json_double_value.end()) return false; + + if (!TINYGLTF_DOUBLE_EQUAL(it.second, otherIt->second)) return false; + } + + if (!Equals(this->number_array, other.number_array)) return false; + + if (this->string_value != other.string_value) return false; + + return true; +} +bool PerspectiveCamera::operator==(const PerspectiveCamera &other) const { + return TINYGLTF_DOUBLE_EQUAL(this->aspectRatio, other.aspectRatio) && + this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->yfov, other.yfov) && + TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && + TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); +} +bool Primitive::operator==(const Primitive &other) const { + return this->attributes == other.attributes && this->extras == other.extras && + this->indices == other.indices && this->material == other.material && + this->mode == other.mode && this->targets == other.targets; +} +bool Sampler::operator==(const Sampler &other) const { + return this->extras == other.extras && this->magFilter == other.magFilter && + this->minFilter == other.minFilter && this->name == other.name && + this->wrapR == other.wrapR && this->wrapS == other.wrapS && + this->wrapT == other.wrapT; +} +bool Scene::operator==(const Scene &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->nodes == other.nodes; + ; +} +bool Skin::operator==(const Skin &other) const { + return this->inverseBindMatrices == other.inverseBindMatrices && + this->joints == other.joints && this->name == other.name && + this->skeleton == other.skeleton; +} +bool Texture::operator==(const Texture &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->sampler == other.sampler && + this->source == other.source; +} +bool Value::operator==(const Value &other) const { + return Equals(*this, other); +} + +static void swap4(unsigned int *val) { +#ifdef TINYGLTF_LITTLE_ENDIAN + (void)val; +#else + unsigned int tmp = *val; + unsigned char *dst = reinterpret_cast(val); + unsigned char *src = reinterpret_cast(&tmp); + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +#endif +} + +static std::string JoinPath(const std::string &path0, + const std::string &path1) { + if (path0.empty()) { + return path1; + } else { + // check '/' + char lastChar = *path0.rbegin(); + if (lastChar != '/') { + return path0 + std::string("/") + path1; + } else { + return path0 + path1; + } + } +} + +static std::string FindFile(const std::vector &paths, + const std::string &filepath, FsCallbacks *fs) { + if (fs == nullptr || fs->ExpandFilePath == nullptr || + fs->FileExists == nullptr) { + // Error, fs callback[s] missing + return std::string(); + } + + for (size_t i = 0; i < paths.size(); i++) { + std::string absPath = + fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data); + if (fs->FileExists(absPath, fs->user_data)) { + return absPath; + } + } + + return std::string(); +} + +static std::string GetFilePathExtension(const std::string &FileName) { + if (FileName.find_last_of(".") != std::string::npos) + return FileName.substr(FileName.find_last_of(".") + 1); + return ""; +} + +static std::string GetBaseDir(const std::string &filepath) { + if (filepath.find_last_of("/\\") != std::string::npos) + return filepath.substr(0, filepath.find_last_of("/\\")); + return ""; +} + +// https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path +static std::string GetBaseFilename(const std::string &filepath) { + return filepath.substr(filepath.find_last_of("/\\") + 1); +} + +std::string base64_encode(unsigned char const *, unsigned int len); +std::string base64_decode(std::string const &s); + +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wglobal-constructors" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wconversion" +#endif +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(unsigned char const *bytes_to_encode, + unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for (j = i; j < 3; j++) char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + + for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; + + while ((i++ < 3)) ret += '='; + } + + return ret; +} + +std::string base64_decode(std::string const &encoded_string) { + int in_len = static_cast(encoded_string.size()); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && (encoded_string[in_] != '=') && + is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; + in_++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_array_4[i] = + static_cast(base64_chars.find(char_array_4[i])); + + char_array_3[0] = + (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) char_array_4[j] = 0; + + for (j = 0; j < 4; j++) + char_array_4[j] = + static_cast(base64_chars.find(char_array_4[j])); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +static bool LoadExternalFile(std::vector *out, std::string *err, + std::string *warn, const std::string &filename, + const std::string &basedir, bool required, + size_t reqBytes, bool checkSize, FsCallbacks *fs) { + if (fs == nullptr || fs->FileExists == nullptr || + fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) { + // This is a developer error, assert() ? + if (err) { + (*err) += "FS callback[s] not set\n"; + } + return false; + } + + std::string *failMsgOut = required ? err : warn; + + out->clear(); + + std::vector paths; + paths.push_back(basedir); + paths.push_back("."); + + std::string filepath = FindFile(paths, filename, fs); + if (filepath.empty() || filename.empty()) { + if (failMsgOut) { + (*failMsgOut) += "File not found : " + filename + "\n"; + } + return false; + } + + std::vector buf; + std::string fileReadErr; + bool fileRead = + fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data); + if (!fileRead) { + if (failMsgOut) { + (*failMsgOut) += + "File read error : " + filepath + " : " + fileReadErr + "\n"; + } + return false; + } + + size_t sz = buf.size(); + if (sz == 0) { + if (failMsgOut) { + (*failMsgOut) += "File is empty : " + filepath + "\n"; + } + return false; + } + + if (checkSize) { + if (reqBytes == sz) { + out->swap(buf); + return true; + } else { + std::stringstream ss; + ss << "File size mismatch : " << filepath << ", requestedBytes " + << reqBytes << ", but got " << sz << std::endl; + if (failMsgOut) { + (*failMsgOut) += ss.str(); + } + return false; + } + } + + out->swap(buf); + return true; +} + +void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) { + LoadImageData = func; + load_image_user_data_ = user_data; +} + +#ifndef TINYGLTF_NO_STB_IMAGE +bool LoadImageData(Image *image, std::string *err, std::string *warn, + int req_width, int req_height, const unsigned char *bytes, + int size, void *) { + (void)warn; + + int w, h, comp, req_comp; + + // force 32-bit textures for common Vulkan compatibility. It appears that + // some GPU drivers do not support 24-bit images for Vulkan + req_comp = 4; + + // if image cannot be decoded, ignore parsing and keep it by its path + // don't break in this case + // FIXME we should only enter this function if the image is embedded. If + // image->uri references + // an image file, it should be left as it is. Image loading should not be + // mandatory (to support other formats) + unsigned char *data = + stbi_load_from_memory(bytes, size, &w, &h, &comp, req_comp); + if (!data) { + // NOTE: you can use `warn` instead of `err` + if (err) { + (*err) += "Unknown image format.\n"; + } + return false; + } + + if (w < 1 || h < 1) { + free(data); + if (err) { + (*err) += "Invalid image data.\n"; + } + return false; + } + + if (req_width > 0) { + if (req_width != w) { + free(data); + if (err) { + (*err) += "Image width mismatch.\n"; + } + return false; + } + } + + if (req_height > 0) { + if (req_height != h) { + free(data); + if (err) { + (*err) += "Image height mismatch.\n"; + } + return false; + } + } + + image->width = w; + image->height = h; + image->component = req_comp; + image->image.resize(static_cast(w * h * req_comp)); + std::copy(data, data + w * h * req_comp, image->image.begin()); + + free(data); + + return true; +} +#endif + +void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) { + WriteImageData = func; + write_image_user_data_ = user_data; +} + +#ifndef TINYGLTF_NO_STB_IMAGE_WRITE +static void WriteToMemory_stbi(void *context, void *data, int size) { + std::vector *buffer = + reinterpret_cast *>(context); + + unsigned char *pData = reinterpret_cast(data); + + buffer->insert(buffer->end(), pData, pData + size); +} + +bool WriteImageData(const std::string *basepath, const std::string *filename, + Image *image, bool embedImages, void *fsPtr) { + const std::string ext = GetFilePathExtension(*filename); + + // Write image to temporary buffer + std::string header; + std::vector data; + + if (ext == "png") { + if (!stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0], 0)) { + return false; + } + header = "data:image/png;base64,"; + } else if (ext == "jpg") { + if (!stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0], 100)) { + return false; + } + header = "data:image/jpeg;base64,"; + } else if (ext == "bmp") { + if (!stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0])) { + return false; + } + header = "data:image/bmp;base64,"; + } else if (!embedImages) { + // Error: can't output requested format to file + return false; + } + + if (embedImages) { + // Embed base64-encoded image into URI + if (data.size()) { + image->uri = + header + + base64_encode(&data[0], static_cast(data.size())); + } else { + // Throw error? + } + } else { + // Write image to disc + FsCallbacks *fs = reinterpret_cast(fsPtr); + if (fs != nullptr && fs->WriteWholeFile == nullptr) { + const std::string imagefilepath = JoinPath(*basepath, *filename); + std::string writeError; + if (!fs->WriteWholeFile(&writeError, imagefilepath, data, + fs->user_data)) { + // Could not write image file to disc; Throw error ? + return false; + } + } else { + // Throw error? + } + image->uri = *filename; + } + + return true; +} +#endif + +void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; } + +#ifndef TINYGLTF_NO_FS +// Default implementations of filesystem functions + +bool FileExists(const std::string &abs_filename, void *) { + bool ret; +#ifdef _WIN32 + FILE *fp; + errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); + if (err != 0) { + return false; + } +#else + FILE *fp = fopen(abs_filename.c_str(), "rb"); +#endif + if (fp) { + ret = true; + fclose(fp); + } else { + ret = false; + } + + return ret; +} + +std::string ExpandFilePath(const std::string &filepath, void *) { +#ifdef _WIN32 + DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0); + char *str = new char[len]; + ExpandEnvironmentStringsA(filepath.c_str(), str, len); + + std::string s(str); + + delete[] str; + + return s; +#else + +#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \ + defined(__ANDROID__) || defined(__EMSCRIPTEN__) + // no expansion + std::string s = filepath; +#else + std::string s; + wordexp_t p; + + if (filepath.empty()) { + return ""; + } + + // char** w; + int ret = wordexp(filepath.c_str(), &p, 0); + if (ret) { + // err + s = filepath; + return s; + } + + // Use first element only. + if (p.we_wordv) { + s = std::string(p.we_wordv[0]); + wordfree(&p); + } else { + s = filepath; + } + +#endif + + return s; +#endif +} + +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *) { + std::ifstream f(filepath.c_str(), std::ifstream::binary); + if (!f) { + if (err) { + (*err) += "File open error : " + filepath + "\n"; + } + return false; + } + + f.seekg(0, f.end); + size_t sz = static_cast(f.tellg()); + f.seekg(0, f.beg); + + if (int(sz) < 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } else if (sz == 0) { + if (err) { + (*err) += "File is empty : " + filepath + "\n"; + } + return false; + } + + out->resize(sz); + f.read(reinterpret_cast(&out->at(0)), + static_cast(sz)); + f.close(); + + return true; +} + +bool WriteWholeFile(std::string *err, const std::string &filepath, + const std::vector &contents, void *) { + std::ofstream f(filepath.c_str(), std::ofstream::binary); + if (!f) { + if (err) { + (*err) += "File open error for writing : " + filepath + "\n"; + } + return false; + } + + f.write(reinterpret_cast(&contents.at(0)), + static_cast(contents.size())); + if (!f) { + if (err) { + (*err) += "File write error: " + filepath + "\n"; + } + return false; + } + + f.close(); + return true; +} + +#endif // TINYGLTF_NO_FS + +static std::string MimeToExt(const std::string &mimeType) { + if (mimeType == "image/jpeg") { + return "jpg"; + } else if (mimeType == "image/png") { + return "png"; + } else if (mimeType == "image/bmp") { + return "bmp"; + } else if (mimeType == "image/gif") { + return "gif"; + } + + return ""; +} + +static void UpdateImageObject(Image &image, std::string &baseDir, int index, + bool embedImages, + WriteImageDataFunction *WriteImageData = nullptr, + void *user_data = nullptr) { + std::string filename; + std::string ext; + + // If image have uri. Use it it as a filename + if (image.uri.size()) { + filename = GetBaseFilename(image.uri); + ext = GetFilePathExtension(filename); + + } else if (image.name.size()) { + ext = MimeToExt(image.mimeType); + // Otherwise use name as filename + filename = image.name + "." + ext; + } else { + ext = MimeToExt(image.mimeType); + // Fallback to index of image as filename + filename = std::to_string(index) + "." + ext; + } + + // If callback is set, modify image data object + if (*WriteImageData != nullptr) { + std::string uri; + (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data); + } +} + +bool IsDataURI(const std::string &in) { + std::string header = "data:application/octet-stream;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/jpeg;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/png;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/bmp;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/gif;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:application/gltf-buffer;base64,"; + if (in.find(header) == 0) { + return true; + } + + return false; +} + +bool DecodeDataURI(std::vector *out, std::string &mime_type, + const std::string &in, size_t reqBytes, bool checkSize) { + std::string header = "data:application/octet-stream;base64,"; + std::string data; + if (in.find(header) == 0) { + data = base64_decode(in.substr(header.size())); // cut mime string. + } + + if (data.empty()) { + header = "data:image/jpeg;base64,"; + if (in.find(header) == 0) { + mime_type = "image/jpeg"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/png;base64,"; + if (in.find(header) == 0) { + mime_type = "image/png"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/bmp;base64,"; + if (in.find(header) == 0) { + mime_type = "image/bmp"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/gif;base64,"; + if (in.find(header) == 0) { + mime_type = "image/gif"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + mime_type = "text/plain"; + data = base64_decode(in.substr(header.size())); + } + } + + if (data.empty()) { + header = "data:application/gltf-buffer;base64,"; + if (in.find(header) == 0) { + data = base64_decode(in.substr(header.size())); + } + } + + if (data.empty()) { + return false; + } + + if (checkSize) { + if (data.size() != reqBytes) { + return false; + } + out->resize(reqBytes); + } else { + out->resize(data.size()); + } + std::copy(data.begin(), data.end(), out->begin()); + return true; +} + +static bool ParseJsonAsValue(Value *ret, const json &o) { + Value val{}; + switch (o.type()) { + case json::value_t::object: { + Value::Object value_object; + for (auto it = o.begin(); it != o.end(); it++) { + Value entry; + ParseJsonAsValue(&entry, it.value()); + if (entry.Type() != NULL_TYPE) value_object[it.key()] = entry; + } + if (value_object.size() > 0) val = Value(value_object); + } break; + case json::value_t::array: { + Value::Array value_array; + for (auto it = o.begin(); it != o.end(); it++) { + Value entry; + ParseJsonAsValue(&entry, it.value()); + if (entry.Type() != NULL_TYPE) value_array.push_back(entry); + } + if (value_array.size() > 0) val = Value(value_array); + } break; + case json::value_t::string: + val = Value(o.get()); + break; + case json::value_t::boolean: + val = Value(o.get()); + break; + case json::value_t::number_integer: + case json::value_t::number_unsigned: + val = Value(static_cast(o.get())); + break; + case json::value_t::number_float: + val = Value(o.get()); + break; + case json::value_t::null: + case json::value_t::discarded: + // default: + break; + } + if (ret) *ret = val; + + return val.Type() != NULL_TYPE; +} + +static bool ParseExtrasProperty(Value *ret, const json &o) { + json::const_iterator it = o.find("extras"); + if (it == o.end()) { + return false; + } + + return ParseJsonAsValue(ret, it.value()); +} + +static bool ParseBooleanProperty(bool *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_boolean()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a bool type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = it.value().get(); + } + + return true; +} + +static bool ParseNumberProperty(double *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_number()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a number type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = it.value().get(); + } + + return true; +} + +static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, + const json &o, const std::string &property, + bool required, + const std::string &parent_node = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!it.value().is_array()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an array"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + ret->clear(); + for (json::const_iterator i = it.value().begin(); i != it.value().end(); + i++) { + if (!i.value().is_number()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a number.\n"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + ret->push_back(i.value()); + } + + return true; +} + +static bool ParseStringProperty( + std::string *ret, std::string *err, const json &o, + const std::string &property, bool required, + const std::string &parent_node = std::string()) { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (parent_node.empty()) { + (*err) += ".\n"; + } else { + (*err) += " in `" + parent_node + "'.\n"; + } + } + } + return false; + } + + if (!it.value().is_string()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a string type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = it.value(); + } + + return true; +} + +static bool ParseStringIntProperty(std::map *ret, + std::string *err, const json &o, + const std::string &property, bool required, + const std::string &parent = "") { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + if (!parent.empty()) { + (*err) += + "'" + property + "' property is missing in " + parent + ".\n"; + } else { + (*err) += "'" + property + "' property is missing.\n"; + } + } + } + return false; + } + + // Make sure we are dealing with an object / dictionary. + if (!it.value().is_object()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an object.\n"; + } + } + return false; + } + + ret->clear(); + const json &dict = it.value(); + + json::const_iterator dictIt(dict.begin()); + json::const_iterator dictItEnd(dict.end()); + + for (; dictIt != dictItEnd; ++dictIt) { + if (!dictIt.value().is_number()) { + if (required) { + if (err) { + (*err) += "'" + property + "' value is not an int.\n"; + } + } + return false; + } + + // Insert into the list. + (*ret)[dictIt.key()] = static_cast(dictIt.value()); + } + return true; +} + +static bool ParseJSONProperty(std::map *ret, + std::string *err, const json &o, + const std::string &property, bool required) { + json::const_iterator it = o.find(property); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing. \n'"; + } + } + return false; + } + + if (!it.value().is_object()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a JSON object.\n"; + } + } + return false; + } + + ret->clear(); + const json &obj = it.value(); + json::const_iterator it2(obj.begin()); + json::const_iterator itEnd(obj.end()); + for (; it2 != itEnd; it2++) { + if (it2.value().is_number()) + ret->insert(std::pair(it2.key(), it2.value())); + } + + return true; +} + +static bool ParseParameterProperty(Parameter *param, std::string *err, + const json &o, const std::string &prop, + bool required) { + // A parameter value can either be a string or an array of either a boolean or + // a number. Booleans of any kind aren't supported here. Granted, it + // complicates the Parameter structure and breaks it semantically in the sense + // that the client probably works off the assumption that if the string is + // empty the vector is used, etc. Would a tagged union work? + if (ParseStringProperty(¶m->string_value, err, o, prop, false)) { + // Found string property. + return true; + } else if (ParseNumberArrayProperty(¶m->number_array, err, o, prop, + false)) { + // Found a number array. + return true; + } else if (ParseNumberProperty(¶m->number_value, err, o, prop, false)) { + return param->has_number_value = true; + } else if (ParseJSONProperty(¶m->json_double_value, err, o, prop, + false)) { + return true; + } else if (ParseBooleanProperty(¶m->bool_value, err, o, prop, false)) { + return true; + } else { + if (required) { + if (err) { + (*err) += "parameter must be a string or number / number array.\n"; + } + } + return false; + } +} + +static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err, + const json &o) { + (void)err; + + json::const_iterator it = o.find("extensions"); + if (it == o.end()) { + return false; + } + if (!it.value().is_object()) { + return false; + } + ExtensionMap extensions; + json::const_iterator extIt = it.value().begin(); + for (; extIt != it.value().end(); extIt++) { + if (!extIt.value().is_object()) continue; + if (!ParseJsonAsValue(&extensions[extIt.key()], extIt.value())) { + if (!extIt.key().empty()) { + // create empty object so that an extension object is still of type object + extensions[extIt.key()] = Value{ Value::Object{} }; + } + } + } + if (ret) { + (*ret) = extensions; + } + return true; +} + +static bool ParseAsset(Asset *asset, std::string *err, const json &o) { + ParseStringProperty(&asset->version, err, o, "version", true, "Asset"); + ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset"); + ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset"); + + ParseExtensionsProperty(&asset->extensions, err, o); + + // Unity exporter version is added as extra here + ParseExtrasProperty(&(asset->extras), o); + + return true; +} + +static bool ParseImage(Image *image, std::string *err, std::string *warn, + const json &o, const std::string &basedir, + FsCallbacks *fs, + LoadImageDataFunction *LoadImageData = nullptr, + void *load_image_user_data = nullptr) { + // A glTF image must either reference a bufferView or an image uri + + // schema says oneOf [`bufferView`, `uri`] + // TODO(syoyo): Check the type of each parameters. + bool hasBufferView = (o.find("bufferView") != o.end()); + bool hasURI = (o.find("uri") != o.end()); + + if (hasBufferView && hasURI) { + // Should not both defined. + if (err) { + (*err) += + "Only one of `bufferView` or `uri` should be defined, but both are " + "defined for Image.\n"; + } + return false; + } + + if (!hasBufferView && !hasURI) { + if (err) { + (*err) += "Neither required `bufferView` nor `uri` defined for Image.\n"; + } + return false; + } + + ParseStringProperty(&image->name, err, o, "name", false); + ParseExtensionsProperty(&image->extensions, err, o); + ParseExtrasProperty(&image->extras, o); + + if (hasBufferView) { + double bufferView = -1; + if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true)) { + if (err) { + (*err) += "Failed to parse `bufferView` for Image.\n"; + } + return false; + } + + std::string mime_type; + ParseStringProperty(&mime_type, err, o, "mimeType", false); + + double width = 0.0; + ParseNumberProperty(&width, err, o, "width", false); + + double height = 0.0; + ParseNumberProperty(&height, err, o, "height", false); + + // Just only save some information here. Loading actual image data from + // bufferView is done after this `ParseImage` function. + image->bufferView = static_cast(bufferView); + image->mimeType = mime_type; + image->width = static_cast(width); + image->height = static_cast(height); + + return true; + } + + // Parse URI & Load image data. + + std::string uri; + std::string tmp_err; + if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) { + if (err) { + (*err) += "Failed to parse `uri` for Image.\n"; + } + return false; + } + + std::vector img; + + if (IsDataURI(uri)) { + if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) { + if (err) { + (*err) += "Failed to decode 'uri' for image parameter.\n"; + } + return false; + } + } else { + // Assume external file + // Keep texture path (for textures that cannot be decoded) + image->uri = uri; +#ifdef TINYGLTF_NO_EXTERNAL_IMAGE + return true; +#endif + if (!LoadExternalFile(&img, err, warn, uri, basedir, false, 0, false, fs)) { + if (warn) { + (*warn) += "Failed to load external 'uri' for image parameter\n"; + } + // If the image cannot be loaded, keep uri as image->uri. + return true; + } + + if (img.empty()) { + if (warn) { + (*warn) += "Image is empty.\n"; + } + return false; + } + } + + if (*LoadImageData == nullptr) { + if (err) { + (*err) += "No LoadImageData callback specified.\n"; + } + return false; + } + return (*LoadImageData)(image, err, warn, 0, 0, &img.at(0), + static_cast(img.size()), load_image_user_data); +} + +static bool ParseTexture(Texture *texture, std::string *err, const json &o, + const std::string &basedir) { + (void)basedir; + double sampler = -1.0; + double source = -1.0; + ParseNumberProperty(&sampler, err, o, "sampler", false); + + ParseNumberProperty(&source, err, o, "source", false); + + texture->sampler = static_cast(sampler); + texture->source = static_cast(source); + + ParseExtensionsProperty(&texture->extensions, err, o); + ParseExtrasProperty(&texture->extras, o); + + ParseStringProperty(&texture->name, err, o, "name", false); + + return true; +} + +static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, + FsCallbacks *fs, const std::string &basedir, + bool is_binary = false, + const unsigned char *bin_data = nullptr, + size_t bin_size = 0) { + double byteLength; + if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, "Buffer")) { + return false; + } + + // In glTF 2.0, uri is not mandatory anymore + buffer->uri.clear(); + ParseStringProperty(&buffer->uri, err, o, "uri", false, "Buffer"); + + // having an empty uri for a non embedded image should not be valid + if (!is_binary && buffer->uri.empty()) { + if (err) { + (*err) += "'uri' is missing from non binary glTF file buffer.\n"; + } + } + + json::const_iterator type = o.find("type"); + if (type != o.end()) { + if (type.value().is_string()) { + const std::string &ty = type.value(); + if (ty.compare("arraybuffer") == 0) { + // buffer.type = "arraybuffer"; + } + } + } + + size_t bytes = static_cast(byteLength); + if (is_binary) { + // Still binary glTF accepts external dataURI. + if (!buffer->uri.empty()) { + // First try embedded data URI. + if (IsDataURI(buffer->uri)) { + std::string mime_type; + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, + true)) { + if (err) { + (*err) += + "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; + } + return false; + } + } else { + // External .bin file. + if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, + buffer->uri, basedir, true, bytes, true, fs)) { + return false; + } + } + } else { + // load data from (embedded) binary data + + if ((bin_size == 0) || (bin_data == nullptr)) { + if (err) { + (*err) += "Invalid binary data in `Buffer'.\n"; + } + return false; + } + + if (byteLength > bin_size) { + if (err) { + std::stringstream ss; + ss << "Invalid `byteLength'. Must be equal or less than binary size: " + "`byteLength' = " + << byteLength << ", binary size = " << bin_size << std::endl; + (*err) += ss.str(); + } + return false; + } + + // Read buffer data + buffer->data.resize(static_cast(byteLength)); + memcpy(&(buffer->data.at(0)), bin_data, static_cast(byteLength)); + } + + } else { + if (IsDataURI(buffer->uri)) { + std::string mime_type; + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, true)) { + if (err) { + (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; + } + return false; + } + } else { + // Assume external .bin file. + if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, buffer->uri, + basedir, true, bytes, true, fs)) { + return false; + } + } + } + + ParseStringProperty(&buffer->name, err, o, "name", false); + + return true; +} + +static bool ParseBufferView(BufferView *bufferView, std::string *err, + const json &o) { + double buffer = -1.0; + if (!ParseNumberProperty(&buffer, err, o, "buffer", true, "BufferView")) { + return false; + } + + double byteOffset = 0.0; + ParseNumberProperty(&byteOffset, err, o, "byteOffset", false); + + double byteLength = 1.0; + if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, + "BufferView")) { + return false; + } + + size_t byteStride = 0; + double byteStrideValue = 0.0; + if (!ParseNumberProperty(&byteStrideValue, err, o, "byteStride", false)) { + // Spec says: When byteStride of referenced bufferView is not defined, it + // means that accessor elements are tightly packed, i.e., effective stride + // equals the size of the element. + // We cannot determine the actual byteStride until Accessor are parsed, thus + // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner) + byteStride = 0; + } else { + byteStride = static_cast(byteStrideValue); + } + + if ((byteStride > 252) || ((byteStride % 4) != 0)) { + if (err) { + std::stringstream ss; + ss << "Invalid `byteStride' value. `byteStride' must be the multiple of " + "4 : " + << byteStride << std::endl; + + (*err) += ss.str(); + } + return false; + } + + double target = 0.0; + ParseNumberProperty(&target, err, o, "target", false); + int targetValue = static_cast(target); + if ((targetValue == TINYGLTF_TARGET_ARRAY_BUFFER) || + (targetValue == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { + // OK + } else { + targetValue = 0; + } + bufferView->target = targetValue; + + ParseStringProperty(&bufferView->name, err, o, "name", false); + + bufferView->buffer = static_cast(buffer); + bufferView->byteOffset = static_cast(byteOffset); + bufferView->byteLength = static_cast(byteLength); + bufferView->byteStride = static_cast(byteStride); + + return true; +} + +static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o) { + double bufferView = -1.0; + if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true, + "Accessor")) { + return false; + } + + double byteOffset = 0.0; + ParseNumberProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); + + bool normalized = false; + ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor"); + + double componentType = 0.0; + if (!ParseNumberProperty(&componentType, err, o, "componentType", true, + "Accessor")) { + return false; + } + + double count = 0.0; + if (!ParseNumberProperty(&count, err, o, "count", true, "Accessor")) { + return false; + } + + std::string type; + if (!ParseStringProperty(&type, err, o, "type", true, "Accessor")) { + return false; + } + + if (type.compare("SCALAR") == 0) { + accessor->type = TINYGLTF_TYPE_SCALAR; + } else if (type.compare("VEC2") == 0) { + accessor->type = TINYGLTF_TYPE_VEC2; + } else if (type.compare("VEC3") == 0) { + accessor->type = TINYGLTF_TYPE_VEC3; + } else if (type.compare("VEC4") == 0) { + accessor->type = TINYGLTF_TYPE_VEC4; + } else if (type.compare("MAT2") == 0) { + accessor->type = TINYGLTF_TYPE_MAT2; + } else if (type.compare("MAT3") == 0) { + accessor->type = TINYGLTF_TYPE_MAT3; + } else if (type.compare("MAT4") == 0) { + accessor->type = TINYGLTF_TYPE_MAT4; + } else { + std::stringstream ss; + ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n"; + if (err) { + (*err) += ss.str(); + } + return false; + } + + ParseStringProperty(&accessor->name, err, o, "name", false); + + accessor->minValues.clear(); + accessor->maxValues.clear(); + ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false, + "Accessor"); + + ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false, + "Accessor"); + + accessor->count = static_cast(count); + accessor->bufferView = static_cast(bufferView); + accessor->byteOffset = static_cast(byteOffset); + accessor->normalized = normalized; + { + int comp = static_cast(componentType); + if (comp >= TINYGLTF_COMPONENT_TYPE_BYTE && + comp <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { + // OK + accessor->componentType = comp; + } else { + std::stringstream ss; + ss << "Invalid `componentType` in accessor. Got " << comp << "\n"; + if (err) { + (*err) += ss.str(); + } + return false; + } + } + + ParseExtrasProperty(&(accessor->extras), o); + + return true; +} + +static bool ParsePrimitive(Primitive *primitive, std::string *err, + const json &o) { + double material = -1.0; + ParseNumberProperty(&material, err, o, "material", false); + primitive->material = static_cast(material); + + double mode = static_cast(TINYGLTF_MODE_TRIANGLES); + ParseNumberProperty(&mode, err, o, "mode", false); + + int primMode = static_cast(mode); + primitive->mode = primMode; // Why only triangled were supported ? + + double indices = -1.0; + ParseNumberProperty(&indices, err, o, "indices", false); + primitive->indices = static_cast(indices); + if (!ParseStringIntProperty(&primitive->attributes, err, o, "attributes", + true, "Primitive")) { + return false; + } + + // Look for morph targets + json::const_iterator targetsObject = o.find("targets"); + if ((targetsObject != o.end()) && targetsObject.value().is_array()) { + for (json::const_iterator i = targetsObject.value().begin(); + i != targetsObject.value().end(); i++) { + std::map targetAttribues; + + const json &dict = i.value(); + json::const_iterator dictIt(dict.begin()); + json::const_iterator dictItEnd(dict.end()); + + for (; dictIt != dictItEnd; ++dictIt) { + targetAttribues[dictIt.key()] = static_cast(dictIt.value()); + } + primitive->targets.push_back(targetAttribues); + } + } + + ParseExtrasProperty(&(primitive->extras), o); + + return true; +} + +static bool ParseMesh(Mesh *mesh, std::string *err, const json &o) { + ParseStringProperty(&mesh->name, err, o, "name", false); + + mesh->primitives.clear(); + json::const_iterator primObject = o.find("primitives"); + if ((primObject != o.end()) && primObject.value().is_array()) { + for (json::const_iterator i = primObject.value().begin(); + i != primObject.value().end(); i++) { + Primitive primitive; + if (ParsePrimitive(&primitive, err, i.value())) { + // Only add the primitive if the parsing succeeds. + mesh->primitives.push_back(primitive); + } + } + } + + // Look for morph targets + json::const_iterator targetsObject = o.find("targets"); + if ((targetsObject != o.end()) && targetsObject.value().is_array()) { + for (json::const_iterator i = targetsObject.value().begin(); + i != targetsObject.value().end(); i++) { + std::map targetAttribues; + + const json &dict = i.value(); + json::const_iterator dictIt(dict.begin()); + json::const_iterator dictItEnd(dict.end()); + + for (; dictIt != dictItEnd; ++dictIt) { + targetAttribues[dictIt.key()] = static_cast(dictIt.value()); + } + mesh->targets.push_back(targetAttribues); + } + } + + // Should probably check if has targets and if dimensions fit + ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false); + + ParseExtensionsProperty(&mesh->extensions, err, o); + ParseExtrasProperty(&(mesh->extras), o); + + return true; +} + +static bool ParseLight(Light *light, std::string *err, const json &o) { + ParseStringProperty(&light->name, err, o, "name", false); + ParseNumberArrayProperty(&light->color, err, o, "color", false); + ParseStringProperty(&light->type, err, o, "type", false); + return true; +} + +static bool ParseNode(Node *node, std::string *err, const json &o) { + ParseStringProperty(&node->name, err, o, "name", false); + + double skin = -1.0; + ParseNumberProperty(&skin, err, o, "skin", false); + node->skin = static_cast(skin); + + // Matrix and T/R/S are exclusive + if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { + ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); + ParseNumberArrayProperty(&node->scale, err, o, "scale", false); + ParseNumberArrayProperty(&node->translation, err, o, "translation", false); + } + + double camera = -1.0; + ParseNumberProperty(&camera, err, o, "camera", false); + node->camera = static_cast(camera); + + double mesh = -1.0; + ParseNumberProperty(&mesh, err, o, "mesh", false); + node->mesh = int(mesh); + + node->children.clear(); + json::const_iterator childrenObject = o.find("children"); + if ((childrenObject != o.end()) && childrenObject.value().is_array()) { + for (json::const_iterator i = childrenObject.value().begin(); + i != childrenObject.value().end(); i++) { + if (!i.value().is_number()) { + if (err) { + (*err) += "Invalid `children` array.\n"; + } + return false; + } + const int &childrenNode = static_cast(i.value()); + node->children.push_back(childrenNode); + } + } + + ParseExtensionsProperty(&node->extensions, err, o); + ParseExtrasProperty(&(node->extras), o); + + return true; +} + +static bool ParseMaterial(Material *material, std::string *err, const json &o) { + material->values.clear(); + material->extensions.clear(); + material->additionalValues.clear(); + + json::const_iterator it(o.begin()); + json::const_iterator itEnd(o.end()); + + for (; it != itEnd; it++) { + if (it.key() == "pbrMetallicRoughness") { + if (it.value().is_object()) { + const json &values_object = it.value(); + + json::const_iterator itVal(values_object.begin()); + json::const_iterator itValEnd(values_object.end()); + + for (; itVal != itValEnd; itVal++) { + Parameter param; + if (ParseParameterProperty(¶m, err, values_object, itVal.key(), + false)) { + material->values[itVal.key()] = param; + } + } + } + } else if (it.key() == "extensions" || it.key() == "extras") { + // done later, skip, otherwise poorly parsed contents will be saved in the + // parametermap and serialized again later + } else { + Parameter param; + if (ParseParameterProperty(¶m, err, o, it.key(), false)) { + material->additionalValues[it.key()] = param; + } + } + } + + ParseExtensionsProperty(&material->extensions, err, o); + ParseExtrasProperty(&(material->extras), o); + + return true; +} + +static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, + const json &o) { + double samplerIndex = -1.0; + double targetIndex = -1.0; + if (!ParseNumberProperty(&samplerIndex, err, o, "sampler", true, + "AnimationChannel")) { + if (err) { + (*err) += "`sampler` field is missing in animation channels\n"; + } + return false; + } + + json::const_iterator targetIt = o.find("target"); + if ((targetIt != o.end()) && targetIt.value().is_object()) { + const json &target_object = targetIt.value(); + + if (!ParseNumberProperty(&targetIndex, err, target_object, "node", true)) { + if (err) { + (*err) += "`node` field is missing in animation.channels.target\n"; + } + return false; + } + + if (!ParseStringProperty(&channel->target_path, err, target_object, "path", + true)) { + if (err) { + (*err) += "`path` field is missing in animation.channels.target\n"; + } + return false; + } + } + + channel->sampler = static_cast(samplerIndex); + channel->target_node = static_cast(targetIndex); + + ParseExtrasProperty(&(channel->extras), o); + + return true; +} + +static bool ParseAnimation(Animation *animation, std::string *err, + const json &o) { + { + json::const_iterator channelsIt = o.find("channels"); + if ((channelsIt != o.end()) && channelsIt.value().is_array()) { + for (json::const_iterator i = channelsIt.value().begin(); + i != channelsIt.value().end(); i++) { + AnimationChannel channel; + if (ParseAnimationChannel(&channel, err, i.value())) { + // Only add the channel if the parsing succeeds. + animation->channels.push_back(channel); + } + } + } + } + + { + json::const_iterator samplerIt = o.find("samplers"); + if ((samplerIt != o.end()) && samplerIt.value().is_array()) { + const json &sampler_array = samplerIt.value(); + + json::const_iterator it = sampler_array.begin(); + json::const_iterator itEnd = sampler_array.end(); + + for (; it != itEnd; it++) { + const json &s = it->get(); + + AnimationSampler sampler; + double inputIndex = -1.0; + double outputIndex = -1.0; + if (!ParseNumberProperty(&inputIndex, err, s, "input", true)) { + if (err) { + (*err) += "`input` field is missing in animation.sampler\n"; + } + return false; + } + if (!ParseStringProperty(&sampler.interpolation, err, s, + "interpolation", true)) { + if (err) { + (*err) += "`interpolation` field is missing in animation.sampler\n"; + } + return false; + } + if (!ParseNumberProperty(&outputIndex, err, s, "output", true)) { + if (err) { + (*err) += "`output` field is missing in animation.sampler\n"; + } + return false; + } + sampler.input = static_cast(inputIndex); + sampler.output = static_cast(outputIndex); + ParseExtrasProperty(&(sampler.extras), s); + animation->samplers.push_back(sampler); + } + } + } + + ParseStringProperty(&animation->name, err, o, "name", false); + + ParseExtrasProperty(&(animation->extras), o); + + return true; +} + +static bool ParseSampler(Sampler *sampler, std::string *err, const json &o) { + ParseStringProperty(&sampler->name, err, o, "name", false); + + double minFilter = + static_cast(TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR); + double magFilter = static_cast(TINYGLTF_TEXTURE_FILTER_LINEAR); + double wrapS = static_cast(TINYGLTF_TEXTURE_WRAP_REPEAT); + double wrapT = static_cast(TINYGLTF_TEXTURE_WRAP_REPEAT); + ParseNumberProperty(&minFilter, err, o, "minFilter", false); + ParseNumberProperty(&magFilter, err, o, "magFilter", false); + ParseNumberProperty(&wrapS, err, o, "wrapS", false); + ParseNumberProperty(&wrapT, err, o, "wrapT", false); + + sampler->minFilter = static_cast(minFilter); + sampler->magFilter = static_cast(magFilter); + sampler->wrapS = static_cast(wrapS); + sampler->wrapT = static_cast(wrapT); + + ParseExtrasProperty(&(sampler->extras), o); + + return true; +} + +static bool ParseSkin(Skin *skin, std::string *err, const json &o) { + ParseStringProperty(&skin->name, err, o, "name", false, "Skin"); + + std::vector joints; + if (!ParseNumberArrayProperty(&joints, err, o, "joints", false, "Skin")) { + return false; + } + + double skeleton = -1.0; + ParseNumberProperty(&skeleton, err, o, "skeleton", false, "Skin"); + skin->skeleton = static_cast(skeleton); + + skin->joints.resize(joints.size()); + for (size_t i = 0; i < joints.size(); i++) { + skin->joints[i] = static_cast(joints[i]); + } + + double invBind = -1.0; + ParseNumberProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin"); + skin->inverseBindMatrices = static_cast(invBind); + + return true; +} + +static bool ParsePerspectiveCamera(PerspectiveCamera *camera, std::string *err, + const json &o) { + double yfov = 0.0; + if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "PerspectiveCamera")) { + return false; + } + + double aspectRatio = 0.0; // = invalid + ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false, + "PerspectiveCamera"); + + double zfar = 0.0; // = invalid + ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera"); + + camera->aspectRatio = aspectRatio; + camera->zfar = zfar; + camera->yfov = yfov; + camera->znear = znear; + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseOrthographicCamera(OrthographicCamera *camera, + std::string *err, const json &o) { + double xmag = 0.0; + if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) { + return false; + } + + double ymag = 0.0; + if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) { + return false; + } + + double zfar = 0.0; + if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "OrthographicCamera")) { + return false; + } + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + camera->xmag = xmag; + camera->ymag = ymag; + camera->zfar = zfar; + camera->znear = znear; + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseCamera(Camera *camera, std::string *err, const json &o) { + if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) { + return false; + } + + if (camera->type.compare("orthographic") == 0) { + if (o.find("orthographic") == o.end()) { + if (err) { + std::stringstream ss; + ss << "Orhographic camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const json &v = o.find("orthographic").value(); + if (!v.is_object()) { + if (err) { + std::stringstream ss; + ss << "\"orthographic\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParseOrthographicCamera(&camera->orthographic, err, v.get())) { + return false; + } + } else if (camera->type.compare("perspective") == 0) { + if (o.find("perspective") == o.end()) { + if (err) { + std::stringstream ss; + ss << "Perspective camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const json &v = o.find("perspective").value(); + if (!v.is_object()) { + if (err) { + std::stringstream ss; + ss << "\"perspective\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParsePerspectiveCamera(&camera->perspective, err, v.get())) { + return false; + } + } else { + if (err) { + std::stringstream ss; + ss << "Invalid camera type: \"" << camera->type + << "\". Must be \"perspective\" or \"orthographic\"" << std::endl; + (*err) += ss.str(); + } + return false; + } + + ParseStringProperty(&camera->name, err, o, "name", false); + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + return true; +} + +bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, + const char *str, unsigned int length, + const std::string &base_dir, + unsigned int check_sections) { + if (length < 4) { + if (err) { + (*err) = "JSON string too short.\n"; + } + return false; + } + + json v; + +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \ + defined(_CPPUNWIND)) && \ + not defined(TINYGLTF_NOEXCEPTION) + try { + v = json::parse(str, str + length); + + } catch (const std::exception &e) { + if (err) { + (*err) = e.what(); + } + return false; + } +#else + { + v = json::parse(str, str + length, nullptr, /* exception */ false); + + if (!v.is_object()) { + // Assume parsing was failed. + if (err) { + (*err) = "Failed to parse JSON object\n"; + } + return false; + } + } +#endif + + if (!v.is_object()) { + // root is not an object. + if (err) { + (*err) = "Root element is not a JSON object\n"; + } + return false; + } + + // scene is not mandatory. + // FIXME Maybe a better way to handle it than removing the code + + { + json::const_iterator it = v.find("scenes"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } else if (check_sections & REQUIRE_SCENES) { + if (err) { + (*err) += "\"scenes\" object not found in .gltf or not an array type\n"; + } + return false; + } + } + + { + json::const_iterator it = v.find("nodes"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } else if (check_sections & REQUIRE_NODES) { + if (err) { + (*err) += "\"nodes\" object not found in .gltf\n"; + } + return false; + } + } + + { + json::const_iterator it = v.find("accessors"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } else if (check_sections & REQUIRE_ACCESSORS) { + if (err) { + (*err) += "\"accessors\" object not found in .gltf\n"; + } + return false; + } + } + + { + json::const_iterator it = v.find("buffers"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } else if (check_sections & REQUIRE_BUFFERS) { + if (err) { + (*err) += "\"buffers\" object not found in .gltf\n"; + } + return false; + } + } + + { + json::const_iterator it = v.find("bufferViews"); + if ((it != v.end()) && it.value().is_array()) { + // OK + } else if (check_sections & REQUIRE_BUFFER_VIEWS) { + if (err) { + (*err) += "\"bufferViews\" object not found in .gltf\n"; + } + return false; + } + } + + model->buffers.clear(); + model->bufferViews.clear(); + model->accessors.clear(); + model->meshes.clear(); + model->cameras.clear(); + model->nodes.clear(); + model->extensionsUsed.clear(); + model->extensionsRequired.clear(); + model->extensions.clear(); + model->defaultScene = -1; + + // 1. Parse Asset + { + json::const_iterator it = v.find("asset"); + if ((it != v.end()) && it.value().is_object()) { + const json &root = it.value(); + + ParseAsset(&model->asset, err, root); + } + } + + // 2. Parse extensionUsed + { + json::const_iterator it = v.find("extensionsUsed"); + if ((it != v.end()) && it.value().is_array()) { + const json &root = it.value(); + for (unsigned int i = 0; i < root.size(); ++i) { + model->extensionsUsed.push_back(root[i].get()); + } + } + } + + { + json::const_iterator it = v.find("extensionsRequired"); + if ((it != v.end()) && it.value().is_array()) { + const json &root = it.value(); + for (unsigned int i = 0; i < root.size(); ++i) { + model->extensionsRequired.push_back(root[i].get()); + } + } + } + + // 3. Parse Buffer + { + json::const_iterator rootIt = v.find("buffers"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`buffers' does not contain an JSON object."; + } + return false; + } + Buffer buffer; + if (!ParseBuffer(&buffer, err, it->get(), &fs, base_dir, + is_binary_, bin_data_, bin_size_)) { + return false; + } + + model->buffers.push_back(buffer); + } + } + } + + // 4. Parse BufferView + { + json::const_iterator rootIt = v.find("bufferViews"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`bufferViews' does not contain an JSON object."; + } + return false; + } + BufferView bufferView; + if (!ParseBufferView(&bufferView, err, it->get())) { + return false; + } + + model->bufferViews.push_back(bufferView); + } + } + } + + // 5. Parse Accessor + { + json::const_iterator rootIt = v.find("accessors"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`accessors' does not contain an JSON object."; + } + return false; + } + Accessor accessor; + if (!ParseAccessor(&accessor, err, it->get())) { + return false; + } + + model->accessors.push_back(accessor); + } + } + } + + // 6. Parse Mesh + { + json::const_iterator rootIt = v.find("meshes"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`meshes' does not contain an JSON object."; + } + return false; + } + Mesh mesh; + if (!ParseMesh(&mesh, err, it->get())) { + return false; + } + + model->meshes.push_back(mesh); + } + } + } + + // 7. Parse Node + { + json::const_iterator rootIt = v.find("nodes"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`nodes' does not contain an JSON object."; + } + return false; + } + Node node; + if (!ParseNode(&node, err, it->get())) { + return false; + } + + model->nodes.push_back(node); + } + } + } + + // 8. Parse scenes. + { + json::const_iterator rootIt = v.find("scenes"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!(it.value().is_object())) { + if (err) { + (*err) += "`scenes' does not contain an JSON object."; + } + return false; + } + const json &o = it->get(); + std::vector nodes; + if (!ParseNumberArrayProperty(&nodes, err, o, "nodes", false)) { + return false; + } + + Scene scene; + ParseStringProperty(&scene.name, err, o, "name", false); + std::vector nodesIds; + for (size_t i = 0; i < nodes.size(); i++) { + nodesIds.push_back(static_cast(nodes[i])); + } + scene.nodes = nodesIds; + + ParseExtensionsProperty(&scene.extensions, err, o); + ParseExtrasProperty(&scene.extras, o); + + model->scenes.push_back(scene); + } + } + } + + // 9. Parse default scenes. + { + json::const_iterator rootIt = v.find("scene"); + if ((rootIt != v.end()) && rootIt.value().is_number()) { + const int defaultScene = rootIt.value(); + + model->defaultScene = static_cast(defaultScene); + } + } + + // 10. Parse Material + { + json::const_iterator rootIt = v.find("materials"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`materials' does not contain an JSON object."; + } + return false; + } + json jsonMaterial = it->get(); + + Material material; + ParseStringProperty(&material.name, err, jsonMaterial, "name", false); + + if (!ParseMaterial(&material, err, jsonMaterial)) { + return false; + } + + model->materials.push_back(material); + } + } + } + + // 11. Parse Image + { + json::const_iterator rootIt = v.find("images"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`images' does not contain an JSON object."; + } + return false; + } + Image image; + if (!ParseImage(&image, err, warn, it.value(), base_dir, &fs, + &this->LoadImageData, load_image_user_data_)) { + return false; + } + + if (image.bufferView != -1) { + // Load image from the buffer view. + if (size_t(image.bufferView) >= model->bufferViews.size()) { + if (err) { + std::stringstream ss; + ss << "bufferView \"" << image.bufferView + << "\" not found in the scene." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const BufferView &bufferView = + model->bufferViews[size_t(image.bufferView)]; + const Buffer &buffer = model->buffers[size_t(bufferView.buffer)]; + + if (*LoadImageData == nullptr) { + if (err) { + (*err) += "No LoadImageData callback specified.\n"; + } + return false; + } + bool ret = LoadImageData(&image, err, warn, image.width, image.height, + &buffer.data[bufferView.byteOffset], + static_cast(bufferView.byteLength), + load_image_user_data_); + if (!ret) { + return false; + } + } + + model->images.push_back(image); + } + } + } + + // 12. Parse Texture + { + json::const_iterator rootIt = v.find("textures"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; it++) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`textures' does not contain an JSON object."; + } + return false; + } + Texture texture; + if (!ParseTexture(&texture, err, it->get(), base_dir)) { + return false; + } + + model->textures.push_back(texture); + } + } + } + + // 13. Parse Animation + { + json::const_iterator rootIt = v.find("animations"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`animations' does not contain an JSON object."; + } + return false; + } + Animation animation; + if (!ParseAnimation(&animation, err, it->get())) { + return false; + } + + model->animations.push_back(animation); + } + } + } + + // 14. Parse Skin + { + json::const_iterator rootIt = v.find("skins"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`skins' does not contain an JSON object."; + } + return false; + } + Skin skin; + if (!ParseSkin(&skin, err, it->get())) { + return false; + } + + model->skins.push_back(skin); + } + } + } + + // 15. Parse Sampler + { + json::const_iterator rootIt = v.find("samplers"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`samplers' does not contain an JSON object."; + } + return false; + } + Sampler sampler; + if (!ParseSampler(&sampler, err, it->get())) { + return false; + } + + model->samplers.push_back(sampler); + } + } + } + + // 16. Parse Camera + { + json::const_iterator rootIt = v.find("cameras"); + if ((rootIt != v.end()) && rootIt.value().is_array()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + if (!it.value().is_object()) { + if (err) { + (*err) += "`cameras' does not contain an JSON object."; + } + return false; + } + Camera camera; + if (!ParseCamera(&camera, err, it->get())) { + return false; + } + + model->cameras.push_back(camera); + } + } + } + + // 17. Parse Extensions + ParseExtensionsProperty(&model->extensions, err, v); + + // 18. Specific extension implementations + { + json::const_iterator rootIt = v.find("extensions"); + if ((rootIt != v.end()) && rootIt.value().is_object()) { + const json &root = rootIt.value(); + + json::const_iterator it(root.begin()); + json::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + // parse KHR_lights_cmn extension + if ((it.key().compare("KHR_lights_cmn") == 0) && + it.value().is_object()) { + const json &object = it.value(); + json::const_iterator itLight(object.find("lights")); + json::const_iterator itLightEnd(object.end()); + if (itLight == itLightEnd) { + continue; + } + + if (!itLight.value().is_array()) { + continue; + } + + const json &lights = itLight.value(); + json::const_iterator arrayIt(lights.begin()); + json::const_iterator arrayItEnd(lights.end()); + for (; arrayIt != arrayItEnd; ++arrayIt) { + Light light; + if (!ParseLight(&light, err, arrayIt.value())) { + return false; + } + model->lights.push_back(light); + } + } + } + } + } + + // 19. Parse Extras + ParseExtrasProperty(&model->extras, v); + + return true; +} + +bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err, + std::string *warn, const char *str, + unsigned int length, + const std::string &base_dir, + unsigned int check_sections) { + is_binary_ = false; + bin_data_ = nullptr; + bin_size_ = 0; + + return LoadFromString(model, err, warn, str, length, base_dir, + check_sections); +} + +bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err, + std::string *warn, const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + size_t sz = data.size(); + if (sz == 0) { + if (err) { + (*err) = "Empty file."; + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadASCIIFromString( + model, err, warn, reinterpret_cast(&data.at(0)), + static_cast(data.size()), basedir, check_sections); + + return ret; +} + +bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, + std::string *warn, + const unsigned char *bytes, + unsigned int size, + const std::string &base_dir, + unsigned int check_sections) { + if (size < 20) { + if (err) { + (*err) = "Too short data size for glTF Binary."; + } + return false; + } + + if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' && + bytes[3] == 'F') { + // ok + } else { + if (err) { + (*err) = "Invalid magic."; + } + return false; + } + + unsigned int version; // 4 bytes + unsigned int length; // 4 bytes + unsigned int model_length; // 4 bytes + unsigned int model_format; // 4 bytes; + + // @todo { Endian swap for big endian machine. } + memcpy(&version, bytes + 4, 4); + swap4(&version); + memcpy(&length, bytes + 8, 4); + swap4(&length); + memcpy(&model_length, bytes + 12, 4); + swap4(&model_length); + memcpy(&model_format, bytes + 16, 4); + swap4(&model_format); + + // In case the Bin buffer is not present, the size is exactly 20 + size of + // JSON contents, + // so use "greater than" operator. + if ((20 + model_length > size) || (model_length < 1) || + (model_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format. + if (err) { + (*err) = "Invalid glTF binary."; + } + return false; + } + + // Extract JSON string. + std::string jsonString(reinterpret_cast(&bytes[20]), + model_length); + + is_binary_ = true; + bin_data_ = bytes + 20 + model_length + + 8; // 4 bytes (buffer_length) + 4 bytes(buffer_format) + bin_size_ = + length - (20 + model_length); // extract header + JSON scene data. + + bool ret = LoadFromString(model, err, warn, + reinterpret_cast(&bytes[20]), + model_length, base_dir, check_sections); + if (!ret) { + return ret; + } + + return true; +} + +bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err, + std::string *warn, + const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadBinaryFromMemory(model, err, warn, &data.at(0), + static_cast(data.size()), + basedir, check_sections); + + return ret; +} + +/////////////////////// +// GLTF Serialization +/////////////////////// + +// typedef std::pair json_object_pair; + +template +static void SerializeNumberProperty(const std::string &key, T number, + json &obj) { + // obj.insert( + // json_object_pair(key, json(static_cast(number)))); + // obj[key] = static_cast(number); + obj[key] = number; +} + +template +static void SerializeNumberArrayProperty(const std::string &key, + const std::vector &value, + json &obj) { + json o; + json vals; + + for (unsigned int i = 0; i < value.size(); ++i) { + vals.push_back(static_cast(value[i])); + } + if (!vals.is_null()) { + obj[key] = vals; + } +} + +static void SerializeStringProperty(const std::string &key, + const std::string &value, json &obj) { + obj[key] = value; +} + +static void SerializeStringArrayProperty(const std::string &key, + const std::vector &value, + json &obj) { + json o; + json vals; + + for (unsigned int i = 0; i < value.size(); ++i) { + vals.push_back(value[i]); + } + + obj[key] = vals; +} + +static bool ValueToJson(const Value &value, json *ret) { + json obj; + switch (value.Type()) { + case NUMBER_TYPE: + obj = json(value.Get()); + break; + case INT_TYPE: + obj = json(value.Get()); + break; + case BOOL_TYPE: + obj = json(value.Get()); + break; + case STRING_TYPE: + obj = json(value.Get()); + break; + case ARRAY_TYPE: { + for (unsigned int i = 0; i < value.ArrayLen(); ++i) { + Value elementValue = value.Get(int(i)); + json elementJson; + if (ValueToJson(value.Get(int(i)), &elementJson)) + obj.push_back(elementJson); + } + break; + } + case BINARY_TYPE: + // TODO + // obj = json(value.Get>()); + return false; + break; + case OBJECT_TYPE: { + Value::Object objMap = value.Get(); + for (auto &it : objMap) { + json elementJson; + if (ValueToJson(it.second, &elementJson)) obj[it.first] = elementJson; + } + break; + } + case NULL_TYPE: + default: + return false; + } + if (ret) *ret = obj; + return true; +} + +static void SerializeValue(const std::string &key, const Value &value, + json &obj) { + json ret; + if (ValueToJson(value, &ret)) obj[key] = ret; +} + +static void SerializeGltfBufferData(const std::vector &data, + json &o) { + std::string header = "data:application/octet-stream;base64,"; + std::string encodedData = + base64_encode(&data[0], static_cast(data.size())); + SerializeStringProperty("uri", header + encodedData, o); +} + +static void SerializeGltfBufferData(const std::vector &data, + const std::string &binFilename) { + std::ofstream output(binFilename.c_str(), std::ofstream::binary); + output.write(reinterpret_cast(&data[0]), + std::streamsize(data.size())); + output.close(); +} + +static void SerializeParameterMap(ParameterMap ¶m, json &o) { + for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end(); + ++paramIt) { + if (paramIt->second.number_array.size()) { + SerializeNumberArrayProperty(paramIt->first, + paramIt->second.number_array, o); + } else if (paramIt->second.json_double_value.size()) { + json json_double_value; + for (std::map::iterator it = + paramIt->second.json_double_value.begin(); + it != paramIt->second.json_double_value.end(); ++it) { + if (it->first == "index") { + json_double_value[it->first] = paramIt->second.TextureIndex(); + } else { + json_double_value[it->first] = it->second; + } + } + + o[paramIt->first] = json_double_value; + } else if (!paramIt->second.string_value.empty()) { + SerializeStringProperty(paramIt->first, paramIt->second.string_value, o); + } else if (paramIt->second.has_number_value) { + o[paramIt->first] = paramIt->second.number_value; + } else { + o[paramIt->first] = paramIt->second.bool_value; + } + } +} + +static void SerializeExtensionMap(ExtensionMap &extensions, json &o) { + if (!extensions.size()) return; + + json extMap; + for (ExtensionMap::iterator extIt = extensions.begin(); + extIt != extensions.end(); ++extIt) { + json extension_values; + + // Allow an empty object for extension(#97) + json ret; + if (ValueToJson(extIt->second, &ret)) { + extMap[extIt->first] = ret; + } + if(ret.is_null()) { + if (!(extIt->first.empty())) { // name should not be empty, but for sure + // create empty object so that an extension name is still included in json. + extMap[extIt->first] = json({}); + } + } + } + o["extensions"] = extMap; +} + +static void SerializeGltfAccessor(Accessor &accessor, json &o) { + SerializeNumberProperty("bufferView", accessor.bufferView, o); + + if (accessor.byteOffset != 0.0) + SerializeNumberProperty("byteOffset", int(accessor.byteOffset), o); + + SerializeNumberProperty("componentType", accessor.componentType, o); + SerializeNumberProperty("count", accessor.count, o); + SerializeNumberArrayProperty("min", accessor.minValues, o); + SerializeNumberArrayProperty("max", accessor.maxValues, o); + std::string type; + switch (accessor.type) { + case TINYGLTF_TYPE_SCALAR: + type = "SCALAR"; + break; + case TINYGLTF_TYPE_VEC2: + type = "VEC2"; + break; + case TINYGLTF_TYPE_VEC3: + type = "VEC3"; + break; + case TINYGLTF_TYPE_VEC4: + type = "VEC4"; + break; + case TINYGLTF_TYPE_MAT2: + type = "MAT2"; + break; + case TINYGLTF_TYPE_MAT3: + type = "MAT3"; + break; + case TINYGLTF_TYPE_MAT4: + type = "MAT4"; + break; + } + + SerializeStringProperty("type", type, o); + if (!accessor.name.empty()) SerializeStringProperty("name", accessor.name, o); + + if (accessor.extras.Type() != NULL_TYPE) { + SerializeValue("extras", accessor.extras, o); + } +} + +static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) { + SerializeNumberProperty("sampler", channel.sampler, o); + json target; + SerializeNumberProperty("node", channel.target_node, target); + SerializeStringProperty("path", channel.target_path, target); + + o["target"] = target; + + if (channel.extras.Type() != NULL_TYPE) { + SerializeValue("extras", channel.extras, o); + } +} + +static void SerializeGltfAnimationSampler(AnimationSampler &sampler, json &o) { + SerializeNumberProperty("input", sampler.input, o); + SerializeNumberProperty("output", sampler.output, o); + SerializeStringProperty("interpolation", sampler.interpolation, o); + + if (sampler.extras.Type() != NULL_TYPE) { + SerializeValue("extras", sampler.extras, o); + } +} + +static void SerializeGltfAnimation(Animation &animation, json &o) { + if (!animation.name.empty()) + SerializeStringProperty("name", animation.name, o); + json channels; + for (unsigned int i = 0; i < animation.channels.size(); ++i) { + json channel; + AnimationChannel gltfChannel = animation.channels[i]; + SerializeGltfAnimationChannel(gltfChannel, channel); + channels.push_back(channel); + } + o["channels"] = channels; + + json samplers; + for (unsigned int i = 0; i < animation.samplers.size(); ++i) { + json sampler; + AnimationSampler gltfSampler = animation.samplers[i]; + SerializeGltfAnimationSampler(gltfSampler, sampler); + samplers.push_back(sampler); + } + + o["samplers"] = samplers; + + if (animation.extras.Type() != NULL_TYPE) { + SerializeValue("extras", animation.extras, o); + } +} + +static void SerializeGltfAsset(Asset &asset, json &o) { + if (!asset.generator.empty()) { + SerializeStringProperty("generator", asset.generator, o); + } + + if (!asset.version.empty()) { + SerializeStringProperty("version", asset.version, o); + } + + if (asset.extras.Keys().size()) { + SerializeValue("extras", asset.extras, o); + } + + SerializeExtensionMap(asset.extensions, o); +} + +static void SerializeGltfBuffer(Buffer &buffer, json &o) { + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeGltfBufferData(buffer.data, o); + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + if (buffer.extras.Type() != NULL_TYPE) { + SerializeValue("extras", buffer.extras, o); + } +} + +static void SerializeGltfBuffer(Buffer &buffer, json &o, + const std::string &binFilename, + const std::string &binBaseFilename) { + SerializeGltfBufferData(buffer.data, binFilename); + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeStringProperty("uri", binBaseFilename, o); + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + if (buffer.extras.Type() != NULL_TYPE) { + SerializeValue("extras", buffer.extras, o); + } +} + +static void SerializeGltfBufferView(BufferView &bufferView, json &o) { + SerializeNumberProperty("buffer", bufferView.buffer, o); + SerializeNumberProperty("byteLength", bufferView.byteLength, o); + + // byteStride is optional, minimum allowed is 4 + if (bufferView.byteStride >= 4) { + SerializeNumberProperty("byteStride", bufferView.byteStride, o); + } + // byteOffset is optional, default is 0 + if (bufferView.byteOffset > 0) { + SerializeNumberProperty("byteOffset", bufferView.byteOffset, o); + } + // Target is optional, check if it contains a valid value + if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER || + bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { + SerializeNumberProperty("target", bufferView.target, o); + } + if (bufferView.name.size()) { + SerializeStringProperty("name", bufferView.name, o); + } + + if (bufferView.extras.Type() != NULL_TYPE) { + SerializeValue("extras", bufferView.extras, o); + } +} + +static void SerializeGltfImage(Image &image, json &o) { + SerializeStringProperty("uri", image.uri, o); + + if (image.name.size()) { + SerializeStringProperty("name", image.name, o); + } + + if (image.extras.Type() != NULL_TYPE) { + SerializeValue("extras", image.extras, o); + } + + SerializeExtensionMap(image.extensions, o); +} + +static void SerializeGltfMaterial(Material &material, json &o) { + if (material.extras.Size()) SerializeValue("extras", material.extras, o); + SerializeExtensionMap(material.extensions, o); + + if (material.values.size()) { + json pbrMetallicRoughness; + SerializeParameterMap(material.values, pbrMetallicRoughness); + o["pbrMetallicRoughness"] = pbrMetallicRoughness; + } + + SerializeParameterMap(material.additionalValues, o); + + if (material.name.size()) { + SerializeStringProperty("name", material.name, o); + } + + if (material.extras.Type() != NULL_TYPE) { + SerializeValue("extras", material.extras, o); + } +} + +static void SerializeGltfMesh(Mesh &mesh, json &o) { + json primitives; + for (unsigned int i = 0; i < mesh.primitives.size(); ++i) { + json primitive; + json attributes; + Primitive gltfPrimitive = mesh.primitives[i]; + for (std::map::iterator attrIt = + gltfPrimitive.attributes.begin(); + attrIt != gltfPrimitive.attributes.end(); ++attrIt) { + SerializeNumberProperty(attrIt->first, attrIt->second, attributes); + } + + primitive["attributes"] = attributes; + + // Indicies is optional + if (gltfPrimitive.indices > -1) { + SerializeNumberProperty("indices", gltfPrimitive.indices, primitive); + } + // Material is optional + if (gltfPrimitive.material > -1) { + SerializeNumberProperty("material", gltfPrimitive.material, + primitive); + } + SerializeNumberProperty("mode", gltfPrimitive.mode, primitive); + + // Morph targets + if (gltfPrimitive.targets.size()) { + json targets; + for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) { + json targetAttributes; + std::map targetData = gltfPrimitive.targets[k]; + for (std::map::iterator attrIt = targetData.begin(); + attrIt != targetData.end(); ++attrIt) { + SerializeNumberProperty(attrIt->first, attrIt->second, + targetAttributes); + } + + targets.push_back(targetAttributes); + } + primitive["targets"] = targets; + } + + if (gltfPrimitive.extras.Type() != NULL_TYPE) { + SerializeValue("extras", gltfPrimitive.extras, primitive); + } + + primitives.push_back(primitive); + } + + o["primitives"] = primitives; + if (mesh.weights.size()) { + SerializeNumberArrayProperty("weights", mesh.weights, o); + } + + if (mesh.name.size()) { + SerializeStringProperty("name", mesh.name, o); + } + + if (mesh.extras.Type() != NULL_TYPE) { + SerializeValue("extras", mesh.extras, o); + } +} + +static void SerializeGltfLight(Light &light, json &o) { + if (!light.name.empty()) SerializeStringProperty("name", light.name, o); + SerializeNumberArrayProperty("color", light.color, o); + SerializeStringProperty("type", light.type, o); +} + +static void SerializeGltfNode(Node &node, json &o) { + if (node.translation.size() > 0) { + SerializeNumberArrayProperty("translation", node.translation, o); + } + if (node.rotation.size() > 0) { + SerializeNumberArrayProperty("rotation", node.rotation, o); + } + if (node.scale.size() > 0) { + SerializeNumberArrayProperty("scale", node.scale, o); + } + if (node.matrix.size() > 0) { + SerializeNumberArrayProperty("matrix", node.matrix, o); + } + if (node.mesh != -1) { + SerializeNumberProperty("mesh", node.mesh, o); + } + + if (node.skin != -1) { + SerializeNumberProperty("skin", node.skin, o); + } + + if (node.camera != -1) { + SerializeNumberProperty("camera", node.camera, o); + } + + if (node.extras.Type() != NULL_TYPE) { + SerializeValue("extras", node.extras, o); + } + + SerializeExtensionMap(node.extensions, o); + if (!node.name.empty()) SerializeStringProperty("name", node.name, o); + SerializeNumberArrayProperty("children", node.children, o); +} + +static void SerializeGltfSampler(Sampler &sampler, json &o) { + SerializeNumberProperty("magFilter", sampler.magFilter, o); + SerializeNumberProperty("minFilter", sampler.minFilter, o); + SerializeNumberProperty("wrapS", sampler.wrapS, o); + SerializeNumberProperty("wrapT", sampler.wrapT, o); + + if (sampler.extras.Type() != NULL_TYPE) { + SerializeValue("extras", sampler.extras, o); + } +} + +static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, + json &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + SerializeNumberProperty("xmag", camera.xmag, o); + SerializeNumberProperty("ymag", camera.ymag, o); + + if (camera.extras.Type() != NULL_TYPE) { + SerializeValue("extras", camera.extras, o); + } +} + +static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, + json &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + if (camera.aspectRatio > 0) { + SerializeNumberProperty("aspectRatio", camera.aspectRatio, o); + } + + if (camera.yfov > 0) { + SerializeNumberProperty("yfov", camera.yfov, o); + } + + if (camera.extras.Type() != NULL_TYPE) { + SerializeValue("extras", camera.extras, o); + } +} + +static void SerializeGltfCamera(const Camera &camera, json &o) { + SerializeStringProperty("type", camera.type, o); + if (!camera.name.empty()) { + SerializeStringProperty("name", camera.name, o); + } + + if (camera.type.compare("orthographic") == 0) { + json orthographic; + SerializeGltfOrthographicCamera(camera.orthographic, orthographic); + o["orthographic"] = orthographic; + } else if (camera.type.compare("perspective") == 0) { + json perspective; + SerializeGltfPerspectiveCamera(camera.perspective, perspective); + o["perspective"] = perspective; + } else { + // ??? + } +} + +static void SerializeGltfScene(Scene &scene, json &o) { + SerializeNumberArrayProperty("nodes", scene.nodes, o); + + if (scene.name.size()) { + SerializeStringProperty("name", scene.name, o); + } + if (scene.extras.Type() != NULL_TYPE) { + SerializeValue("extras", scene.extras, o); + } + SerializeExtensionMap(scene.extensions, o); +} + +static void SerializeGltfSkin(Skin &skin, json &o) { + if (skin.inverseBindMatrices != -1) + SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); + + SerializeNumberArrayProperty("joints", skin.joints, o); + SerializeNumberProperty("skeleton", skin.skeleton, o); + if (skin.name.size()) { + SerializeStringProperty("name", skin.name, o); + } +} + +static void SerializeGltfTexture(Texture &texture, json &o) { + if (texture.sampler > -1) { + SerializeNumberProperty("sampler", texture.sampler, o); + } + if (texture.source > -1) { + SerializeNumberProperty("source", texture.source, o); + } + if (texture.extras.Type() != NULL_TYPE) { + SerializeValue("extras", texture.extras, o); + } + SerializeExtensionMap(texture.extensions, o); +} + +static bool WriteGltfFile(const std::string &output, + const std::string &content) { + std::ofstream gltfFile(output.c_str()); + if (!gltfFile.is_open()) return false; + gltfFile << content << std::endl; + return true; +} + +bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename, + bool embedImages = false, + bool embedBuffers = false, + bool prettyPrint = true + /*, bool writeBinary*/) { + json output; + + // ACCESSORS + json accessors; + for (unsigned int i = 0; i < model->accessors.size(); ++i) { + json accessor; + SerializeGltfAccessor(model->accessors[i], accessor); + accessors.push_back(accessor); + } + output["accessors"] = accessors; + + // ANIMATIONS + if (model->animations.size()) { + json animations; + for (unsigned int i = 0; i < model->animations.size(); ++i) { + if (model->animations[i].channels.size()) { + json animation; + SerializeGltfAnimation(model->animations[i], animation); + animations.push_back(animation); + } + } + output["animations"] = animations; + } + + // ASSET + json asset; + SerializeGltfAsset(model->asset, asset); + output["asset"] = asset; + + std::string defaultBinFilename = GetBaseFilename(filename); + std::string defaultBinFileExt = ".bin"; + std::string::size_type pos = defaultBinFilename.rfind('.', defaultBinFilename.length()); + + if (pos != std::string::npos) { + defaultBinFilename = defaultBinFilename.substr(0, pos); + } + std::string baseDir = GetBaseDir(filename); + if (baseDir.empty()) { + baseDir = "./"; + } + + // BUFFERS + std::vector usedUris; + json buffers; + for (unsigned int i = 0; i < model->buffers.size(); ++i) { + json buffer; + if (embedBuffers) { + SerializeGltfBuffer(model->buffers[i], buffer); + } else { + std::string binSavePath; + std::string binUri; + if (!model->buffers[i].uri.empty() + && !IsDataURI(model->buffers[i].uri)) { + binUri = model->buffers[i].uri; + } + else { + binUri = defaultBinFilename + defaultBinFileExt; + bool inUse = true; + int numUsed = 0; + while(inUse) { + inUse = false; + for (const std::string& usedName : usedUris) { + if (binUri.compare(usedName) != 0) continue; + inUse = true; + binUri = defaultBinFilename + std::to_string(numUsed++) + defaultBinFileExt; + break; + } + } + } + usedUris.push_back(binUri); + binSavePath = JoinPath(baseDir, binUri); + SerializeGltfBuffer(model->buffers[i], buffer, binSavePath, + binUri); + } + buffers.push_back(buffer); + } + output["buffers"] = buffers; + + // BUFFERVIEWS + json bufferViews; + for (unsigned int i = 0; i < model->bufferViews.size(); ++i) { + json bufferView; + SerializeGltfBufferView(model->bufferViews[i], bufferView); + bufferViews.push_back(bufferView); + } + output["bufferViews"] = bufferViews; + + // Extensions used + if (model->extensionsUsed.size()) { + SerializeStringArrayProperty("extensionsUsed", model->extensionsUsed, + output); + } + + // Extensions required + if (model->extensionsRequired.size()) { + SerializeStringArrayProperty("extensionsRequired", + model->extensionsRequired, output); + } + + // IMAGES + if (model->images.size()) { + json images; + for (unsigned int i = 0; i < model->images.size(); ++i) { + json image; + + UpdateImageObject(model->images[i], baseDir, int(i), embedImages, + &this->WriteImageData, &this->write_image_user_data_); + SerializeGltfImage(model->images[i], image); + images.push_back(image); + } + output["images"] = images; + } + + // MATERIALS + if (model->materials.size()) { + json materials; + for (unsigned int i = 0; i < model->materials.size(); ++i) { + json material; + SerializeGltfMaterial(model->materials[i], material); + materials.push_back(material); + } + output["materials"] = materials; + } + + // MESHES + if (model->meshes.size()) { + json meshes; + for (unsigned int i = 0; i < model->meshes.size(); ++i) { + json mesh; + SerializeGltfMesh(model->meshes[i], mesh); + meshes.push_back(mesh); + } + output["meshes"] = meshes; + } + + // NODES + if (model->nodes.size()) { + json nodes; + for (unsigned int i = 0; i < model->nodes.size(); ++i) { + json node; + SerializeGltfNode(model->nodes[i], node); + nodes.push_back(node); + } + output["nodes"] = nodes; + } + + // SCENE + if (model->defaultScene > -1) { + SerializeNumberProperty("scene", model->defaultScene, output); + } + + // SCENES + if (model->scenes.size()) { + json scenes; + for (unsigned int i = 0; i < model->scenes.size(); ++i) { + json currentScene; + SerializeGltfScene(model->scenes[i], currentScene); + scenes.push_back(currentScene); + } + output["scenes"] = scenes; + } + + // SKINS + if (model->skins.size()) { + json skins; + for (unsigned int i = 0; i < model->skins.size(); ++i) { + json skin; + SerializeGltfSkin(model->skins[i], skin); + skins.push_back(skin); + } + output["skins"] = skins; + } + + // TEXTURES + if (model->textures.size()) { + json textures; + for (unsigned int i = 0; i < model->textures.size(); ++i) { + json texture; + SerializeGltfTexture(model->textures[i], texture); + textures.push_back(texture); + } + output["textures"] = textures; + } + + // SAMPLERS + if (model->samplers.size()) { + json samplers; + for (unsigned int i = 0; i < model->samplers.size(); ++i) { + json sampler; + SerializeGltfSampler(model->samplers[i], sampler); + samplers.push_back(sampler); + } + output["samplers"] = samplers; + } + + // CAMERAS + if (model->cameras.size()) { + json cameras; + for (unsigned int i = 0; i < model->cameras.size(); ++i) { + json camera; + SerializeGltfCamera(model->cameras[i], camera); + cameras.push_back(camera); + } + output["cameras"] = cameras; + } + + // EXTENSIONS + SerializeExtensionMap(model->extensions, output); + + // LIGHTS as KHR_lights_cmn + if (model->lights.size()) { + json lights; + for (unsigned int i = 0; i < model->lights.size(); ++i) { + json light; + SerializeGltfLight(model->lights[i], light); + lights.push_back(light); + } + json khr_lights_cmn; + khr_lights_cmn["lights"] = lights; + json ext_j; + + if (output.find("extensions") != output.end()) { + ext_j = output["extensions"]; + } + + ext_j["KHR_lights_cmn"] = khr_lights_cmn; + + output["extensions"] = ext_j; + } + + // EXTRAS + if (model->extras.Type() != NULL_TYPE) { + SerializeValue("extras", model->extras, output); + } + + // pretty printing with spacing 2 + return WriteGltfFile(filename, output.dump(prettyPrint ? 2 : 0)); +} + +} // namespace tinygltf + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // TINYGLTF_IMPLEMENTATION From 8eb1d39cc084438a188cdac83c0cb201505afd3f Mon Sep 17 00:00:00 2001 From: Vasu Mahesh Date: Tue, 16 Oct 2018 18:44:44 -0400 Subject: [PATCH 6/6] add: readme --- Graphs.docx | Bin 0 -> 39829 bytes README.md | 61 +++++++++-- gltfs/box/Empty box.bin | Bin 0 -> 7760 bytes gltfs/box/Empty box.gltf | 183 +++++++++++++++++++++++++++++++++ gltfs/spheres/SphereMesh1.bin | Bin 0 -> 2832 bytes gltfs/spheres/SphereMesh1.gltf | 172 +++++++++++++++++++++++++++++++ gltfs/spheres/SphereMesh2.bin | Bin 0 -> 19320 bytes gltfs/spheres/SphereMesh2.gltf | 172 +++++++++++++++++++++++++++++++ gltfs/spheres/SphereMesh3.bin | Bin 0 -> 39552 bytes gltfs/spheres/SphereMesh3.gltf | 172 +++++++++++++++++++++++++++++++ gltfs/spheres/SphereMesh4.bin | Bin 0 -> 154152 bytes gltfs/spheres/SphereMesh4.gltf | 172 +++++++++++++++++++++++++++++++ images/Duck4X.PNG | Bin 0 -> 27920 bytes images/DuckNoAA.PNG | Bin 0 -> 26634 bytes images/aa_graph.PNG | Bin 0 -> 9440 bytes images/color_bary_interp.PNG | Bin 0 -> 21485 bytes images/cover.PNG | Bin 0 -> 28737 bytes images/texture.PNG | Bin 0 -> 5393 bytes images/triangle_fill_graph.PNG | Bin 0 -> 9914 bytes src/rasterize.cu | 150 +++++++++------------------ 20 files changed, 971 insertions(+), 111 deletions(-) create mode 100644 Graphs.docx create mode 100644 gltfs/box/Empty box.bin create mode 100644 gltfs/box/Empty box.gltf create mode 100644 gltfs/spheres/SphereMesh1.bin create mode 100644 gltfs/spheres/SphereMesh1.gltf create mode 100644 gltfs/spheres/SphereMesh2.bin create mode 100644 gltfs/spheres/SphereMesh2.gltf create mode 100644 gltfs/spheres/SphereMesh3.bin create mode 100644 gltfs/spheres/SphereMesh3.gltf create mode 100644 gltfs/spheres/SphereMesh4.bin create mode 100644 gltfs/spheres/SphereMesh4.gltf create mode 100644 images/Duck4X.PNG create mode 100644 images/DuckNoAA.PNG create mode 100644 images/aa_graph.PNG create mode 100644 images/color_bary_interp.PNG create mode 100644 images/cover.PNG create mode 100644 images/texture.PNG create mode 100644 images/triangle_fill_graph.PNG diff --git a/Graphs.docx b/Graphs.docx new file mode 100644 index 0000000000000000000000000000000000000000..2b2d6643362cc359093fe3f0efd95520d8bda6c9 GIT binary patch literal 39829 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� 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 URXEPN%pHSYq0)G`4(EtDd literal 0 HcmV?d00001 diff --git a/gltfs/box/Empty box.gltf b/gltfs/box/Empty box.gltf new file mode 100644 index 0000000..48ebfe8 --- /dev/null +++ b/gltfs/box/Empty box.gltf @@ -0,0 +1,183 @@ +{ + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 228, + "max": [ + 227.0 + ], + "min": [ + 0.0 + ], + "name": "buffer-0-accessor-indices-buffer-0-mesh-0", + "type": "SCALAR" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 228, + "max": [ + 0.0, + 201429.6875, + 699870.625 + ], + "min": [ + -781469.8125, + -664.0, + 0.0 + ], + "name": "buffer-0-accessor-position-buffer-0-mesh-0", + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 2736, + "componentType": 5126, + "count": 228, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "name": "buffer-0-accessor-normal-buffer-0-mesh-0", + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 228, + "max": [ + 1.0, + 0.0 + ], + "min": [ + 0.0, + -1.0 + ], + "name": "buffer-0-accessor-texcoord-buffer-0-mesh-0", + "type": "VEC2" + }, + { + "bufferView": 3, + "byteOffset": 0, + "componentType": 5126, + "count": 0, + "max": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "min": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "name": "buffer-0-accessor-color-buffer-0-mesh-0", + "type": "VEC4" + } + ], + "asset": { + "generator": "Obj2GltfConverter", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 456, + "byteOffset": 0, + "byteStride": 0, + "name": "buffer-0-bufferview-ushort", + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 1824, + "byteOffset": 456, + "byteStride": 8, + "name": "buffer-0-bufferview-vec2", + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 5472, + "byteOffset": 2280, + "byteStride": 12, + "name": "buffer-0-bufferview-vec3", + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 0, + "byteOffset": 7760, + "byteStride": 16, + "name": "buffer-0-bufferview-vec4", + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 7760, + "name": "buffer-0", + "uri": "Empty box.bin" + } + ], + "materials": [ + { + "alphaMode": "OPAQUE", + "doubleSided": true, + "name": "wire_225143087", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.8824, + 0.5608, + 0.3412, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.9068708298809346 + } + } + ], + "meshes": [ + { + "name": "buffer-0-mesh-0", + "primitives": [ + { + "attributes": { + "POSITION": 1, + "NORMAL": 2, + "TEXCOORD_0": 3 + }, + "indices": 0, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "mesh": 0, + "name": "node-0" + } + ], + "scenes": [ + { + "name": "scene-0", + "nodes": [ + 0 + ] + } + ] +} \ No newline at end of file diff --git a/gltfs/spheres/SphereMesh1.bin b/gltfs/spheres/SphereMesh1.bin new file mode 100644 index 0000000000000000000000000000000000000000..3c3c65f523c1cb426395b0db64229ef71bc240e7 GIT binary patch literal 2832 zcma)6OK(k46#mNXLm#MjQ7+ngw%p#9f#jV20VB*hkf<_}G^Qk17!X0i$iV9lNc2i0 zY1(@yNF+og2Bu|XR%$BNx6j(=uCpEG?Ck9IefwK$e{1!zR2Z#Ys=qFMFO0@OC0@$G zQ^CE(%)34QVEU)#6WTZYm+BFr2XgN^TEBv+7JDw{Yk$WEb|z)^O1R_n0e9<=acpyVwq3o-eUGE^W=MJ z|I%Lu%Y6Qd*>7p_T6IzV{|nRajnC^xc>14Lh;Ih>v#*|2>C@{&T>SXS-q*g|o-X@- zc!qev+^gSm;Qy~!{l5tOXR!FsVDVcH{AaNEPw@}&d*$k_s`sCVFPr;EF9&ON%np8$T2*bs~DPv3iT> z-^SXD<87>-BjbPBSUt0j_rGl1{A*^u@9TK2L(b*m{zMZ|V`I&YT7Jz$p0}~+Bd=jh zOGPvA&y$2b8~Gd{gG{u-#|t z*Q*QfI!EBX3jeNU14DDORj;GwY`x$y|5v1%p}FH#&5f-_a%1KvwHg}!-$?X;)mU#< zW4+nfdeB_=o_ZE9*Tq-9PpeVS%#Cd?)`QikXYHZUi}hwTIo@jYmhG1lNxTL#bU&-4 zmxe~`yV+WOGS{Szt#9=?nw+`0w_J~sOAl_Ib`Dy{X)LdA>3lME+>FWCnbJSMr7 z2;k{H#jw1@UPs<;@_u`sn6c+8CMHkJ(|yG9zQ>I1cD(96PcF@ocuem-Pt1GISM=WV z#J-l($n);x+kuvRU3tb%r)N>lif(_$^Ne!r&%eVH-12!2)CpHt-ecAvUzC%d#}(zz zDBpw?vYN0G1vH~YRx?`BhE-^nwF({RL>E@e>cSeVMK{*T>c)C(z(#D6wGo@K1wGg* ys|VY#9lh8gs~0;_#4hZ{9`vCf1K5jw*pCA^h(j2}5Dw!AN*G2NBN)X|9K&y+JH!?M literal 0 HcmV?d00001 diff --git a/gltfs/spheres/SphereMesh1.gltf b/gltfs/spheres/SphereMesh1.gltf new file mode 100644 index 0000000..3a17758 --- /dev/null +++ b/gltfs/spheres/SphereMesh1.gltf @@ -0,0 +1,172 @@ +{ + "asset": { + "copyright": "(C)2018 vasum", + "generator": "Maya2glTF V0.9.10 b7c0eed", + "version": "2.0" + }, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "scene": 0, + "nodes": [ + { + "mesh": 0, + "name": "SphereMesh" + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2, + "TANGENT": 3, + "TEXCOORD_0": 4 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "SphereMeshShape" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 72, + "max": [ + 55 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 56, + "max": [ + 0.6785984039306641, + 0.8628562688827515, + 0.6785983443260193 + ], + "min": [ + -0.6785984635353088, + -0.8628562092781067, + -0.6785984635353088 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 672, + "componentType": 5126, + "count": 56, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 56, + "max": [ + 0.7071068286895752, + 1.3769580675671026e-7, + 0.70710688829422, + 1.0 + ], + "min": [ + -0.70710688829422, + -1.3769580675671026e-7, + -0.7071068286895752, + 1.0 + ], + "type": "VEC4" + }, + { + "bufferView": 3, + "byteOffset": 0, + "componentType": 5126, + "count": 56, + "max": [ + 1.0, + 1.0 + ], + "min": [ + 0.0, + 0.0 + ], + "type": "VEC2" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.4000000059604645, + 0.4000000059604645, + 0.4000000059604645, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 1.0 + }, + "name": "lambert1" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 2688, + "byteLength": 144, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 896, + "byteLength": 1344, + "byteStride": 12, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 896, + "byteStride": 16, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 2240, + "byteLength": 448, + "byteStride": 8, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 2832, + "uri": "SphereMesh1.bin" + } + ] +} diff --git a/gltfs/spheres/SphereMesh2.bin b/gltfs/spheres/SphereMesh2.bin new file mode 100644 index 0000000000000000000000000000000000000000..0e6194940cb945dbdd5c94306ad1b953991a443b GIT binary patch literal 19320 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 z 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 CG2>zY literal 0 HcmV?d00001 diff --git a/gltfs/spheres/SphereMesh4.gltf b/gltfs/spheres/SphereMesh4.gltf new file mode 100644 index 0000000..6b61045 --- /dev/null +++ b/gltfs/spheres/SphereMesh4.gltf @@ -0,0 +1,172 @@ +{ + "asset": { + "copyright": "(C)2018 vasum", + "generator": "Maya2glTF V0.9.10 b7c0eed", + "version": "2.0" + }, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "scene": 0, + "nodes": [ + { + "mesh": 0, + "name": "SphereMesh" + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2, + "TANGENT": 3, + "TEXCOORD_0": 4 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "SphereMeshShape" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 14700, + "max": [ + 2598 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 2599, + "max": [ + 1.0, + 1.0, + 0.9980267882347108 + ], + "min": [ + -1.0, + -1.0, + -0.9980268478393556 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 31188, + "componentType": 5126, + "count": 2599, + "max": [ + 1.0, + 1.0, + 0.9980270266532898 + ], + "min": [ + -1.0000005960464478, + -1.0, + -0.9980275630950928 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 2599, + "max": [ + 1.0, + 0.03137221932411194, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -0.03137212246656418, + -1.0, + 1.0 + ], + "type": "VEC4" + }, + { + "bufferView": 3, + "byteOffset": 0, + "componentType": 5126, + "count": 2599, + "max": [ + 0.9999995827674866, + 1.0 + ], + "min": [ + 0.0, + 0.0 + ], + "type": "VEC2" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.4000000059604645, + 0.4000000059604645, + 0.4000000059604645, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 1.0 + }, + "name": "lambert1" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 124752, + "byteLength": 29400, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 41584, + "byteLength": 62376, + "byteStride": 12, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 41584, + "byteStride": 16, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 103960, + "byteLength": 20792, + "byteStride": 8, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 154152, + "uri": "SphereMesh4.bin" + } + ] +} diff --git a/images/Duck4X.PNG b/images/Duck4X.PNG new file mode 100644 index 0000000000000000000000000000000000000000..90cf0261c4ca811e8f06a69d1adda520c4912bc2 GIT binary patch literal 27920 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 literal 0 HcmV?d00001 diff --git a/images/DuckNoAA.PNG b/images/DuckNoAA.PNG new file mode 100644 index 0000000000000000000000000000000000000000..0544850d542a2f7f7df4d00579a380f950c3b2d1 GIT binary patch literal 26634 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 literal 0 HcmV?d00001 diff --git a/images/aa_graph.PNG b/images/aa_graph.PNG new file mode 100644 index 0000000000000000000000000000000000000000..790449b8b1a2f727906fa13f41601267df964447 GIT binary patch literal 9440 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 literal 0 HcmV?d00001 diff --git a/images/texture.PNG b/images/texture.PNG new file mode 100644 index 0000000000000000000000000000000000000000..40d42d9bdbef3abe2008d4af85bc4318df744e7a GIT binary patch literal 5393 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%0DL z+%l!(KA}6t8}wo;uez=UYpM5FAgm7g)dRHM5pAy#3ZIU6iy%fyOB!xfr~z;L-jK`_ z%ik*8xT?Yd@wKgKR+iGW2VUiSB{0ks{j9t<`|}n==iI+=WrPw5g!SYCI=9h-R}L@) z8b4Tjn&H(IkTAeoHc;Q^^6Bx4)+7EPq-3_;3FK!{uyAnC2hz2(<}Lv{A0s>FKr-4wMw?D zn+uE&`(V2Kw?%`CSI(o)_i#3YH{x(0hy!4=;C0h>z?G)rZg6!g*aCRt`LN!Z@fP2` zmzH2IT`Pr__Z`mz_QpG^nT+4s2=)l_j~bl?N=-}H1*UtZkw6Y;39*XZ0~ol6U>%sG z4g^lE`91Z$X+Fj1GWh!u{C$3B+b2mO1hG%!zq?ic*SmH{M;${}ltuqiTO8`%5D2z0 Om0VFLoS8??-~100F+q3$ literal 0 HcmV?d00001 diff --git a/images/triangle_fill_graph.PNG b/images/triangle_fill_graph.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6aca368f7a32ecdbe15d0cf749d01cb2ccc4d172 GIT binary patch literal 9914 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 literal 0 HcmV?d00001 diff --git a/src/rasterize.cu b/src/rasterize.cu index d10842f..59d25bf 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -18,6 +18,11 @@ #include #include + // #define USE_LINE_SEGMENT_CHECK +#define USE_BARY_CHECK + +#define DO_COLOR_LERP + namespace { typedef unsigned short VertexIndex; @@ -963,6 +968,14 @@ __device__ void TryStoreFragment(const Primitive& target, float xCoord, int yCoo v3.pos[2]))); const int fragmentIntegerDepth = fragmentDepth * INT_MAX; +#ifdef DO_COLOR_LERP + const glm::vec3 interpolatedColor = fragmentDepth * ((ratio1 * (glm::vec3(1,0,0) / v1.pos[2])) + (ratio2 * (glm::vec3(0,1,0) / v2. + pos[2])) + (ratio3 * (glm::vec3(0,0,1) / v3.pos[2]))); +#else + const glm::vec3 interpolatedColor = fragmentDepth * ((ratio1 * (v1.col / v1.pos[2])) + (ratio2 * (v2.col / v2. + pos[2])) + (ratio3 * (v3.col / v3.pos[2]))); +#endif + const glm::vec2 interpolatedUV = fragmentDepth * ((ratio1 * (v1.texcoord0 / v1.pos[2])) + (ratio2 * (v2.texcoord0 / v2 .pos[2])) + (ratio3 * (v3.texcoord0 / v3.pos[2]))); const glm::vec3 interpolatedEyeNormal = fragmentDepth * ((ratio1 * (v1.eyeNor / v1.pos[2])) + (ratio2 * (v2.eyeNor / @@ -970,9 +983,6 @@ __device__ void TryStoreFragment(const Primitive& target, float xCoord, int yCoo const glm::vec3 interpolatedEyePos = fragmentDepth * ((ratio1 * (v1.eyePos / v1.pos[2])) + (ratio2 * (v2.eyePos / v2. pos[2])) + (ratio3 * (v3.eyePos / v3.pos[2]))); - const glm::vec3 interpolatedColor = fragmentDepth * ((ratio1 * (v1.col / v1.pos[2])) + (ratio2 * (v2.col / v2. - pos[2])) + (ratio3 * (v3.col / v3.pos[2]))); - Fragment targetFragment; targetFragment.color = interpolatedColor; targetFragment.uv = interpolatedUV; @@ -1054,9 +1064,6 @@ __device__ void TryStoreFragmentPoint(const Primitive& target, float xCoord, int } } -// #define USE_LINE_SEGMENT_CHECK -#define USE_BARY_CHECK - __global__ void _rasterizeTriangles(int numPrimitives, Primitive* dev_primitives, int screenWidth, int screenHeight, int* depth, Fragment* fragmentBuffer) { @@ -1070,133 +1077,70 @@ __global__ void _rasterizeTriangles(int numPrimitives, Primitive* dev_primitives const Primitive& target = dev_primitives[primtiveId]; - if (target.primitiveType == Triangle) - { - const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); - const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); - const glm::vec2 p2 = glm::vec2(target.v[2].pos[0], target.v[2].pos[1]); + const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); + const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); + const glm::vec2 p2 = glm::vec2(target.v[2].pos[0], target.v[2].pos[1]); - const BoundingBox boundingBox = getBoundingBoxForTriangle(p0, p1, p2); + const BoundingBox boundingBox = getBoundingBoxForTriangle(p0, p1, p2); #ifdef USE_LINE_SEGMENT_CHECK - int rasterStartY = floor(boundingBox.min.y); - int rasterEndY = ceil(boundingBox.max.y); - ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); +int rasterStartY = floor(boundingBox.min.y); +int rasterEndY = ceil(boundingBox.max.y); +ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); - const float slope0 = GetLineSegmentSlope(p0, p1); - const float slope1 = GetLineSegmentSlope(p1, p2); - const float slope2 = GetLineSegmentSlope(p2, p0); +const float slope0 = GetLineSegmentSlope(p0, p1); +const float slope1 = GetLineSegmentSlope(p1, p2); +const float slope2 = GetLineSegmentSlope(p2, p0); - for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { +for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { - float rasterStartX = 0; - float rasterEndX = 0; + float rasterStartX = 0; + float rasterEndX = 0; - const bool result = CalculateIntersection(p0, p1, p2, slope0, slope1, slope2, boundingBox, rasterStartX, rasterEndX, yValue); + const bool result = CalculateIntersection(p0, p1, p2, slope0, slope1, slope2, boundingBox, rasterStartX, rasterEndX, yValue); - if (!result) { - continue; - } + if (!result) { + continue; + } - ClampRange(rasterStartX, rasterEndX, 0, screenWidth - 1); + ClampRange(rasterStartX, rasterEndX, 0, screenWidth - 1); - for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) { - const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); - const int pixelIndex = xValue + (yValue * screenWidth); - TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); - } + for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) { + const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); + const int pixelIndex = xValue + (yValue * screenWidth); + TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], baryCoordinates, pixelIndex, depth, fragmentBuffer); } +} #endif #ifdef USE_BARY_CHECK - int rasterStartX = floor(boundingBox.min.x); - int rasterEndX = ceil(boundingBox.max.x); - - ClampRangeInt(rasterStartX, rasterEndX, 0, screenWidth - 1); - - for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) - { - int rasterStartY = floor(boundingBox.min.y); - int rasterEndY = ceil(boundingBox.max.y); - ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); - - for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) - { - const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); + int rasterStartX = floor(boundingBox.min.x); + int rasterEndX = ceil(boundingBox.max.x); - if (!isBarycentricCoordInBounds(baryCoordinates)) - { - continue; - } + ClampRangeInt(rasterStartX, rasterEndX, 0, screenWidth - 1); - const int pixelIndex = xValue + (yValue * screenWidth); - TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], - baryCoordinates, pixelIndex, depth, fragmentBuffer); - } - } -#endif - } - - else if (target.primitiveType == Line) + for (int xValue = rasterStartX; xValue <= rasterEndX; ++xValue) { - const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); - const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); - - const BoundingBox boundingBox = getBoundingBoxForLine(p0, p1); - int rasterStartY = floor(boundingBox.min.y); int rasterEndY = ceil(boundingBox.max.y); ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); - const float slope0 = GetLineSegmentSlope(p0, p1); - - for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { - float xIntercept; - - const bool doesIntersect = CheckLineSegmentIntersect(p0, p1, yValue, slope0, &xIntercept); - - if (!doesIntersect) - { - continue; - } - - int xValue = (int)glm::clamp(xIntercept, 0.0f, float(screenWidth - 1)); - - const int pixelIndex = xValue + (yValue * screenWidth); - TryStoreFragmentLine(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], pixelIndex, depth, fragmentBuffer); - } - } - - else if (target.primitiveType == Point) - { - const glm::vec2 p0 = glm::vec2(target.v[0].pos[0], target.v[0].pos[1]); - const glm::vec2 p1 = glm::vec2(target.v[1].pos[0], target.v[1].pos[1]); - - const BoundingBox boundingBox = getBoundingBoxForLine(p0, p1); - - int rasterStartY = floor(boundingBox.min.y); - int rasterEndY = ceil(boundingBox.max.y); - ClampRangeInt(rasterStartY, rasterEndY, 0, screenHeight - 1); - - const float slope0 = GetLineSegmentSlope(p0, p1); - - for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) { - float xIntercept; - - const bool doesIntersect = CheckLineSegmentIntersect(p0, p1, yValue, slope0, &xIntercept); + for (int yValue = rasterStartY; yValue <= rasterEndY; yValue += 1) + { + const glm::vec3 baryCoordinates = calculateBarycentricCoordinate(p0, p1, p2, glm::vec2(xValue, yValue)); - if (!doesIntersect) + if (!isBarycentricCoordInBounds(baryCoordinates)) { continue; } - int xValue = (int)glm::clamp(xIntercept, 0.0f, float(screenWidth - 1)); - const int pixelIndex = xValue + (yValue * screenWidth); - TryStoreFragmentLine(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], pixelIndex, depth, fragmentBuffer); + TryStoreFragment(target, xValue, yValue, screenWidth, screenHeight, target.v[0], target.v[1], target.v[2], + baryCoordinates, pixelIndex, depth, fragmentBuffer); } } +#endif } __global__ void _rasterizeLines(int numPrimitives, Primitive* dev_primitives, int screenWidth, int screenHeight, int* depth,