Browse Source

GLTF importer fixes

Panagiotis Christopoulos Charitos 3 years ago
parent
commit
3039ddff04

+ 1 - 1
AnKi/Collision/Aabb.cpp

@@ -21,7 +21,7 @@ Aabb Aabb::getTransformed(const Transform& trf) const
 	Vec4 newC = trf.transform(center);
 	Vec4 newE = Vec4(absM * (extend * trf.getScale()), 0.0f);
 
-	return Aabb(newC - newE, newC + newE);
+	return Aabb(newC - newE, newC + newE + Vec4(EPSILON, EPSILON, EPSILON, 0.0f));
 }
 
 Aabb Aabb::getCompoundShape(const Aabb& b) const

+ 2 - 2
AnKi/Importer/GltfImporter.cpp

@@ -1269,7 +1269,7 @@ Error GltfImporter::writeLight(const cgltf_node& node, const HashMapAuto<CString
 	auto shadow = extras.find("shadow");
 	if(shadow != extras.getEnd())
 	{
-		if(*shadow == "true")
+		if(*shadow == "true" || *shadow == "1")
 		{
 			ANKI_CHECK(m_sceneFile.writeText("lcomp:setShadowEnabled(1)\n"));
 		}
@@ -1377,7 +1377,7 @@ Error GltfImporter::writeCamera(const cgltf_node& node, const HashMapAuto<CStrin
 		return Error::NONE;
 	}
 
-	const cgltf_camera_perspective& cam = node.camera->perspective;
+	const cgltf_camera_perspective& cam = node.camera->data.perspective;
 	ANKI_IMPORTER_LOGI("Importing camera %s", getNodeName(node).cstr());
 
 	ANKI_CHECK(m_sceneFile.writeText("\nnode = scene:newPerspectiveCameraNode(\"%s\")\n", getNodeName(node).cstr()));

+ 38 - 30
AnKi/Importer/GltfImporterMaterial.cpp

@@ -53,8 +53,7 @@ static CString getTextureUri(const cgltf_texture_view& view)
 }
 
 /// Read the texture and find out if
-static Error identifyMetallicRoughnessTexture(CString fname, F32& constantMetalines, F32& constantRoughness,
-											  GenericMemoryPoolAllocator<U8>& alloc)
+static Error findConstantColorsInImage(CString fname, Vec4& constantColor, GenericMemoryPoolAllocator<U8>& alloc)
 {
 	ImageLoader iloader(alloc);
 	ANKI_CHECK(iloader.load(fname));
@@ -69,25 +68,21 @@ static Error identifyMetallicRoughnessTexture(CString fname, F32& constantMetali
 		for(U32 x = 0; x < iloader.getHeight(); ++x)
 		{
 			const U8Vec4& pixel = *(data + y * iloader.getWidth() + x);
-			const F32 m = F32(pixel.z()) / 255.0f;
-			const F32 r = F32(pixel.y()) / 255.0f;
+			const Vec4 pixelf = Vec4(pixel) / 255.0f;
 
 			if(x == 0 && y == 0)
 			{
 				// Initialize
-				constantMetalines = m;
-				constantRoughness = r;
+				constantColor = pixelf;
 			}
 			else
 			{
-				if(constantMetalines < 0.0f || absolute(m - constantMetalines) > epsilon)
+				for(U32 i = 0; i < 4; ++i)
 				{
-					constantMetalines = -1.0f;
-				}
-
-				if(constantRoughness < 0.0f || absolute(r - constantRoughness) > epsilon)
-				{
-					constantRoughness = -1.0f;
+					if(absolute(pixelf[i] - constantColor[i]) > epsilon)
+					{
+						constantColor[i] = -1.0f;
+					}
 				}
 			}
 		}
@@ -128,14 +123,14 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 		uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.pbr_metallic_roughness.base_color_texture).cstr());
 
 		xml.replaceAll("%diff%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"u_diffTex\" value=\"%s\"/>", uri.cstr()));
+					   StringAuto(m_alloc).sprintf("<input shaderVar=\"u_diffTex\" value=\"%s\"/>", uri.cstr()));
 		xml.replaceAll("%diffTexMutator%", "1");
 	}
 	else
 	{
 		const F32* diffCol = &mtl.pbr_metallic_roughness.base_color_factor[0];
 
-		xml.replaceAll("%diff%", StringAuto{m_alloc}.sprintf("<input shaderVar=\"m_diffColor\" value=\"%f %f %f\"/>",
+		xml.replaceAll("%diff%", StringAuto(m_alloc).sprintf("<input shaderVar=\"m_diffColor\" value=\"%f %f %f\"/>",
 															 diffCol[0], diffCol[1], diffCol[2]));
 
 		xml.replaceAll("%diffTexMutator%", "0");
@@ -167,7 +162,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 			specular = Vec3(0.04f);
 		}
 
-		xml.replaceAll("%spec%", StringAuto{m_alloc}.sprintf("<input shaderVar=\"m_specColor\" value=\"%f %f %f\"/>",
+		xml.replaceAll("%spec%", StringAuto(m_alloc).sprintf("<input shaderVar=\"m_specColor\" value=\"%f %f %f\"/>",
 															 specular.x(), specular.y(), specular.z()));
 
 		xml.replaceAll("%specTexMutator%", "0");
@@ -179,7 +174,10 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 	{
 		const CString fname = getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture);
 
-		ANKI_CHECK(identifyMetallicRoughnessTexture(fname, constantMetaliness, constantRoughness, m_alloc));
+		Vec4 constantColor;
+		ANKI_CHECK(findConstantColorsInImage(fname, constantColor, m_alloc));
+		constantRoughness = constantColor.y();
+		constantMetaliness = constantColor.z();
 	}
 
 	// Roughness
@@ -190,7 +188,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 					getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture).cstr());
 
 		xml.replaceAll("%roughness%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"u_roughnessTex\" value=\"%s\"/>", uri.cstr()));
+					   StringAuto(m_alloc).sprintf("<input shaderVar=\"u_roughnessTex\" value=\"%s\"/>", uri.cstr()));
 
 		xml.replaceAll("%roughnessTexMutator%", "1");
 	}
@@ -201,7 +199,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 								  : mtl.pbr_metallic_roughness.roughness_factor;
 
 		xml.replaceAll("%roughness%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"m_roughness\" value=\"%f\"/>", roughness));
+					   StringAuto(m_alloc).sprintf("<input shaderVar=\"m_roughness\" value=\"%f\"/>", roughness));
 
 		xml.replaceAll("%roughnessTexMutator%", "0");
 	}
@@ -214,7 +212,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 					getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture).cstr());
 
 		xml.replaceAll("%metallic%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"u_metallicTex\" value=\"%s\"/>", uri.cstr()));
+					   StringAuto(m_alloc).sprintf("<input shaderVar=\"u_metallicTex\" value=\"%s\"/>", uri.cstr()));
 
 		xml.replaceAll("%metalTexMutator%", "1");
 	}
@@ -225,7 +223,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 								  : mtl.pbr_metallic_roughness.metallic_factor;
 
 		xml.replaceAll("%metallic%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"m_metallic\" value=\"%f\"/>", metalines));
+					   StringAuto(m_alloc).sprintf("<input shaderVar=\"m_metallic\" value=\"%f\"/>", metalines));
 
 		xml.replaceAll("%metalTexMutator%", "0");
 	}
@@ -233,13 +231,23 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 	// Normal texture
 	if(mtl.normal_texture.texture)
 	{
-		StringAuto uri(m_alloc);
-		uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.normal_texture).cstr());
+		Vec4 constantColor;
+		ANKI_CHECK(findConstantColorsInImage(getTextureUri(mtl.normal_texture).cstr(), constantColor, m_alloc));
+		if(constantColor.xyz() == -1.0f)
+		{
+			StringAuto uri(m_alloc);
+			uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.normal_texture).cstr());
 
-		xml.replaceAll("%normal%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"u_normalTex\" value=\"%s\"/>", uri.cstr()));
+			xml.replaceAll("%normal%",
+						   StringAuto(m_alloc).sprintf("<input shaderVar=\"u_normalTex\" value=\"%s\"/>", uri.cstr()));
 
-		xml.replaceAll("%normalTexMutator%", "1");
+			xml.replaceAll("%normalTexMutator%", "1");
+		}
+		else
+		{
+			xml.replaceAll("%normal%", "");
+			xml.replaceAll("%normalTexMutator%", "0");
+		}
 	}
 	else
 	{
@@ -254,7 +262,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 		uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.emissive_texture).cstr());
 
 		xml.replaceAll("%emission%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"u_emissiveTex\" value=\"%s\"/>", uri.cstr()));
+					   StringAuto(m_alloc).sprintf("<input shaderVar=\"u_emissiveTex\" value=\"%s\"/>", uri.cstr()));
 
 		xml.replaceAll("%emissiveTexMutator%", "1");
 	}
@@ -262,7 +270,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 	{
 		const F32* emissionCol = &mtl.emissive_factor[0];
 
-		xml.replaceAll("%emission%", StringAuto{m_alloc}.sprintf("<input shaderVar=\"m_emission\" value=\"%f %f %f\"/>",
+		xml.replaceAll("%emission%", StringAuto(m_alloc).sprintf("<input shaderVar=\"m_emission\" value=\"%f %f %f\"/>",
 																 emissionCol[0], emissionCol[1], emissionCol[2]));
 
 		xml.replaceAll("%emissiveTexMutator%", "0");
@@ -282,7 +290,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 		}
 
 		xml.replaceAll("%subsurface%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"m_subsurface\" value=\"%f\"/>", subsurface));
+					   StringAuto(m_alloc).sprintf("<input shaderVar=\"m_subsurface\" value=\"%f\"/>", subsurface));
 	}
 
 	// Height texture
@@ -293,7 +301,7 @@ Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayT
 		uri.sprintf("%s%s", m_texrpath.cstr(), it->cstr());
 
 		xml.replaceAll("%height%",
-					   StringAuto{m_alloc}.sprintf("<input shaderVar=\"u_heightTex\" value=\"%s\" \"/>\n"
+					   StringAuto(m_alloc).sprintf("<input shaderVar=\"u_heightTex\" value=\"%s\" \"/>\n"
 												   "\t\t<input shaderVar=\"m_heightmapScale\" value=\"0.05\"/>",
 												   uri.cstr()));
 

+ 1 - 1
AnKi/Importer/GltfImporterMesh.cpp

@@ -734,7 +734,7 @@ Error GltfImporter::writeMesh(const cgltf_mesh& mesh, CString nameOverride, F32
 			const U32 idx = submesh.m_indices[i] + vertCount;
 			if(idx > MAX_U16)
 			{
-				ANKI_IMPORTER_LOGE("Only supports 16bit indices for now");
+				ANKI_IMPORTER_LOGE("Only supports 16bit indices for now (%u): %s", idx, fname.cstr());
 				return Error::USER_DATA;
 			}
 

+ 150 - 42
AnKi/Importer/ImageImporter.cpp

@@ -212,6 +212,26 @@ static ANKI_USE_RESULT Error checkConfig(const ImageImporterConfig& config)
 	return Error::NONE;
 }
 
+static ANKI_USE_RESULT Error identifyImage(CString filename, U32& width, U32& height, U32& channelCount, Bool& hdr)
+{
+	I32 iwidth, iheight, ichannelCount;
+	const I ok = stbi_info(filename.cstr(), &iwidth, &iheight, &ichannelCount);
+	if(!ok)
+	{
+		ANKI_IMPORTER_LOGE("STB failed to load file: %s", filename.cstr());
+		return Error::FUNCTION_FAILED;
+	}
+
+	const I ihdr = stbi_is_hdr(filename.cstr());
+
+	width = U32(iwidth);
+	height = U32(iheight);
+	channelCount = U32(ichannelCount);
+	hdr = U32(ihdr);
+
+	return Error::NONE;
+}
+
 static ANKI_USE_RESULT Error checkInputImages(const ImageImporterConfig& config, U32& width, U32& height,
 											  U32& channelCount, Bool& isHdr)
 {
@@ -219,43 +239,26 @@ static ANKI_USE_RESULT Error checkInputImages(const ImageImporterConfig& config,
 	height = 0;
 	channelCount = 0;
 	isHdr = false;
-	I isHdr2 = -1;
 
 	for(U32 i = 0; i < config.m_inputFilenames.getSize(); ++i)
 	{
-		I32 iwidth, iheight, ichannelCount;
-		const I ok = stbi_info(config.m_inputFilenames[i].cstr(), &iwidth, &iheight, &ichannelCount);
-		if(!ok)
-		{
-			ANKI_IMPORTER_LOGE("STB failed to load file: %s", config.m_inputFilenames[i].cstr());
-			return Error::FUNCTION_FAILED;
-		}
+		U32 nwidth, nheight, nchannelCount;
+		Bool nhdr;
+		ANKI_CHECK(identifyImage(config.m_inputFilenames[i], nwidth, nheight, nchannelCount, nhdr));
 
-		if(width == 0)
+		if(i == 0)
 		{
-			width = U32(iwidth);
-			height = U32(iheight);
-			channelCount = U32(ichannelCount);
+			width = nwidth;
+			height = nheight;
+			channelCount = nchannelCount;
+			isHdr = nhdr;
 		}
-		else if(width != U32(iwidth) || height != U32(iheight) || channelCount != U32(ichannelCount))
+		else if(width != nwidth || height != nheight || channelCount != nchannelCount || isHdr != nhdr)
 		{
 			ANKI_IMPORTER_LOGE("Input image doesn't match previous input images: %s",
 							   config.m_inputFilenames[i].cstr());
 			return Error::USER_DATA;
 		}
-
-		const I hdr = stbi_is_hdr(config.m_inputFilenames[i].cstr());
-		if(isHdr2 < 0)
-		{
-			isHdr2 = hdr;
-			isHdr = hdr != 0;
-		}
-		else if(hdr != isHdr2)
-		{
-			ANKI_IMPORTER_LOGE("Input image doesn't match previous HDR parameters: %s",
-							   config.m_inputFilenames[i].cstr());
-			return Error::USER_DATA;
-		}
 	}
 
 	ANKI_ASSERT(width > 0 && height > 0 && channelCount > 0);
@@ -268,6 +271,80 @@ static ANKI_USE_RESULT Error checkInputImages(const ImageImporterConfig& config,
 	return Error::NONE;
 }
 
+static ANKI_USE_RESULT Error resizeImage(CString inImageFilename, U32 outWidth, U32 outHeight, CString tempDirectory,
+										 GenericMemoryPoolAllocator<U8> alloc, StringAuto& tmpFilename)
+{
+	U32 inWidth, inHeight, channelCount;
+	Bool hdr;
+	ANKI_CHECK(identifyImage(inImageFilename, inWidth, inHeight, channelCount, hdr));
+
+	// Load
+	void* inPixels;
+	if(!hdr)
+	{
+		I32 width, height;
+		inPixels = stbi_load(inImageFilename.cstr(), &width, &height, nullptr, channelCount);
+	}
+	else
+	{
+		I32 width, height;
+		inPixels = stbi_loadf(inImageFilename.cstr(), &width, &height, nullptr, channelCount);
+	}
+
+	if(!inPixels)
+	{
+		ANKI_IMPORTER_LOGE("STB load failed: %s", inImageFilename.cstr());
+		return Error::FUNCTION_FAILED;
+	}
+
+	// Resize
+	I ok;
+	DynamicArrayAuto<U8> outPixels(alloc);
+	if(!hdr)
+	{
+		outPixels.resize(outWidth * outHeight * channelCount);
+		ok = stbir_resize_uint8(static_cast<const U8*>(inPixels), inWidth, inHeight, 0, outPixels.getBegin(), outWidth,
+								outHeight, 0, channelCount);
+	}
+	else
+	{
+		outPixels.resize(outWidth * outHeight * channelCount * sizeof(F32));
+		ok = stbir_resize_float(static_cast<const F32*>(inPixels), inWidth, inHeight, 0,
+								reinterpret_cast<F32*>(outPixels.getBegin()), outWidth, outHeight, 0, channelCount);
+	}
+
+	stbi_image_free(inPixels);
+
+	if(!ok)
+	{
+		ANKI_IMPORTER_LOGE("stbir_resize_xxx() failed to resize the image: %s", inImageFilename.cstr());
+		return Error::FUNCTION_FAILED;
+	}
+
+	// Store
+	tmpFilename.sprintf("%s/AnKiImageImporter_%u.%s", tempDirectory.cstr(), U32(std::rand()), (hdr) ? "exr" : "png");
+	ANKI_IMPORTER_LOGV("Will store: %s", tmpFilename.cstr());
+
+	if(!hdr)
+	{
+		ok = stbi_write_png(tmpFilename.cstr(), outWidth, outHeight, channelCount, outPixels.getBegin(), 0);
+	}
+	else
+	{
+		const I ret = SaveEXR(reinterpret_cast<const F32*>(outPixels.getBegin()), outWidth, outHeight, channelCount, 0,
+							  tmpFilename.cstr(), nullptr);
+		ok = ret >= 0;
+	}
+
+	if(!ok)
+	{
+		ANKI_IMPORTER_LOGE("Failed to create: %s", tmpFilename.cstr());
+		return Error::FUNCTION_FAILED;
+	}
+
+	return Error::NONE;
+}
+
 static Vec3 linearToSRgb(Vec3 p)
 {
 	Vec3 cutoff;
@@ -362,18 +439,17 @@ static ANKI_USE_RESULT Error loadFirstMipmap(const ImageImporterConfig& config,
 
 	for(U32 i = 0; i < config.m_inputFilenames.getSize(); ++i)
 	{
-		I32 width, height, c;
-		stbi_set_flip_vertically_on_load_thread(true);
+		I32 width, height;
+		stbi_set_flip_vertically_on_load_thread(config.m_flipImage);
 		void* data;
 		if(!ctx.m_hdr)
 		{
-			data = stbi_load(config.m_inputFilenames[i].cstr(), &width, &height, &c, ctx.m_channelCount);
+			data = stbi_load(config.m_inputFilenames[i].cstr(), &width, &height, nullptr, ctx.m_channelCount);
 		}
 		else
 		{
-			data = stbi_loadf(config.m_inputFilenames[i].cstr(), &width, &height, &c, ctx.m_channelCount);
+			data = stbi_loadf(config.m_inputFilenames[i].cstr(), &width, &height, nullptr, ctx.m_channelCount);
 		}
-		ANKI_ASSERT(U32(c) == ctx.m_channelCount);
 
 		if(!data)
 		{
@@ -836,16 +912,55 @@ static ANKI_USE_RESULT Error storeAnkiImage(const ImageImporterConfig& config, c
 	return Error::NONE;
 }
 
-static ANKI_USE_RESULT Error importImageInternal(const ImageImporterConfig& config)
+static ANKI_USE_RESULT Error importImageInternal(const ImageImporterConfig& configOriginal)
 {
+	GenericMemoryPoolAllocator<U8> alloc = configOriginal.m_allocator;
+	ImageImporterConfig config = configOriginal;
+
+	config.m_minMipmapDimension = max(config.m_minMipmapDimension, 4u);
+	if(!!(config.m_compressions & ImageBinaryDataCompression::ASTC))
+	{
+		config.m_minMipmapDimension = max(config.m_minMipmapDimension, config.m_astcBlockSize.x());
+		config.m_minMipmapDimension = max(config.m_minMipmapDimension, config.m_astcBlockSize.y());
+	}
+
 	// Checks
 	ANKI_CHECK(checkConfig(config));
 	U32 width, height, channelCount;
 	Bool isHdr;
 	ANKI_CHECK(checkInputImages(config, width, height, channelCount, isHdr));
 
+	// Resize
+	DynamicArrayAuto<StringAuto> newFilenames(alloc);
+	DynamicArrayAuto<CString> newFilenamesCString(alloc);
+	DynamicArrayAuto<CleanupFile> resizedImagesCleanup(alloc);
+	if(width < config.m_minMipmapDimension || height < config.m_minMipmapDimension)
+	{
+		const U32 newWidth = max(width, config.m_minMipmapDimension);
+		const U32 newHeight = max(height, config.m_minMipmapDimension);
+
+		ANKI_IMPORTER_LOGV("Image is smaller than the min mipmap dimension. Will resize it to %ux%u", newWidth,
+						   newHeight);
+
+		newFilenames.resize(config.m_inputFilenames.getSize(), StringAuto(alloc));
+		newFilenamesCString.resize(config.m_inputFilenames.getSize());
+
+		for(U32 i = 0; i < config.m_inputFilenames.getSize(); ++i)
+		{
+			ANKI_CHECK(resizeImage(config.m_inputFilenames[i], newWidth, newHeight, config.m_tempDirectory, alloc,
+								   newFilenames[i]));
+
+			newFilenamesCString[i] = newFilenames[i];
+			resizedImagesCleanup.emplaceBack(alloc, newFilenames[i]);
+		}
+
+		// Override config
+		config.m_inputFilenames = newFilenamesCString;
+		width = newWidth;
+		height = newHeight;
+	}
+
 	// Init image
-	GenericMemoryPoolAllocator<U8> alloc = config.m_allocator;
 	ImageImporterContext ctx(alloc);
 
 	ctx.m_width = width;
@@ -899,17 +1014,10 @@ static ANKI_USE_RESULT Error importImageInternal(const ImageImporterConfig& conf
 	ANKI_CHECK(loadFirstMipmap(config, ctx));
 
 	// Generate mipmaps
-	U32 minMipDimension = max(config.m_minMipmapDimension, 4u);
-	if(!!(config.m_compressions & ImageBinaryDataCompression::ASTC))
-	{
-		minMipDimension = max(minMipDimension, config.m_astcBlockSize.x());
-		minMipDimension = max(minMipDimension, config.m_astcBlockSize.y());
-	}
-
 	const U32 mipCount =
 		min(config.m_mipmapCount, (config.m_type == ImageBinaryType::_3D)
-									  ? computeMaxMipmapCount3d(width, height, ctx.m_depth, minMipDimension)
-									  : computeMaxMipmapCount2d(width, height, minMipDimension));
+									  ? computeMaxMipmapCount3d(width, height, ctx.m_depth, config.m_minMipmapDimension)
+									  : computeMaxMipmapCount2d(width, height, config.m_minMipmapDimension));
 	for(U32 mip = 1; mip < mipCount; ++mip)
 	{
 		ctx.m_mipmaps.emplaceBack(alloc);

+ 1 - 0
AnKi/Importer/ImageImporter.h

@@ -32,6 +32,7 @@ public:
 	UVec2 m_astcBlockSize = UVec2(8u);
 	Bool m_sRgbToLinear = false;
 	Bool m_linearToSRgb = false;
+	Bool m_flipImage = true;
 };
 
 /// Converts images to AnKi's specific format.

+ 1 - 1
AnKi/Resource/MeshBinaryLoader.cpp

@@ -46,7 +46,7 @@ Error MeshBinaryLoader::load(const ResourceFilename& filename)
 
 			for(U d = 0; d < 3; ++d)
 			{
-				if(sm.m_aabbMin[i] >= sm.m_aabbMax[i])
+				if(sm.m_aabbMin[d] >= sm.m_aabbMax[d])
 				{
 					ANKI_RESOURCE_LOGE("Wrong bounding box");
 					return Error::USER_DATA;

+ 1 - 0
AnKi/Resource/Stb.cpp

@@ -7,6 +7,7 @@
 
 #define STB_IMAGE_IMPLEMENTATION
 #define STB_IMAGE_WRITE_IMPLEMENTATION
+#define STB_IMAGE_RESIZE_IMPLEMENTATION
 
 #if ANKI_COMPILER_GCC_COMPATIBLE
 #	pragma GCC diagnostic push

+ 1 - 0
AnKi/Resource/Stb.h

@@ -9,3 +9,4 @@
 #define STBI_NO_FAILURE_STRINGS 1 // No need
 #include <Stb/stb_image.h>
 #include <Stb/stb_image_write.h>
+#include <Stb/stb_image_resize.h>

+ 1 - 1
Tools/GltfImporter/Main.cpp

@@ -173,7 +173,7 @@ static Error parseCommandLineArgs(int argc, char** argv, CmdLineArgs& info)
 
 	if(!texrpathFound)
 	{
-		info.m_texRpath = info.m_outDir;
+		info.m_texRpath = info.m_rpath;
 	}
 
 	return Error::NONE;

+ 22 - 0
Tools/Image/ImageImporterMain.cpp

@@ -20,6 +20,7 @@ Options:
 -verbose               : Verbose log
 -to-linear             : Convert sRGB to linear
 -to-srgb               : Convert linear to sRGB
+-flip-image <0|1>      : Flip the image. Default is 1
 )";
 
 static Error parseCommandLineArgs(int argc, char** argv, ImageImporterConfig& config,
@@ -176,6 +177,27 @@ static Error parseCommandLineArgs(int argc, char** argv, ImageImporterConfig& co
 		{
 			config.m_linearToSRgb = true;
 		}
+		else if(CString(argv[i]) == "-flip-image")
+		{
+			++i;
+			if(i >= argc)
+			{
+				return Error::USER_DATA;
+			}
+
+			if(CString(argv[i]) == "1")
+			{
+				config.m_flipImage = true;
+			}
+			else if(CString(argv[i]) == "0")
+			{
+				config.m_flipImage = false;
+			}
+			else
+			{
+				return Error::USER_DATA;
+			}
+		}
 		else
 		{
 			filenames.emplaceBack(filenames.getAllocator(), argv[i]);