Raw File
model.cpp
// model.cpp
#include "model.h"

#include "window.h"

#define TINYOBJLOADER_IMPLEMENTATION
#include "../../external/tinyobjloader/tiny_obj_loader.h"

#define STB_IMAGE_IMPLEMENTATION
#include "../../external/stb/stb_image.h"

#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "../../external/stb/stb_image_resize.h"

#include "../../external/glm/glm/glm.hpp"
#include "../../external/glm/glm/gtc/matrix_transform.hpp"
#include "../../external/glm/glm/gtc/constants.hpp"

#include <memory>
#include <unordered_map>
#include <unordered_set>

namespace platform {

using namespace gfx;
using namespace Slang;

// TinyObj provides a tuple type that bundles up indices, but doesn't
// provide equality comparison or hashing for that type. We'd like
// to have a hash function so that we can unique indices.
//
// In the simplest case, we could define hashing and operator== operations
// directly on `tinobj::index_t`, but that would create problems if they
// revise their API.
//
// We will instead define our own wrapper type that supports equality
// comparisons.
//
struct ObjIndexKey
{
    tinyobj::index_t index;
};

bool operator==(ObjIndexKey const& left, ObjIndexKey const& right)
{
    return left.index.vertex_index == right.index.vertex_index
        && left.index.normal_index == right.index.normal_index
        && left.index.texcoord_index == right.index.texcoord_index;
}

struct Hasher
{
    template<typename T>
    void add(T const& v)
    {
        state ^= std::hash<T>()(v) + 0x9e3779b9 + (state << 6) + (state >> 2);
    }
    size_t state = 0;
};

struct SmoothingGroupVertexID
{
    size_t smoothingGroup;
    size_t positionID;
};
bool operator==(SmoothingGroupVertexID const& left, SmoothingGroupVertexID const& right)
{
    return left.smoothingGroup == right.smoothingGroup
        && left.positionID == right.positionID;
}

}

namespace std
{
    template<> struct hash<platform::ObjIndexKey>
    {
        size_t operator()(platform::ObjIndexKey const& key) const
        {
            platform::Hasher hasher;
            hasher.add(key.index.vertex_index);
            hasher.add(key.index.normal_index);
            hasher.add(key.index.texcoord_index);
            return hasher.state;
        }
    };

    template <> struct hash<platform::SmoothingGroupVertexID>
    {
        size_t operator()(platform::SmoothingGroupVertexID const& id) const
        {
            platform::Hasher hasher;
            hasher.add(id.smoothingGroup);
            hasher.add(id.positionID);
            return hasher.state;
        }
    };
}

namespace platform
{

ComPtr<ITextureResource> loadTextureImage(
    IDevice*   device,
    char const* path)
{
    int extentX = 0;
    int extentY = 0;
    int originalChannelCount = 0;
    int requestedChannelCount = 4; // force to 4-component result
    stbi_uc* data = stbi_load(
        path,
        &extentX,
        &extentY,
        &originalChannelCount,
        requestedChannelCount);
    if(!data)
        return nullptr;

    int channelCount = requestedChannelCount ? requestedChannelCount : originalChannelCount;

    Format format;
    switch(channelCount)
    {
    default:
        return nullptr;

    case 4: format = Format::R8G8B8A8_UNORM;

    // TODO: handle other cases here if/when we stop forcing 4-component
    // results when loading the image with stb_image.
    }

    std::vector<ITextureResource::SubresourceData> subresourceInitData;

    ptrdiff_t stride = extentX * channelCount * sizeof(stbi_uc);

    ITextureResource::SubresourceData baseInitData;
    baseInitData.data = data;
    baseInitData.strideY = stride;
    baseInitData.strideZ = 0;

    subresourceInitData.push_back(baseInitData);

    // create down-sampled images for the different mip levels
    bool generateMips = true;
    if(generateMips)
    {
        int prevExtentX = extentX;
        int prevExtentY = extentY;
        stbi_uc* prevData = data;
        int prevStride = int(stride);

        for(;;)
        {
            if(prevExtentX == 1 && prevExtentY == 1)
                break;

            int newExtentX = prevExtentX / 2;
            int newExtentY = prevExtentY / 2;

            if(!newExtentX) newExtentX = 1;
            if(!newExtentY) newExtentY = 1;

            stbi_uc* newData = (stbi_uc*) malloc(newExtentX * newExtentY * channelCount * sizeof(stbi_uc));
            int newStride = int(newExtentX * channelCount * sizeof(stbi_uc));

            stbir_resize_uint8_srgb(
                prevData, prevExtentX, prevExtentY, prevStride,
                 newData,  newExtentX,  newExtentY,  newStride,
                channelCount,
                STBIR_ALPHA_CHANNEL_NONE,
                STBIR_FLAG_ALPHA_PREMULTIPLIED);


            ITextureResource::SubresourceData mipInitData;
            mipInitData.data = newData;
            mipInitData.strideY = newStride;
            mipInitData.strideZ = 0;

            subresourceInitData.push_back(mipInitData);

            prevExtentX = newExtentX;
            prevExtentY = newExtentY;
            prevData = newData;
            prevStride = newStride;
        }
    }

    int mipCount = (int) subresourceInitData.size();

    ITextureResource::Desc desc = {};
    desc.type = IResource::Type::Texture2D;
    desc.defaultState = ResourceState::ShaderResource;
    desc.allowedStates = ResourceStateSet(ResourceState::ShaderResource);
    desc.format = format;
    desc.size.width = extentX;
    desc.size.height = extentY;
    desc.size.depth = 1;
    desc.numMipLevels = mipCount;
    auto texture = device->createTextureResource(desc, subresourceInitData.data());
    free(data);

    return texture;
}

static std::string makeString(const char* start, const char* end)
{
    return std::string(start, size_t(end - start));
}

SlangResult ModelLoader::load(
    char const* inputPath,
    void**      outModel)
{
    // TODO: need to actually allocate/load the data

    tinyobj::attrib_t objVertexAttributes;
    std::vector<tinyobj::shape_t> objShapes;
    std::vector<tinyobj::material_t> objMaterials;

    std::string baseDir;
    if( auto lastSlash = strrchr(inputPath, '/') )
    {
        baseDir = makeString(inputPath, lastSlash);
    }

    std::string diagnostics;
    bool shouldTriangulate = true;
    bool success = tinyobj::LoadObj(
        &objVertexAttributes,
        &objShapes,
        &objMaterials,
        &diagnostics,
        inputPath,
        baseDir.size() ? baseDir.c_str() : nullptr,
        shouldTriangulate);

    if(!diagnostics.empty())
    {
        printf("%s", diagnostics.c_str());
    }
    if(!success)
    {
        return SLANG_FAIL;
    }

    // Translate each material imported by TinyObj into a format that
    // we can actually use for rendering.
    //
    std::vector<void*> materials;
    for(auto& objMaterial : objMaterials)
    {
        MaterialData materialData;

        materialData.diffuseColor = glm::vec3(
            objMaterial.diffuse[0],
            objMaterial.diffuse[1],
            objMaterial.diffuse[2]);

        materialData.specularColor = glm::vec3(
            objMaterial.specular[0],
            objMaterial.specular[1],
            objMaterial.specular[2]);

        materialData.specularity = objMaterial.shininess;

        // load any referenced textures here
        if(objMaterial.diffuse_texname.length())
        {
            materialData.diffuseMap = loadTextureImage(
                device,
                objMaterial.diffuse_texname.c_str());
        }

        auto material = callbacks->createMaterial(materialData);
        materials.push_back(material);
    }

    // Flip the winding order on all faces if we are asked to...
    //
    if(loadFlags & LoadFlag::FlipWinding)
    {
        for(auto& objShape : objShapes)
        {
            size_t objIndexCounter = 0;
            size_t objFaceCounter = 0;
            for(auto objFaceVertexCount : objShape.mesh.num_face_vertices)
            {
                size_t beginIndex = objIndexCounter;
                size_t endIndex = beginIndex + objFaceVertexCount;
                objIndexCounter = endIndex;

                size_t halfCount = objFaceVertexCount / 2;
                for(size_t ii = 0; ii < halfCount; ++ii)
                {
                    std::swap(
                        objShape.mesh.indices[beginIndex + ii],
                        objShape.mesh.indices[endIndex - (ii + 1)]);
                }
            }
        }

    }

    // Identify cases where a face has a vertex without a normal, and in that
    // case remember that the given vertex needs to be "smoothed" as part of
    // the smoothing group for that face. Note that it is possible for the
    // same vertex (position) to be part of faces in distinct smoothing groups.
    //
    std::unordered_map<SmoothingGroupVertexID, size_t> smoothedVertexNormals;
    size_t firstSmoothedNormalID = objVertexAttributes.normals.size() / 3;
    size_t flatFaceCounter = 0;
    for(auto& objShape : objShapes)
    {
        size_t objIndexCounter = 0;
        size_t objFaceCounter = 0;
        for(auto objFaceVertexCount : objShape.mesh.num_face_vertices)
        {
            const size_t flatFaceIndex = flatFaceCounter++;
            const size_t objFaceIndex = objFaceCounter++;
            size_t smoothingGroup = objShape.mesh.smoothing_group_ids[objFaceIndex];
            if(!smoothingGroup)
            {
                smoothingGroup = ~flatFaceIndex;
            }

            for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
            {
                tinyobj::index_t& objIndex = objShape.mesh.indices[objIndexCounter++];

                if(objIndex.normal_index < 0)
                {
                    SmoothingGroupVertexID smoothVertexID;
                    smoothVertexID.positionID = objIndex.vertex_index;
                    smoothVertexID.smoothingGroup = smoothingGroup;

                    if(smoothedVertexNormals.find(smoothVertexID) == smoothedVertexNormals.end())
                    {
                        size_t normalID = objVertexAttributes.normals.size() / 3;
                        objVertexAttributes.normals.push_back(0);
                        objVertexAttributes.normals.push_back(0);
                        objVertexAttributes.normals.push_back(0);

                        smoothedVertexNormals.insert(std::make_pair(smoothVertexID, normalID));

                        objIndex.normal_index = int(normalID);
                    }
                }
            }
        }
    }
    //
    // Having identified which vertices we need to smooth, we will make another
    // pass to compute face normals and apply them to the vertices that belong
    // to the same smoothing group.
    //
    flatFaceCounter = 0;
    for(auto& objShape : objShapes)
    {
        size_t objIndexCounter = 0;
        size_t objFaceCounter = 0;
        for(auto objFaceVertexCount : objShape.mesh.num_face_vertices)
        {
            const size_t flatFaceIndex = flatFaceCounter++;
            const size_t objFaceIndex = objFaceCounter++;
            size_t smoothingGroup = objShape.mesh.smoothing_group_ids[objFaceIndex];
            if(!smoothingGroup)
            {
                smoothingGroup = ~flatFaceIndex;
            }

            glm::vec3 faceNormal;
            if(objFaceVertexCount >= 3)
            {
                glm::vec3 v[3];
                for(size_t objFaceVertex = 0; objFaceVertex < 3; ++objFaceVertex)
                {
                    tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter + objFaceVertex];
                    if(objIndex.vertex_index >= 0)
                    {
                        v[objFaceVertex] = glm::vec3(
                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 0],
                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 1],
                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 2]);
                    }
                }
                faceNormal = cross(v[1] - v[0], v[2] - v[0]);
            }

            // Add this face normal to any to-be-smoothed vertex on the face.
            for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
            {
                tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter++];

                SmoothingGroupVertexID smoothVertexID;
                smoothVertexID.positionID = objIndex.vertex_index;
                smoothVertexID.smoothingGroup = smoothingGroup;

                auto ii = smoothedVertexNormals.find(smoothVertexID);
                if(ii != smoothedVertexNormals.end())
                {
                    size_t normalID = ii->second;
                    objVertexAttributes.normals[normalID * 3 + 0] += faceNormal.x;
                    objVertexAttributes.normals[normalID * 3 + 1] += faceNormal.y;
                    objVertexAttributes.normals[normalID * 3 + 2] += faceNormal.z;
                }
            }
        }
    }
    //
    // Once we've added all contributions from each smoothing group,
    // we can normalize the normals to compute the area-weighted average.
    //
    size_t normalCount = objVertexAttributes.normals.size() / 3;
    for(size_t ii = firstSmoothedNormalID; ii < normalCount; ++ii)
    {
        glm::vec3 normal = glm::vec3(
            objVertexAttributes.normals[3 * ii + 0],
            objVertexAttributes.normals[3 * ii + 1],
            objVertexAttributes.normals[3 * ii + 2]);

        normal = normalize(normal);

        objVertexAttributes.normals[3 * ii + 0] = normal.x;
        objVertexAttributes.normals[3 * ii + 1] = normal.y;
        objVertexAttributes.normals[3 * ii + 2] = normal.z;
    }

    // TODO: we should sort the faces to group faces with
    // the same material ID together, in case they weren't
    // grouped in the original file.

    // We need to undo the .obj indexing stuff so that we have
    // standard position/normal/etc. data in a single flat array

    std::unordered_map<ObjIndexKey, Index> mapObjIndexToFlatIndex;
    std::vector<Vertex> flatVertices;
    std::vector<Index> flatIndices;

    MeshData* currentMesh = nullptr;
    MeshData currentMeshStorage;

    std::vector<void*> meshes;

    void* defaultMaterial = nullptr;

    for(auto& objShape : objShapes)
    {
        size_t objIndexCounter = 0;
        size_t objFaceCounter = 0;
        for(auto objFaceVertexCount : objShape.mesh.num_face_vertices)
        {
            size_t objFaceIndex = objFaceCounter++;
            int faceMaterialID = objShape.mesh.material_ids[objFaceIndex];
            void* faceMaterial = nullptr;
            if( faceMaterialID < 0 )
            {
                if( !defaultMaterial )
                {
                    MaterialData defaultMaterialData;
                    defaultMaterialData.diffuseColor = glm::vec3(0.5, 0.5, 0.5);
                    defaultMaterial = callbacks->createMaterial(defaultMaterialData);
                }
                faceMaterial = defaultMaterial;
            }
            else
            {
                faceMaterial = materials[faceMaterialID];
            }

            if(!currentMesh || (faceMaterial != currentMesh->material))
            {
                // finish old mesh.
                if(currentMesh)
                {
                    meshes.push_back(callbacks->createMesh(*currentMesh));
                }

                // Need to start a new mesh.
                currentMesh = &currentMeshStorage;
                currentMesh->material = faceMaterial;
                currentMesh->firstIndex = (int)flatIndices.size();
                currentMesh->indexCount = 0;
            }

            for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
            {
                tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter++];
                ObjIndexKey objIndexKey; objIndexKey.index = objIndex;


                Index flatIndex = Index(-1);
                auto iter = mapObjIndexToFlatIndex.find(objIndexKey);
                if(iter != mapObjIndexToFlatIndex.end())
                {
                    flatIndex = iter->second;
                }
                else
                {
                    Vertex flatVertex;
                    if(objIndex.vertex_index >= 0)
                    {
                        flatVertex.position = scale * glm::vec3(
                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 0],
                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 1],
                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 2]);
                    }
                    if(objIndex.normal_index >= 0)
                    {
                        flatVertex.normal = glm::vec3(
                            objVertexAttributes.normals[3 * objIndex.normal_index + 0],
                            objVertexAttributes.normals[3 * objIndex.normal_index + 1],
                            objVertexAttributes.normals[3 * objIndex.normal_index + 2]);
                    }
                    if(objIndex.texcoord_index >= 0)
                    {
                        flatVertex.uv = glm::vec2(
                            objVertexAttributes.texcoords[2 * objIndex.texcoord_index + 0],
                            objVertexAttributes.texcoords[2 * objIndex.texcoord_index + 1]);
                    }

                    flatIndex = uint32_t(flatVertices.size());
                    mapObjIndexToFlatIndex.insert(std::make_pair(objIndexKey, flatIndex));
                    flatVertices.push_back(flatVertex);
                }

                flatIndices.push_back(flatIndex);
                currentMesh->indexCount++;
            }
        }
    }

    // finish last mesh.
    if(currentMesh)
    {
        meshes.push_back(callbacks->createMesh(*currentMesh));
    }

    ModelData modelData;

    modelData.vertexCount = (int)flatVertices.size();
    modelData.indexCount = (int)flatIndices.size();

    modelData.meshCount = int(meshes.size());
    modelData.meshes = meshes.data();

    IBufferResource::Desc vertexBufferDesc;
    vertexBufferDesc.type = IResource::Type::Buffer;
    vertexBufferDesc.sizeInBytes = modelData.vertexCount * sizeof(Vertex);
    vertexBufferDesc.allowedStates =
        ResourceStateSet(ResourceState::VertexBuffer, ResourceState::CopyDestination);
    vertexBufferDesc.defaultState = ResourceState::VertexBuffer;

    modelData.vertexBuffer = device->createBufferResource(vertexBufferDesc, flatVertices.data());
    if(!modelData.vertexBuffer) return SLANG_FAIL;

    IBufferResource::Desc indexBufferDesc;
    indexBufferDesc.type = IResource::Type::Buffer;
    indexBufferDesc.sizeInBytes = modelData.indexCount * sizeof(Index);
    indexBufferDesc.allowedStates =
        ResourceStateSet(ResourceState::IndexBuffer, ResourceState::CopyDestination);
    indexBufferDesc.defaultState = ResourceState::IndexBuffer;

    modelData.indexBuffer = device->createBufferResource(indexBufferDesc, flatIndices.data());
    if(!modelData.indexBuffer) return SLANG_FAIL;

    *outModel = callbacks->createModel(modelData);

    return SLANG_OK;
}

} // gfx
back to top