Browse Source

Some work on the GLTF importer

Panagiotis Christopoulos Charitos 6 years ago
parent
commit
888c52030e

+ 1 - 0
samples/simple_scene/assets/scene.lua

@@ -52,6 +52,7 @@ node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 node = scene:newPointLightNode("Point")
 node = scene:newPointLightNode("Point")
 lcomp = node:getSceneNodeBase():getLightComponent()
 lcomp = node:getSceneNodeBase():getLightComponent()
 lcomp:setDiffuseColor(Vec4.new(10.000000, 10.000000, 10.000000, 1))
 lcomp:setDiffuseColor(Vec4.new(10.000000, 10.000000, 10.000000, 1))
+lcomp:setShadowEnabled(1)
 lcomp:setRadius(31.622776)
 lcomp:setRadius(31.622776)
 trf = Transform.new()
 trf = Transform.new()
 trf:setOrigin(Vec4.new(0.068084, 9.579867, 0.030239, 0))
 trf:setOrigin(Vec4.new(0.068084, 9.579867, 0.030239, 0))

+ 2 - 0
shaders/LightFunctions.glsl

@@ -17,6 +17,7 @@ const F32 ESM_CONSTANT = 40.0;
 #endif
 #endif
 
 
 // Fresnel term unreal
 // Fresnel term unreal
+// specular: The specular color aka F0
 Vec3 F_Unreal(Vec3 specular, F32 VoH)
 Vec3 F_Unreal(Vec3 specular, F32 VoH)
 {
 {
 	return specular + (1.0 - specular) * pow(2.0, (-5.55473 * VoH - 6.98316) * VoH);
 	return specular + (1.0 - specular) * pow(2.0, (-5.55473 * VoH - 6.98316) * VoH);
@@ -24,6 +25,7 @@ Vec3 F_Unreal(Vec3 specular, F32 VoH)
 
 
 // Fresnel Schlick: "An Inexpensive BRDF Model for Physically-Based Rendering"
 // Fresnel Schlick: "An Inexpensive BRDF Model for Physically-Based Rendering"
 // It has lower VGRPs than F_Unreal
 // It has lower VGRPs than F_Unreal
+// specular: The specular color aka F0
 Vec3 F_Schlick(Vec3 specular, F32 VoH)
 Vec3 F_Schlick(Vec3 specular, F32 VoH)
 {
 {
 	const F32 a = 1.0 - VoH;
 	const F32 a = 1.0 - VoH;

+ 30 - 0
src/anki/util/DynamicArray.h

@@ -317,6 +317,21 @@ public:
 		Base::destroy(m_alloc);
 		Base::destroy(m_alloc);
 	}
 	}
 
 
+	/// Copy.
+	DynamicArrayAuto(const DynamicArrayAuto& b)
+		: Base()
+		, m_alloc(b.m_alloc)
+	{
+		if(b.getSize())
+		{
+			create(b.getSize());
+			for(PtrSize i = 0; i < b.getSize(); ++i)
+			{
+				(*this)[i] = b[i];
+			}
+		}
+	}
+
 	/// Move.
 	/// Move.
 	DynamicArrayAuto& operator=(DynamicArrayAuto&& b)
 	DynamicArrayAuto& operator=(DynamicArrayAuto&& b)
 	{
 	{
@@ -333,6 +348,21 @@ public:
 		return *this;
 		return *this;
 	}
 	}
 
 
+	/// Copy.
+	DynamicArrayAuto& operator=(const DynamicArrayAuto& b)
+	{
+		destroy();
+		if(b.getSize())
+		{
+			create(b.getSize());
+			for(PtrSize i = 0; i < b.getSize(); ++i)
+			{
+				(*this)[i] = b[i];
+			}
+		}
+		return *this;
+	}
+
 	/// @copydoc DynamicArray::create
 	/// @copydoc DynamicArray::create
 	void create(PtrSize size)
 	void create(PtrSize size)
 	{
 	{

+ 30 - 3
src/anki/util/HashMap.h

@@ -194,7 +194,7 @@ public:
 		return m_sparseArr.find(hash);
 		return m_sparseArr.find(hash);
 	}
 	}
 
 
-private:
+protected:
 	SparseArrayType m_sparseArr;
 	SparseArrayType m_sparseArr;
 };
 };
 
 
@@ -222,10 +222,17 @@ public:
 		*this = std::move(b);
 		*this = std::move(b);
 	}
 	}
 
 
+	/// Copy.
+	HashMapAuto(const HashMapAuto& b)
+		: Base()
+	{
+		copy(b);
+	}
+
 	/// Destructor.
 	/// Destructor.
 	~HashMapAuto()
 	~HashMapAuto()
 	{
 	{
-		Base::destroy(m_alloc);
+		destroy();
 	}
 	}
 
 
 	/// Move.
 	/// Move.
@@ -236,11 +243,18 @@ public:
 		return *this;
 		return *this;
 	}
 	}
 
 
+	/// Copy.
+	HashMapAuto& operator=(const HashMapAuto& b)
+	{
+		copy(b);
+		return *this;
+	}
+
 	/// Construct an element inside the map.
 	/// Construct an element inside the map.
 	template<typename... TArgs>
 	template<typename... TArgs>
 	typename Base::Iterator emplace(const TKey& key, TArgs&&... args)
 	typename Base::Iterator emplace(const TKey& key, TArgs&&... args)
 	{
 	{
-		return Base::emplace(m_alloc, std::forward(args)...);
+		return Base::emplace(m_alloc, key, std::forward<TArgs>(args)...);
 	}
 	}
 
 
 	/// Erase element.
 	/// Erase element.
@@ -249,8 +263,21 @@ public:
 		Base::erase(m_alloc, it);
 		Base::erase(m_alloc, it);
 	}
 	}
 
 
+	/// Clean up the map.
+	void destroy()
+	{
+		Base::destroy(m_alloc);
+	}
+
 private:
 private:
 	GenericMemoryPoolAllocator<U8> m_alloc;
 	GenericMemoryPoolAllocator<U8> m_alloc;
+
+	void copy(const HashMapAuto& b)
+	{
+		destroy();
+		m_alloc = b.m_alloc;
+		b.m_sparseArr.clone(m_alloc, Base::m_sparseArr);
+	}
 };
 };
 /// @}
 /// @}
 
 

+ 10 - 0
src/anki/util/SparseArray.h

@@ -125,6 +125,12 @@ public:
 		return !(*this == b);
 		return !(*this == b);
 	}
 	}
 
 
+	U32 getKey() const
+	{
+		check();
+		return m_elementIdx;
+	}
+
 private:
 private:
 	TSparseArrayPtr m_array;
 	TSparseArrayPtr m_array;
 	U32 m_elementIdx;
 	U32 m_elementIdx;
@@ -340,6 +346,10 @@ public:
 	/// Check the validity of the array.
 	/// Check the validity of the array.
 	void validate() const;
 	void validate() const;
 
 
+	/// Create a copy of this.
+	template<typename TAlloc>
+	void clone(TAlloc& alloc, SparseArray& b) const;
+
 protected:
 protected:
 	/// Element metadata.
 	/// Element metadata.
 	class Metadata
 	class Metadata

+ 33 - 0
src/anki/util/SparseArray.inl.h

@@ -332,4 +332,37 @@ TIndex SparseArray<T, TIndex>::findInternal(Index idx) const
 	return MAX_U32;
 	return MAX_U32;
 }
 }
 
 
+template<typename T, typename TIndex>
+template<typename TAlloc>
+void SparseArray<T, TIndex>::clone(TAlloc& alloc, SparseArray& b) const
+{
+	ANKI_ASSERT(b.m_elements == nullptr && b.m_metadata == nullptr);
+	if(m_capacity == 0)
+	{
+		return;
+	}
+
+	// Allocate memory
+	b.m_elements = static_cast<Value*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Value), alignof(Value)));
+	b.m_metadata =
+		static_cast<Metadata*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Metadata), alignof(Metadata)));
+	memcpy(b.m_metadata, m_metadata, m_capacity * sizeof(Metadata));
+
+	for(U i = 0; i < m_capacity; ++i)
+	{
+		if(m_metadata[i].m_alive)
+		{
+			::new(&b.m_elements[i]) Value(m_elements[i]);
+		}
+	}
+
+	// Set the rest
+	b.m_elementCount = m_elementCount;
+	b.m_capacity = m_capacity;
+	b.m_initialStorageSize = m_initialStorageSize;
+	b.m_probeCount = m_probeCount;
+	b.m_maxLoadFactor = m_maxLoadFactor;
+	b.invalidateIterators();
+}
+
 } // end namespace anki
 } // end namespace anki

+ 0 - 7
tools/gltf_importer/CgltfImpl.cpp

@@ -1,7 +0,0 @@
-// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#define CGLTF_IMPLEMENTATION
-#include <cgltf/cgltf.h>

+ 83 - 33
tools/gltf_importer/Importer.cpp

@@ -5,6 +5,9 @@
 
 
 #include "Importer.h"
 #include "Importer.h"
 
 
+#define CGLTF_IMPLEMENTATION
+#include <cgltf/cgltf.h>
+
 namespace anki
 namespace anki
 {
 {
 
 
@@ -146,14 +149,14 @@ Error Importer::writeAll()
 	{
 	{
 		for(cgltf_node* const* node = scene->nodes; node < scene->nodes + scene->nodes_count; ++node)
 		for(cgltf_node* const* node = scene->nodes; node < scene->nodes + scene->nodes_count; ++node)
 		{
 		{
-			ANKI_CHECK(visitNode(*(*node), Transform::getIdentity()));
+			ANKI_CHECK(visitNode(*(*node), Transform::getIdentity(), HashMapAuto<CString, StringAuto>(m_alloc)));
 		}
 		}
 	}
 	}
 
 
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
-Error Importer::getExtras(const cgltf_extras& extras, HashMapAuto<StringAuto, StringAuto>& out)
+Error Importer::getExtras(const cgltf_extras& extras, HashMapAuto<CString, StringAuto>& out)
 {
 {
 	cgltf_size extrasSize;
 	cgltf_size extrasSize;
 	cgltf_copy_extras_json(m_gltf, &extras, nullptr, &extrasSize);
 	cgltf_copy_extras_json(m_gltf, &extras, nullptr, &extrasSize);
@@ -173,7 +176,53 @@ Error Importer::getExtras(const cgltf_extras& extras, HashMapAuto<StringAuto, St
 
 
 	json[json.getSize() - 1] = '\0';
 	json[json.getSize() - 1] = '\0';
 
 
-	printf("%s\n", &json[0]);
+	// Get token count
+	CString jsonTxt(&json[0]);
+	jsmn_parser parser;
+	jsmn_init(&parser);
+	const I tokenCount = jsmn_parse(&parser, jsonTxt.cstr(), jsonTxt.getLength(), nullptr, 0);
+	if(tokenCount < 1)
+	{
+		return Error::NONE;
+	}
+
+	DynamicArrayAuto<jsmntok_t> tokens(m_alloc);
+	tokens.create(tokenCount);
+
+	// Get tokens
+	jsmn_init(&parser);
+	jsmn_parse(&parser, jsonTxt.cstr(), jsonTxt.getLength(), &tokens[0], tokens.getSize());
+
+	StringListAuto tokenStrings(m_alloc);
+	for(const jsmntok_t& token : tokens)
+	{
+		if(token.type != JSMN_STRING)
+		{
+			continue;
+		}
+
+		StringAuto tokenStr(m_alloc);
+		tokenStr.create(&jsonTxt[token.start], &jsonTxt[token.end]);
+		tokenStrings.pushBack(tokenStr.toCString());
+	}
+
+	if((tokenStrings.getSize() % 2) != 0)
+	{
+		ANKI_GLTF_LOGE("Unable to parse: %s", jsonTxt.cstr());
+		return Error::FUNCTION_FAILED;
+	}
+
+	// Write them to the map
+	auto it = tokenStrings.getBegin();
+	while(it != tokenStrings.getEnd())
+	{
+		auto it2 = it;
+		++it2;
+
+		out.emplace(it->toCString(), StringAuto(m_alloc, it2->toCString()));
+		++it;
+		++it;
+	}
 
 
 	return Error::NONE;
 	return Error::NONE;
 }
 }
@@ -209,46 +258,44 @@ Error Importer::parseArrayOfNumbers(CString str, DynamicArrayAuto<F64>& out, con
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
-Error Importer::visitNode(const cgltf_node& node, const Transform& parentTrf)
+Error Importer::visitNode(
+	const cgltf_node& node, const Transform& parentTrf, const HashMapAuto<CString, StringAuto>& parentExtras)
 {
 {
-	Transform localTrf;
-	ANKI_CHECK(getNodeTransform(node, localTrf));
-	const Transform finalTrf = parentTrf.combineTransformations(localTrf);
-
-	Transform newParentTrf = Transform::getIdentity();
-	HashMapAuto<StringAuto, StringAuto> parentExtras(m_alloc);
-	Bool writeTrf = true;
+	HashMapAuto<CString, StringAuto> outExtras(m_alloc);
+	Bool dummyNode = false;
 	if(node.light)
 	if(node.light)
 	{
 	{
-		ANKI_CHECK(writeLight(node));
+		ANKI_CHECK(writeLight(node, parentExtras));
 	}
 	}
 	else if(node.camera)
 	else if(node.camera)
 	{
 	{
-		ANKI_CHECK(writeCamera(node));
+		ANKI_CHECK(writeCamera(node, parentExtras));
 	}
 	}
 	else if(node.mesh)
 	else if(node.mesh)
 	{
 	{
 		ANKI_CHECK(writeModel(*node.mesh));
 		ANKI_CHECK(writeModel(*node.mesh));
-		ANKI_CHECK(writeModelNode(node));
+		ANKI_CHECK(writeModelNode(node, parentExtras));
 	}
 	}
 	else
 	else
 	{
 	{
 		ANKI_GLTF_LOGW("Ignoring node %s. Assuming transform node", node.name);
 		ANKI_GLTF_LOGW("Ignoring node %s. Assuming transform node", node.name);
-		newParentTrf = finalTrf;
-		ANKI_CHECK(getExtras(node.extras, parentExtras));
-		writeTrf = false;
+		ANKI_CHECK(getExtras(node.extras, outExtras));
+		dummyNode = true;
 	}
 	}
 
 
 	// Write transform
 	// Write transform
-	if(writeTrf)
+	Transform localTrf;
+	ANKI_CHECK(getNodeTransform(node, localTrf));
+	Transform trf = parentTrf.combineTransformations(localTrf);
+	if(!dummyNode)
 	{
 	{
-		ANKI_CHECK(writeTransform(finalTrf));
+		ANKI_CHECK(writeTransform(trf));
 	}
 	}
 
 
 	// Visit children
 	// Visit children
 	for(cgltf_node* const* c = node.children; c < node.children + node.children_count; ++c)
 	for(cgltf_node* const* c = node.children; c < node.children + node.children_count; ++c)
 	{
 	{
-		ANKI_CHECK(visitNode(*(*c), newParentTrf));
+		ANKI_CHECK(visitNode(*(*c), (dummyNode) ? trf : Transform::getIdentity(), outExtras));
 	}
 	}
 
 
 	return Error::NONE;
 	return Error::NONE;
@@ -287,7 +334,7 @@ Error Importer::writeModel(const cgltf_mesh& mesh)
 		return Error::USER_DATA;
 		return Error::USER_DATA;
 	}
 	}
 
 
-	HashMapAuto<StringAuto, StringAuto> extras(m_alloc);
+	HashMapAuto<CString, StringAuto> extras(m_alloc);
 	ANKI_CHECK(getExtras(mesh.extras, extras));
 	ANKI_CHECK(getExtras(mesh.extras, extras));
 
 
 	File file;
 	File file;
@@ -300,13 +347,13 @@ Error Importer::writeModel(const cgltf_mesh& mesh)
 
 
 	ANKI_CHECK(file.writeText("\t\t\t<mesh>%s%s.ankimesh</mesh>\n", m_rpath.cstr(), mesh.name));
 	ANKI_CHECK(file.writeText("\t\t\t<mesh>%s%s.ankimesh</mesh>\n", m_rpath.cstr(), mesh.name));
 
 
-	auto lod1 = extras.find(StringAuto(m_alloc, "lod1"));
+	auto lod1 = extras.find("lod1");
 	if(lod1 != extras.getEnd())
 	if(lod1 != extras.getEnd())
 	{
 	{
 		ANKI_CHECK(file.writeText("\t\t\t<mesh1>%s%s</mesh1>\n", m_rpath.cstr(), lod1->cstr()));
 		ANKI_CHECK(file.writeText("\t\t\t<mesh1>%s%s</mesh1>\n", m_rpath.cstr(), lod1->cstr()));
 	}
 	}
 
 
-	auto mtlOverride = extras.find(StringAuto(m_alloc, "material_override"));
+	auto mtlOverride = extras.find("material_override");
 	if(mtlOverride != extras.getEnd())
 	if(mtlOverride != extras.getEnd())
 	{
 	{
 		ANKI_CHECK(file.writeText("\t\t\t<material>%s%s</material>\n", m_rpath.cstr(), mtlOverride->cstr()));
 		ANKI_CHECK(file.writeText("\t\t\t<material>%s%s</material>\n", m_rpath.cstr(), mtlOverride->cstr()));
@@ -327,12 +374,12 @@ Error Importer::writeModel(const cgltf_mesh& mesh)
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
-Error Importer::writeLight(const cgltf_node& node)
+Error Importer::writeLight(const cgltf_node& node, const HashMapAuto<CString, StringAuto>& parentExtras)
 {
 {
 	const cgltf_light& light = *node.light;
 	const cgltf_light& light = *node.light;
 	ANKI_GLTF_LOGI("Exporting light %s", light.name);
 	ANKI_GLTF_LOGI("Exporting light %s", light.name);
 
 
-	HashMapAuto<StringAuto, StringAuto> extras(m_alloc);
+	HashMapAuto<CString, StringAuto> extras(parentExtras);
 	ANKI_CHECK(getExtras(node.extras, extras));
 	ANKI_CHECK(getExtras(node.extras, extras));
 
 
 	CString lightTypeStr;
 	CString lightTypeStr;
@@ -360,7 +407,7 @@ Error Importer::writeLight(const cgltf_node& node)
 	ANKI_CHECK(
 	ANKI_CHECK(
 		m_sceneFile.writeText("lcomp:setDiffuseColor(Vec4.new(%f, %f, %f, 1))\n", color.x(), color.y(), color.z()));
 		m_sceneFile.writeText("lcomp:setDiffuseColor(Vec4.new(%f, %f, %f, 1))\n", color.x(), color.y(), color.z()));
 
 
-	auto shadow = extras.find(StringAuto(m_alloc, "shadow"));
+	auto shadow = extras.find("shadow");
 	if(shadow != extras.getEnd())
 	if(shadow != extras.getEnd())
 	{
 	{
 		if(*shadow == "true")
 		if(*shadow == "true")
@@ -384,13 +431,13 @@ Error Importer::writeLight(const cgltf_node& node)
 		ANKI_CHECK(m_sceneFile.writeText("lcomp:setInnerAngle(%f)\n", light.spot_inner_cone_angle));
 		ANKI_CHECK(m_sceneFile.writeText("lcomp:setInnerAngle(%f)\n", light.spot_inner_cone_angle));
 	}
 	}
 
 
-	auto lensFlaresFname = extras.find(StringAuto(m_alloc, "lens_flare"));
+	auto lensFlaresFname = extras.find("lens_flare");
 	if(lensFlaresFname != extras.getEnd())
 	if(lensFlaresFname != extras.getEnd())
 	{
 	{
 		ANKI_CHECK(m_sceneFile.writeText("node:loadLensFlare(\"%s\")\n", lensFlaresFname->cstr()));
 		ANKI_CHECK(m_sceneFile.writeText("node:loadLensFlare(\"%s\")\n", lensFlaresFname->cstr()));
 
 
-		auto lsSpriteSize = extras.find(StringAuto(m_alloc, "lens_flare_first_sprite_size"));
-		auto lsColor = extras.find(StringAuto(m_alloc, "lens_flare_color"));
+		auto lsSpriteSize = extras.find("lens_flare_first_sprite_size");
+		auto lsColor = extras.find("lens_flare_color");
 
 
 		if(lsSpriteSize != extras.getEnd() || lsColor != extras.getEnd())
 		if(lsSpriteSize != extras.getEnd() || lsColor != extras.getEnd())
 		{
 		{
@@ -420,8 +467,8 @@ Error Importer::writeLight(const cgltf_node& node)
 		}
 		}
 	}
 	}
 
 
-	auto lightEventIntensity = extras.find(StringAuto(m_alloc, "lens_flare"));
-	auto lightEventFrequency = extras.find(StringAuto(m_alloc, "light_event_frequency"));
+	auto lightEventIntensity = extras.find("lens_flare");
+	auto lightEventFrequency = extras.find("light_event_frequency");
 	if(lightEventIntensity != extras.getEnd() || lightEventFrequency != extras.getEnd())
 	if(lightEventIntensity != extras.getEnd() || lightEventFrequency != extras.getEnd())
 	{
 	{
 		ANKI_CHECK(m_sceneFile.writeText("event = events:newLightEvent(0.0, -1.0, node:getSceneNodeBase())\n"));
 		ANKI_CHECK(m_sceneFile.writeText("event = events:newLightEvent(0.0, -1.0, node:getSceneNodeBase())\n"));
@@ -450,7 +497,7 @@ Error Importer::writeLight(const cgltf_node& node)
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
-Error Importer::writeCamera(const cgltf_node& node)
+Error Importer::writeCamera(const cgltf_node& node, const HashMapAuto<CString, StringAuto>& parentExtras)
 {
 {
 	if(node.camera->type != cgltf_camera_type_perspective)
 	if(node.camera->type != cgltf_camera_type_perspective)
 	{
 	{
@@ -474,10 +521,13 @@ Error Importer::writeCamera(const cgltf_node& node)
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
-Error Importer::writeModelNode(const cgltf_node& node)
+Error Importer::writeModelNode(const cgltf_node& node, const HashMapAuto<CString, StringAuto>& parentExtras)
 {
 {
 	ANKI_GLTF_LOGI("Exporting model node %s", node.name);
 	ANKI_GLTF_LOGI("Exporting model node %s", node.name);
 
 
+	HashMapAuto<CString, StringAuto> extras(parentExtras);
+	ANKI_CHECK(getExtras(node.extras, extras));
+
 	StringAuto modelFname(m_alloc);
 	StringAuto modelFname(m_alloc);
 	modelFname.sprintf("%s%s_%s.ankimdl", m_rpath.cstr(), node.mesh->name, node.mesh->primitives[0].material->name);
 	modelFname.sprintf("%s%s_%s.ankimdl", m_rpath.cstr(), node.mesh->name, node.mesh->primitives[0].material->name);
 
 

+ 6 - 5
tools/gltf_importer/Importer.h

@@ -39,7 +39,7 @@ private:
 
 
 	File m_sceneFile;
 	File m_sceneFile;
 
 
-	ANKI_USE_RESULT Error getExtras(const cgltf_extras& extras, HashMapAuto<StringAuto, StringAuto>& out);
+	ANKI_USE_RESULT Error getExtras(const cgltf_extras& extras, HashMapAuto<CString, StringAuto>& out);
 	ANKI_USE_RESULT Error parseArrayOfNumbers(
 	ANKI_USE_RESULT Error parseArrayOfNumbers(
 		CString str, DynamicArrayAuto<F64>& out, const U* expectedArraySize = nullptr);
 		CString str, DynamicArrayAuto<F64>& out, const U* expectedArraySize = nullptr);
 
 
@@ -57,10 +57,11 @@ private:
 
 
 	// Scene
 	// Scene
 	ANKI_USE_RESULT Error writeTransform(const Transform& trf);
 	ANKI_USE_RESULT Error writeTransform(const Transform& trf);
-	ANKI_USE_RESULT Error visitNode(const cgltf_node& node, const Transform& parentTrf);
-	ANKI_USE_RESULT Error writeLight(const cgltf_node& node);
-	ANKI_USE_RESULT Error writeCamera(const cgltf_node& node);
-	ANKI_USE_RESULT Error writeModelNode(const cgltf_node& node);
+	ANKI_USE_RESULT Error visitNode(
+		const cgltf_node& node, const Transform& parentTrf, const HashMapAuto<CString, StringAuto>& parentExtras);
+	ANKI_USE_RESULT Error writeLight(const cgltf_node& node, const HashMapAuto<CString, StringAuto>& parentExtras);
+	ANKI_USE_RESULT Error writeCamera(const cgltf_node& node, const HashMapAuto<CString, StringAuto>& parentExtras);
+	ANKI_USE_RESULT Error writeModelNode(const cgltf_node& node, const HashMapAuto<CString, StringAuto>& parentExtras);
 };
 };
 
 
 #define ANKI_GLTF_LOGI(...) ANKI_LOG("GLTF", NORMAL, __VA_ARGS__)
 #define ANKI_GLTF_LOGI(...) ANKI_LOG("GLTF", NORMAL, __VA_ARGS__)

+ 1 - 1
tools/gltf_importer/ImporterMesh.cpp

@@ -215,7 +215,7 @@ Error Importer::writeMesh(const cgltf_mesh& mesh)
 
 
 				// Check angle of the normals
 				// Check angle of the normals
 				Vec3& otherNormal = submesh.m_normals[prevV];
 				Vec3& otherNormal = submesh.m_normals[prevV];
-				const F32 ang = acos(otherNormal.dot(normal));
+				const F32 ang = acos(clamp(otherNormal.dot(normal), -1.0f, 1.0f));
 				if(ang > m_normalsMergeAngle)
 				if(ang > m_normalsMergeAngle)
 				{
 				{
 					continue;
 					continue;