Ver Fonte

Loads an IES Photometric profile.

ADDED: Ability to add IES profile as the cookie texture slot in both point lights and spot lights

TODO:
Have the IES Profile also drive the settings for the lights.
Make it work with Cookie textures. IES profiles are to be another slot in the advanced light section.
marauder2k7 há 1 ano atrás
pai
commit
a12d915180

+ 1 - 1
Engine/source/CMakeLists.txt

@@ -79,7 +79,7 @@ if(TORQUE_SFX_OPENAL AND NOT TORQUE_DEDICATED)
     endif()
 endif()
 # Handle GFX
-torqueAddSourceDirectories("gfx" "gfx/Null" "gfx/test" "gfx/bitmap" "gfx/bitmap/loaders"
+torqueAddSourceDirectories("gfx" "gfx/Null" "gfx/test" "gfx/bitmap" "gfx/bitmap/loaders" "gfx/bitmap/loaders/ies"
                              "gfx/util" "gfx/video" "gfx/sim" )
 
 # add the stb headers

+ 104 - 4
Engine/source/gfx/bitmap/loaders/bitmapSTB.cpp

@@ -28,6 +28,7 @@
 #include "core/strings/stringFunctions.h"
 #include "gfx/bitmap/gBitmap.h"
 #include "gfx/bitmap/imageUtils.h"
+#include "gfx/bitmap/loaders/ies/ies_loader.h"
 
 #ifdef __clang__
 #define STBIWDEF static inline
@@ -69,6 +70,7 @@ static struct _privateRegisterSTB
       reg.extensions.push_back("psd");
       reg.extensions.push_back("hdr");
       reg.extensions.push_back("tga");
+      reg.extensions.push_back("ies");
 
       reg.readFunc = sReadSTB;
       reg.readStreamFunc = sReadStreamSTB;
@@ -93,6 +95,103 @@ bool sReadSTB(const Torque::Path& path, GBitmap* bitmap)
 
    U32 prevWaterMark = FrameAllocator::getWaterMark();
 
+   // if this is an ies profile we need to create a texture for it.
+   if (ext.equal("ies"))
+   {
+      String textureName = path.getFullPath();
+      textureName.replace(".ies", ".png");
+      x = 256;
+      y = 1;
+      n = 4;
+      channels = 4;
+      GFXFormat format = GFXFormatR8G8B8A8;
+
+      if (Torque::FS::IsFile(textureName.c_str()))
+      {
+         // if the txture already exist, load it.
+         unsigned char* data = stbi_load(textureName.c_str(), &x, &y, &n, channels);
+
+         // actually allocate the bitmap space...
+         bitmap->allocateBitmap(x, y,
+            false,            // don't extrude miplevels...
+            format);          // use determined format...
+
+         U8* pBase = (U8*)bitmap->getBits();
+
+         U32 rowBytes = bitmap->getByteSize();
+
+         dMemcpy(pBase, data, rowBytes);
+
+         stbi_image_free(data);
+
+         FrameAllocator::setWaterMark(prevWaterMark);
+
+         return true;
+      }
+      else
+      {
+         FileStream* readIes = new FileStream;
+
+         if (!readIes->open(path.getFullPath(), Torque::FS::File::Read))
+         {
+            Con::printf("Failed to open IES profile:%s", path.getFullFileName().c_str());
+            return false;
+         }
+
+         if (readIes->getStatus() != Stream::Ok)
+         {
+            Con::printf("Failed to open IES profile:%s", path.getFullFileName().c_str());
+            return false;
+         }
+
+         U32 buffSize = readIes->getStreamSize();
+         char* buffer = new char[buffSize];
+         readIes->read(buffSize, buffer);
+         
+
+         IESFileInfo info;
+         IESLoadHelper IESLoader;
+
+         if (!IESLoader.load(buffer, buffSize, info))
+         {
+            Con::printf("Failed to load IES profile:%s \n LoaderError: %s", path.getFullFileName().c_str(), info.error().c_str());
+            return false;
+         }
+
+         float* data = new float[x*y*channels];
+
+         if (!IESLoader.saveAs1D(info, data, x, channels))
+         {
+            Con::printf("Failed to create 2d Texture for IES profile:%s", path.getFullFileName().c_str());
+            return false;
+         }
+
+         // use stb function to convert float data to uchar
+         unsigned char* dataChar = stbi__hdr_to_ldr(data, x, y, channels);
+
+         bitmap->deleteImage();
+         // actually allocate the bitmap space...
+         bitmap->allocateBitmap(x, y,
+            false,
+            format);
+
+         U8* pBase = (U8*)bitmap->getBits();
+
+         U32 rowBytes = x * y * channels;
+
+         dMemcpy(pBase, dataChar, rowBytes);
+
+         stbi_image_free(dataChar);
+
+         FrameAllocator::setWaterMark(prevWaterMark);
+
+         sWriteSTB(textureName, bitmap, 10);
+
+         return true;
+      }
+
+   }
+
    if (!stbi_info(path.getFullPath().c_str(), &x, &y, &channels))
    {
       FrameAllocator::setWaterMark(prevWaterMark);
@@ -142,21 +241,22 @@ bool sReadSTB(const Torque::Path& path, GBitmap* bitmap)
    if (ext.equal("hdr"))
    {
       // force load to 4 channel.
-      float* data = stbi_loadf(path.getFullPath().c_str(), &x, &y, &n, 4);
+      float* data = stbi_loadf(path.getFullPath().c_str(), &x, &y, &n, 0);
 
-      unsigned char* dataChar = stbi__hdr_to_ldr(data, x, y, 4);
+      unsigned char* dataChar = stbi__hdr_to_ldr(data, x, y, n);
       bitmap->deleteImage();
       // actually allocate the bitmap space...
       bitmap->allocateBitmap(x, y,
          false,
-         GFXFormatR8G8B8A8);
+         GFXFormatR8G8B8);
 
       U8* pBase = (U8*)bitmap->getBits();
 
-      U32 rowBytes = x * y * 4;
+      U32 rowBytes = x * y * n;
 
       dMemcpy(pBase, dataChar, rowBytes);
 
+      //stbi_image_free(data);
       stbi_image_free(dataChar);
 
       FrameAllocator::setWaterMark(prevWaterMark);

+ 579 - 0
Engine/source/gfx/bitmap/loaders/ies/ies_loader.cpp

@@ -0,0 +1,579 @@
+// +----------------------------------------------------------------------
+// | Project : ray.
+// | All rights reserved.
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017.
+// +----------------------------------------------------------------------
+// | * Redistribution and use of this software in source and binary forms,
+// |   with or without modification, are permitted provided that the following
+// |   conditions are met:
+// |
+// | * Redistributions of source code must retain the above
+// |   copyright notice, this list of conditions and the
+// |   following disclaimer.
+// |
+// | * Redistributions in binary form must reproduce the above
+// |   copyright notice, this list of conditions and the
+// |   following disclaimer in the documentation and/or other
+// |   materials provided with the distribution.
+// |
+// | * Neither the name of the ray team, nor the names of its
+// |   contributors may be used to endorse or promote products
+// |   derived from this software without specific prior
+// |   written permission of the ray team.
+// |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// +----------------------------------------------------------------------
+#include "ies_loader.h"
+#include <assert.h>
+#include <algorithm>
+#include <functional>
+
+IESFileInfo::IESFileInfo()
+	: _cachedIntegral(std::numeric_limits<float>::max())
+	, _error("No data loaded")
+{
+}
+
+bool
+IESFileInfo::valid() const
+{
+	return _error.empty();
+}
+
+const std::string&
+IESFileInfo::error() const
+{
+	return _error;
+}
+
+IESLoadHelper::IESLoadHelper()
+{
+}
+
+IESLoadHelper::~IESLoadHelper()
+{
+}
+
+bool
+IESLoadHelper::load(const char* data, std::size_t dataLength, IESFileInfo& info)
+{
+	assert(!info.valid());
+	return this->load(std::string(data, dataLength), info);
+}
+
+bool
+IESLoadHelper::load(const std::string& data, IESFileInfo& info)
+{
+	assert(!info.valid());
+
+	std::string dataPos;
+
+	std::string version;
+	this->getLineContent(data, dataPos, version, false, false);
+
+	if (version.empty())
+	{
+		info._error = "Unknown IES version";
+		return false;
+	}
+	else if (version == "IESNA:LM-63-1995")
+		info._version = "IESNA:LM-63-1995";
+	else if (version == "IESNA91")
+		info._version = "IESNA91";
+	else if (version == "IESNA:LM-63-2002")
+		info._version = "IESNA:LM-63-2002";
+	else
+		info._version = version;
+
+	while (!dataPos.empty())
+	{
+		std::string line;
+		this->getLineContent(dataPos, dataPos, line, false, false);
+
+		if (line.compare(0, 9, "TILT=NONE", 9) == 0 ||
+			line.compare(0, 10, "TILT= NONE", 10) == 0 ||
+			line.compare(0, 10, "TILT =NONE", 10) == 0 ||
+			line.compare(0, 11, "TILT = NONE", 11) == 0)
+		{
+			break;
+		}
+		else if (line.compare(0, 5, "TILT=", 5) == 0 ||
+			line.compare(0, 5, "TILT =", 5) == 0)
+		{
+			info._error = "Not supported yet.";
+			return false;
+		}
+	}
+
+	this->getFloat(dataPos, dataPos, info.totalLights);
+	if (info.totalLights < 0 || info.totalLights > std::numeric_limits<short>::max())
+	{
+		info._error = "Light Count is not valid";
+		return false;
+	}
+
+	this->getFloat(dataPos, dataPos, info.totalLumens);
+	if (info.totalLumens < 0)
+	{
+		info._error = "TotalLumens is not positive number";
+		return false;
+	}
+
+	this->getFloat(dataPos, dataPos, info.candalaMult);
+	if (info.candalaMult < 0)
+	{
+		info._error = "CandalaMult is not positive number";
+		return false;
+	}
+
+	this->getInt(dataPos, dataPos, info.anglesNumV);
+	if (info.anglesNumV < 0 || info.anglesNumV > std::numeric_limits<short>::max())
+	{
+		info._error = "VAnglesNum is not valid";
+		return false;
+	}
+
+	this->getInt(dataPos, dataPos, info.anglesNumH);
+	if (info.anglesNumH < 0 || info.anglesNumH > std::numeric_limits<short>::max())
+	{
+		info._error = "HAnglesNum is not valid";
+		return false;
+	}
+
+	this->getInt(dataPos, dataPos, info.typeOfPhotometric);
+	this->getInt(dataPos, dataPos, info.typeOfUnit);
+
+	this->getFloat(dataPos, dataPos, info.width);
+	this->getFloat(dataPos, dataPos, info.length);
+	this->getFloat(dataPos, dataPos, info.height);
+
+	this->getFloat(dataPos, dataPos, info.ballastFactor);
+	this->getFloat(dataPos, dataPos, info.futureUse);
+	this->getFloat(dataPos, dataPos, info.inputWatts);
+
+	float minSoFarV = std::numeric_limits<float>::lowest();
+	float minSoFarH = std::numeric_limits<float>::lowest();
+
+	info._anglesV.reserve(info.anglesNumV);
+	info._anglesH.reserve(info.anglesNumH);
+
+	for (std::int32_t y = 0; y < info.anglesNumV; ++y)
+	{
+		float value;
+		this->getFloat(dataPos, dataPos, value, true, true);
+
+		if (value < minSoFarV)
+		{
+			info._error = "V Values is not valid";
+			return false;
+		}
+
+		minSoFarV = value;
+		info._anglesV.push_back(value);
+	}
+
+	for (std::int32_t x = 0; x < info.anglesNumH; ++x)
+	{
+		float value;
+		this->getFloat(dataPos, dataPos, value, true, true);
+
+		if (value < minSoFarH)
+		{
+			info._error = "H Values is not valid";
+			return false;
+		}
+
+		minSoFarH = value;
+		info._anglesH.push_back(value);
+	}
+
+	info._candalaValues.reserve(info.anglesNumH * info.anglesNumV);
+
+	for (std::int32_t y = 0; y < info.anglesNumH; ++y)
+	{
+		for (std::int32_t x = 0; x < info.anglesNumV; ++x)
+		{
+			float value;
+			this->getFloat(dataPos, dataPos, value, true, true);
+			info._candalaValues.push_back(value * info.candalaMult);
+		}
+	}
+
+	skipSpaceAndLineEnd(dataPos, dataPos);
+
+	if (!dataPos.empty())
+	{
+		std::string line;
+		this->getLineContent(dataPos, dataPos, line, true, false);
+
+		if (line == "END")
+			skipSpaceAndLineEnd(dataPos, dataPos);
+
+		if (!dataPos.empty())
+		{
+			info._error = "Unexpected content after END.";
+			return false;
+		}
+	}
+
+	info._error.clear();
+
+	return true;
+}
+
+bool
+IESLoadHelper::saveAs1D(const IESFileInfo& info, float* data, std::uint32_t width, std::uint8_t channel) noexcept
+{
+	assert(data);
+	assert(width > 0);
+	assert(channel == 1 || channel == 3 || channel == 4);
+	assert(info.valid());
+
+	float invW = 1.0f / width;
+	float invMaxValue = this->computeInvMax(info._candalaValues);
+
+	for (std::uint32_t x = 0; x < width; ++x)
+	{
+		float fraction = x * invW;
+		float value = invMaxValue * interpolate1D(info, fraction * 180.0f);
+
+		switch (channel)
+		{
+		case 1:
+			*data++ = value;
+			break;
+		case 3:
+			*data++ = value;
+			*data++ = value;
+			*data++ = value;
+			break;
+		case 4:
+			*data++ = value;
+			*data++ = value;
+			*data++ = value;
+			*data++ = 1.0f;
+			break;
+		default:
+			return false;
+		}
+	}
+
+	return true;
+}
+
+bool
+IESLoadHelper::saveAs2D(const IESFileInfo& info, float* data, std::uint32_t width, std::uint32_t height, std::uint8_t channel) noexcept
+{
+	assert(data);
+	assert(width > 0 && height > 0);
+	assert(channel == 1 || channel == 3 || channel == 4);
+	assert(info.valid());
+
+	float invW = 1.0f / width;
+	float invH = 1.0f / height;
+	float invMaxValue = this->computeInvMax(info._candalaValues);
+
+	for (std::uint32_t y = 0; y < height; ++y)
+	{
+		for (std::uint32_t x = 0; x < width; ++x)
+		{
+			float fractionV = x * invW * 180.0f;
+			float fractionH = y * invH * 180.0f;
+			float value = invMaxValue * interpolate2D(info, fractionV, fractionH);
+
+			switch (channel)
+			{
+			case 1:
+				*data++ = value;
+				break;
+			case 3:
+				*data++ = value;
+				*data++ = value;
+				*data++ = value;
+				break;
+			case 4:
+				*data++ = value;
+				*data++ = value;
+				*data++ = value;
+				*data++ = 1.0;
+				break;
+			default:
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+bool
+IESLoadHelper::saveAsPreview(const IESFileInfo& info, std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint8_t channel) noexcept
+{
+	assert(data);
+	assert(width > 0 && height > 0);
+	assert(channel == 1 || channel == 3 || channel == 4);
+	assert(info.valid());
+
+	std::vector<float> ies(256);
+	if (!this->saveAs1D(info, ies.data(), ies.size(), 1))
+		return false;
+
+	float maxValue = this->computeInvMax(info._candalaValues);
+
+	auto TonemapHable = [](float x)
+	{
+		const float A = 0.22f;
+		const float B = 0.30f;
+		const float C = 0.10f;
+		const float D = 0.20f;
+		const float E = 0.01f;
+		const float F = 0.30f;
+		return ((x*(A*x + C*B) + D*E) / (x*(A*x + B) + D*F)) - E / F;
+	};
+
+	for (int y = 0; y < height; y++)
+	{
+		for (int x = 0; x < width; x++)
+		{
+			float u = ((float)x / width) * 2.0f - 1.0f;
+			float v = 1.0f - ((float)y / height) * 2.0f - 1.0f;
+
+			u *= 2.2f;
+			v *= 2.4f;
+
+			// float3(0.0f, 0.0f, -0.5f) - ray::float3(u, v, 0.0f)
+			float lx = +0.0f - u;
+			float ly = +0.0f - v;
+			float lz = -0.5f - 0.0f;
+
+			// normalize
+			float length = std::sqrt(lx * lx + ly * ly + lz * lz);
+			lx /= length;
+			ly /= length;
+			lz /= length;
+
+			float angle = 1.0 - std::acos(lx * 0.0 + ly * -1.0 + lz * 0.0f) / 3.141592654;
+
+			float intensity = ies[angle * 255] * maxValue / length;
+
+			std::uint8_t value = std::min(std::max((int)std::floor(TonemapHable(intensity) / TonemapHable(maxValue) * 255.0f), 0), 255);
+
+			switch (channel)
+			{
+			case 1:
+				*data++ = value;
+				break;
+			case 3:
+				*data++ = value;
+				*data++ = value;
+				*data++ = value;
+				break;
+			case 4:
+				*data++ = value;
+				*data++ = value;
+				*data++ = value;
+				*data++ = 1.0;
+				break;
+			default:
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+float
+IESLoadHelper::computeInvMax(const std::vector<float>& candalaValues) const
+{
+	assert(candalaValues.size());
+
+	float candala = *std::max_element(candalaValues.begin(), candalaValues.end());
+	return 1.0f / candala;
+}
+
+float
+IESLoadHelper::computeFilterPos(float value, const std::vector<float>& angles) const
+{
+	assert(angles.size());
+
+	std::size_t start = 0;
+	std::size_t end = angles.size() - 1;
+
+	if (value < angles[start]) return 0.0f;
+	if (value > angles[end]) return (float)end;
+
+	while (start < end)
+	{
+		std::size_t index = (start + end + 1) / 2;
+
+		float angle = angles[index];
+		if (value >= angle)
+		{
+			assert(start != index);
+			start = index;
+		}
+		else
+		{
+			assert(end != index - 1);
+			end = index - 1;
+		}
+	}
+
+	float leftValue = angles[start];
+	float fraction = 0.0f;
+
+	if (start + 1 < (std::uint32_t)angles.size())
+	{
+		float rightValue = angles[start + 1];
+		float deltaValue = rightValue - leftValue;
+
+		if (deltaValue > 0.0001f)
+		{
+			fraction = (value - leftValue) / deltaValue;
+		}
+	}
+
+	return start + fraction;
+}
+
+float
+IESLoadHelper::interpolate1D(const IESFileInfo& info, float angle) const
+{
+	float angleV = this->computeFilterPos(angle, info._anglesV);
+	float anglesNum = (float)info._anglesH.size();
+	float angleTotal = 0.0f;
+
+	for (float x = 0; x < anglesNum; x++)
+		angleTotal += this->interpolateBilinear(info, x, angleV);
+
+	return angleTotal / anglesNum;
+}
+
+float
+IESLoadHelper::interpolate2D(const IESFileInfo& info, float angleV, float angleH) const
+{
+	float u = this->computeFilterPos(angleH, info._anglesH);
+	float v = this->computeFilterPos(angleV, info._anglesV);
+	return this->interpolateBilinear(info, u, v);
+}
+
+float
+IESLoadHelper::interpolatePoint(const IESFileInfo& info, std::uint32_t x, std::uint32_t y) const
+{
+	assert(x >= 0);
+	assert(y >= 0);
+
+	std::size_t anglesNumH = info._anglesH.size();
+	std::size_t anglesNumV = info._anglesV.size();
+
+	x %= anglesNumH;
+	y %= anglesNumV;
+
+	assert(x < anglesNumH);
+	assert(y < anglesNumV);
+
+	return info._candalaValues[y + anglesNumV * x];
+}
+
+float
+IESLoadHelper::interpolateBilinear(const IESFileInfo& info, float x, float y) const
+{
+	int ix = (int)std::floor(x);
+	int iy = (int)std::floor(y);
+
+	float fracX = x - ix;
+	float fracY = y - iy;
+
+	float p00 = this->interpolatePoint(info, ix + 0, iy + 0);
+	float p10 = this->interpolatePoint(info, ix + 1, iy + 0);
+	float p01 = this->interpolatePoint(info, ix + 0, iy + 1);
+	float p11 = this->interpolatePoint(info, ix + 1, iy + 1);
+
+	auto lerp = [](float t1, float t2, float t3) -> float { return t1 + (t2 - t1) * t3; };
+
+	float p0 = lerp(p00, p01, fracY);
+	float p1 = lerp(p10, p11, fracY);
+
+	return lerp(p0, p1, fracX);
+}
+
+void
+IESLoadHelper::skipSpaceAndLineEnd(const std::string& data, std::string& out, bool stopOnComma)
+{
+	std::size_t dataBegin = 0;
+	std::size_t dataEnd = data.size();
+
+	while (dataBegin < dataEnd)
+	{
+		if (data[dataBegin] != '\r' && data[dataBegin] != '\n' && data[dataBegin] > ' ')
+			break;
+		dataBegin++;
+	}
+
+	if (stopOnComma)
+	{
+		while (dataBegin < dataEnd)
+		{
+			if (data[dataBegin] != ',')
+				break;
+			dataBegin++;
+		}
+	}
+
+	out = data.substr(dataBegin, data.size() - dataBegin);
+}
+
+void
+IESLoadHelper::getLineContent(const std::string& data, std::string& next, std::string& line, bool stopOnWhiteSpace, bool stopOnComma)
+{
+	skipSpaceAndLineEnd(data, next);
+
+	auto it = data.begin();
+	auto end = data.end();
+
+	for (; it < end; ++it)
+	{
+		if ((*it == '\r') ||
+			(*it == '\n') ||
+			(*it <= ' ' && stopOnWhiteSpace) ||
+			(*it == ',' && stopOnComma))
+		{
+			break;
+		}
+	}
+
+	line.assign(data, 0, it - data.begin());
+	next.assign(data, it - data.begin(), end - it);
+
+	skipSpaceAndLineEnd(next, next, stopOnComma);
+}
+
+void
+IESLoadHelper::getFloat(const std::string& data, std::string& next, float& ret, bool stopOnWhiteSpace, bool stopOnComma)
+{
+	std::string line;
+	getLineContent(data, next, line, stopOnWhiteSpace, stopOnComma);
+	assert(!line.empty());
+	ret = (float)std::atof(line.c_str());
+}
+
+void
+IESLoadHelper::getInt(const std::string& data, std::string& next, std::int32_t& ret, bool stopOnWhiteSpace, bool stopOnComma)
+{
+	std::string line;
+	getLineContent(data, next, line, stopOnWhiteSpace, stopOnComma);
+	assert(!line.empty());
+	ret = std::atoi(line.c_str());
+}

+ 116 - 0
Engine/source/gfx/bitmap/loaders/ies/ies_loader.h

@@ -0,0 +1,116 @@
+// +----------------------------------------------------------------------
+// | Project : ray.
+// | All rights reserved.
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017.
+// +----------------------------------------------------------------------
+// | * Redistribution and use of this software in source and binary forms,
+// |   with or without modification, are permitted provided that the following
+// |   conditions are met:
+// |
+// | * Redistributions of source code must retain the above
+// |   copyright notice, this list of conditions and the
+// |   following disclaimer.
+// |
+// | * Redistributions in binary form must reproduce the above
+// |   copyright notice, this list of conditions and the
+// |   following disclaimer in the documentation and/or other
+// |   materials provided with the distribution.
+// |
+// | * Neither the name of the ray team, nor the names of its
+// |   contributors may be used to endorse or promote products
+// |   derived from this software without specific prior
+// |   written permission of the ray team.
+// |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// +----------------------------------------------------------------------
+#ifndef _H_IES_LOADER_H_
+#define _H_IES_LOADER_H_
+
+#include <vector>
+#include <string>
+
+// https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2016/ENU/3DSMax/files/GUID-EA0E3DE0-275C-42F7-83EC-429A37B2D501-htm.html
+class IESFileInfo
+{
+public:
+	IESFileInfo();
+
+	bool valid() const;
+
+	const std::string& error() const;
+
+public:
+	float totalLights;
+	float totalLumens;
+
+	float candalaMult;
+
+	std::int32_t typeOfPhotometric;
+	std::int32_t typeOfUnit;
+
+	std::int32_t anglesNumH;
+	std::int32_t anglesNumV;
+
+	float width;
+	float length;
+	float height;
+
+	float ballastFactor;
+	float futureUse;
+	float inputWatts;
+
+private:
+	friend class IESLoadHelper;
+
+	float _cachedIntegral;
+
+	std::string _error;
+	std::string _version;
+
+	std::vector<float> _anglesH;
+	std::vector<float> _anglesV;
+	std::vector<float> _candalaValues;
+};
+
+class IESLoadHelper final
+{
+public:
+	IESLoadHelper();
+	~IESLoadHelper();
+
+	bool load(const std::string& data, IESFileInfo& info);
+	bool load(const char* data, std::size_t dataLength, IESFileInfo& info);
+
+	bool saveAs1D(const IESFileInfo& info, float* data, std::uint32_t width = 256, std::uint8_t channel = 3) noexcept;
+	bool saveAs2D(const IESFileInfo& info, float* data, std::uint32_t width = 256, std::uint32_t height = 256, std::uint8_t channel = 3) noexcept;
+	bool saveAsPreview(const IESFileInfo& info, std::uint8_t* data, std::uint32_t width = 64, std::uint32_t height = 64, std::uint8_t channel = 3) noexcept;
+
+private:
+	float computeInvMax(const std::vector<float>& candalaValues) const;
+	float computeFilterPos(float value, const std::vector<float>& angle) const;
+
+	float interpolate1D(const IESFileInfo& info, float angle) const;
+	float interpolate2D(const IESFileInfo& info, float angleV, float angleH) const;
+	float interpolatePoint(const IESFileInfo& info, std::uint32_t x, std::uint32_t y) const;
+	float interpolateBilinear(const IESFileInfo& info, float x, float y) const;
+
+private:
+	static void skipSpaceAndLineEnd(const std::string& data, std::string& out, bool stopOnComma = false);
+
+	static void getLineContent(const std::string& data, std::string& next, std::string& line, bool stopOnWhiteSpace, bool stopOnComma);
+	static void getFloat(const std::string& data, std::string& next, float& ret, bool stopOnWhiteSpace = true, bool stopOnComma = false);
+	static void getInt(const std::string& data, std::string& next, std::int32_t& ret, bool stopOnWhiteSpace = true, bool stopOnComma = false);
+};
+
+#endif

+ 1 - 0
Engine/source/lighting/advanced/advancedLightBinManager.cpp

@@ -830,6 +830,7 @@ void AdvancedLightBinManager::LightMaterialInfo::setLightParameters( const Light
          const F32 radius = lightInfo->getRange().x;
          const F32 invSqrRadius = 1.0f / (radius * radius);
          matParams->setSafe( lightRange, radius);
+         matParams->setSafe( lightDirection, -lightInfo->getTransform().getUpVector());
          matParams->setSafe( lightInvSqrRange, invSqrRadius);  
          luxTargMultiplier =radius;
       }

+ 22 - 22
Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/pointLightP.hlsl

@@ -109,7 +109,7 @@ TORQUE_UNIFORM_SAMPLER2D(colorBuffer, 3);
 TORQUE_UNIFORM_SAMPLER2D(matInfoBuffer, 4);
 #ifdef USE_COOKIE_TEX
 /// The texture for cookie rendering.
-TORQUE_UNIFORM_SAMPLERCUBE(cookieMap, 5);
+TORQUE_UNIFORM_SAMPLER2D(cookieMap, 5);
 #endif
 
 uniform float4 rtParams0;
@@ -117,6 +117,7 @@ uniform float4 lightColor;
 
 uniform float  lightBrightness;
 uniform float3 lightPosition;
+uniform float3 lightDirection;
 
 uniform float4 lightMapParams;
 uniform float4 vsFarPlane;
@@ -163,31 +164,20 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
    #ifndef NO_SHADOW
    if (getFlag(surface.matFlag, 0)) //also skip if we don't recieve shadows
    {
-   #ifdef SHADOW_CUBE
+      #ifdef SHADOW_CUBE
 
-      // TODO: We need to fix shadow cube to handle soft shadows!
-      float occ = TORQUE_TEXCUBE( shadowMap, mul( worldToLightProj, -surfaceToLight.L ) ).r;
-      shadow = saturate( exp( lightParams.y * ( occ - distToLight ) ) );
+         // TODO: We need to fix shadow cube to handle soft shadows!
+         float occ = TORQUE_TEXCUBE( shadowMap, mul( worldToLightProj, -surfaceToLight.L ) ).r;
+         shadow = saturate( exp( lightParams.y * ( occ - distToLight ) ) );
 
-   #else
-      float2 shadowCoord = decodeShadowCoord( mul( worldToLightProj, -surfaceToLight.L ) ).xy;
-      shadow = softShadow_filter(TORQUE_SAMPLER2D_MAKEARG(shadowMap), ssPos.xy, shadowCoord, shadowSoftness, distToLight, surfaceToLight.NdotL, lightParams.y);
-   #endif
+      #else
+         float2 shadowCoord = decodeShadowCoord( mul( worldToLightProj, -surfaceToLight.L ) ).xy;
+         shadow = softShadow_filter(TORQUE_SAMPLER2D_MAKEARG(shadowMap), ssPos.xy, shadowCoord, shadowSoftness, distToLight, surfaceToLight.NdotL, lightParams.y);
+      #endif
    }
    #endif // !NO_SHADOW
    
       float3 lightCol = lightColor.rgb;
-   #ifdef USE_COOKIE_TEX
-      // Lookup the cookie sample.
-      float4 cookie = TORQUE_TEXCUBE(cookieMap, mul(worldToLightProj, -surfaceToLight.L));
-      // Multiply the light with the cookie tex.
-      lightCol *= cookie.rgb;
-      // Use a maximum channel luminance to attenuate 
-      // the lighting else we get specular in the dark
-      // regions of the cookie texture.
-      lightCol *= max(cookie.r, max(cookie.g, cookie.b));
-   #endif
-
    #ifdef DIFFUSE_LIGHT_VIZ
       float attenuation = getDistanceAtt(surfaceToLight.Lu, radius);
       float3 factor = lightColor * max(surfaceToLight.NdotL, 0) * shadow * lightIntensity * attenuation;
@@ -198,7 +188,7 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
    #endif
 
    #ifdef SPECULAR_LIGHT_VIZ
-   float attenuation = getDistanceAtt(surfaceToLight.Lu, radius);
+      float attenuation = getDistanceAtt(surfaceToLight.Lu, radius);
       float3 factor = lightColor * max(surfaceToLight.NdotL, 0) * shadow * lightIntensity * attenuation;
 
       float3 diffuse = BRDF_GetDebugSpecular(surface,surfaceToLight) * factor;
@@ -219,7 +209,17 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
 
       //get punctual light contribution   
       lighting = getPunctualLight(surface, surfaceToLight, lightCol, lightBrightness, lightInvSqrRange, shadow);
+
+   #ifdef USE_COOKIE_TEX
+      // Lookup the cookie sample.d
+      float cosTheta = dot(-surfaceToLight.L, lightDirection); 
+      float angle = acos(cosTheta) * ( M_1OVER_PI_F); 
+      float cookie = TORQUE_TEX2D(cookieMap, float2(angle, 0.0)).r; 
+      // Multiply the light with the cookie tex.
+      lighting *= cookie;
+   #endif
    }
-      
+   
+
    return float4(lighting, 0);
 }

+ 9 - 10
Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/spotLightP.hlsl

@@ -109,16 +109,7 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
          //distance to light in shadow map space
          float distToLight = pxlPosLightProj.z / lightRange;
          shadow = softShadow_filter(TORQUE_SAMPLER2D_MAKEARG(shadowMap), ssPos.xy, shadowCoord, shadowSoftness, distToLight, surfaceToLight.NdotL, lightParams.y);
-         #ifdef USE_COOKIE_TEX
-            // Lookup the cookie sample.
-            float4 cookie = TORQUE_TEX2D(cookieMap, shadowCoord);
-            // Multiply the light with the cookie tex.
-            lightCol *= cookie.rgb;
-            // Use a maximum channel luminance to attenuate 
-            // the lighting else we get specular in the dark
-            // regions of the cookie texture.
-            lightCol *= max(cookie.r, max(cookie.g, cookie.b));
-         #endif
+         
       }
       #endif
 
@@ -153,6 +144,14 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
 
       //get spot light contribution   
       lighting = getSpotlight(surface, surfaceToLight, lightCol, lightBrightness, lightInvSqrRange, lightDirection, lightSpotParams, shadow);
+      #ifdef USE_COOKIE_TEX
+         // Lookup the cookie sample.d
+         float cosTheta = dot(-surfaceToLight.L, lightDirection); 
+         float angle = acos(cosTheta) * ( M_1OVER_PI_F); 
+         float cookie = TORQUE_TEX2D(cookieMap, float2(angle, 0.0)).r; 
+         // Multiply the light with the cookie tex.
+         lighting *= cookie;
+      #endif 
    }
    
    return float4(lighting, 0);