2
0
Эх сурвалжийг харах

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 1 жил өмнө
parent
commit
a12d915180

+ 1 - 1
Engine/source/CMakeLists.txt

@@ -79,7 +79,7 @@ if(TORQUE_SFX_OPENAL AND NOT TORQUE_DEDICATED)
     endif()
     endif()
 endif()
 endif()
 # Handle GFX
 # 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" )
                              "gfx/util" "gfx/video" "gfx/sim" )
 
 
 # add the stb headers
 # add the stb headers

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

@@ -28,6 +28,7 @@
 #include "core/strings/stringFunctions.h"
 #include "core/strings/stringFunctions.h"
 #include "gfx/bitmap/gBitmap.h"
 #include "gfx/bitmap/gBitmap.h"
 #include "gfx/bitmap/imageUtils.h"
 #include "gfx/bitmap/imageUtils.h"
+#include "gfx/bitmap/loaders/ies/ies_loader.h"
 
 
 #ifdef __clang__
 #ifdef __clang__
 #define STBIWDEF static inline
 #define STBIWDEF static inline
@@ -69,6 +70,7 @@ static struct _privateRegisterSTB
       reg.extensions.push_back("psd");
       reg.extensions.push_back("psd");
       reg.extensions.push_back("hdr");
       reg.extensions.push_back("hdr");
       reg.extensions.push_back("tga");
       reg.extensions.push_back("tga");
+      reg.extensions.push_back("ies");
 
 
       reg.readFunc = sReadSTB;
       reg.readFunc = sReadSTB;
       reg.readStreamFunc = sReadStreamSTB;
       reg.readStreamFunc = sReadStreamSTB;
@@ -93,6 +95,103 @@ bool sReadSTB(const Torque::Path& path, GBitmap* bitmap)
 
 
    U32 prevWaterMark = FrameAllocator::getWaterMark();
    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))
    if (!stbi_info(path.getFullPath().c_str(), &x, &y, &channels))
    {
    {
       FrameAllocator::setWaterMark(prevWaterMark);
       FrameAllocator::setWaterMark(prevWaterMark);
@@ -142,21 +241,22 @@ bool sReadSTB(const Torque::Path& path, GBitmap* bitmap)
    if (ext.equal("hdr"))
    if (ext.equal("hdr"))
    {
    {
       // force load to 4 channel.
       // 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();
       bitmap->deleteImage();
       // actually allocate the bitmap space...
       // actually allocate the bitmap space...
       bitmap->allocateBitmap(x, y,
       bitmap->allocateBitmap(x, y,
          false,
          false,
-         GFXFormatR8G8B8A8);
+         GFXFormatR8G8B8);
 
 
       U8* pBase = (U8*)bitmap->getBits();
       U8* pBase = (U8*)bitmap->getBits();
 
 
-      U32 rowBytes = x * y * 4;
+      U32 rowBytes = x * y * n;
 
 
       dMemcpy(pBase, dataChar, rowBytes);
       dMemcpy(pBase, dataChar, rowBytes);
 
 
+      //stbi_image_free(data);
       stbi_image_free(dataChar);
       stbi_image_free(dataChar);
 
 
       FrameAllocator::setWaterMark(prevWaterMark);
       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 radius = lightInfo->getRange().x;
          const F32 invSqrRadius = 1.0f / (radius * radius);
          const F32 invSqrRadius = 1.0f / (radius * radius);
          matParams->setSafe( lightRange, radius);
          matParams->setSafe( lightRange, radius);
+         matParams->setSafe( lightDirection, -lightInfo->getTransform().getUpVector());
          matParams->setSafe( lightInvSqrRange, invSqrRadius);  
          matParams->setSafe( lightInvSqrRange, invSqrRadius);  
          luxTargMultiplier =radius;
          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);
 TORQUE_UNIFORM_SAMPLER2D(matInfoBuffer, 4);
 #ifdef USE_COOKIE_TEX
 #ifdef USE_COOKIE_TEX
 /// The texture for cookie rendering.
 /// The texture for cookie rendering.
-TORQUE_UNIFORM_SAMPLERCUBE(cookieMap, 5);
+TORQUE_UNIFORM_SAMPLER2D(cookieMap, 5);
 #endif
 #endif
 
 
 uniform float4 rtParams0;
 uniform float4 rtParams0;
@@ -117,6 +117,7 @@ uniform float4 lightColor;
 
 
 uniform float  lightBrightness;
 uniform float  lightBrightness;
 uniform float3 lightPosition;
 uniform float3 lightPosition;
+uniform float3 lightDirection;
 
 
 uniform float4 lightMapParams;
 uniform float4 lightMapParams;
 uniform float4 vsFarPlane;
 uniform float4 vsFarPlane;
@@ -163,31 +164,20 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
    #ifndef NO_SHADOW
    #ifndef NO_SHADOW
    if (getFlag(surface.matFlag, 0)) //also skip if we don't recieve shadows
    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
    #endif // !NO_SHADOW
    
    
       float3 lightCol = lightColor.rgb;
       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
    #ifdef DIFFUSE_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 factor = lightColor * max(surfaceToLight.NdotL, 0) * shadow * lightIntensity * attenuation;
@@ -198,7 +188,7 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
    #endif
    #endif
 
 
    #ifdef SPECULAR_LIGHT_VIZ
    #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 factor = lightColor * max(surfaceToLight.NdotL, 0) * shadow * lightIntensity * attenuation;
 
 
       float3 diffuse = BRDF_GetDebugSpecular(surface,surfaceToLight) * factor;
       float3 diffuse = BRDF_GetDebugSpecular(surface,surfaceToLight) * factor;
@@ -219,7 +209,17 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
 
 
       //get punctual light contribution   
       //get punctual light contribution   
       lighting = getPunctualLight(surface, surfaceToLight, lightCol, lightBrightness, lightInvSqrRange, shadow);
       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);
    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
          //distance to light in shadow map space
          float distToLight = pxlPosLightProj.z / lightRange;
          float distToLight = pxlPosLightProj.z / lightRange;
          shadow = softShadow_filter(TORQUE_SAMPLER2D_MAKEARG(shadowMap), ssPos.xy, shadowCoord, shadowSoftness, distToLight, surfaceToLight.NdotL, lightParams.y);
          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
       #endif
 
 
@@ -153,6 +144,14 @@ float4 main(   ConvexConnectP IN ) : SV_TARGET
 
 
       //get spot light contribution   
       //get spot light contribution   
       lighting = getSpotlight(surface, surfaceToLight, lightCol, lightBrightness, lightInvSqrRange, lightDirection, lightSpotParams, shadow);
       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);
    return float4(lighting, 0);