// Fill out your copyright notice in the Description page of Project Settings. #include "MasterTestProject.h" #include <time.h> #include "ProceduralEntity.h" #include "ImageDownloader.h" #include "GLTFDownloader.h" #include <assimp/DefaultLogger.hpp> #include <assimp/Logger.hpp> // Sets default values AProceduralEntity::AProceduralEntity() : _requiresFullRecreation(true), _meshCurrentlyProcessed(0) { this->SetActorEnableCollision(true); // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; //_rootComp = CreateDefaultSubobject<USceneComponent>("RootComp"); //RootComponent = _rootComp; _mesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProcMesh")); if (_mesh) { _mesh->CastShadow = true; //_mesh->SetCollisionObjectType(ECC_WorldDynamic); //_mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); //_mesh->SetCollisionResponseToAllChannels(ECR_Block); // _mesh->UpdateCollisionProfile(); RootComponent = _mesh; } /*if (!IsTemplate(RF_Transient)) { std::string filename(TCHAR_TO_UTF8(*_filePath)); loadModel(filename);UGLTFDownloader }*/ } // Called when the game starts or when spawned void AProceduralEntity::BeginPlay() { Super::BeginPlay(); //UGLTFDownloader *downloader = NewObject<UGLTFDownloader>(UGLTFDownloader::StaticClass()); //FString base = FString(TEXT("")); //downloader->GetGLTF(this, base, _filePath); } void AProceduralEntity::PostActorCreated() { Super::PostActorCreated(); if (!IsTemplate(RF_Transient)) { // UE_LOG(GLTF, Warning, TEXT("File Path: %s"), *_filePath); // std::string filename(TCHAR_TO_UTF8(*_filePath)); // loadModel(filename); } } // Called every frame void AProceduralEntity::Tick(float DeltaTime) { Super::Tick( DeltaTime ); //struct _stat buf; //int result; //std::string sfilename(TCHAR_TO_UTF8(*_filePath)); //// quick C++11 trick to get a char * from a std::string //char* filename = &sfilename[0]; //result = _stat(filename, &buf); //if (result != 0) { // UE_LOG(GLTF, Warning, TEXT("Problem getting info")); // switch (errno) { // case ENOENT: // UE_LOG(GLTF, Warning, TEXT("File not found.")); // break; // case EINVAL: // UE_LOG(GLTF, Warning, TEXT("Invalid parameter to _stat.")); // break; // default: // UE_LOG(GLTF, Warning, TEXT("Unexpected error.")); // } //} //else { // if (_lastModifiedTime == 0 || (int)buf.st_mtime > _lastModifiedTime) { // _lastModifiedTime = (int)buf.st_mtime; // // UE_LOG(GLTF, Warning, TEXT("Reloading model. %s"), *_filePath); // //loadModel(filename); // } //} } void AProceduralEntity::loadModelFromBlueprint() { std::string filename(TCHAR_TO_UTF8(*_filePath)); //FIXME //loadModel(filename); } /* ASSIMP IMPORT */ void AProceduralEntity::processMesh(aiMesh* mesh, const aiScene* scene, FMatrix modelTransform) { //UE_LOG(GLTF, Warning, TEXT("Processing mesh")); // the very first time this method runs, we'll need to create the empty arrays // we can't really do that in the class constructor because we don't know how many meshes we'll read, and this data can change between imports if (_vertices.Num() <= _meshCurrentlyProcessed) { _vertices.AddZeroed(); _normals.AddZeroed(); _uvs.AddZeroed(); _tangents.AddZeroed(); _vertexColors.AddZeroed(); _indices.AddZeroed(); } // we check whether the current data to read has a different amount of vertices compared to the last time we generated the mesh // if so, it means we'll need to recreate the mesh and resupply new indices. if (mesh->mNumVertices != _vertices[_meshCurrentlyProcessed].Num()) _requiresFullRecreation = true; // we reinitialize the arrays for the new data we're reading _vertices[_meshCurrentlyProcessed].Empty(); _normals[_meshCurrentlyProcessed].Empty(); _uvs[_meshCurrentlyProcessed].Empty(); // this if actually seems useless, seeing what it does without it //if (_requiresFullRecreation) { _tangents[_meshCurrentlyProcessed].Empty(); _vertexColors[_meshCurrentlyProcessed].Empty(); _indices[_meshCurrentlyProcessed].Empty(); //} FMatrix normalMatrix = modelTransform.Inverse().GetTransposed(); bool isIdentity = modelTransform.Equals(FMatrix::Identity); for (unsigned int i = 0; i < mesh->mNumVertices; i++) { FVector4 vertex; FVector normal; // process vertex positions, normals and UVs vertex.X = mesh->mVertices[i].x; vertex.Y = mesh->mVertices[i].y; vertex.Z = mesh->mVertices[i].z; vertex.W = 1.0; normal.X = mesh->mNormals[i].x; normal.Y = mesh->mNormals[i].y; normal.Z = mesh->mNormals[i].z; // if the mesh contains tex coords if (mesh->mTextureCoords[0]) { FVector2D uvs; uvs.X = mesh->mTextureCoords[0][i].x; uvs.Y = mesh->mTextureCoords[0][i].y; _uvs[_meshCurrentlyProcessed].Add(uvs); } else { _uvs[_meshCurrentlyProcessed].Add(FVector2D(0.f, 0.f)); } if (isIdentity) { _vertices[_meshCurrentlyProcessed].Add(vertex); _normals[_meshCurrentlyProcessed].Add(normal); } else { FVector4 pos4 = modelTransform.TransformFVector4(vertex); FVector dehomogenisert = FVector(pos4 / pos4.W); _vertices[_meshCurrentlyProcessed].Add(dehomogenisert); //noremalen brauchen keine translation _normals[_meshCurrentlyProcessed].Add(normalMatrix.TransformVector(normal)); } } if (_requiresFullRecreation) { // process indices for (uint32 i = 0; i < mesh->mNumFaces; i++) { aiFace face = mesh->mFaces[i]; _indices[_meshCurrentlyProcessed].Add(face.mIndices[2]); _indices[_meshCurrentlyProcessed].Add(face.mIndices[1]); _indices[_meshCurrentlyProcessed].Add(face.mIndices[0]); } } // we can finally either update or create the mesh if (_requiresFullRecreation) { //UE_LOG(GLTF, Warning, TEXT("Full recreation")); _mesh->CreateMeshSection(_meshCurrentlyProcessed, _vertices[_meshCurrentlyProcessed], _indices[_meshCurrentlyProcessed], _normals[_meshCurrentlyProcessed], _uvs[_meshCurrentlyProcessed], _vertexColors[_meshCurrentlyProcessed], _tangents[_meshCurrentlyProcessed], true); } else { // UE_LOG(GLTF, Warning, TEXT("Update")); _mesh->UpdateMeshSection(_meshCurrentlyProcessed, _vertices[_meshCurrentlyProcessed], _normals[_meshCurrentlyProcessed], _uvs[_meshCurrentlyProcessed], _vertexColors[_meshCurrentlyProcessed], _tangents[_meshCurrentlyProcessed]); } //load material UMaterialInterface *material = GetMaterialForIndex(mesh->mMaterialIndex, scene); _mesh->SetMaterial(_meshCurrentlyProcessed, material); //UE_LOG(GLTF, Warning, TEXT("Done creating the mesh")); _requiresFullRecreation = false; } void AProceduralEntity::processNode(aiNode* node, const aiScene* scene, FMatrix modelTransform) { //UE_LOG(GLTF, Warning, TEXT("Processing a node")); FMatrix child = FMatrix(); aiVector3D nodeScale, nodePos; aiQuaternion nodeQuat; node->mTransformation.Decompose(nodeScale, nodeQuat, nodePos); FQuat lightQuat = FQuat(nodeQuat.x, nodeQuat.y, nodeQuat.z, nodeQuat.w); FRotator lightRot = FRotator::MakeFromEuler(lightQuat.Euler()); FVector trans = FVector(nodePos.x, nodePos.y, nodePos.z); FVector scale = FVector(nodeScale.x, nodeScale.y, nodeScale.z); FTransform LocalTransform = FTransform(lightRot, trans, scale); //UE_LOG(GLTF, Warning, TEXT("Transform: Rot: %s Trans: %s Scale: %s "),*lightRot.ToString(), * trans.ToString(), *scale.ToString() ); FMatrix absoluteTransform = LocalTransform.ToMatrixWithScale() * modelTransform; // process all the node's meshes for (uint32 i = 0; i < node->mNumMeshes; i++) { aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; //UE_LOG(GLTF, Warning, TEXT("New mesh in the node %i"), mesh->mNumVertices); _numVertecies += mesh->mNumVertices; processMesh(mesh, scene, absoluteTransform); ++_meshCurrentlyProcessed; } // do the same for all of its children for (uint32 i = 0; i < node->mNumChildren; i++) { //UE_LOG(GLTF, Warning, TEXT("New child in the node")); processNode(node->mChildren[i], scene, absoluteTransform); } } void AProceduralEntity::loadModel(const uint8* RawFileData, int32 lenght) { if (MasterMaterialRef == nullptr) { MasterMaterialRef = LoadObject<UMaterialInterface>(NULL, TEXT("/Game/Materials/TexturedMaterial"), NULL, LOAD_None, NULL); //= FindObject<UMaterialInterface>(GetWorld(), TEXT("TexturedMaterial")); } if (UniformMaterialRef == nullptr) { UniformMaterialRef = LoadObject<UMaterialInterface>(NULL, TEXT("/Game/Materials/TexturedMaterial"), NULL, LOAD_None, NULL); //= FindObject<UMaterialInterface>(GetWorld(), TEXT("UniformMaterial")); } Assimp::Importer importer; const aiScene* scene = importer.ReadFileFromMemory(RawFileData, lenght, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenNormals, "glb"); //UE_LOG(GLTF, Warning, TEXT("START LOGGING")); //const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenNormals); if (!scene) { UE_LOG(GLTF, Error, TEXT("Failed, Scene is empty: %s"), *FString(importer.GetErrorString())); std::string extensions; importer.GetExtensionList(extensions); UE_LOG(GLTF, Error, TEXT("Extension list: %s"), *FString(extensions.c_str())); //swchar_t buffer[260]; //GetModuleFileName(NULL, buffer, 260); //UE_LOG(GLTF, Error, TEXT("Curretn Path: %s"), *FString(buffer)); return; } double start = FPlatformTime::Seconds(); _numVertecies = 0; _meshCurrentlyProcessed = 0; processNode(scene->mRootNode, scene, FMatrix::Identity); double end = FPlatformTime::Seconds(); UE_LOG(GLTF, Log, TEXT("GLTF loaded: %i Vertecies in %f seconds"), _numVertecies, end - start); } AProceduralEntity* AProceduralEntity::clone() { FActorSpawnParameters Parameters; Parameters.Template = this; class AProceduralEntity * New = GetWorld()->SpawnActor<AProceduralEntity>(GetClass(), Parameters); New->SetOwner(GetOwner()); #if WITH_EDITOR New->SetFolderPath(this->GetFolderPath()); #endif return New; } void AProceduralEntity::setActorDisabled(bool disabled) { this->SetActorHiddenInGame(disabled); this->SetActorEnableCollision(!disabled); this->SetActorTickEnabled(!disabled); } UMaterialInterface *AProceduralEntity::GetMaterialForIndex(unsigned int index, const aiScene* scene){ if (MaterialMap.Contains(index)) { return *MaterialMap.Find(index); } else { //UE_LOG(GLTF, Warning, TEXT("Parsing Material %d"),index); UMaterialInstanceDynamic* newMaterial = UMaterialInstanceDynamic::Create(MasterMaterialRef, this); aiMaterial* AMat = scene->mMaterials[index]; if (AMat->GetTextureCount(aiTextureType_DIFFUSE) > 0) { aiString Path; if (AMat->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) { FString fileName = FString(Path.C_Str()); if (Path.data[0] == '*') { //embedded int32 textureIndex = FCString::Atoi(*fileName.RightChop(1)); //TODO "(note Atoi is unsafe; no way to indicate errors)" from Unreal Wiki if (textureIndex >= 0) { aiTexture *aiTexture = scene->mTextures[textureIndex]; // UE_LOG(GLTF, Warning, TEXT("Importing Texture with Index \"%s\" %d (%d|%d)"), *fileName, textureIndex, aiTexture->mWidth, aiTexture->mHeight); if (aiTexture->mHeight == 0) { // If mHeight = 0 this is a pointer to a memory buffer of size mWidth containing the compressed texture data. Good luck, have fun! FString extension = FString(aiTexture->achFormatHint); EImageFormat::Type format = UImageDownloader::GetImageTypeByExtension(extension); bool isValid; int32 width, height; UTexture2D* diffTexture = UImageDownloader::LoadTexture2D((uint8*)aiTexture->pcData, aiTexture->mWidth, format, isValid, width, height); if (isValid) newMaterial->SetTextureParameterValue(FName("DiffuseTexture"), diffTexture); } else { //TODO not implemented UE_LOG(GLTF, Error, TEXT("not Implemented Texture Format")); } } else { UE_LOG(GLTF, Error, TEXT("Invalid Texture Index \"%s\""), *fileName); } } else { UImageDownloader *downloader = NewObject<UImageDownloader>(UImageDownloader::StaticClass()); downloader->GetTextureForMaterial(newMaterial, FName("DiffuseTexture"), _filePath, fileName); } } } else { newMaterial = UMaterialInstanceDynamic::Create(UniformMaterialRef, this); aiColor3D color(0.f, 0.f, 0.f); if (AI_SUCCESS == AMat->Get(AI_MATKEY_COLOR_DIFFUSE, color)) { //UE_LOG(GLTF, Warning, TEXT("Material Property : %s, %f %f %f"), TEXT("AI_MATKEY_COLOR_DIFFUSE"), color.r, color.g, color.b); FLinearColor Fcolor = FLinearColor(color.r, color.g, color.b); newMaterial->SetVectorParameterValue(FName("Diffuse"), Fcolor); } } aiColor3D color(0.f, 0.f, 0.f); if (AI_SUCCESS == AMat->Get(AI_MATKEY_COLOR_SPECULAR, color)) { //UE_LOG(GLTF, Warning, TEXT("Material Property: %s, %f %f %f"), TEXT("AI_MATKEY_COLOR_SPECULAR"), color.r, color.g, color.b); FLinearColor Fcolor = FLinearColor(color.r, color.g, color.b); newMaterial->SetVectorParameterValue(FName("Specular"), Fcolor); } //TODO //float shininess; //if (AI_SUCCESS == AMat->Get(AI_MATKEY_SHININESS, shininess)) { //UE_LOG(GLTF, Warning, TEXT("Material Property: %s, %f"), TEXT("AI_MATKEY_SHININESS"), shininess); //newMaterial->SetVectorParameterValue(FName("Specular"), Fcolor); //} MaterialMap.Add(index, newMaterial); return newMaterial; } }