Commit 61274d62 by wester

assimp

parent 0906751a
[submodule "Plugins/UnrealGLTFLoader"] [submodule "Plugins/UnrealGLTFLoader"]
path = Plugins/UnrealGLTFLoader path = Plugins/UnrealGLTFLoader
url = ssh://git@git.breab.org:2223/kai/glTFLoader.git url = ssh://git@git.breab.org:2223/kai/glTFLoader.git
[submodule "ThirdParty/assimp"]
path = ThirdParty/assimp
url = https://github.com/assimp/assimp.git
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
#include "MasterTestProject.h" #include "MasterTestProject.h"
#include <locale>
#include <codecvt> #include <codecvt>
#define TINYGLTF_LOADER_IMPLEMENTATION #define TINYGLTF_LOADER_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "tiny_gltf_loader.h" #include "tiny_gltf_loader.h"
#include "Developer/RawMesh/Public/RawMesh.h"
#include "StaticGLTFComponent.h" #include "StaticGLTFComponent.h"
...@@ -29,31 +31,70 @@ UStaticGLTFComponent::~UStaticGLTFComponent() { ...@@ -29,31 +31,70 @@ UStaticGLTFComponent::~UStaticGLTFComponent() {
void UStaticGLTFComponent::OnRegister() void UStaticGLTFComponent::OnRegister()
{ {
Super::OnRegister(); Super::OnRegister();
std::string TempError; std::string temperror;
tinygltfLoadSuccess = Loader->LoadFromFile((*Scene), TempError, ToStdString(GLTFPath)); tinygltfLoadSuccess = Loader->LoadFromFile((*Scene), temperror, ToStdString(GLTFPath));
tinygltfError = ToFString(TempError); tinygltfError = ToFString(temperror);
if(!tinygltfLoadSuccess){ if(!tinygltfLoadSuccess){
UE_LOG(GLTF, Error, TEXT("GLTF loading Error: %s"), *tinygltfError); UE_LOG(GLTF, Error, TEXT("gltf loading error: %s"), *tinygltfError);
return; return;
} }
UE_LOG(GLTF, Log,TEXT("GLTF parsed %s"), *tinygltfError); UE_LOG(GLTF, Log, TEXT("gltf parsed %s"), *tinygltfError);
FRawMesh newrawmesh;
//get Default Scene and Root Nodes //get default scene and root nodes
if (Scene->defaultScene.empty()) { if (Scene->defaultScene.empty()) {
UE_LOG(GLTF, Error, TEXT("Default Scene Empty")); UE_LOG(GLTF, Error, TEXT("default scene empty "));
}
else {
ImportNodes( Scene->scenes[Scene->defaultScene], newrawmesh, FMatrix());
} }
for (auto RootNode : Scene->scenes[Scene->defaultScene]) {
UE_LOG(GLTF, Log, TEXT("Root Node %s"), *ToFString(RootNode));
}
} }
void UStaticGLTFComponent::ImportNodes( std::vector<std::string> nodes, FRawMesh& RawMesh, FMatrix ModelTransform)
{
for (auto CurrentNodeName : nodes) {
UE_LOG(GLTF, Log, TEXT("parsed Node %s"), *ToFString(CurrentNodeName));
tinygltf::Node CurrentNode = Scene->nodes[CurrentNodeName];
for (std::string meshName : CurrentNode.meshes) {
tinygltf::Mesh mesh = Scene->meshes[meshName];
for (tinygltf::Primitive primitive : mesh.primitives) {
if (primitive.mode != TINYGLTF_MODE_TRIANGLES) {
UE_LOG(GLTF, Error, TEXT("Unsupported Mesh Primitive Mode %d "), primitive.mode);
}
//TODO parse or link Material if necesssary
//primitive.material;
ModelTransform = ModelTransform * GetNodeTransform(&CurrentNode);
//TODO CorrectUp Direction
//FTransform Temp(FRotator(0.0f, 0.0f, -90.0f));
//TotalMatrix = TotalMatrix * Temp.ToMatrixWithScale();
//GetBufferData(OutArray, Scene->accessors[primitive.attributes["POSITION"]]);
}
}
ImportNodes( CurrentNode.children, RawMesh, ModelTransform);
}
}
FString UStaticGLTFComponent::ToFString(std::string InString) FString UStaticGLTFComponent::ToFString(std::string InString)
{ {
return FString(InString.c_str()); return FString(InString.c_str());
...@@ -66,4 +107,91 @@ std::string UStaticGLTFComponent::ToStdString(FString InString) ...@@ -66,4 +107,91 @@ std::string UStaticGLTFComponent::ToStdString(FString InString)
std::wstring WideString(&CharArray[0]); std::wstring WideString(&CharArray[0]);
std::wstring_convert< std::codecvt_utf8<wchar_t> > Convert; std::wstring_convert< std::codecvt_utf8<wchar_t> > Convert;
return Convert.to_bytes(WideString); return Convert.to_bytes(WideString);
} }
\ No newline at end of file
FMatrix UStaticGLTFComponent::GetNodeTransform(tinygltf::Node* Node)
{
if (Node->matrix.size() == 16)
{
FMatrix Ret;
for (int32 i = 0; i < 4; ++i)
{
for (int32 j = 0; j < 4; ++j)
{
// Reverse order since glTF is column major and FMatrix is row major
Ret.M[j][i] = Node->matrix[(4 * i) + j];
}
}
return Ret;
}
else if (Node->rotation.size() == 4 && Node->scale.size() == 3 && Node->translation.size() == 3)
{
FQuat Rotation((float)Node->rotation[0], (float)Node->rotation[1], (float)Node->rotation[2], (float)Node->rotation[3]);
FVector Scale((float)Node->scale[0], (float)Node->scale[1], (float)Node->scale[2]);
FVector Translation((float)Node->translation[0], (float)Node->translation[1], (float)Node->translation[2]);
return FTransform(Rotation, Translation, Scale).ToMatrixWithScale();
}
else
{
return FMatrix::Identity;
}
}
//Buffer Copy Methods:
// Retrieve a value from the buffer, implicitly accounting for endianness
// Adapted from http://stackoverflow.com/questions/13001183/how-to-read-little-endian-integers-from-file-in-c
template <typename T> T UStaticGLTFComponent::BufferValue(void* Data/*, uint8 Size*/)
{
T Ret = T(0);
auto NewData = reinterpret_cast<unsigned char*>(Data);
for (int i = 0; i < sizeof(T); ++i)
{
Ret |= (T)(NewData[i]) << (8 * i);
}
return Ret;
}
// Use unions for floats and doubles since they don't have a bitwise OR operator
template <> float UStaticGLTFComponent::BufferValue(void* Data)
{
assert(sizeof(float) == sizeof(int32));
union
{
float Ret;
int32 IntRet;
};
Ret = 0.0f;
auto NewData = reinterpret_cast<unsigned char*>(Data);
for (int i = 0; i < sizeof(int32); ++i)
{
IntRet |= (int32)(NewData[i]) << (8 * i);
}
return Ret;
}
template <> double UStaticGLTFComponent::BufferValue(void* Data)
{
assert(sizeof(float) == sizeof(int64));
union
{
double Ret;
int64 IntRet;
};
Ret = 0.0;
auto NewData = reinterpret_cast<unsigned char*>(Data);
for (int i = 0; i < sizeof(int64); ++i)
{
IntRet |= (int64)(NewData[i]) << (8 * i);
}
return Ret;
}
...@@ -4,14 +4,12 @@ ...@@ -4,14 +4,12 @@
#include "Components/StaticMeshComponent.h" #include "Components/StaticMeshComponent.h"
#include <string> #include <string>
#include <vector> #include <vector>
#include "StaticGLTFComponent.generated.h" #include "StaticGLTFComponent.generated.h"
struct FRawMesh;
/// Forward-declared TinyGLTF types since its header can only be #included in one source file. /// Forward-declared TinyGLTF types since its header can only be #included in one source file.
/// This also means that we must use pointers to these types outside of GLTFMeshBuilder.cpp. /// This also means that we must use pointers to these types outside of GLTFMeshBuilder.cpp.
...@@ -58,11 +56,29 @@ private: ...@@ -58,11 +56,29 @@ private:
FString tinygltfError; FString tinygltfError;
void ImportNodes(std::vector<std::string> nodes, FRawMesh& RawMesh, FMatrix ModelTransform);
/// @name String Conversion /// @name String Conversion
///@{ ///@{
/// Helper functions for converting between Unreal's and STL's strings. /// Helper functions for converting between Unreal's and STL's strings.
static FString ToFString(std::string InString); static FString ToFString(std::string InString);
static std::string ToStdString(FString InString); static std::string ToStdString(FString InString);
///@} ///@}
/// Returns the transform of a node relative to its parent.
FMatrix GetNodeTransform(tinygltf::Node* Node);
//Copy methods
/// @name Level 1: BufferValue
///@{
/// Obtains a single value from the geometry data buffer, accounting for endianness.
/// Adapted from http://stackoverflow.com/questions/13001183/how-to-read-little-endian-integers-from-file-in-c
/// @param Data A pointer to the raw data to cast to the desired type.
/// @return The typed data value.
template <typename T> T BufferValue(void* Data);
///@}
}; };
...@@ -40,19 +40,22 @@ void AGLTFDisplay::BeginPlay() ...@@ -40,19 +40,22 @@ void AGLTFDisplay::BeginPlay()
FVector zero = FVector(0); FVector zero = FVector(0);
//StaticMeshComponent->RegisterComponent(); //StaticMeshComponent->RegisterComponent();
//StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); //StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
//StaticMeshComponent->SetupAttachment(RootComponent);
if (!originalGLTFImport->IsRegistered()) //StaticMeshComponent->SetupAttachment(RootComponent);
originalGLTFImport->RegisterComponent(); if (!originalGLTFImport->IsRegistered()) {
//originalGLTFImport->RegisterComponent();
}
originalGLTFImport->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); originalGLTFImport->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
StaticMeshComponentCopy = DuplicateObject<UStaticMeshComponent>(originalGLTFImport, this); StaticMeshComponentCopy = DuplicateObject<UStaticMeshComponent>(originalGLTFImport, this);
if (!StaticMeshComponentCopy->IsRegistered()) if (!StaticMeshComponentCopy->IsRegistered()) {
StaticMeshComponentCopy->RegisterComponent(); StaticMeshComponentCopy->RegisterComponent();
StaticMeshComponentCopy->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); }
StaticMeshComponentCopy->SetRelativeLocation(FVector(100.f));
StaticMeshComponentCopy->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
StaticMeshComponentCopy->SetRelativeLocation(FVector(100.f));
StaticGLTFComponent->SetRelativeLocation(FVector(-100.f)); StaticGLTFComponent->SetRelativeLocation(FVector(-100.f));
......
// Fill out your copyright notice in the Description page of Project Settings. // Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool; using UnrealBuildTool;
using System;
using System.IO;
public class MasterTestProject : ModuleRules public class MasterTestProject : ModuleRules
{ {
public MasterTestProject(TargetInfo Target) private string ModulePath
{
get { return ModuleDirectory; }
}
private string ThirdPartyPath
{
get { return Path.GetFullPath(Path.Combine(ModulePath, "../../ThirdParty/")); }
}
public MasterTestProject(TargetInfo Target)
{ {
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", }); PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "RawMesh", "ProceduralMeshComponent" });
PrivateDependencyModuleNames.AddRange(new string[] { }); PrivateDependencyModuleNames.AddRange(new string[] { });
LoadAssimp(Target);
// Uncomment if you are using Slate UI // Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
...@@ -21,4 +35,35 @@ public class MasterTestProject : ModuleRules ...@@ -21,4 +35,35 @@ public class MasterTestProject : ModuleRules
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
} }
public bool LoadAssimp(TargetInfo Target)
{
bool isLibrarySupported = false;
if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Win32))
{
isLibrarySupported = true;
string PlatformString = (Target.Platform == UnrealTargetPlatform.Win64) ? "x64" : "x86";
string LibrariesPath = Path.Combine(ThirdPartyPath, "Assimp", "lib");
//test your path with:
//using System; Console.WriteLine("");
Console.WriteLine("... LibrariesPath -> " + LibrariesPath);
PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "assimp-vc140-mt.lib"));// + PlatformString + ".lib"));
}
if (isLibrarySupported)
{
// Include path
PublicIncludePaths.Add(Path.Combine(ThirdPartyPath, "BobsMagic", "Includes"));
}
Definitions.Add(string.Format("WITH_ASSIMP_BINDING={0}", isLibrarySupported ? 1 : 0));
return isLibrarySupported;
}
} }
// Fill out your copyright notice in the Description page of Project Settings.
#include "MasterTestProject.h"
#include <time.h>
#include "ProceduralEntity.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);
}*/
}
// Called when the game starts or when spawned
void AProceduralEntity::BeginPlay()
{
Super::BeginPlay();
}
void AProceduralEntity::PostActorCreated() {
Super::PostActorCreated();
if (!IsTemplate(RF_Transient)) {
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(LogTemp, Warning, TEXT("Problem getting info"));
switch (errno) {
case ENOENT:
UE_LOG(LogTemp, Warning, TEXT("File not found."));
break;
case EINVAL:
UE_LOG(LogTemp, Warning, TEXT("Invalid parameter to _stat."));
break;
default:
UE_LOG(LogTemp, Warning, TEXT("Unexpected error."));
}
}
else {
if (_lastModifiedTime == 0 || (int)buf.st_mtime > _lastModifiedTime) {
_lastModifiedTime = (int)buf.st_mtime;
UE_LOG(LogTemp, Warning, TEXT("Reloading model."));
loadModel(sfilename);
}
}
}
void AProceduralEntity::loadModelFromBlueprint() {
std::string filename(TCHAR_TO_UTF8(*_filePath));
loadModel(filename);
}
/* ASSIMP IMPORT */
void AProceduralEntity::processMesh(aiMesh* mesh, const aiScene* scene) {
UE_LOG(LogTemp, 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();
//}
for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
FVector vertex, 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;
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));
}
_vertices[_meshCurrentlyProcessed].Add(vertex);
_normals[_meshCurrentlyProcessed].Add(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(LogTemp, Warning, TEXT("Full recreation"));
_mesh->CreateMeshSection(_meshCurrentlyProcessed, _vertices[_meshCurrentlyProcessed], _indices[_meshCurrentlyProcessed], _normals[_meshCurrentlyProcessed], _uvs[_meshCurrentlyProcessed], _vertexColors[_meshCurrentlyProcessed], _tangents[_meshCurrentlyProcessed], true);
}
else {
UE_LOG(LogTemp, Warning, TEXT("Update"));
_mesh->UpdateMeshSection(_meshCurrentlyProcessed, _vertices[_meshCurrentlyProcessed], _normals[_meshCurrentlyProcessed], _uvs[_meshCurrentlyProcessed], _vertexColors[_meshCurrentlyProcessed], _tangents[_meshCurrentlyProcessed]);
}
UE_LOG(LogTemp, Warning, TEXT("Done creating the mesh"));
_requiresFullRecreation = false;
}
void AProceduralEntity::processNode(aiNode* node, const aiScene* scene) {
UE_LOG(LogTemp, Warning, TEXT("Processing a node"));
// process all the node's meshes
for (uint32 i = 0; i < node->mNumMeshes; i++) {
UE_LOG(LogTemp, Warning, TEXT("New mesh in the node"));
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
processMesh(mesh, scene);
++_meshCurrentlyProcessed;
}
// do the same for all of its children
for (uint32 i = 0; i < node->mNumChildren; i++) {
UE_LOG(LogTemp, Warning, TEXT("New child in the node"));
processNode(node->mChildren[i], scene);
}
}
void AProceduralEntity::loadModel(std::string path) {
Assimp::Importer importer;
UE_LOG(LogTemp, Warning, TEXT("START LOGGING"));
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenNormals);
if (!scene)
return;
_meshCurrentlyProcessed = 0;
processNode(scene->mRootNode, scene);
}
\ No newline at end of file
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include <string>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "ProceduralEntity.generated.h"
UCLASS()
class AProceduralEntity : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generation")
UProceduralMeshComponent* _mesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generation")
FString _filePath;
// Sets default values for this actor's properties
AProceduralEntity();
void PostActorCreated();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
UFUNCTION(BlueprintCallable, Category = ProceduralEntity)
void loadModelFromBlueprint();
private:
int32 _selectedVertex;
int32 _meshCurrentlyProcessed;
bool _addModifier;
int _lastModifiedTime;
bool _requiresFullRecreation;
TArray<TArray<FVector>> _vertices;
TArray<TArray<int32>> _indices;
TArray<TArray<FVector>> _normals;
TArray<TArray<FVector2D>> _uvs;
TArray<TArray<FProcMeshTangent>> _tangents;
TArray<TArray<FColor>> _vertexColors;
//USceneComponent* _rootComp;
/* ################################################### */
/* ################# Input methods ################### */
/* ################################################### */
/*
template<int vertexNb>
void SelectVertex();
template<bool addModifier>
void ChangeAddModifier();
void ChangeVertex(FVector dv);*/
void processMesh(aiMesh* mesh, const aiScene* scene);
void processNode(aiNode* node, const aiScene* scene);
void loadModel(std::string path);
};
\ No newline at end of file
A very basic showcase of how to use ProceduralMeshComponent to load models (thanks to assimp) directly into UE4, at runtime. The code also includes a small feature monitoring the FBX file of choice so it reloads the model as soon as the file is modified.
Subproject commit db1c6f5fc862ace1f833ed35646a7479e5f053b9
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment