
Added the files provided for the tutorial Added the SplitMeshNode and SubMeshNode classes
480 lines
17 KiB
C++
480 lines
17 KiB
C++
#include "ResourceManager.h"
|
|
#include "DirectXFramework.h"
|
|
#include <sstream>
|
|
#include "WICTextureLoader.h"
|
|
#include <locale>
|
|
#include <codecvt>
|
|
#include "MeshRenderer.h"
|
|
#include "SubMeshRenderer.h"
|
|
|
|
#pragma comment(lib, "../Assimp/lib/release/assimp-vc140-mt.lib")
|
|
|
|
using namespace Assimp;
|
|
|
|
//-------------------------------------------------------------------------------------------
|
|
// Utility functions to convert from wstring to string and back
|
|
// Copied from https://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string
|
|
|
|
wstring s2ws(const std::string& str)
|
|
{
|
|
using convert_typeX = std::codecvt_utf8<wchar_t>;
|
|
std::wstring_convert<convert_typeX, wchar_t> converterX;
|
|
|
|
return converterX.from_bytes(str);
|
|
}
|
|
|
|
string ws2s(const std::wstring& wstr)
|
|
{
|
|
using convert_typeX = std::codecvt_utf8<wchar_t>;
|
|
std::wstring_convert<convert_typeX, wchar_t> converterX;
|
|
|
|
return converterX.to_bytes(wstr);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------
|
|
|
|
ResourceManager::ResourceManager()
|
|
{
|
|
_device = DirectXFramework::GetDXFramework()->GetDevice();
|
|
_deviceContext = DirectXFramework::GetDXFramework()->GetDeviceContext();
|
|
|
|
// Create a default texture for use where none is specified. If white.png is not available, then
|
|
// the default texture will be null, i.e. black. This causes problems for materials that do not
|
|
// provide a texture, unless we provide a totally different shader just for those cases. That
|
|
// might be more efficient, but is a lot of work at this stage for little gain.
|
|
if (FAILED(CreateWICTextureFromFile(_device.Get(),
|
|
_deviceContext.Get(),
|
|
L"white.png",
|
|
nullptr,
|
|
_defaultTexture.GetAddressOf()
|
|
)))
|
|
{
|
|
_defaultTexture = nullptr;
|
|
}
|
|
|
|
}
|
|
|
|
ResourceManager::~ResourceManager(void)
|
|
{
|
|
}
|
|
|
|
shared_ptr<Renderer> ResourceManager::GetRenderer(wstring rendererName)
|
|
{
|
|
// Return different renderers based on the name requested
|
|
// At the moment, only one renderer is handled, but more would
|
|
// be added as different shaders are required, etc
|
|
//
|
|
// Look to see if an instance of the renderer has already been created
|
|
RendererResourceMap::iterator it = _rendererResources.find(rendererName);
|
|
if (it != _rendererResources.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
else
|
|
{
|
|
if (rendererName == L"PNT")
|
|
{
|
|
shared_ptr<Renderer> renderer = make_shared<MeshRenderer>();
|
|
_rendererResources[rendererName] = renderer;
|
|
return renderer;
|
|
}
|
|
else if (rendererName == L"SMR")
|
|
{
|
|
shared_ptr<Renderer> renderer = make_shared<SubMeshRenderer>();
|
|
_rendererResources[rendererName] = renderer;
|
|
return renderer;
|
|
}
|
|
// If we wanted additional renderers, we could test for them here
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
shared_ptr<Mesh> ResourceManager::GetMesh(wstring modelName)
|
|
{
|
|
// CHeck to see if the mesh has already been loaded
|
|
MeshResourceMap::iterator it = _meshResources.find(modelName);
|
|
if (it != _meshResources.end())
|
|
{
|
|
// Update reference count and return pointer to existing mesh
|
|
it->second.ReferenceCount++;
|
|
return it->second.MeshPointer;
|
|
}
|
|
else
|
|
{
|
|
// This is the first request for this model. Load the mesh and
|
|
// save a reference to it.
|
|
shared_ptr<Mesh> mesh = LoadModelFromFile(modelName);
|
|
if (mesh != nullptr)
|
|
{
|
|
MeshResourceStruct resourceStruct;
|
|
resourceStruct.ReferenceCount = 1;
|
|
resourceStruct.MeshPointer = mesh;
|
|
_meshResources[modelName] = resourceStruct;
|
|
return mesh;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResourceManager::ReleaseMesh(wstring modelName)
|
|
{
|
|
MeshResourceMap::iterator it = _meshResources.find(modelName);
|
|
if (it != _meshResources.end())
|
|
{
|
|
it->second.ReferenceCount--;
|
|
if (it->second.ReferenceCount == 0)
|
|
{
|
|
// Release any materials used by this mesh
|
|
shared_ptr<Mesh> mesh = it->second.MeshPointer;
|
|
unsigned int subMeshCount = static_cast<unsigned int>(mesh->GetSubMeshCount());
|
|
// Loop through all submeshes in the mesh
|
|
for (unsigned int i = 0; i < subMeshCount; i++)
|
|
{
|
|
shared_ptr<SubMesh> subMesh = mesh->GetSubMesh(i);
|
|
wstring materialName = subMesh->GetMaterial()->GetMaterialName();
|
|
ReleaseMaterial(materialName);
|
|
}
|
|
// If no other nodes are using this mesh, remove it frmo the map
|
|
// (which will also release the resources).
|
|
it->second.MeshPointer = nullptr;
|
|
_meshResources.erase(modelName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResourceManager::CreateMaterialFromTexture(wstring textureName)
|
|
{
|
|
// We have no diffuse or specular colours here since we are just building a default material structure
|
|
// based on a provided texture. Just use the texture name as the material name in this case
|
|
return InitialiseMaterial(textureName,
|
|
XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f),
|
|
XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f),
|
|
0,
|
|
1.0f,
|
|
textureName);
|
|
}
|
|
|
|
void ResourceManager::CreateMaterialWithNoTexture(wstring materialName, XMFLOAT4 diffuseColour, XMFLOAT4 specularColour, float shininess, float opacity)
|
|
{
|
|
return InitialiseMaterial(materialName, diffuseColour, specularColour, shininess, opacity, L"");
|
|
}
|
|
|
|
void ResourceManager::CreateMaterial(wstring materialName, XMFLOAT4 diffuseColour, XMFLOAT4 specularColour, float shininess, float opacity, wstring textureName)
|
|
{
|
|
return InitialiseMaterial(materialName, diffuseColour, specularColour, shininess, opacity, textureName);
|
|
}
|
|
|
|
shared_ptr<Material> ResourceManager::GetMaterial(wstring materialName)
|
|
{
|
|
// This works a bit different to the GetMesh method. We can only find
|
|
// materials we have previously created (usually when the mesh was loaded
|
|
// from the file).
|
|
MaterialResourceMap::iterator it = _materialResources.find(materialName);
|
|
if (it != _materialResources.end())
|
|
{
|
|
it->second.ReferenceCount++;
|
|
return it->second.MaterialPointer;
|
|
}
|
|
else
|
|
{
|
|
// Material not previously created.
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void ResourceManager::ReleaseMaterial(wstring materialName)
|
|
{
|
|
MaterialResourceMap::iterator it = _materialResources.find(materialName);
|
|
if (it != _materialResources.end())
|
|
{
|
|
it->second.ReferenceCount--;
|
|
if (it->second.ReferenceCount == 0)
|
|
{
|
|
it->second.MaterialPointer = nullptr;
|
|
_meshResources.erase(materialName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResourceManager::InitialiseMaterial(wstring materialName, XMFLOAT4 diffuseColour, XMFLOAT4 specularColour, float shininess, float opacity, wstring textureName)
|
|
{
|
|
MaterialResourceMap::iterator it = _materialResources.find(materialName);
|
|
if (it == _materialResources.end())
|
|
{
|
|
// We are creating the material for the first time
|
|
ComPtr<ID3D11ShaderResourceView> texture;
|
|
if (textureName.size() > 0)
|
|
{
|
|
// A texture was specified. Try to load it.
|
|
if (FAILED(CreateWICTextureFromFile(_device.Get(),
|
|
_deviceContext.Get(),
|
|
textureName.c_str(),
|
|
nullptr,
|
|
texture.GetAddressOf()
|
|
)))
|
|
{
|
|
// If we cannot load the texture, then just use the default.
|
|
texture = _defaultTexture;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
texture = _defaultTexture;
|
|
}
|
|
shared_ptr<Material> material = make_shared<Material>(materialName, diffuseColour, specularColour, shininess, opacity, texture);
|
|
MaterialResourceStruct resourceStruct;
|
|
resourceStruct.ReferenceCount = 0;
|
|
resourceStruct.MaterialPointer = material;
|
|
_materialResources[materialName] = resourceStruct;
|
|
}
|
|
}
|
|
|
|
shared_ptr<Node> ResourceManager::CreateNodes(aiNode * sceneNode)
|
|
{
|
|
shared_ptr<Node> node = make_shared<Node>();
|
|
node->SetName(s2ws(string(sceneNode->mName.C_Str())));
|
|
// Get the meshes associated with this node
|
|
unsigned int meshCount = sceneNode->mNumMeshes;
|
|
for (unsigned int i = 0; i < meshCount; i++)
|
|
{
|
|
node->AddMesh(sceneNode->mMeshes[i]);
|
|
}
|
|
// Now process the children of this node
|
|
unsigned int childrenCount = sceneNode->mNumChildren;
|
|
for (unsigned int i = 0; i < childrenCount; i++)
|
|
{
|
|
node->AddChild(CreateNodes(sceneNode->mChildren[i]));
|
|
}
|
|
return node;
|
|
}
|
|
|
|
shared_ptr<Mesh> ResourceManager::LoadModelFromFile(wstring modelName)
|
|
{
|
|
ComPtr<ID3D11Buffer> vertexBuffer;
|
|
ComPtr<ID3D11Buffer> indexBuffer;
|
|
wstring * materials = nullptr;
|
|
|
|
Importer importer;
|
|
|
|
unsigned int postProcessSteps = aiProcess_Triangulate |
|
|
aiProcess_ConvertToLeftHanded;
|
|
string modelNameUTF8 = ws2s(modelName);
|
|
const aiScene * scene = importer.ReadFile(modelNameUTF8.c_str(), postProcessSteps);
|
|
if (!scene)
|
|
{
|
|
// If failed to load, there is nothing to do
|
|
return nullptr;
|
|
}
|
|
if (!scene->HasMeshes())
|
|
{
|
|
//If there are no meshes, then there is nothing to do.
|
|
return nullptr;
|
|
}
|
|
if (scene->HasMaterials())
|
|
{
|
|
// We need to find the directory part of the model name since we will need to add it to any texture names.
|
|
// There is definately a more elegant and accurate way to do this using Windows API calls, but this is a quick
|
|
// and dirty approach
|
|
string::size_type slashIndex = modelNameUTF8.find_last_of("\\");
|
|
string directory;
|
|
if (slashIndex == string::npos)
|
|
{
|
|
directory = ".";
|
|
}
|
|
else if (slashIndex == 0)
|
|
{
|
|
directory = "/";
|
|
}
|
|
else
|
|
{
|
|
directory = modelNameUTF8.substr(0, slashIndex);
|
|
}
|
|
// Let's deal with the materials/textures first
|
|
materials = new wstring[scene->mNumMaterials];
|
|
for (unsigned int i = 0; i < scene->mNumMaterials; i++)
|
|
{
|
|
// Get the core material properties. Ideally, we would be looking for more information
|
|
// e.g. emissive colour, etc. This is a task for later.
|
|
aiMaterial * material = scene->mMaterials[i];
|
|
|
|
aiColor3D &defaultColour = aiColor3D(0.0f, 0.0f, 0.0f);
|
|
aiColor3D& diffuseColour = aiColor3D(0.0f, 0.0f, 0.0f);
|
|
if (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColour) != aiReturn_SUCCESS)
|
|
{
|
|
diffuseColour = defaultColour;
|
|
}
|
|
aiColor3D& specularColour = aiColor3D(0.0f, 0.0f, 0.0f);
|
|
if (material->Get(AI_MATKEY_COLOR_SPECULAR, specularColour) != aiReturn_SUCCESS)
|
|
{
|
|
specularColour = defaultColour;
|
|
}
|
|
float defaultShininess = 0.0f;
|
|
float& shininess = defaultShininess;
|
|
material->Get(AI_MATKEY_SHININESS, shininess);
|
|
float defaultOpacity = 1.0f;
|
|
float& opacity = defaultOpacity;
|
|
material->Get(AI_MATKEY_OPACITY, opacity);
|
|
bool defaultTwoSided = false;
|
|
bool& twoSided = defaultTwoSided;
|
|
material->Get(AI_MATKEY_TWOSIDED, twoSided);
|
|
string fullTextureNamePath = "";
|
|
if (material->GetTextureCount(aiTextureType_DIFFUSE) > 0)
|
|
{
|
|
aiString textureName;
|
|
float blendFactor;
|
|
aiTextureOp blendOp;
|
|
if (material->GetTexture(aiTextureType_DIFFUSE, 0, &textureName, NULL, NULL, &blendFactor, &blendOp, NULL) == AI_SUCCESS)
|
|
{
|
|
// Get full path to texture by prepending the same folder as included in the model name. This
|
|
// does assume that textures are in the same folder as the model files
|
|
fullTextureNamePath = directory + "\\" + textureName.data;
|
|
}
|
|
}
|
|
// Now create a unique name for the material based on the model name and loop count
|
|
stringstream materialNameStream;
|
|
materialNameStream << modelNameUTF8 << i;
|
|
string materialName = materialNameStream.str();
|
|
wstring materialNameWS = s2ws(materialName);
|
|
CreateMaterial(materialNameWS,
|
|
XMFLOAT4(diffuseColour.r, diffuseColour.g, diffuseColour.b, 1.0f),
|
|
XMFLOAT4(specularColour.r, specularColour.g, specularColour.b, 1.0f),
|
|
shininess,
|
|
opacity,
|
|
s2ws(fullTextureNamePath));
|
|
materials[i] = materialNameWS;
|
|
}
|
|
}
|
|
// Now we have created all of the materials, build up the mesh
|
|
shared_ptr<Mesh> resourceMesh = make_shared<Mesh>();
|
|
for (unsigned int sm = 0; sm < scene->mNumMeshes; sm++)
|
|
{
|
|
aiMesh * subMesh = scene->mMeshes[sm];
|
|
unsigned int numVertices = subMesh->mNumVertices;
|
|
bool hasNormals = subMesh->HasNormals();
|
|
bool hasTexCoords = subMesh->HasTextureCoords(0);
|
|
if (numVertices == 0 || !hasNormals)
|
|
{
|
|
return nullptr;
|
|
}
|
|
// Build up our vertex structure
|
|
aiVector3D * subMeshVertices = subMesh->mVertices;
|
|
aiVector3D * subMeshNormals = subMesh->mNormals;
|
|
// We only handle one set of UV coordinates at the moment. Again, handling multiple sets of UV
|
|
// coordinates is a future enhancement.
|
|
aiVector3D * subMeshTexCoords = subMesh->mTextureCoords[0];
|
|
VERTEX * modelVertices = new VERTEX[numVertices];
|
|
VERTEX * currentVertex = modelVertices;
|
|
for (unsigned int i = 0; i < numVertices; i++)
|
|
{
|
|
currentVertex->Position = XMFLOAT3(subMeshVertices->x, subMeshVertices->y, subMeshVertices->z);
|
|
currentVertex->Normal = XMFLOAT3(subMeshNormals->x, subMeshNormals->y, subMeshNormals->z);
|
|
subMeshVertices++;
|
|
subMeshNormals++;
|
|
if (!hasTexCoords)
|
|
{
|
|
// If the model does not have texture coordinates, set them to 0
|
|
currentVertex->TexCoord = XMFLOAT2(0.0f, 0.0f);
|
|
}
|
|
else
|
|
{
|
|
// Handle negative texture coordinates by wrapping them to positive. This should
|
|
// ideally be handled in the shader. Note we are assuming that negative coordinates
|
|
// here are no smaller than -1.0 - this may not be a valid assumption.
|
|
if (subMeshTexCoords->x < 0)
|
|
{
|
|
currentVertex->TexCoord.x = subMeshTexCoords->x + 1.0f;
|
|
}
|
|
else
|
|
{
|
|
currentVertex->TexCoord.x = subMeshTexCoords->x;
|
|
}
|
|
if (subMeshTexCoords->y < 0)
|
|
{
|
|
currentVertex->TexCoord.y = subMeshTexCoords->y + 1.0f;
|
|
}
|
|
else
|
|
{
|
|
currentVertex->TexCoord.y = subMeshTexCoords->y;
|
|
}
|
|
subMeshTexCoords++;
|
|
}
|
|
currentVertex++;
|
|
}
|
|
|
|
D3D11_BUFFER_DESC vertexBufferDescriptor;
|
|
vertexBufferDescriptor.Usage = D3D11_USAGE_IMMUTABLE;
|
|
vertexBufferDescriptor.ByteWidth = sizeof(VERTEX) * numVertices;
|
|
vertexBufferDescriptor.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
|
vertexBufferDescriptor.CPUAccessFlags = 0;
|
|
vertexBufferDescriptor.MiscFlags = 0;
|
|
vertexBufferDescriptor.StructureByteStride = 0;
|
|
|
|
// Now set up a structure that tells DirectX where to get the
|
|
// data for the vertices from
|
|
D3D11_SUBRESOURCE_DATA vertexInitialisationData;
|
|
vertexInitialisationData.pSysMem = modelVertices;
|
|
|
|
// and create the vertex buffer
|
|
if (FAILED(_device->CreateBuffer(&vertexBufferDescriptor, &vertexInitialisationData, vertexBuffer.GetAddressOf())))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Now extract the indices from the file
|
|
unsigned int numberOfFaces = subMesh->mNumFaces;
|
|
unsigned int numberOfIndices = numberOfFaces * 3;
|
|
aiFace * subMeshFaces = subMesh->mFaces;
|
|
if (subMeshFaces->mNumIndices != 3)
|
|
{
|
|
// We are not dealing with triangles, so we cannot handle it
|
|
return nullptr;
|
|
}
|
|
unsigned int * modelIndices = new unsigned int[numberOfIndices * 3];
|
|
unsigned int * currentIndex = modelIndices;
|
|
for (unsigned int i = 0; i < numberOfFaces; i++)
|
|
{
|
|
*currentIndex++ = subMeshFaces->mIndices[0];
|
|
*currentIndex++ = subMeshFaces->mIndices[1];
|
|
*currentIndex++ = subMeshFaces->mIndices[2];
|
|
subMeshFaces++;
|
|
}
|
|
// Setup the structure that specifies how big the index
|
|
// buffer should be
|
|
D3D11_BUFFER_DESC indexBufferDescriptor;
|
|
indexBufferDescriptor.Usage = D3D11_USAGE_IMMUTABLE;
|
|
indexBufferDescriptor.ByteWidth = sizeof(UINT) * numberOfIndices * 3;
|
|
indexBufferDescriptor.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
|
indexBufferDescriptor.CPUAccessFlags = 0;
|
|
indexBufferDescriptor.MiscFlags = 0;
|
|
indexBufferDescriptor.StructureByteStride = 0;
|
|
|
|
// Now set up a structure that tells DirectX where to get the
|
|
// data for the indices from
|
|
D3D11_SUBRESOURCE_DATA indexInitialisationData;
|
|
indexInitialisationData.pSysMem = modelIndices;
|
|
|
|
// and create the index buffer
|
|
if (FAILED(_device->CreateBuffer(&indexBufferDescriptor, &indexInitialisationData, indexBuffer.GetAddressOf())))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Do we have a material associated with this mesh?
|
|
shared_ptr<Material> material = nullptr;
|
|
if (scene->HasMaterials())
|
|
{
|
|
material = GetMaterial(materials[subMesh->mMaterialIndex]);
|
|
}
|
|
shared_ptr<SubMesh> resourceSubMesh = make_shared<SubMesh>(vertexBuffer, indexBuffer, numVertices, numberOfIndices, material);
|
|
resourceMesh->AddSubMesh(resourceSubMesh);
|
|
delete[] modelVertices;
|
|
delete[] modelIndices;
|
|
}
|
|
// Now build the hierarchy of nodes
|
|
resourceMesh->SetRootNode(CreateNodes(scene->mRootNode));
|
|
return resourceMesh;
|
|
}
|