diff --git a/application/application.sln b/application/application.sln
new file mode 100644
index 0000000..ccfbf3c
--- /dev/null
+++ b/application/application.sln
@@ -0,0 +1,27 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.24720.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "application", "application.vcxproj", "{950B6F86-8BF8-4DC1-A161-F64B64AA2146}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}.Debug|Win32.ActiveCfg = Debug|Win32
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}.Debug|Win32.Build.0 = Debug|Win32
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}.Debug|x64.ActiveCfg = Debug|x64
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}.Debug|x64.Build.0 = Debug|x64
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}.Release|Win32.ActiveCfg = Release|Win32
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}.Release|Win32.Build.0 = Release|Win32
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}.Release|x64.ActiveCfg = Release|x64
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/application/application.vcxproj b/application/application.vcxproj
new file mode 100644
index 0000000..db86a2c
--- /dev/null
+++ b/application/application.vcxproj
@@ -0,0 +1,190 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ Win32
+
+
+ Release
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {950B6F86-8BF8-4DC1-A161-F64B64AA2146}
+ Win32Proj
+ application
+ application
+ 8.1
+
+
+
+ Application
+ true
+ v140
+ Unicode
+
+
+ Application
+ true
+ v140
+
+
+ Application
+ false
+ v140
+ true
+ Unicode
+
+
+ Application
+ false
+ v140
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+
+
+
+ Level3
+ Disabled
+ WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(ProjectDir)\..\libs\glew-1.13.0\include;$(ProjectDir)\..\libs\glfw-3.1.2\INSTALL\include;$(ProjectDir)\..\libs\glm\INSTALL\include;$(ProjectDir)\..\libs\soil\src;$(ProjectDir)\..\libs\AntTweakBar\include;$(ProjectDir)\src;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ $(ProjectDir)\..\libs\glew-1.13.0\lib\Debug\Win32;$(ProjectDir)\..\libs\glfw-3.1.2\INSTALL\lib;$(ProjectDir)\..\libs\AntTweakBar\lib;$(ProjectDir)\..\libs\soil\projects\VC9\Debug;%(AdditionalLibraryDirectories)
+ Opengl32.lib;glfw3.lib;AntTweakBar.lib;glew32sd.lib;SOIL.lib;%(AdditionalDependencies)
+
+
+ copy $(ProjectDir)..\libs\AntTweakBar\lib\AntTweakBar.dll $(ProjectDir)
+
+
+
+
+
+
+ Level3
+ Disabled
+ WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(ProjectDir)\..\libs\glew-1.13.0\include;$(ProjectDir)\..\libs\glfw-3.1.2\INSTALL\include;$(ProjectDir)\..\libs\glm\INSTALL\include;$(ProjectDir)\..\libs\soil\src;$(ProjectDir)\..\libs\AntTweakBar\include;$(ProjectDir)\src;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ $(ProjectDir)\..\libs\glew-1.13.0\lib\Debug\x64;$(ProjectDir)\..\libs\glfw-3.1.2\INSTALL\lib;$(ProjectDir)\..\libs\AntTweakBar\lib;$(ProjectDir)\..\libs\soil\projects\VC9\x64\Debug;%(AdditionalLibraryDirectories)
+ Opengl32.lib;glfw3.lib;AntTweakBar64.lib;glew32sd.lib;SOIL.lib;%(AdditionalDependencies)
+
+
+ copy $(ProjectDir)..\libs\AntTweakBar\lib\AntTweakBar64.dll $(ProjectDir)
+
+
+
+
+ Level3
+
+
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(ProjectDir)\..\libs\glew-1.13.0\include;$(ProjectDir)\..\libs\glfw-3.1.2\INSTALL\include;$(ProjectDir)\..\libs\glm\INSTALL\include;$(ProjectDir)\..\libs\soil\src;$(ProjectDir)\..\libs\AntTweakBar\include;$(ProjectDir)\src;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ true
+ true
+ $(ProjectDir)\..\libs\glew-1.13.0\lib\Release\Win32;$(ProjectDir)\..\libs\glfw-3.1.2\INSTALL\lib;$(ProjectDir)\..\libs\AntTweakBar\lib;$(ProjectDir)\..\libs\soil\projects\VC9\Release;%(AdditionalLibraryDirectories)
+ Opengl32.lib;glfw3.lib;AntTweakBar.lib;glew32s.lib;SOIL.lib;%(AdditionalDependencies)
+
+
+ copy $(ProjectDir)..\libs\AntTweakBar\lib\AntTweakBar.dll $(ProjectDir)
+
+
+
+
+ Level3
+
+
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(ProjectDir)\..\libs\glew-1.13.0\include;$(ProjectDir)\..\libs\glfw-3.1.2\INSTALL\include;$(ProjectDir)\..\libs\glm\INSTALL\include;$(ProjectDir)\..\libs\soil\src;$(ProjectDir)\..\libs\AntTweakBar\include;$(ProjectDir)\src;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ true
+ true
+ $(ProjectDir)\..\libs\glew-1.13.0\lib\Release\x64;$(ProjectDir)\..\libs\glfw-3.1.2\INSTALL\lib;$(ProjectDir)\..\libs\AntTweakBar\lib;$(ProjectDir)\..\libs\soil\projects\VC9\x64\Release;%(AdditionalLibraryDirectories)
+ Opengl32.lib;glfw3.lib;AntTweakBar64.lib;glew32s.lib;SOIL.lib;%(AdditionalDependencies)
+
+
+ copy $(ProjectDir)..\libs\AntTweakBar\lib\AntTweakBar64.dll $(ProjectDir)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/application/application.vcxproj.filters b/application/application.vcxproj.filters
new file mode 100644
index 0000000..a566e65
--- /dev/null
+++ b/application/application.vcxproj.filters
@@ -0,0 +1,71 @@
+
+
+
+
+ {163050ff-f9e5-4526-a1f3-9aa47bfd959b}
+ h;hpp
+
+
+ {ab5bb2c1-050b-4634-a4cc-adfbad105713}
+ cpp
+
+
+ {d4a529e9-9785-45f4-8c6e-48c9fbe102e1}
+ glsl
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ GLSL Files
+
+
+ GLSL Files
+
+
+ GLSL Files
+
+
+ GLSL Files
+
+
+ GLSL Files
+
+
+ GLSL Files
+
+
+ GLSL Files
+
+
+
\ No newline at end of file
diff --git a/application/media/brick_floor.jpg b/application/media/brick_floor.jpg
new file mode 100644
index 0000000..b6de9ac
Binary files /dev/null and b/application/media/brick_floor.jpg differ
diff --git a/application/media/white_fur.jpg b/application/media/white_fur.jpg
new file mode 100644
index 0000000..90ae25e
Binary files /dev/null and b/application/media/white_fur.jpg differ
diff --git a/application/media/wood.jpg b/application/media/wood.jpg
new file mode 100644
index 0000000..f86a76e
Binary files /dev/null and b/application/media/wood.jpg differ
diff --git a/application/media/wood.png b/application/media/wood.png
new file mode 100644
index 0000000..7bd9033
Binary files /dev/null and b/application/media/wood.png differ
diff --git a/application/shaders/blinn_phong_textured_and_shadowed.fs.glsl b/application/shaders/blinn_phong_textured_and_shadowed.fs.glsl
new file mode 100644
index 0000000..703d8ba
--- /dev/null
+++ b/application/shaders/blinn_phong_textured_and_shadowed.fs.glsl
@@ -0,0 +1,645 @@
+#version 330 core
+
+#define MAX_NUM_LIGHT_SOURCES 8
+
+#define DIRECTIONAL_LIGHT 1
+#define POINT_LIGHT 2
+
+#define NEAR 0.1
+
+// NOTE: display modes
+#define HARD_SHADOWS 0
+#define SOFT_SHADOWS 1
+#define BLOCKER_SEARCH 2
+#define PENUMBRA_ESTIMATE 3
+
+in vec2 vTexcoords;
+in vec3 vNormal;
+in vec3 vViewDir;
+in vec3 vWorldPosition;
+in vec3 vCameraPosition;
+
+struct LightSource
+{
+ vec3 diffuseColor;
+ float diffusePower;
+ vec3 specularColor;
+ float specularPower;
+ vec3 position;
+ int type;
+ float size;
+
+};
+
+layout (std140) uniform LightSources
+{
+ LightSource lightSources[MAX_NUM_LIGHT_SOURCES];
+
+};
+
+uniform sampler2D shadowMap0;
+uniform sampler2D shadowMap1;
+uniform sampler2D shadowMap2;
+uniform sampler2D shadowMap3;
+uniform sampler2D shadowMap4;
+uniform sampler2D shadowMap5;
+uniform sampler2D shadowMap6;
+uniform sampler2D shadowMap7;
+uniform samplerCube shadowCubeMap0;
+uniform samplerCube shadowCubeMap1;
+uniform samplerCube shadowCubeMap2;
+uniform samplerCube shadowCubeMap3;
+uniform samplerCube shadowCubeMap4;
+uniform samplerCube shadowCubeMap5;
+uniform samplerCube shadowCubeMap6;
+uniform samplerCube shadowCubeMap7;
+uniform mat4 shadowMapViewProjection0;
+uniform mat4 shadowMapViewProjection1;
+uniform mat4 shadowMapViewProjection2;
+uniform mat4 shadowMapViewProjection3;
+uniform mat4 shadowMapViewProjection4;
+uniform mat4 shadowMapViewProjection5;
+uniform mat4 shadowMapViewProjection6;
+uniform mat4 shadowMapViewProjection7;
+uniform float directionalLightShadowMapBias;
+uniform float pointLightShadowMapBias;
+
+uniform mat4 invView;
+uniform mat4 lightProjection;
+uniform vec3 eyePosition;
+uniform vec3 ambientColor = vec3(0.1,0.1,0.1);
+uniform vec3 specularColor = vec3(1,1,1);
+uniform float specularity = 0;
+uniform float frustumSize = 1;
+uniform sampler2D tex0;
+uniform sampler1D distribution0;
+uniform sampler1D distribution1;
+uniform int numBlockerSearchSamples = 1;
+uniform int numPCFSamples = 1;
+uniform int displayMode = 0;
+uniform int selectedLightSource = -1;
+
+out vec3 outColor;
+
+//////////////////////////////////////////////////////////////////////////
+vec2 RandomDirection(sampler1D distribution, float u)
+{
+ return texture(distribution, u).xy * 2 - vec2(1);
+}
+
+/*vec3 DisturbDirection(vec3 direction, sampler1D distribution, float u)
+{
+ // TODO:
+ return direction;
+}*/
+
+//////////////////////////////////////////////////////////////////////////
+vec3 BlinnPhong(vec3 materialDiffuseColor,
+ vec3 materialSpecularColor,
+ float materialSpecularity,
+ vec3 lightDiffuseColor,
+ float lightDiffusePower,
+ vec3 lightSpecularColor,
+ float lightSpecularPower,
+ float NdotH,
+ float distanceAttenuation)
+{
+ return NdotH * materialDiffuseColor *
+ lightDiffuseColor * lightDiffusePower / distanceAttenuation +
+ pow(NdotH, materialSpecularity) * materialSpecularColor *
+ lightSpecularColor * lightSpecularPower / distanceAttenuation;
+}
+
+//////////////////////////////////////////////////////////////////////////
+vec3 LightContribution(vec3 diffuseColor, int i)
+{
+ switch (lightSources[i].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return BlinnPhong(diffuseColor,
+ specularColor,
+ specularity,
+ lightSources[i].diffuseColor,
+ lightSources[i].diffusePower,
+ lightSources[i].specularColor,
+ lightSources[i].specularPower,
+ max(0, dot(vNormal, normalize(vViewDir - lightSources[i].position))),
+ 1);
+ case POINT_LIGHT:
+ vec3 lightDir = lightSources[i].position - vWorldPosition;
+ float lightDist = length(lightDir);
+ lightDir /= lightDist;
+ return BlinnPhong(diffuseColor,
+ specularColor,
+ specularity,
+ lightSources[i].diffuseColor,
+ lightSources[i].diffusePower,
+ lightSources[i].specularColor,
+ lightSources[i].specularPower,
+ max(0, dot(vNormal, normalize(lightDir + vViewDir))),
+ lightDist * lightDist);
+ default:
+ return vec3(0);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+vec3 ShadowCoords(mat4 shadowMapViewProjection)
+{
+ vec4 projectedCoords = shadowMapViewProjection * vec4(vWorldPosition, 1);
+ vec3 shadowCoords = projectedCoords.xyz / projectedCoords.w;
+ shadowCoords = shadowCoords * 0.5 + 0.5;
+ return shadowCoords;
+}
+
+//////////////////////////////////////////////////////////////////////////
+bool IsLightEnabled(int i)
+{
+ return lightSources[i].type != 0;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// this search area estimation comes from the following article:
+// http://developer.download.nvidia.com/whitepapers/2008/PCSS_DirectionalLight_Integration.pdf
+float SearchWidth(float uvLightSize, float receiverDistance)
+{
+ return uvLightSize * (receiverDistance - NEAR) / eyePosition.z;
+}
+
+//////////////////////////////////////////////////////////////////////////
+float Depth(vec3 pos)
+{
+ vec3 absPos = abs(pos);
+ float z = -max(absPos.x, max(absPos.y, absPos.z));
+ vec4 clip = lightProjection * vec4(0.0, 0.0, z, 1.0);
+ return (clip.z / clip.w) * 0.5 + 0.5;
+}
+
+//////////////////////////////////////////////////////////////////////////
+float FindBlockerDistance_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
+{
+ int blockers = 0;
+ float avgBlockerDistance = 0;
+ float searchWidth = SearchWidth(uvLightSize, shadowCoords.z);
+ for (int i = 0; i < numBlockerSearchSamples; i++)
+ {
+ float z = texture(shadowMap, shadowCoords.xy + RandomDirection(distribution0, i / float(numBlockerSearchSamples)) * searchWidth).r;
+ if (z < (shadowCoords.z - directionalLightShadowMapBias))
+ {
+ blockers++;
+ avgBlockerDistance += z;
+ }
+ }
+ if (blockers > 0)
+ return avgBlockerDistance / blockers;
+ else
+ return -1;
+}
+
+/*float FindBlockerDistance_PointLight(vec3 direction, float receiverDistance, samplerCube shadowCubeMap, float uvLightSize)
+{
+ int blockers = 0;
+ float avgBlockerDistance = 0;
+ float searchWidth = SearchWidth(uvLightSize, receiverDistance);
+ for (int i = 0; i < numBlockerSearchSamples; i++)
+ {
+ float z = texture(shadowCubeMap, DisturbDirection(direction, distribution0, i / float(numBlockerSearchSamples)) * searchWidth).r;
+ if (z < (receiverDistance - pointLightShadowMapBias))
+ {
+ blockers++;
+ avgBlockerDistance += z;
+ }
+ }
+ if (blockers > 0)
+ return avgBlockerDistance / blockers;
+ else
+ return -1;
+}*/
+
+//////////////////////////////////////////////////////////////////////////
+float PCF_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvRadius)
+{
+ float sum = 0;
+ for (int i = 0; i < numPCFSamples; i++)
+ {
+ float z = texture(shadowMap, shadowCoords.xy + RandomDirection(distribution1, i / float(numPCFSamples)) * uvRadius).r;
+ sum += (z < (shadowCoords.z - directionalLightShadowMapBias)) ? 1 : 0;
+ }
+ return sum / numPCFSamples;
+}
+
+/*float PCF_PointLight(vec3 direction, float receiverDistance, samplerCube shadowCubeMap, float uvRadius)
+{
+ float sum = 0;
+ for (int i = 0; i < numPCFSamples; i++)
+ {
+ float z = texture(shadowCubeMap, DisturbDirection(direction, distribution1, i / float(numPCFSamples)) * uvRadius).r;
+ sum += (z < (receiverDistance - pointLightShadowMapBias)) ? 1 : 0;
+ }
+ return sum / numPCFSamples;
+}*/
+
+//////////////////////////////////////////////////////////////////////////
+float ShadowMapping_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
+{
+ float z = texture(shadowMap, shadowCoords.xy).x;
+ return (z < (shadowCoords.z - directionalLightShadowMapBias)) ? 0 : 1;
+}
+
+float ShadowMapping_PointLight(vec3 lightPosition, samplerCube shadowCubeMap, float uvLightSize)
+{
+ mat4 lightView = mat4(1,0,0,0,
+ 0,1,0,0,
+ 0,0,1,0,
+ -lightPosition.x,-lightPosition.y,-lightPosition.z, 1);
+ vec3 positionLightSpace = (lightView * invView * vec4(vCameraPosition, 1)).xyz;
+ float receiverDistance = Depth(positionLightSpace);
+ float z = texture(shadowCubeMap, positionLightSpace).r;
+ return (z < (receiverDistance - pointLightShadowMapBias)) ? 0 : 1;
+}
+
+//////////////////////////////////////////////////////////////////////////
+float PCSS_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
+{
+ // blocker search
+ float blockerDistance = FindBlockerDistance_DirectionalLight(shadowCoords, shadowMap, uvLightSize);
+ if (blockerDistance == -1)
+ return 1;
+
+ // penumbra estimation
+ float penumbraWidth = (shadowCoords.z - blockerDistance) / blockerDistance;
+
+ // percentage-close filtering
+ float uvRadius = penumbraWidth * uvLightSize * NEAR / shadowCoords.z;
+ return 1 - PCF_DirectionalLight(shadowCoords, shadowMap, uvRadius);
+}
+
+float PCSS_PointLight(vec3 lightPosition, samplerCube shadowCubeMap, float uvLightSize)
+{
+ mat4 lightView = mat4(1,0,0,0,
+ 0,1,0,0,
+ 0,0,1,0,
+ -lightPosition.x,-lightPosition.y,-lightPosition.z, 1);
+ vec3 positionLightSpace = (lightView * invView * vec4(vCameraPosition, 1)).xyz;
+ float receiverDistance = Depth(positionLightSpace);
+ float z = texture(shadowCubeMap, positionLightSpace).r;
+ return (z < (receiverDistance - pointLightShadowMapBias)) ? 0 : 1;
+}
+
+//////////////////////////////////////////////////////////////////////////
+float HardShadow(int i)
+{
+ if (i == 0)
+ {
+ if (IsLightEnabled(0))
+ {
+ switch (lightSources[0].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection0), shadowMap0, lightSources[0].size / frustumSize);
+ case POINT_LIGHT:
+ return ShadowMapping_PointLight(lightSources[0].position, shadowCubeMap0, lightSources[0].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 1)
+ {
+ if (IsLightEnabled(1))
+ {
+ switch (lightSources[1].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection1), shadowMap1, lightSources[1].size / frustumSize);
+ case POINT_LIGHT:
+ return ShadowMapping_PointLight(lightSources[1].position, shadowCubeMap1, lightSources[1].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 2)
+ {
+ if (IsLightEnabled(2))
+ {
+ switch (lightSources[2].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection2), shadowMap2, lightSources[2].size / frustumSize);
+ case POINT_LIGHT:
+ return ShadowMapping_PointLight(lightSources[2].position, shadowCubeMap2, lightSources[2].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 3)
+ {
+ if (IsLightEnabled(3))
+ {
+ switch (lightSources[3].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection3), shadowMap3, lightSources[3].size / frustumSize);
+ case POINT_LIGHT:
+ return ShadowMapping_PointLight(lightSources[3].position, shadowCubeMap3, lightSources[3].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 4)
+ {
+ if (IsLightEnabled(4))
+ {
+ switch (lightSources[4].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection4), shadowMap4, lightSources[4].size / frustumSize);
+ case POINT_LIGHT:
+ return ShadowMapping_PointLight(lightSources[4].position, shadowCubeMap4, lightSources[4].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 5)
+ {
+ if (IsLightEnabled(5))
+ {
+ switch (lightSources[5].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection5), shadowMap5, lightSources[5].size / frustumSize);
+ case POINT_LIGHT:
+ return ShadowMapping_PointLight(lightSources[5].position, shadowCubeMap5, lightSources[5].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 6)
+ {
+ if (IsLightEnabled(6))
+ {
+ switch (lightSources[6].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection6), shadowMap6, lightSources[6].size / frustumSize);
+ case POINT_LIGHT:
+ return ShadowMapping_PointLight(lightSources[6].position, shadowCubeMap6, lightSources[6].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 7)
+ {
+ if (IsLightEnabled(7))
+ {
+ switch (lightSources[7].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection7), shadowMap7, lightSources[7].size / frustumSize);
+ case POINT_LIGHT:
+ return ShadowMapping_PointLight(lightSources[7].position, shadowCubeMap7, lightSources[7].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////
+float SoftShadow(int i)
+{
+ if (i == 0)
+ {
+ if (IsLightEnabled(0))
+ {
+ switch (lightSources[0].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection0), shadowMap0, lightSources[0].size / frustumSize);
+ case POINT_LIGHT:
+ return PCSS_PointLight(lightSources[0].position, shadowCubeMap0, lightSources[0].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 1)
+ {
+ if (IsLightEnabled(1))
+ {
+ switch (lightSources[1].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection1), shadowMap1, lightSources[1].size / frustumSize);
+ case POINT_LIGHT:
+ return PCSS_PointLight(lightSources[1].position, shadowCubeMap1, lightSources[1].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 2)
+ {
+ if (IsLightEnabled(2))
+ {
+ switch (lightSources[2].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection2), shadowMap2, lightSources[2].size / frustumSize);
+ case POINT_LIGHT:
+ return PCSS_PointLight(lightSources[2].position, shadowCubeMap2, lightSources[2].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 3)
+ {
+ if (IsLightEnabled(3))
+ {
+ switch (lightSources[3].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection3), shadowMap3, lightSources[3].size / frustumSize);
+ case POINT_LIGHT:
+ return PCSS_PointLight(lightSources[3].position, shadowCubeMap3, lightSources[3].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 4)
+ {
+ if (IsLightEnabled(4))
+ {
+ switch (lightSources[4].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection4), shadowMap4, lightSources[4].size / frustumSize);
+ case POINT_LIGHT:
+ return PCSS_PointLight(lightSources[4].position, shadowCubeMap4, lightSources[4].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 5)
+ {
+ if (IsLightEnabled(5))
+ {
+ switch (lightSources[5].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection5), shadowMap5, lightSources[5].size / frustumSize);
+ case POINT_LIGHT:
+ return PCSS_PointLight(lightSources[5].position, shadowCubeMap5, lightSources[5].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 6)
+ {
+ if (IsLightEnabled(6))
+ {
+ switch (lightSources[6].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection6), shadowMap6, lightSources[6].size / frustumSize);
+ case POINT_LIGHT:
+ return PCSS_PointLight(lightSources[6].position, shadowCubeMap6, lightSources[6].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else if (i == 7)
+ {
+ if (IsLightEnabled(7))
+ {
+ switch (lightSources[7].type)
+ {
+ case DIRECTIONAL_LIGHT:
+ return PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection7), shadowMap7, lightSources[7].size / frustumSize);
+ case POINT_LIGHT:
+ return PCSS_PointLight(lightSources[7].position, shadowCubeMap7, lightSources[7].size / frustumSize);
+ }
+ }
+ return 0;
+ }
+ else
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////
+void DisplayHardShadows()
+{
+ vec3 diffuseColor = texture(tex0, vTexcoords).rgb;
+ int enabledLights = 0;
+ for (int i = 0; i < MAX_NUM_LIGHT_SOURCES; i++)
+ if (IsLightEnabled(i))
+ enabledLights++;
+ if (enabledLights > 0)
+ {
+ for (int i = 0; i < MAX_NUM_LIGHT_SOURCES; i++)
+ outColor += LightContribution(diffuseColor, i) * HardShadow(i);
+ outColor /= enabledLights;
+ }
+ outColor += ambientColor;
+}
+
+//////////////////////////////////////////////////////////////////////////
+void DisplaySoftShadows()
+{
+ vec3 diffuseColor = texture(tex0, vTexcoords).rgb;
+ int enabledLights = 0;
+ for (int i = 0; i < MAX_NUM_LIGHT_SOURCES; i++)
+ if (IsLightEnabled(i))
+ enabledLights++;
+ if (enabledLights > 0)
+ {
+ for (int i = 0; i < MAX_NUM_LIGHT_SOURCES; i++)
+ outColor += LightContribution(diffuseColor, i) * SoftShadow(i);
+ outColor /= enabledLights;
+ }
+ outColor += ambientColor;
+}
+
+//////////////////////////////////////////////////////////////////////////
+void DisplayBlockerSearch()
+{
+ float blockerDistance = -1;
+ if (selectedLightSource == 0 && IsLightEnabled(0))
+ blockerDistance = FindBlockerDistance_DirectionalLight(ShadowCoords(shadowMapViewProjection0), shadowMap0, lightSources[0].size / frustumSize);
+ else if (selectedLightSource == 1 && IsLightEnabled(1))
+ blockerDistance = FindBlockerDistance_DirectionalLight(ShadowCoords(shadowMapViewProjection1), shadowMap1, lightSources[1].size / frustumSize);
+ else if (selectedLightSource == 2 && IsLightEnabled(2))
+ blockerDistance = FindBlockerDistance_DirectionalLight(ShadowCoords(shadowMapViewProjection2), shadowMap2, lightSources[2].size / frustumSize);
+ else if (selectedLightSource == 3 && IsLightEnabled(3))
+ blockerDistance = FindBlockerDistance_DirectionalLight(ShadowCoords(shadowMapViewProjection3), shadowMap3, lightSources[3].size / frustumSize);
+ else if (selectedLightSource == 4 && IsLightEnabled(4))
+ blockerDistance = FindBlockerDistance_DirectionalLight(ShadowCoords(shadowMapViewProjection4), shadowMap4, lightSources[4].size / frustumSize);
+ else if (selectedLightSource == 5 && IsLightEnabled(5))
+ blockerDistance = FindBlockerDistance_DirectionalLight(ShadowCoords(shadowMapViewProjection5), shadowMap5, lightSources[5].size / frustumSize);
+ else if (selectedLightSource == 6 && IsLightEnabled(6))
+ blockerDistance = FindBlockerDistance_DirectionalLight(ShadowCoords(shadowMapViewProjection6), shadowMap6, lightSources[6].size / frustumSize);
+ else if (selectedLightSource == 7 && IsLightEnabled(7))
+ blockerDistance = FindBlockerDistance_DirectionalLight(ShadowCoords(shadowMapViewProjection7), shadowMap7, lightSources[7].size / frustumSize);
+ if (blockerDistance == -1)
+ outColor = vec3(1);
+ else
+ outColor = vec3(blockerDistance);
+}
+
+//////////////////////////////////////////////////////////////////////////
+float PenumbraWidth(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
+{
+ // blocker search
+ float blockerDistance = FindBlockerDistance_DirectionalLight(shadowCoords, shadowMap, uvLightSize);
+ if (blockerDistance == -1)
+ return -1;
+ // penumbra estimation
+ return (shadowCoords.z - blockerDistance) / blockerDistance;
+}
+
+//////////////////////////////////////////////////////////////////////////
+void DisplayPenumbraEstimate()
+{
+ float penumbraWidth = -1;
+ if (selectedLightSource == 0 && IsLightEnabled(0))
+ penumbraWidth = PenumbraWidth(ShadowCoords(shadowMapViewProjection0), shadowMap0, lightSources[0].size / frustumSize);
+ else if (selectedLightSource == 1 && IsLightEnabled(1))
+ penumbraWidth = PenumbraWidth(ShadowCoords(shadowMapViewProjection1), shadowMap1, lightSources[1].size / frustumSize);
+ else if (selectedLightSource == 2 && IsLightEnabled(2))
+ penumbraWidth = PenumbraWidth(ShadowCoords(shadowMapViewProjection2), shadowMap2, lightSources[2].size / frustumSize);
+ else if (selectedLightSource == 3 && IsLightEnabled(3))
+ penumbraWidth = PenumbraWidth(ShadowCoords(shadowMapViewProjection3), shadowMap3, lightSources[3].size / frustumSize);
+ else if (selectedLightSource == 4 && IsLightEnabled(4))
+ penumbraWidth = PenumbraWidth(ShadowCoords(shadowMapViewProjection4), shadowMap4, lightSources[4].size / frustumSize);
+ else if (selectedLightSource == 5 && IsLightEnabled(5))
+ penumbraWidth = PenumbraWidth(ShadowCoords(shadowMapViewProjection5), shadowMap5, lightSources[5].size / frustumSize);
+ else if (selectedLightSource == 6 && IsLightEnabled(6))
+ penumbraWidth = PenumbraWidth(ShadowCoords(shadowMapViewProjection6), shadowMap6, lightSources[6].size / frustumSize);
+ else if (selectedLightSource == 7 && IsLightEnabled(7))
+ penumbraWidth = PenumbraWidth(ShadowCoords(shadowMapViewProjection7), shadowMap7, lightSources[7].size / frustumSize);
+ if (penumbraWidth == -1)
+ outColor = vec3(0);
+ else
+ outColor = vec3(penumbraWidth);
+}
+
+//////////////////////////////////////////////////////////////////////////
+void main()
+{
+ switch (displayMode)
+ {
+ case HARD_SHADOWS:
+ DisplayHardShadows();
+ break;
+ case SOFT_SHADOWS:
+ DisplaySoftShadows();
+ break;
+ case BLOCKER_SEARCH:
+ DisplayBlockerSearch();
+ break;
+ case PENUMBRA_ESTIMATE:
+ DisplayPenumbraEstimate();
+ break;
+ default:
+ // FIXME: checking invariant
+ outColor = vec3(1,0,0);
+ }
+}
diff --git a/application/shaders/common.vs.glsl b/application/shaders/common.vs.glsl
new file mode 100644
index 0000000..3d8673b
--- /dev/null
+++ b/application/shaders/common.vs.glsl
@@ -0,0 +1,28 @@
+#version 330 core
+
+in vec3 position;
+in vec3 normal;
+in vec2 texcoords;
+
+out vec2 vTexcoords;
+out vec3 vNormal;
+out vec3 vViewDir;
+out vec3 vWorldPosition;
+out vec3 vCameraPosition;
+
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+uniform vec3 eyePosition;
+
+void main()
+{
+ vTexcoords = texcoords;
+ vNormal = (model * vec4(normal, 0)).xyz;
+ vec4 worldPosition = model * vec4(position, 1.0f);
+ vWorldPosition = worldPosition.xyz;
+ vec4 cameraPosition = view * model * vec4(position, 1.0f);
+ vCameraPosition = cameraPosition.xyz;
+ vViewDir = normalize(eyePosition - vWorldPosition);
+ gl_Position = projection * cameraPosition;
+}
diff --git a/application/shaders/draw_shadow_map.fs.glsl b/application/shaders/draw_shadow_map.fs.glsl
new file mode 100644
index 0000000..3b93c61
--- /dev/null
+++ b/application/shaders/draw_shadow_map.fs.glsl
@@ -0,0 +1,12 @@
+#version 330 core
+
+in vec2 vTexcoord;
+
+uniform sampler2D shadowMap;
+
+out vec3 outColor;
+
+void main()
+{
+ outColor = vec3(texture(shadowMap, vTexcoord).r);
+}
\ No newline at end of file
diff --git a/application/shaders/fullscreen.vs.glsl b/application/shaders/fullscreen.vs.glsl
new file mode 100644
index 0000000..f59c8ed
--- /dev/null
+++ b/application/shaders/fullscreen.vs.glsl
@@ -0,0 +1,12 @@
+#version 330 core
+
+const vec2 vertices[4] = vec2[4]( vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0) );
+
+out vec2 vTexcoord;
+
+void main()
+{
+ vec2 vertex = vertices[gl_VertexID];
+ vTexcoord = vertex * 0.5 + 0.5;
+ gl_Position = vec4(vertex, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/application/shaders/light_source.fs.glsl b/application/shaders/light_source.fs.glsl
new file mode 100644
index 0000000..5f4e8d7
--- /dev/null
+++ b/application/shaders/light_source.fs.glsl
@@ -0,0 +1,17 @@
+#version 330 core
+
+in vec2 vTexcoords;
+in vec3 vNormal;
+in vec3 vViewDir;
+in vec3 vLightDir;
+
+uniform vec3 color = vec3(1, 1, 1);
+uniform float transparency = 0.25f;
+
+out vec4 outColor;
+
+void main()
+{
+ float a = transparency * dot(vNormal, vViewDir);
+ outColor = vec4(color, a);
+}
diff --git a/application/shaders/shadow_pass.fs.glsl b/application/shaders/shadow_pass.fs.glsl
new file mode 100644
index 0000000..333dbe0
--- /dev/null
+++ b/application/shaders/shadow_pass.fs.glsl
@@ -0,0 +1,8 @@
+#version 330 core
+
+out float outDepth;
+
+void main()
+{
+ outDepth = gl_FragCoord.z;
+}
\ No newline at end of file
diff --git a/application/shaders/shadow_pass.vs.glsl b/application/shaders/shadow_pass.vs.glsl
new file mode 100644
index 0000000..28be707
--- /dev/null
+++ b/application/shaders/shadow_pass.vs.glsl
@@ -0,0 +1,10 @@
+#version 330 core
+
+in vec3 position;
+
+uniform mat4 modelViewProjection;
+
+void main()
+{
+ gl_Position = modelViewProjection * vec4(position, 1);
+}
\ No newline at end of file
diff --git a/application/src/Camera.h b/application/src/Camera.h
new file mode 100644
index 0000000..baf5eda
--- /dev/null
+++ b/application/src/Camera.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include
+
+#include
+
+struct Camera
+{
+ float fovY;
+ float zn;
+ float zf;
+
+ Camera(float fovY, float zn, float zf)
+ {
+ this->fovY = fovY;
+ this->zn = zn;
+ this->zf = zf;
+ }
+
+ glm::mat4 getProjection(float aspectRatio)
+ {
+ float yScale = std::tan(1.0f / (glm::radians(fovY) * 0.5f));
+ float xScale = yScale / aspectRatio;
+ return glm::mat4(xScale, 0, 0, 0,
+ 0, yScale, 0, 0,
+ 0, 0, zf / (zn - zf), -1,
+ 0, 0, zn * zf / (zn - zf), 0);
+ }
+
+};
+
diff --git a/application/src/GLUtils.h b/application/src/GLUtils.h
new file mode 100644
index 0000000..8bf8c3d
--- /dev/null
+++ b/application/src/GLUtils.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include
+#include
+
+//////////////////////////////////////////////////////////////////////////
+void checkOpenGLError()
+{
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR)
+ std::cout << "GL_ERROR: " << error << std::endl;
+}
diff --git a/application/src/Mesh.h b/application/src/Mesh.h
new file mode 100644
index 0000000..c686ea0
--- /dev/null
+++ b/application/src/Mesh.h
@@ -0,0 +1,114 @@
+#pragma once
+
+#include
+
+#include
+#include
+
+#include "GLUtils.h"
+
+struct Mesh
+{
+ GLuint vao;
+ GLuint positionBuffer;
+ GLuint texcoordsBuffer;
+ GLuint normalBuffer;
+ GLuint indexBuffer;
+ bool hasIndexBuffer;
+ size_t numVertices;
+ size_t numIndices;
+
+ Mesh(const std::vector& vertices, const std::vector& uvs, const std::vector& normals) : hasIndexBuffer(false), numIndices(0)
+ {
+ glGenVertexArrays(1, &vao);
+
+ numVertices = vertices.size();
+ glGenBuffers(1, &positionBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
+ glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);
+
+ glGenBuffers(1, &texcoordsBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, texcoordsBuffer);
+ glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(glm::vec2), &uvs[0], GL_STATIC_DRAW);
+
+ glGenBuffers(1, &normalBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
+ glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals[0], GL_STATIC_DRAW);
+
+ checkOpenGLError();
+ }
+
+ Mesh(const std::vector& vertices, const std::vector& uvs, const std::vector& normals, const std::vector& indices) : Mesh(vertices, uvs, normals)
+ {
+ hasIndexBuffer = true;
+
+ numIndices = indices.size();
+ glGenBuffers(1, &indexBuffer);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices * sizeof(unsigned), &indices[0], GL_STATIC_DRAW);
+
+ checkOpenGLError();
+ }
+
+ virtual ~Mesh()
+ {
+ glDeleteBuffers(1, &positionBuffer);
+ glDeleteBuffers(1, &texcoordsBuffer);
+ glDeleteBuffers(1, &normalBuffer);
+
+ if (hasIndexBuffer)
+ glDeleteBuffers(1, &indexBuffer);
+
+ glDeleteVertexArrays(1, &vao);
+ }
+
+
+ void setup(GLuint program)
+ {
+ glBindVertexArray(vao);
+
+ // Specify the layout of the vertex data
+ GLint positionAttribute = glGetAttribLocation(program, "position");
+ if (positionAttribute != -1)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
+ glEnableVertexAttribArray(positionAttribute);
+ glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
+ }
+
+ GLint texcoordsAttribute = glGetAttribLocation(program, "texcoords");
+ if (texcoordsAttribute != -1)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, texcoordsBuffer);
+ glEnableVertexAttribArray(texcoordsAttribute);
+ glVertexAttribPointer(texcoordsAttribute, 2, GL_FLOAT, GL_FALSE, 0, 0);
+ }
+
+ GLint normalAttribute = glGetAttribLocation(program, "normal");
+ if (normalAttribute != -1)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
+ glEnableVertexAttribArray(normalAttribute);
+ glVertexAttribPointer(normalAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
+ }
+
+ if (hasIndexBuffer)
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+
+ checkOpenGLError();
+ }
+
+
+ void draw() const
+ {
+ glBindVertexArray(vao);
+ if (hasIndexBuffer)
+ glDrawElements(GL_TRIANGLES, (GLsizei)numIndices, GL_UNSIGNED_INT, 0);
+ else
+ glDrawArrays(GL_TRIANGLES, 0, (GLsizei)numVertices);
+
+ checkOpenGLError();
+ }
+
+};
+
diff --git a/application/src/Navigator.h b/application/src/Navigator.h
new file mode 100644
index 0000000..61b2dc8
--- /dev/null
+++ b/application/src/Navigator.h
@@ -0,0 +1,192 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "GLUtils.h"
+
+class Navigator
+{
+private:
+ float moveSpeed, rotateSpeed;
+ int lastPosX, lastPosY;
+ unsigned drag;
+ float phi;
+ float theta;
+ glm::vec3 u;
+ glm::vec3 v;
+ glm::vec3 w;
+ glm::vec3 position;
+ float moveX;
+ float moveZ;
+
+ void rotateH(float delta)
+ {
+ phi = glm::fmod(phi + delta, 2.0f * glm::pi());
+ }
+
+ void rotateV(float delta)
+ {
+ theta = glm::fmod(theta + delta, 2.0f * glm::pi());
+ }
+
+ void pan(float x, float y)
+ {
+ position += x * u + y * v;
+ }
+
+ void walk(float delta)
+ {
+ position += delta * w;
+ }
+
+ void updateAxis()
+ {
+ float cp = glm::cos(phi);
+ float sp = glm::sin(phi);
+ float ct = glm::cos(theta);
+ float st = glm::sin(theta);
+
+ w = glm::vec3(ct * cp, st, ct * sp);
+ v = -glm::vec3(-st * cp, ct, -st * sp);
+ u = cross(v, w);
+ }
+
+public:
+ Navigator(float moveSpeed, float rotateSpeed, const glm::vec3& position = glm::vec3(0, 0, 0), float phi = glm::pi() * 0.5f, float theta = 0) :
+ moveSpeed(moveSpeed),
+ rotateSpeed(rotateSpeed),
+ position(position),
+ phi(phi),
+ theta(theta),
+ drag(0),
+ moveX(0),
+ moveZ(0)
+ {
+ updateAxis();
+ }
+
+ glm::mat4 getLocalToWorldTransform() const
+ {
+ return glm::mat4(u.x, v.x, w.x, 0,
+ u.y, v.y, w.y, 0,
+ u.z, v.z, w.z, 0,
+ -glm::dot(u, position), -glm::dot(v, position), -glm::dot(w, position), 1);
+ }
+
+ virtual glm::vec3 getPosition() const
+ {
+ return position;
+ }
+
+ void buttonDown(int button)
+ {
+ if (button == GLFW_MOUSE_BUTTON_1)
+ drag |= 1;
+ else if (button == GLFW_MOUSE_BUTTON_2)
+ drag |= 2;
+ else if (button == GLFW_MOUSE_BUTTON_3)
+ drag |= 4;
+ }
+
+ void buttonUp(int button)
+ {
+ if (button == GLFW_MOUSE_BUTTON_1)
+ drag &= ~1;
+ else if (button == GLFW_MOUSE_BUTTON_2)
+ drag &= ~2;
+ else if (button == GLFW_MOUSE_BUTTON_3)
+ drag &= ~4;
+ }
+
+ void mouseMove(int x, int y)
+ {
+ if (drag)
+ {
+ int dX = x - lastPosX, dY = y - lastPosY;
+ if (drag & 1) // left
+ {
+ rotateH(dX * rotateSpeed);
+ rotateV(dY * rotateSpeed);
+ }
+ if (drag & 2) // right
+ {
+ int absDX = std::abs(dX), absDY = std::abs(dY);
+ walk(((absDY > absDX) ? (dY < 0 ? 1.0f : -1.0f) : (dX > 0.0f ? 1.0f : -1.0f)) * std::sqrt(static_cast(dX * dX + dY * dY)) * moveSpeed * 0.007f);
+ }
+ if (drag & 4) // middle
+ {
+ pan(dX * moveSpeed * 0.007f, -dY * moveSpeed * 0.007f);
+ }
+ updateAxis();
+ }
+ lastPosX = x;
+ lastPosY = y;
+ }
+
+ void mouseWheel(int delta)
+ {
+ walk(delta * 0.007f);
+ updateAxis();
+ }
+
+ void keyDown(int key)
+ {
+ switch (key)
+ {
+ case GLFW_KEY_LEFT:
+ case GLFW_KEY_A:
+ moveX = moveSpeed;
+ break;
+ case GLFW_KEY_UP:
+ case GLFW_KEY_W:
+ moveZ = -moveSpeed;
+ break;
+ case GLFW_KEY_DOWN:
+ case GLFW_KEY_S:
+ moveZ = moveSpeed;
+ break;
+ case GLFW_KEY_RIGHT:
+ case GLFW_KEY_D:
+ moveX = -moveSpeed;
+ break;
+ }
+ }
+
+ void keyUp(int key)
+ {
+ switch (key)
+ {
+ case GLFW_KEY_LEFT:
+ case GLFW_KEY_A:
+ moveX = 0;
+ break;
+ case GLFW_KEY_UP:
+ case GLFW_KEY_W:
+ moveZ = 0;
+ break;
+ case GLFW_KEY_DOWN:
+ case GLFW_KEY_S:
+ moveZ = 0;
+ break;
+ case GLFW_KEY_RIGHT:
+ case GLFW_KEY_D:
+ moveX = 0;
+ break;
+ }
+ }
+
+ void update(float deltaTime)
+ {
+ if (moveX != 0)
+ pan(moveX * moveSpeed * deltaTime, 0.0f);
+ if (moveZ != 0)
+ walk(moveZ * moveSpeed * deltaTime);
+ if (moveX != 0 || moveZ != 0)
+ updateAxis();
+ }
+
+};
+
diff --git a/application/src/Poisson.cpp b/application/src/Poisson.cpp
new file mode 100644
index 0000000..7731ca9
--- /dev/null
+++ b/application/src/Poisson.cpp
@@ -0,0 +1,205 @@
+/**
+ * \file Poisson.cpp
+ * \brief
+ *
+ * Poisson Disk Points Generator example
+ *
+ * \version 1.1.3
+ * \date 10/03/2016
+ * \author Sergey Kosarevsky, 2014-2016
+ * \author support@linderdaum.com http://www.linderdaum.com http://blog.linderdaum.com
+ */
+
+/*
+ To compile:
+ gcc Poisson.cpp -std=c++11 -lstdc++
+*/
+
+#include
+#include
+#include
+#include
+
+#include "PoissonGenerator.h"
+
+///////////////// User selectable parameters ///////////////////////////////
+
+const int NumPoints = 20000; // minimal number of points to generate
+const int ImageSize = 1024; // generate RGB image [ImageSize x ImageSize]
+
+////////////////////////////////////////////////////////////////////////////
+
+float* g_DensityMap = nullptr;
+
+#if defined( __GNUC__ )
+# define GCC_PACK(n) __attribute__((packed,aligned(n)))
+#else
+# define GCC_PACK(n) __declspec(align(n))
+#endif // __GNUC__
+
+#pragma pack(push, 1)
+struct GCC_PACK( 1 ) sBMPHeader
+{
+ // BITMAPFILEHEADER
+ unsigned short bfType;
+ uint32_t bfSize;
+ unsigned short bfReserved1;
+ unsigned short bfReserved2;
+ uint32_t bfOffBits;
+ // BITMAPINFOHEADER
+ uint32_t biSize;
+ uint32_t biWidth;
+ uint32_t biHeight;
+ unsigned short biPlanes;
+ unsigned short biBitCount;
+ uint32_t biCompression;
+ uint32_t biSizeImage;
+ uint32_t biXPelsPerMeter;
+ uint32_t biYPelsPerMeter;
+ uint32_t biClrUsed;
+ uint32_t biClrImportant;
+};
+#pragma pack(pop)
+
+void SaveBMP( const char* FileName, const void* RawBGRImage, int Width, int Height )
+{
+ sBMPHeader Header;
+
+ int ImageSize = Width * Height * 3;
+
+ Header.bfType = 0x4D * 256 + 0x42;
+ Header.bfSize = ImageSize + sizeof( sBMPHeader );
+ Header.bfReserved1 = 0;
+ Header.bfReserved2 = 0;
+ Header.bfOffBits = 0x36;
+ Header.biSize = 40;
+ Header.biWidth = Width;
+ Header.biHeight = Height;
+ Header.biPlanes = 1;
+ Header.biBitCount = 24;
+ Header.biCompression = 0;
+ Header.biSizeImage = ImageSize;
+ Header.biXPelsPerMeter = 6000;
+ Header.biYPelsPerMeter = 6000;
+ Header.biClrUsed = 0;
+ Header.biClrImportant = 0;
+
+ std::ofstream File( FileName, std::ios::out | std::ios::binary );
+
+ File.write( (const char*)&Header, sizeof( Header ) );
+ File.write( (const char*)RawBGRImage, ImageSize );
+
+ std::cout << "Saved " << FileName << std::endl;
+}
+
+unsigned char* LoadBMP( const char* FileName, int* OutWidth, int* OutHeight )
+{
+ sBMPHeader Header;
+
+ std::ifstream File( FileName, std::ifstream::binary );
+
+ File.read( (char*)&Header, sizeof( Header ) );
+
+ *OutWidth = Header.biWidth;
+ *OutHeight = Header.biHeight;
+
+ size_t DataSize = 3 * Header.biWidth * Header.biHeight;
+
+ unsigned char* Img = new unsigned char[ DataSize ];
+
+ File.read( (char*)Img, DataSize );
+
+ return Img;
+}
+
+void LoadDensityMap( const char* FileName )
+{
+ std::cout << "Loading density map " << FileName << std::endl;
+
+ int W, H;
+ unsigned char* Data = LoadBMP( FileName, &W, &H );
+
+ std::cout << "Loaded ( " << W << " x " << H << " ) " << std::endl;
+
+ if ( W != ImageSize || H != ImageSize )
+ {
+ std::cout << "ERROR: density map should be " << ImageSize << " x " << ImageSize << std::endl;
+
+ exit( 255 );
+ }
+
+ g_DensityMap = new float[ W * H ];
+
+ for ( int y = 0; y != H; y++ )
+ {
+ for ( int x = 0; x != W; x++ )
+ {
+ g_DensityMap[ x + y * W ] = float( Data[ 3 * (x + y * W) ] ) / 255.0f;
+ }
+ }
+
+ delete[]( Data );
+}
+
+void PrintBanner()
+{
+ std::cout << "Poisson disk points generator" << std::endl;
+ std::cout << "Version " << PoissonGenerator::Version << std::endl;
+ std::cout << "Sergey Kosarevsky, 2014-2016" << std::endl;
+ std::cout << "support@linderdaum.com http://www.linderdaum.com http://blog.linderdaum.com" << std::endl;
+ std::cout << std::endl;
+ std::cout << "Usage: Poisson [density-map-rgb24.bmp]" << std::endl;
+ std::cout << std::endl;
+}
+
+int main( int argc, char** argv )
+{
+ PrintBanner();
+
+ if ( argc > 1 )
+ {
+ LoadDensityMap( argv[1] );
+ }
+
+ PoissonGenerator::DefaultPRNG PRNG;
+
+ const auto Points = PoissonGenerator::GeneratePoissonPoints( NumPoints, PRNG );
+
+ // prepare BGR image
+ size_t DataSize = 3 * ImageSize * ImageSize;
+
+ unsigned char* Img = new unsigned char[ DataSize ];
+
+ memset( Img, 0, DataSize );
+
+ for ( auto i = Points.begin(); i != Points.end(); i++ )
+ {
+ int x = int( i->x * ImageSize );
+ int y = int( i->y * ImageSize );
+ if ( g_DensityMap )
+ {
+ // dice
+ float R = PRNG.RandomFloat();
+ float P = g_DensityMap[ x + y * ImageSize ];
+ if ( R > P ) continue;
+ }
+ int Base = 3 * (x + y * ImageSize);
+ Img[ Base+0 ] = Img[ Base+1 ] = Img[ Base+2 ] = 255;
+ }
+
+ SaveBMP( "Poisson.bmp", Img, ImageSize, ImageSize );
+
+ delete[]( Img );
+
+ // dump points to a text file
+ std::ofstream File( "Poisson.txt", std::ios::out );
+
+ File << "NumPoints = " << Points.size() << std::endl;
+
+ for ( const auto& p : Points )
+ {
+ File << "X = " << p.x << "; Y = " << p.y << std::endl;
+ }
+
+ return 0;
+}
diff --git a/application/src/PoissonGenerator.h b/application/src/PoissonGenerator.h
new file mode 100644
index 0000000..297c9aa
--- /dev/null
+++ b/application/src/PoissonGenerator.h
@@ -0,0 +1,276 @@
+/**
+ * \file Poisson.cpp
+ * \brief
+ *
+ * Poisson Disk Points Generator
+ *
+ * \version 1.1.3
+ * \date 10/03/2016
+ * \author Sergey Kosarevsky, 2014-2016
+ * \author support@linderdaum.com http://www.linderdaum.com http://blog.linderdaum.com
+ */
+
+/*
+ Usage example:
+
+ PoissonGenerator::DefaultPRNG PRNG;
+ const auto Points = PoissonGenerator::GeneratePoissonPoints( NumPoints, PRNG );
+*/
+
+// Fast Poisson Disk Sampling in Arbitrary Dimensions
+// http://people.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf
+
+// Implementation based on http://devmag.org.za/2009/05/03/poisson-disk-sampling/
+
+/* Versions history:
+ * 1.1.3 Mar 10, 2016 Header-only library, no global mutable state
+ * 1.1.2 Apr 9, 2015 Output a text file with XY coordinates
+ * 1.1.1 May 23, 2014 Initialize PRNG seed, fixed uninitialized fields
+ * 1.1 May 7, 2014 Support of density maps
+ * 1.0 May 6, 2014
+*/
+
+#include
+#include
+#include
+#include
+
+#define POISSON_PROGRESS_INDICATOR 1
+
+namespace PoissonGenerator
+{
+
+const char* Version = "1.1.3 (10/03/2016)";
+
+class DefaultPRNG
+{
+public:
+ DefaultPRNG()
+ : m_RD()
+ , m_Gen( m_RD() )
+ , m_Dis( 0.0f, 1.0f )
+ {
+ // prepare PRNG
+ m_Gen.seed( time( nullptr ) );
+ }
+
+ float RandomFloat()
+ {
+ return static_cast( m_Dis( m_Gen ) );
+ }
+
+ int RandomInt( int Max )
+ {
+ std::uniform_int_distribution<> DisInt( 0, Max );
+ return DisInt( m_Gen );
+ }
+
+private:
+ std::random_device m_RD;
+ std::mt19937 m_Gen;
+ std::uniform_real_distribution m_Dis;
+};
+
+struct sPoint
+{
+ sPoint()
+ : x( 0 )
+ , y( 0 )
+ , m_Valid( false )
+ {}
+ sPoint( float X, float Y )
+ : x( X )
+ , y( Y )
+ , m_Valid( true )
+ {}
+ float x;
+ float y;
+ bool m_Valid;
+ //
+ bool IsInRectangle() const
+ {
+ return x >= 0 && y >= 0 && x <= 1 && y <= 1;
+ }
+ //
+ bool IsInCircle() const
+ {
+ float fx = x - 0.5f;
+ float fy = y - 0.5f;
+ return ( fx*fx + fy*fy ) <= 0.25f;
+ }
+};
+
+struct sGridPoint
+{
+ sGridPoint( int X, int Y )
+ : x( X )
+ , y( Y )
+ {}
+ int x;
+ int y;
+};
+
+float GetDistance( const sPoint& P1, const sPoint& P2 )
+{
+ return sqrt( ( P1.x - P2.x ) * ( P1.x - P2.x ) + ( P1.y - P2.y ) * ( P1.y - P2.y ) );
+}
+
+sGridPoint ImageToGrid( const sPoint& P, float CellSize )
+{
+ return sGridPoint( ( int )( P.x / CellSize ), ( int )( P.y / CellSize ) );
+}
+
+struct sGrid
+{
+ sGrid( int W, int H, float CellSize )
+ : m_W( W )
+ , m_H( H )
+ , m_CellSize( CellSize )
+ {
+ m_Grid.resize( m_H );
+
+ for ( auto i = m_Grid.begin(); i != m_Grid.end(); i++ ) { i->resize( m_W ); }
+ }
+ void Insert( const sPoint& P )
+ {
+ sGridPoint G = ImageToGrid( P, m_CellSize );
+ m_Grid[ G.x ][ G.y ] = P;
+ }
+ bool IsInNeighbourhood( sPoint Point, float MinDist, float CellSize )
+ {
+ sGridPoint G = ImageToGrid( Point, CellSize );
+
+ // number of adjucent cells to look for neighbour points
+ const int D = 5;
+
+ // scan the neighbourhood of the point in the grid
+ for ( int i = G.x - D; i < G.x + D; i++ )
+ {
+ for ( int j = G.y - D; j < G.y + D; j++ )
+ {
+ if ( i >= 0 && i < m_W && j >= 0 && j < m_H )
+ {
+ sPoint P = m_Grid[ i ][ j ];
+
+ if ( P.m_Valid && GetDistance( P, Point ) < MinDist ) { return true; }
+ }
+ }
+ }
+
+
+ return false;
+ }
+
+private:
+ int m_W;
+ int m_H;
+ float m_CellSize;
+
+ std::vector< std::vector > m_Grid;
+};
+
+template
+sPoint PopRandom( std::vector& Points, PRNG& Generator )
+{
+ const int Idx = Generator.RandomInt( Points.size()-1 );
+ const sPoint P = Points[ Idx ];
+ Points.erase( Points.begin() + Idx );
+ return P;
+}
+
+template
+sPoint GenerateRandomPointAround( const sPoint& P, float MinDist, PRNG& Generator )
+{
+ // start with non-uniform distribution
+ float R1 = Generator.RandomFloat();
+ float R2 = Generator.RandomFloat();
+
+ // radius should be between MinDist and 2 * MinDist
+ float Radius = MinDist * ( R1 + 1.0f );
+
+ // random angle
+ float Angle = 2 * 3.141592653589f * R2;
+
+ // the new point is generated around the point (x, y)
+ float X = P.x + Radius * cos( Angle );
+ float Y = P.y + Radius * sin( Angle );
+
+ return sPoint( X, Y );
+}
+
+/**
+ Return a vector of generated points
+
+ NewPointsCount - refer to bridson-siggraph07-poissondisk.pdf for details (the value 'k')
+ Circle - 'true' to fill a circle, 'false' to fill a rectangle
+ MinDist - minimal distance estimator, use negative value for default
+**/
+template
+std::vector GeneratePoissonPoints(
+ size_t NumPoints,
+ PRNG& Generator,
+ int NewPointsCount = 30,
+ bool Circle = true,
+ float MinDist = -1.0f
+)
+{
+ if ( MinDist < 0.0f )
+ {
+ MinDist = sqrt( float(NumPoints) ) / float(NumPoints);
+ }
+
+ std::vector SamplePoints;
+ std::vector ProcessList;
+
+ // create the grid
+ float CellSize = MinDist / sqrt( 2.0f );
+
+ int GridW = ( int )ceil( 1.0f / CellSize );
+ int GridH = ( int )ceil( 1.0f / CellSize );
+
+ sGrid Grid( GridW, GridH, CellSize );
+
+ sPoint FirstPoint;
+ do {
+ FirstPoint = sPoint( Generator.RandomFloat(), Generator.RandomFloat() );
+ } while (!(Circle ? FirstPoint.IsInCircle() : FirstPoint.IsInRectangle()));
+
+ // update containers
+ ProcessList.push_back( FirstPoint );
+ SamplePoints.push_back( FirstPoint );
+ Grid.Insert( FirstPoint );
+
+ // generate new points for each point in the queue
+ while ( !ProcessList.empty() && SamplePoints.size() < NumPoints )
+ {
+#if POISSON_PROGRESS_INDICATOR
+ // a progress indicator, kind of
+ if ( SamplePoints.size() % 100 == 0 ) std::cout << ".";
+#endif // POISSON_PROGRESS_INDICATOR
+
+ sPoint Point = PopRandom( ProcessList, Generator );
+
+ for ( int i = 0; i < NewPointsCount; i++ )
+ {
+ sPoint NewPoint = GenerateRandomPointAround( Point, MinDist, Generator );
+
+ bool Fits = Circle ? NewPoint.IsInCircle() : NewPoint.IsInRectangle();
+
+ if ( Fits && !Grid.IsInNeighbourhood( NewPoint, MinDist, CellSize ) )
+ {
+ ProcessList.push_back( NewPoint );
+ SamplePoints.push_back( NewPoint );
+ Grid.Insert( NewPoint );
+ continue;
+ }
+ }
+ }
+
+#if POISSON_PROGRESS_INDICATOR
+ std::cout << std::endl << std::endl;
+#endif // POISSON_PROGRESS_INDICATOR
+
+ return SamplePoints;
+}
+
+} // namespace PoissonGenerator
diff --git a/application/src/Shader.h b/application/src/Shader.h
new file mode 100644
index 0000000..f4a8913
--- /dev/null
+++ b/application/src/Shader.h
@@ -0,0 +1,113 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "GLUtils.h"
+
+struct Shader
+{
+ GLuint vertexShader;
+ GLuint fragmentShader;
+ GLuint program;
+ GLint uModel;
+ GLint uView;
+ GLint uProjection;
+ GLint uEye;
+ GLint uLightPosition;
+ GLint uLightIntensity;
+ GLint uLightColor;
+
+ Shader(const std::string& vertexShaderFilename, const std::string& fragmentShaderFilename)
+ {
+ std::fstream fileStream(vertexShaderFilename);
+ if (!fileStream.is_open())
+ {
+ std::cout << "cannot open vertex shader file (" << vertexShaderFilename << ")" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ std::string fileContent((std::istreambuf_iterator(fileStream)), std::istreambuf_iterator());
+ const char* pVertexSource = fileContent.c_str();
+ fileStream.close();
+
+ vertexShader = glCreateShader(GL_VERTEX_SHADER);
+ glShaderSource(vertexShader, 1, &pVertexSource, NULL);
+ glCompileShader(vertexShader);
+
+ checkCompileError(vertexShader, vertexShaderFilename);
+
+ fileStream.open(fragmentShaderFilename);
+ if (!fileStream.is_open())
+ {
+ std::cout << "cannot open fragment shader file (" << fragmentShaderFilename << ")" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ fileContent = std::string((std::istreambuf_iterator(fileStream)), std::istreambuf_iterator());
+ const char* pFragmentSource = fileContent.c_str();
+
+ fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
+ glShaderSource(fragmentShader, 1, &pFragmentSource, NULL);
+ glCompileShader(fragmentShader);
+
+ checkCompileError(fragmentShader, fragmentShaderFilename);
+
+ program = glCreateProgram();
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, fragmentShader);
+ glBindFragDataLocation(program, 0, "outColor");
+ glLinkProgram(program);
+
+ checkLinkError(vertexShaderFilename, fragmentShaderFilename);
+ }
+
+ virtual ~Shader()
+ {
+ glDeleteProgram(program);
+ glDeleteShader(fragmentShader);
+ glDeleteShader(vertexShader);
+ }
+
+ operator GLuint()
+ {
+ return program;
+ }
+
+private:
+ void checkLinkError(const std::string& fileName1, const std::string& fileName2)
+ {
+ GLint isLinked = 0;
+ glGetProgramiv(program, GL_LINK_STATUS, &isLinked);
+ if (isLinked == GL_FALSE)
+ {
+ GLint maxLength = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
+ std::vector infoLog(maxLength);
+ glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);
+ std::cout << "error linking " << fileName1 << " and " << fileName2 << std::endl;
+ glDeleteProgram(program);
+ }
+ }
+
+ void checkCompileError(GLuint shader, const std::string& fileName)
+ {
+ GLint success = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
+ if (success != GL_TRUE)
+ {
+ std::cout << "error compiling " << fileName << std::endl;
+ GLint logSize = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logSize);
+ std::vector errorLog(logSize);
+ glGetShaderInfoLog(shader, logSize, &logSize, &errorLog[0]);
+ for (int i = 0; i < logSize; i++) std::cout << errorLog[i];
+ std::cout << std::endl;
+ std::cin.get();
+ }
+ }
+
+};
+
diff --git a/application/src/main.cpp b/application/src/main.cpp
new file mode 100644
index 0000000..813a19f
--- /dev/null
+++ b/application/src/main.cpp
@@ -0,0 +1,1110 @@
+#define GLEW_STATIC
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include