Explorar o código

Atomic Glow Work

Josh Engebretson %!s(int64=8) %!d(string=hai) anos
pai
achega
865cfca24a
Modificáronse 100 ficheiros con 9927 adicións e 79 borrados
  1. 5 1
      Build/Scripts/BuildLinux.js
  2. 5 1
      Build/Scripts/BuildMac.js
  3. 5 0
      Build/Scripts/BuildWindows.js
  4. 1 1
      Build/Scripts/Windows/CompileAtomicEditorPhase2.bat
  5. 10 3
      Resources/CoreData/Shaders/GLSL/LitSolid.glsl
  6. 7 0
      Resources/CoreData/Shaders/GLSL/Transform.glsl
  7. 3 0
      Resources/CoreData/Shaders/GLSL/Uniforms.glsl
  8. 9 3
      Resources/CoreData/Shaders/HLSL/LitSolid.hlsl
  9. 7 0
      Resources/CoreData/Shaders/HLSL/Transform.hlsl
  10. 3 0
      Resources/CoreData/Shaders/HLSL/Uniforms.hlsl
  11. 9 0
      Resources/EditorData/AtomicEditor/editor/ui/atomicglowoutput.tb.txt
  12. 1 1
      Resources/EditorData/AtomicEditor/editor/ui/colorchooser.tb.txt
  13. 1 1
      Script/AtomicEditor/ui/frames/inspector/CreateComponentButton.ts
  14. 8 2
      Script/AtomicEditor/ui/frames/inspector/ModelInspector.ts
  15. 14 0
      Script/AtomicEditor/ui/frames/inspector/SelectionInspector.ts
  16. 1 1
      Script/AtomicEditor/ui/frames/inspector/SelectionSection.ts
  17. 53 4
      Script/AtomicEditor/ui/frames/inspector/SelectionSectionCoreUI.ts
  18. 4 1
      Script/AtomicEditor/ui/frames/inspector/SelectionSectionUI.ts
  19. 101 0
      Script/AtomicEditor/ui/modal/glow/GlowOutput.ts
  20. 5 4
      Script/Packages/Editor/Editor.json
  21. 28 0
      Source/Atomic/Core/Thread.cpp
  22. 4 0
      Source/Atomic/Core/Thread.h
  23. 18 0
      Source/Atomic/Core/WorkQueue.cpp
  24. 7 0
      Source/Atomic/Core/WorkQueue.h
  25. 14 0
      Source/Atomic/Engine/Engine.cpp
  26. 1 0
      Source/Atomic/Engine/EngineDefs.h
  27. 26 0
      Source/Atomic/Graphics/Batch.cpp
  28. 15 2
      Source/Atomic/Graphics/Batch.h
  29. 5 1
      Source/Atomic/Graphics/Drawable.cpp
  30. 6 0
      Source/Atomic/Graphics/Drawable.h
  31. 3 2
      Source/Atomic/Graphics/Graphics.cpp
  32. 4 0
      Source/Atomic/Graphics/GraphicsDefs.cpp
  33. 4 0
      Source/Atomic/Graphics/GraphicsDefs.h
  34. 4 0
      Source/Atomic/Graphics/LMStaticModel.cpp
  35. 4 0
      Source/Atomic/Graphics/LMStaticModel.h
  36. 27 1
      Source/Atomic/Graphics/StaticModel.cpp
  37. 23 0
      Source/Atomic/Graphics/StaticModel.h
  38. 1 1
      Source/Atomic/IPC/IPC.h
  39. 12 0
      Source/Atomic/IPC/IPCEvents.h
  40. 8 1
      Source/Atomic/IPC/IPCServer.cpp
  41. 7 12
      Source/Atomic/IPC/IPCServer.h
  42. 10 0
      Source/Atomic/Math/Random.cpp
  43. 5 0
      Source/Atomic/Math/Random.h
  44. 49 0
      Source/Atomic/Math/Vector3.h
  45. 98 0
      Source/Atomic/Scene/Scene.cpp
  46. 13 0
      Source/Atomic/Scene/Scene.h
  47. 4 0
      Source/AtomicApp/IPCClientApp.h
  48. 15 1
      Source/AtomicEditor/Application/AEEditorApp.cpp
  49. 2 0
      Source/AtomicEditor/Components/EditorComponents.cpp
  50. 156 0
      Source/AtomicEditor/Components/GlowComponent.cpp
  51. 102 0
      Source/AtomicEditor/Components/GlowComponent.h
  52. 314 0
      Source/AtomicGlow/Atlas/MeshLightmapUVGen.cpp
  53. 70 0
      Source/AtomicGlow/Atlas/MeshLightmapUVGen.h
  54. 532 0
      Source/AtomicGlow/Atlas/ModelPacker.cpp
  55. 109 0
      Source/AtomicGlow/Atlas/ModelPacker.h
  56. 46 0
      Source/AtomicGlow/CMakeLists.txt
  57. 40 0
      Source/AtomicGlow/Common/GlowEvents.h
  58. 30 0
      Source/AtomicGlow/Common/GlowSettings.cpp
  59. 241 0
      Source/AtomicGlow/Common/GlowSettings.h
  60. 339 0
      Source/AtomicGlow/GlowApplication/AtomicGlowApp.cpp
  61. 66 0
      Source/AtomicGlow/GlowApplication/AtomicGlowApp.h
  62. 192 0
      Source/AtomicGlow/GlowService/GlowProcess.cpp
  63. 88 0
      Source/AtomicGlow/GlowService/GlowProcess.h
  64. 320 0
      Source/AtomicGlow/GlowService/GlowService.cpp
  65. 80 0
      Source/AtomicGlow/GlowService/GlowService.h
  66. 44 0
      Source/AtomicGlow/GlowService/GlowServiceEvents.h
  67. 662 0
      Source/AtomicGlow/Kernel/BakeLight.cpp
  68. 502 0
      Source/AtomicGlow/Kernel/BakeLight.h
  69. 167 0
      Source/AtomicGlow/Kernel/BakeMaterial.cpp
  70. 92 0
      Source/AtomicGlow/Kernel/BakeMaterial.h
  71. 611 0
      Source/AtomicGlow/Kernel/BakeMesh.cpp
  72. 222 0
      Source/AtomicGlow/Kernel/BakeMesh.h
  73. 97 0
      Source/AtomicGlow/Kernel/BakeModel.cpp
  74. 68 0
      Source/AtomicGlow/Kernel/BakeModel.h
  75. 41 0
      Source/AtomicGlow/Kernel/BakeNode.cpp
  76. 58 0
      Source/AtomicGlow/Kernel/BakeNode.h
  77. 26 0
      Source/AtomicGlow/Kernel/Embree.h
  78. 91 0
      Source/AtomicGlow/Kernel/EmbreeScene.cpp
  79. 72 0
      Source/AtomicGlow/Kernel/EmbreeScene.h
  80. 43 0
      Source/AtomicGlow/Kernel/GlowTypes.h
  81. 42 0
      Source/AtomicGlow/Kernel/LightMap.cpp
  82. 64 0
      Source/AtomicGlow/Kernel/LightMap.h
  83. 357 0
      Source/AtomicGlow/Kernel/LightMapPacker.cpp
  84. 75 0
      Source/AtomicGlow/Kernel/LightMapPacker.h
  85. 73 0
      Source/AtomicGlow/Kernel/LightRay.cpp
  86. 85 0
      Source/AtomicGlow/Kernel/LightRay.h
  87. 341 0
      Source/AtomicGlow/Kernel/Photons.cpp
  88. 265 0
      Source/AtomicGlow/Kernel/Photons.h
  89. 339 0
      Source/AtomicGlow/Kernel/RadianceMap.cpp
  90. 60 0
      Source/AtomicGlow/Kernel/RadianceMap.h
  91. 708 0
      Source/AtomicGlow/Kernel/Raster.cpp
  92. 28 0
      Source/AtomicGlow/Kernel/Raster.h
  93. 703 0
      Source/AtomicGlow/Kernel/SceneBaker.cpp
  94. 125 0
      Source/AtomicGlow/Kernel/SceneBaker.h
  95. 46 34
      Source/AtomicTool/AtomicTool.cpp
  96. 4 0
      Source/AtomicTool/CMakeLists.txt
  97. 7 0
      Source/CMakeLists.txt
  98. 4 0
      Source/ThirdParty/CMakeLists.txt
  99. 4 1
      Source/ThirdParty/STB/stb_image_write.h
  100. 407 0
      Source/ThirdParty/STB/stb_rect_pack.c

+ 5 - 1
Build/Scripts/BuildLinux.js

@@ -67,6 +67,10 @@ function copyAtomicEditor() {
 
     copyAtomicNET();
 
+    // copy AtomicGlow
+    fs.copySync(atomicRoot + "Artifacts/Build/AtomicGlow/AtomicGlow",
+    editorAppFolder + "Resources/ToolData/AtomicGlow/AtomicGlow");
+
 }
 
 namespace('build', function() {
@@ -77,7 +81,7 @@ namespace('build', function() {
 
         process.chdir(buildDir);
 
-        var cmds = ["make AtomicEditor AtomicPlayer -j2"];
+        var cmds = ["make AtomicGlow AtomicEditor AtomicPlayer -j2"];
 
         jake.exec(cmds, function() {
 

+ 5 - 1
Build/Scripts/BuildMac.js

@@ -46,6 +46,10 @@ function copyAtomicEditor() {
 
     fs.copySync(playerBinary, resourceDest + "ToolData/Deployment/MacOS/AtomicPlayer.app/Contents/MacOS/AtomicPlayer");
 
+    // copy AtomicGlow
+    fs.copySync(atomicRoot + "Artifacts/Build/AtomicGlow/AtomicGlow",
+    resourceDest + "ToolData/AtomicGlow/AtomicGlow");
+
     copyAtomicNET();
 
 }
@@ -59,7 +63,7 @@ namespace('build', function() {
         process.chdir(buildDir);
 
         var cmds = [];
-        cmds.push("xcodebuild -target AtomicEditor -target AtomicPlayer -configuration " + config["config"] + " -parallelizeTargets -jobs 4")
+        cmds.push("xcodebuild -target AtomicGlow -target AtomicEditor -target AtomicPlayer -configuration " + config["config"] + " -parallelizeTargets -jobs 4")
 
         jake.exec(cmds, function() {
 

+ 5 - 0
Build/Scripts/BuildWindows.js

@@ -53,6 +53,11 @@ function copyAtomicEditor() {
     fs.copySync(buildDir +  "Source/AtomicPlayer/Application/" + config["config"] + "/D3DCompiler_47.dll",
     editorAppFolder + "Resources/ToolData/Deployment/Windows/x64/D3DCompiler_47.dll");
 
+    // copy AtomicGlow
+    // TODO: make sure Glow isn't dependent on D3DCompiler_47.dll
+    fs.copySync(atomicRoot + "Artifacts/Build/AtomicGlow/AtomicGlow.exe",
+    editorAppFolder + "Resources/ToolData/AtomicGlow/AtomicGlow.exe");
+
     copyAtomicNET();
 
 }

+ 1 - 1
Build/Scripts/Windows/CompileAtomicEditorPhase2.bat

@@ -7,4 +7,4 @@ if not defined ATOMIC_CMAKE_GENERATOR (
   exit /b 1
 )
 
-msbuild /m Atomic.sln /t:AtomicEditor /t:AtomicPlayer /p:Configuration=%1 /p:Platform=x64
+msbuild /m Atomic.sln /t:AtomicGlow /t:AtomicEditor /t:AtomicPlayer /p:Configuration=%1 /p:Platform=x64

+ 10 - 3
Resources/CoreData/Shaders/GLSL/LitSolid.glsl

@@ -86,7 +86,10 @@ void VS()
             // If using lightmap, disregard zone ambient light
             // If using AO, calculate ambient in the PS
             vVertexLight = vec3(0.0, 0.0, 0.0);
-            vTexCoord2 = iTexCoord1;
+            // ATOMIC BEGIN
+            // vTexCoord2 = iTexCoord1;
+            vTexCoord2 = GetLightMapTexCoord(iTexCoord1);
+            // ATOMIC END
         #else
             vVertexLight = GetAmbient(GetZonePos(worldPos));
         #endif
@@ -199,7 +202,9 @@ void PS()
             finalColor += cMatEnvMapColor * textureCube(sEnvCubeMap, reflect(vReflectionVec, normal)).rgb;
         #endif
         #ifdef LIGHTMAP
-            finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * diffColor.rgb;
+            // ATOMIC BEGIN
+            finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * vec3(4, 4, 4) * diffColor.rgb;
+            // ATOMIC END
         #endif
         #ifdef EMISSIVEMAP
             finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb;
@@ -232,7 +237,9 @@ void PS()
             finalColor += cMatEnvMapColor * textureCube(sEnvCubeMap, reflect(vReflectionVec, normal)).rgb;
         #endif
         #ifdef LIGHTMAP
-            finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * diffColor.rgb;
+            // ATOMIC BEGIN
+            finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * vec3(4, 4, 4) * diffColor.rgb;
+            // ATOMIC END
         #endif
         #ifdef EMISSIVEMAP
             finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb;

+ 7 - 0
Resources/CoreData/Shaders/GLSL/Transform.glsl

@@ -53,6 +53,13 @@ vec2 GetTexCoord(vec2 texCoord)
     return vec2(dot(texCoord, cUOffset.xy) + cUOffset.w, dot(texCoord, cVOffset.xy) + cVOffset.w);
 }
 
+// ATOMIC BEGIN
+vec2 GetLightMapTexCoord(vec2 texCoord)
+{
+    return vec2(texCoord.x * cLMOffset.x + cLMOffset.z, texCoord.y * cLMOffset.y + cLMOffset.w);
+}
+// ATOMIC END
+
 vec4 GetClipPos(vec3 worldPos)
 {
     vec4 ret = vec4(worldPos, 1.0) * cViewProj;

+ 3 - 0
Resources/CoreData/Shaders/GLSL/Uniforms.glsl

@@ -28,6 +28,9 @@ uniform mat4 cViewInv;
 uniform mat4 cViewProj;
 uniform vec4 cUOffset;
 uniform vec4 cVOffset;
+// ATOMIC BEGIN
+uniform vec4 cLMOffset;
+// ATOMIC END
 uniform mat4 cZone;
 #if !defined(GL_ES) || defined(WEBGL)
     uniform mat4 cLightMatrices[4];

+ 9 - 3
Resources/CoreData/Shaders/HLSL/LitSolid.hlsl

@@ -118,7 +118,9 @@ void VS(float4 iPos : POSITION,
             // If using lightmap, disregard zone ambient light
             // If using AO, calculate ambient in the PS
             oVertexLight = float3(0.0, 0.0, 0.0);
-            oTexCoord2 = iTexCoord2;
+            // ATOMIC BEGIN
+            oTexCoord2 = GetLightMapTexCoord(iTexCoord2);
+            // ATOMIC END
         #else
             oVertexLight = GetAmbient(GetZonePos(worldPos));
         #endif
@@ -273,7 +275,9 @@ void PS(
             finalColor += cMatEnvMapColor * SampleCube(EnvCubeMap, reflect(iReflectionVec, normal)).rgb;
         #endif
         #ifdef LIGHTMAP
-            finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * diffColor.rgb;
+            // ATOMIC BEGIN
+            finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * float3(4, 4, 4) * diffColor.rgb;
+            // ATOMIC END
         #endif
         #ifdef EMISSIVEMAP
             finalColor += cMatEmissiveColor * Sample2D(EmissiveMap, iTexCoord.xy).rgb;
@@ -306,7 +310,9 @@ void PS(
             finalColor += cMatEnvMapColor * SampleCube(EnvCubeMap, reflect(iReflectionVec, normal)).rgb;
         #endif
         #ifdef LIGHTMAP
-            finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * diffColor.rgb;
+            // ATOMIC BEGIN
+            finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * float3(4, 4, 4) * diffColor.rgb;
+            // ATOMIC END
         #endif
         #ifdef EMISSIVEMAP
             finalColor += cMatEmissiveColor * Sample2D(EmissiveMap, iTexCoord.xy).rgb;

+ 7 - 0
Resources/CoreData/Shaders/HLSL/Transform.hlsl

@@ -21,6 +21,13 @@ float2 GetTexCoord(float2 iTexCoord)
     return float2(dot(iTexCoord, cUOffset.xy) + cUOffset.w, dot(iTexCoord, cVOffset.xy) + cVOffset.w);
 };
 
+// ATOMIC BEGIN
+float2 GetLightMapTexCoord(float2 texCoord)
+{
+    return float2(texCoord.x * cLMOffset.x + cLMOffset.z, texCoord.y * cLMOffset.y + cLMOffset.w);
+}
+// ATOMIC END
+
 float4 GetClipPos(float3 worldPos)
 {
     return mul(float4(worldPos, 1.0), cViewProj);

+ 3 - 0
Resources/CoreData/Shaders/HLSL/Uniforms.hlsl

@@ -27,6 +27,9 @@ uniform float4x3 cViewInv;
 uniform float4x4 cViewProj;
 uniform float4 cUOffset;
 uniform float4 cVOffset;
+// ATOMIC BEGIN
+uniform float4 cLMOffset;
+// ATOMIC END
 uniform float4x3 cZone;
 #ifdef SKINNED
     uniform float4x3 cSkinMatrices[MAXBONES];

+ 9 - 0
Resources/EditorData/AtomicEditor/editor/ui/atomicglowoutput.tb.txt

@@ -0,0 +1,9 @@
+TBLayout: axis: y, distribution: gravity, position: left
+	TBTextField: text: "Output:"
+	TBEditField: multiline: 1, styling: 1, gravity: all, id: output, readonly: 1, adapt-to-content: 0
+		lp: min-width: 640, min-height: 480
+		font: size: 11
+	TBSeparator: gravity: left right, skin: AESeparator
+	TBLayout:
+		TBButton: text: OK, id: ok
+		TBButton: text: Cancel, id: cancel

+ 1 - 1
Resources/EditorData/AtomicEditor/editor/ui/colorchooser.tb.txt

@@ -8,7 +8,7 @@ TBLayout: axis: y, distribution: gravity, position: left
 	TBLayout: axis: x, distribution: gravity
 		TBLayout: axis: x, distribution: gravity
 			TBColorWheel: id: colorwheel, skin: HSVSkin
-					lp: width: 256, height: 256
+				lp: width: 256, height: 256
 			TBSlider: id: lslider, axis: y, min: 0, max: 255, value: 128
 
 		TBLayout: axis: y, distribution: gravity

+ 1 - 1
Script/AtomicEditor/ui/frames/inspector/CreateComponentButton.ts

@@ -95,9 +95,9 @@ subsystemCreateSource.addItem(new Atomic.UIMenuItem("PhysicsWorld", "create comp
 
 var editorCreateSource = new Atomic.UIMenuItemSource();
 
+editorCreateSource.addItem(new Atomic.UIMenuItem("GlowComponent", "GlowComponent"));
 editorCreateSource.addItem(new Atomic.UIMenuItem("CubemapGenerator", "CubemapGenerator"));
 
-
 var componentCreateSource = new Atomic.UIMenuItemSource();
 
 var sources = {

+ 8 - 2
Script/AtomicEditor/ui/frames/inspector/ModelInspector.ts

@@ -43,9 +43,11 @@ class ModelInspector extends InspectorWidget {
     onApply() {
 
         this.importer.scale = Number(this.scaleEdit.text);
+        this.importer.generateLightmapUV = this.genLightmapUVBox.value ? true : false;
 
         this.importer.importAnimations = this.importAnimationBox.value ? true : false;
         this.importer.setImportMaterials(this.importMaterials.value ? true : false);
+        
 
         for (var i = 0; i < this.importer.animationCount; i++) {
 
@@ -101,17 +103,20 @@ class ModelInspector extends InspectorWidget {
         }));
 
         this.scaleEdit = InspectorUtils.createAttrEditField("Scale", modelLayout);
-        this.scaleEdit.text = this.importer.scale.toString();
+        this.scaleEdit.text = this.importer.scale.toString();        
 
         this.importMaterials = this.createAttrCheckBox("Import Materials", modelLayout);
         this.importMaterials.value = this.importer.getImportMaterials() ? 1 : 0;
 
+        this.genLightmapUVBox = this.createAttrCheckBox("Generate Lightmap UV", modelLayout);
+        this.genLightmapUVBox.value = this.importer.generateLightmapUV ? 1 : 0;
+
         // Animations Section
         var animationLayout = this.createSection(rootLayout, "Animation", 1);
 
         this.importAnimationBox = this.createAttrCheckBox("Import Animations", animationLayout);
         this.importAnimationBox.value = this.importer.importAnimations ? 1 : 0;
-
+        
         this.importAnimationArray = new ArrayEditWidget("Animation Count");
         animationLayout.addChild(this.importAnimationArray);
 
@@ -193,6 +198,7 @@ class ModelInspector extends InspectorWidget {
 
     // model
     scaleEdit: Atomic.UIEditField;
+    genLightmapUVBox: Atomic.UICheckBox;
 
     // animation
     importAnimationBox: Atomic.UICheckBox;

+ 14 - 0
Script/AtomicEditor/ui/frames/inspector/SelectionInspector.ts

@@ -470,6 +470,20 @@ class SelectionInspector extends ScriptWidget {
 
     }
 
+    getSection(editType: SerializableEditType) {
+
+        for (var i in this.sections) {
+
+            var section = this.sections[i];
+            if (section.editType == editType)
+                return section;
+        
+        }
+
+        return null;
+
+    }    
+
     removeSection(section: SelectionSection) {
 
         SelectionInspector.sectionStates[section.editType.typeName] = section.value ? true : false;

+ 1 - 1
Script/AtomicEditor/ui/frames/inspector/SelectionSection.ts

@@ -177,7 +177,7 @@ abstract class SelectionSection extends Atomic.UISection {
         if (SelectionSection.customSectionUI[this.editType.typeName]) {
 
             this.customUI = new SelectionSection.customSectionUI[this.editType.typeName]();
-            this.customUI.createUI(this.editType);
+            this.customUI.createUI(this.editType, this);
             attrLayout.addChild(this.customUI);
 
         }

+ 53 - 4
Script/AtomicEditor/ui/frames/inspector/SelectionSectionCoreUI.ts

@@ -28,12 +28,13 @@ import SerializableEditType = require("./SerializableEditType");
 
 import ProgressModal = require("ui/modal/ProgressModal");
 
+import GlowOutput = require("ui/modal/glow/GlowOutput");
 
 class CollisionShapeSectionUI extends SelectionSectionUI {
 
-    createUI(editType: SerializableEditType) {
+    createUI(editType: SerializableEditType, selectionSection: SelectionSection) {
 
-        this.editType = editType;
+        super.createUI(editType, selectionSection);
 
         var button = new Atomic.UIButton();
         button.fontDescription = InspectorUtils.attrFontDesc;
@@ -64,9 +65,9 @@ class CollisionShapeSectionUI extends SelectionSectionUI {
 
 class CubemapGeneratorSectionUI extends SelectionSectionUI {
 
-    createUI(editType: SerializableEditType) {
+    createUI(editType: SerializableEditType, selectionSection: SelectionSection) {
 
-        this.editType = editType;
+        super.createUI(editType, selectionSection);
 
         var button = new Atomic.UIButton();
         button.fontDescription = InspectorUtils.attrFontDesc;
@@ -132,5 +133,53 @@ class CubemapGeneratorSectionUI extends SelectionSectionUI {
 
 }
 
+class GlowComponentSectionUI extends SelectionSectionUI {
+
+    createUI(editType: SerializableEditType, selectionSection: SelectionSection) {
+
+        super.createUI(editType, selectionSection);
+
+        var button = new Atomic.UIButton();
+        button.fontDescription = InspectorUtils.attrFontDesc;
+        button.gravity = Atomic.UI_GRAVITY.UI_GRAVITY_RIGHT;
+        button.text = "Bake";
+
+        button.onClick = () => {
+
+            var glow = <Editor.GlowComponent>this.editType.objects[0];
+
+            var glowOutput:GlowOutput;
+
+            glow.subscribeToEvent(Editor.AtomicGlowBakeResultEvent((evData:Editor.AtomicGlowBakeResultEvent) => {
+
+                if (glowOutput) {
+                    glowOutput.hide();
+                    glowOutput.close();                    
+                }
+
+                glowOutput = null;
+
+                glow.unsubscribeFromEvent(Editor.AtomicGlowBakeResultEventType);
+
+            }));
+
+            if (glow.bake()) {
+
+                this.selectionSection.refresh();
+                glowOutput = new GlowOutput();
+                glowOutput.show();
+            }
+
+
+        };
+
+        this.addChild(button);
+
+    }
+
+}
+
+
 SelectionSection.registerCustomSectionUI("CollisionShape", CollisionShapeSectionUI);
 SelectionSection.registerCustomSectionUI("CubemapGenerator", CubemapGeneratorSectionUI);
+SelectionSection.registerCustomSectionUI("GlowComponent", GlowComponentSectionUI);

+ 4 - 1
Script/AtomicEditor/ui/frames/inspector/SelectionSectionUI.ts

@@ -21,17 +21,20 @@
 //
 
 import SerializableEditType = require("./SerializableEditType");
+import SelectionSection = require("./SelectionSection");
 
 class SelectionSectionUI extends Atomic.UILayout {
 
     editType: SerializableEditType;
+    selectionSection: SelectionSection;
 
     refresh() {
 
     }
 
-    createUI(editType: SerializableEditType) {
+    createUI(editType: SerializableEditType, selectionSection: SelectionSection) {
 
+      this.selectionSection = selectionSection;
       this.editType = editType;
 
     }

+ 101 - 0
Script/AtomicEditor/ui/modal/glow/GlowOutput.ts

@@ -0,0 +1,101 @@
+//
+// Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+import EditorUI = require("../../EditorUI");
+
+
+class GlowOutput extends Atomic.UIWindow {
+
+    constructor() {
+
+        super();
+
+        this.settings = Atomic.UI_WINDOW_SETTINGS.UI_WINDOW_SETTINGS_DEFAULT & ~Atomic.UI_WINDOW_SETTINGS.UI_WINDOW_SETTINGS_CLOSE_BUTTON;
+
+        this.text = "Atomic Glow";
+        this.load("AtomicEditor/editor/ui/atomicglowoutput.tb.txt");
+
+        this.outputField = <Atomic.UIEditField>this.getWidget("output");
+
+        this.resizeToFitContent();
+        this.center();
+
+        this.subscribeToEvent(Editor.AtomicGlowLogEvent((ev: Editor.AtomicGlowLogEvent) => {
+
+            this.textOutput += ev.message + "\n";
+            this.outputField.text = this.textOutput;
+            this.outputField.scrollTo(0, 0xffffff);
+
+        }));
+
+        this.subscribeToEvent(this, Atomic.UIWidgetEvent((data) => this.handleWidgetEvent(data)));
+
+        this.dimmer = new Atomic.UIDimmer();
+
+    }
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
+
+        if (ev.type == Atomic.UI_EVENT_TYPE.UI_EVENT_TYPE_CLICK) {
+
+            if (ev.target.id == "cancel") {
+                this.sendEvent(Editor.AtomicGlowBakeCancelEventType);
+                return true;
+            }
+
+            if (ev.target.id == "ok") {
+                return true;
+            }
+
+        }
+
+        return false;
+    }
+
+    show() {
+
+        var view = EditorUI.getView();
+        view.addChild(this.dimmer);
+        view.addChild(this);
+
+    }
+
+    hide() {
+
+        this.unsubscribeFromEvent(Editor.AtomicGlowLogEventType);
+
+        if (this.dimmer.parent)
+            this.dimmer.parent.removeChild(this.dimmer, false);
+
+        if (this.parent)
+            this.parent.removeChild(this, false);
+
+    }
+
+    dimmer: Atomic.UIDimmer;
+    
+    textOutput: string = "";
+    outputField: Atomic.UIEditField;
+
+}
+
+export = GlowOutput;

+ 5 - 4
Script/Packages/Editor/Editor.json

@@ -2,9 +2,10 @@
 	"name" : "Editor",
 	"includes" : ["<Atomic/Graphics/DebugRenderer.h>", "<AtomicWebView/UIWebView.h>"],
 	"sources" : ["Source/AtomicEditor/Application", "Source/AtomicEditor/Utils",
-							 "Source/AtomicEditor/EditorMode", "Source/AtomicEditor/PlayerMode",
-							 "Source/AtomicEditor/Editors", "Source/AtomicEditor/Editors/SceneEditor3D",
-						   "Source/AtomicEditor/Components"],
+				 "Source/AtomicEditor/EditorMode", "Source/AtomicEditor/PlayerMode",
+				 "Source/AtomicEditor/Editors", "Source/AtomicEditor/Editors/SceneEditor3D",
+				 "Source/AtomicEditor/Components"],
 	"classes" : ["EditorMode", "PlayerMode", "FileUtils", "ResourceEditor", "JSResourceEditor",
-							 "SceneEditor3D", "SceneView3D", "SceneSelection", "EditorComponent", "CubemapGenerator", "Gizmo3D"]
+				 "SceneEditor3D", "SceneView3D", "SceneSelection", "EditorComponent", "CubemapGenerator", "Gizmo3D",
+			 	 "GlowComponent"]
 }

+ 28 - 0
Source/Atomic/Core/Thread.cpp

@@ -23,6 +23,7 @@
 #include "../Precompiled.h"
 
 #include "../Core/Thread.h"
+#include "../IO/Log.h"
 
 #ifdef _WIN32
 #include <windows.h>
@@ -156,4 +157,31 @@ bool Thread::IsMainThread()
 #endif // ATOMIC_THREADING
 }
 
+// ATOMIC BEGIN
+
+void Thread::Kill()
+{
+#ifdef ATOMIC_THREADING
+#ifdef ATOMIC_PLATFORM_WINDOWS
+    if (handle_)
+    {
+        DWORD exitCode = EXIT_SUCCESS;
+        TerminateThread((HANDLE)handle_, exitCode);
+    }
+#elif defined(ATOMIC_PLATFORM_OSX) 
+
+    ATOMIC_LOGINFO("Thread::Kill() - macOS implementation required");
+
+#elif defined(ATOMIC_PLATFORM_LINUX)
+    pthread_t* thread = (pthread_t*)handle_;
+    if (thread)
+    {
+        pthread_cancel(*thread);
+    }
+#endif
+#endif // ATOMIC_THREADING
+}
+
+// ATOMIC END
+
 }

+ 4 - 0
Source/Atomic/Core/Thread.h

@@ -63,6 +63,10 @@ public:
     /// Return whether is executing in the main thread.
     static bool IsMainThread();
 
+    // ATOMIC BEGIN
+    void Kill();
+    // ATOMIC END
+
 protected:
     /// Thread handle.
     void* handle_;

+ 18 - 0
Source/Atomic/Core/WorkQueue.cpp

@@ -423,4 +423,22 @@ void WorkQueue::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
     PurgePool();
 }
 
+// ATOMIC BEGIN
+
+
+void WorkQueue::TerminateThreads()
+{
+    // this currently works on Windows, may work on Linux, hangs macOS on applicaiton exit
+#ifdef ATOMIC_PLATFORM_WINDOWS
+
+    for (unsigned i = 0; i < threads_.Size(); ++i)
+        threads_[i]->Kill();
+
+    threads_.Clear();
+
+#endif
+}
+
+// ATOMIC END
+
 }

+ 7 - 0
Source/Atomic/Core/WorkQueue.h

@@ -123,6 +123,13 @@ public:
     /// Return how many milliseconds maximum to spend on non-threaded low-priority work.
     int GetNonThreadedWorkMs() const { return maxNonThreadedWorkMs_; }
 
+    // ATOMIC_BEGIN
+
+    /// Terminates all worker threads, only use at exit
+    void TerminateThreads();
+
+    // ATOMIC END
+
 private:
     /// Process work items until shut down. Called by the worker threads.
     void ProcessItems(unsigned threadIndex);

+ 14 - 0
Source/Atomic/Engine/Engine.cpp

@@ -272,6 +272,15 @@ bool Engine::Initialize(const VariantMap& parameters)
     // unpredictable extra synchronization overhead. Also reserve one core for the main thread
 #ifdef ATOMIC_THREADING
     unsigned numThreads = GetParameter(parameters, EP_WORKER_THREADS, true).GetBool() ? GetNumPhysicalCPUs() - 1 : 0;
+
+    // ATOMIC BEGIN
+    if (numThreads)
+    {
+        // check for explicitly set worker thread count
+        numThreads = GetParameter(parameters, EP_WORKER_THREADS_COUNT, numThreads).GetUInt();
+    }
+    // ATOMIC END
+
     if (numThreads)
     {
         GetSubsystem<WorkQueue>()->CreateThreads(numThreads);
@@ -1031,6 +1040,11 @@ VariantMap Engine::ParseParameters(const Vector<String>& arguments)
             else if (argument == "-autometrics") // --autometrics
             {
                 ret[EP_AUTO_METRICS] = true;
+            }            
+            else if (argument == "workerthreadcount" && !value.Empty())
+            {
+                ret[EP_WORKER_THREADS_COUNT] = ToInt(value);
+                ++i;
             }
             // ATOMIC END
 #ifdef ATOMIC_TESTING

+ 1 - 0
Source/Atomic/Engine/EngineDefs.h

@@ -78,5 +78,6 @@ static const String EP_WINDOW_MAXIMIZED = "WindowMaximized";
 static const String EP_AUTO_METRICS = "AutoMetrics";
 static const String EP_PROFILER_LISTEN = "ProfilerListen";
 static const String EP_PROFILER_PORT = "ProfilerPort";
+static const String EP_WORKER_THREADS_COUNT = "WorkerThreadsCount";
 // ATOMIC END
 }

+ 26 - 0
Source/Atomic/Graphics/Batch.cpp

@@ -186,6 +186,10 @@ void Batch::Prepare(View* view, Camera* camera, bool setModelTransform, bool all
     Light* light = lightQueue_ ? lightQueue_->light_ : 0;
     Texture2D* shadowMap = lightQueue_ ? lightQueue_->shadowMap_ : 0;
 
+    // ATOMIC BEGIN
+    Scene* scene = view->GetScene();
+    // ATOMIC END
+
     // Set shaders first. The available shader parameters and their register/uniform positions depend on the currently set shaders
     graphics->SetShaders(vertexShader_, pixelShader_);
 
@@ -260,6 +264,13 @@ void Batch::Prepare(View* view, Camera* camera, bool setModelTransform, bool all
         }
     }
 
+    // ATOMIC BEGIN
+    if (lightmapTilingOffset_)
+    {
+        graphics->SetShaderParameter(VSP_LMOFFSET, *lightmapTilingOffset_);
+    }
+    // ATOMIC END
+
     // Set zone-related shader parameters
     BlendMode blend = graphics->GetBlendMode();
     // If the pass is additive, override fog color to black so that shaders do not need a separate additive path
@@ -603,12 +614,27 @@ void Batch::Prepare(View* view, Camera* camera, bool setModelTransform, bool all
                 graphics->SetShaderParameter(i->first_, i->second_.value_);
         }
 
+        // ATOMIC BEGIN
+
         const HashMap<TextureUnit, SharedPtr<Texture> >& textures = material_->GetTextures();
         for (HashMap<TextureUnit, SharedPtr<Texture> >::ConstIterator i = textures.Begin(); i != textures.End(); ++i)
         {
             if (graphics->HasTextureUnit(i->first_))
+            {
+                if (i->first_ == TU_EMISSIVE && lightmapTilingOffset_)
+                    continue;
+
                 graphics->SetTexture(i->first_, i->second_.Get());
+            }
+        }
+
+        if (lightmapTilingOffset_ && scene)
+        {
+            scene->SetLightmapTexture(lightmapTextureID_);
         }
+
+        // ATOMIC END
+
     }
 
     // Set light-related textures

+ 15 - 2
Source/Atomic/Graphics/Batch.h

@@ -52,7 +52,11 @@ struct Batch
     /// Construct with defaults.
     Batch() :
         isBase_(false),
-        lightQueue_(0)
+        lightQueue_(0),
+        // ATOMIC BEGIN
+        lightmapTilingOffset_(0),
+        lightmapTextureID_(0)
+        // ATOMIC END
     {
     }
 
@@ -67,7 +71,11 @@ struct Batch
         numWorldTransforms_(rhs.numWorldTransforms_),
         instancingData_(rhs.instancingData_),
         lightQueue_(0),
-        geometryType_(rhs.geometryType_)
+        geometryType_(rhs.geometryType_),
+        // ATOMIC BEGIN        
+        lightmapTilingOffset_(rhs.lightmapTilingOffset_),
+        lightmapTextureID_(rhs.lightmapTextureID_)
+        // ATOMIC END
     {
     }
 
@@ -110,6 +118,11 @@ struct Batch
     ShaderVariation* pixelShader_;
     /// %Geometry type.
     GeometryType geometryType_;
+
+    // ATOMIC BEGIN
+     Vector4* lightmapTilingOffset_;
+     unsigned lightmapTextureID_;
+    // ATOMIC END
 };
 
 /// Data for one geometry instance.

+ 5 - 1
Source/Atomic/Graphics/Drawable.cpp

@@ -57,7 +57,11 @@ SourceBatch::SourceBatch() :
     worldTransform_(&Matrix3x4::IDENTITY),
     numWorldTransforms_(1),
     instancingData_((void*)0),
-    geometryType_(GEOM_STATIC)
+    geometryType_(GEOM_STATIC),
+    // ATOMIC BEGIN
+    lightmapTilingOffset_(0),
+    lightmapTextureID_(0)
+    // ATOMIC END
 {
 }
 

+ 6 - 0
Source/Atomic/Graphics/Drawable.h

@@ -104,6 +104,12 @@ struct ATOMIC_API SourceBatch
     void* instancingData_;
     /// %Geometry type.
     GeometryType geometryType_;
+
+    // ATOMIC BEGIN
+    /// Lightmap Tiling Offset
+    Vector4* lightmapTilingOffset_;
+    unsigned lightmapTextureID_;
+    // ATOMIC END
 };
 
 /// Base class for visible components.

+ 3 - 2
Source/Atomic/Graphics/Graphics.cpp

@@ -52,6 +52,9 @@
 #include "../IO/FileSystem.h"
 #include "../IO/Log.h"
 
+// FIXME: Hack
+#include "../Resource/ResourceCache.h"
+
 // ATOMIC BEGIN
 
 #include "Text3D/Text3DFont.h"
@@ -446,8 +449,6 @@ void Graphics::RaiseWindow()
     if (window_)
         SDL_RaiseWindow(window_);
 }
-
-
 // ATOMIC END
 
 }

+ 4 - 0
Source/Atomic/Graphics/GraphicsDefs.cpp

@@ -90,6 +90,10 @@ extern ATOMIC_API const StringHash PSP_LIGHTLENGTH("LightLength");
 extern ATOMIC_API const StringHash PSP_ZONEMIN("ZoneMin");
 extern ATOMIC_API const StringHash PSP_ZONEMAX("ZoneMax");
 
+// ATOMIC BEGIN
+extern ATOMIC_API const StringHash VSP_LMOFFSET("LMOffset");
+// ATOMIC END
+
 extern ATOMIC_API const Vector3 DOT_SCALE(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
 
 extern ATOMIC_API const VertexElement LEGACY_VERTEXELEMENTS[] =

+ 4 - 0
Source/Atomic/Graphics/GraphicsDefs.h

@@ -427,6 +427,10 @@ extern ATOMIC_API const StringHash PSP_LIGHTLENGTH;
 extern ATOMIC_API const StringHash PSP_ZONEMIN;
 extern ATOMIC_API const StringHash PSP_ZONEMAX;
 
+// ATOMIC BEGIN
+extern ATOMIC_API const StringHash VSP_LMOFFSET;
+// ATOMIC END
+
 // Scale calculation from bounding box diagonal.
 extern ATOMIC_API const Vector3 DOT_SCALE;
 

+ 4 - 0
Source/Atomic/Graphics/LMStaticModel.cpp

@@ -8,6 +8,8 @@
 #include "../Graphics/Technique.h"
 #include "LMStaticModel.h"
 
+#ifdef __DISABLED
+
 namespace Atomic
 {
 
@@ -103,4 +105,6 @@ void LMStaticModel::UpdateBatches(const FrameInfo& frame)
 
 }
 
+#endif
+
 

+ 4 - 0
Source/Atomic/Graphics/LMStaticModel.h

@@ -8,6 +8,8 @@
 #include "../Graphics/Texture2D.h"
 #include "../Graphics/Material.h"
 
+#ifdef __DISABLED
+
 namespace Atomic
 {
 
@@ -44,3 +46,5 @@ private:
 };
 
 }
+
+#endif

+ 27 - 1
Source/Atomic/Graphics/StaticModel.cpp

@@ -47,7 +47,14 @@ extern const char* GEOMETRY_CATEGORY;
 StaticModel::StaticModel(Context* context) :
     Drawable(context, DRAWABLE_GEOMETRY),
     occlusionLodLevel_(M_MAX_UNSIGNED),
-    materialsAttr_(Material::GetTypeStatic())
+    materialsAttr_(Material::GetTypeStatic()),
+    // ATOMIC BEGIN
+    lightmap_(false),
+    lightmapScale_(1.0f),
+    lightmapSize_(0),
+    lightmapIndex_(0),
+    lightmapTilingOffset_(1.0f, 1.0f, 0.0f, 0.0f)
+    // ATOMIC END
 {
 }
 
@@ -77,6 +84,13 @@ void StaticModel::RegisterObject(Context* context)
     ATOMIC_ACCESSOR_ATTRIBUTE("Geometry Enabled", GetGeometryEnabledAttr, SetGeometryEnabledAttr, VariantVector,
         Variant::emptyVariantVector, AM_FILE | AM_NOEDIT);
 
+    ATOMIC_ATTRIBUTE("Lightmap", bool, lightmap_, false, AM_DEFAULT);
+    ATOMIC_ATTRIBUTE("Lightmap Scale", float, lightmapScale_, 1.0f, AM_DEFAULT);
+    ATOMIC_ATTRIBUTE("Lightmap Size", unsigned, lightmapSize_, 0, AM_DEFAULT);
+
+    ATOMIC_ATTRIBUTE("Lightmap Index", unsigned, lightmapIndex_, 0, AM_FILE | AM_NOEDIT);
+    ATOMIC_ATTRIBUTE("Lightmap Tiling Offset", Vector4, lightmapTilingOffset_ , Vector4(1.0f, 1.0f, 0.0f, 0.0f), AM_FILE | AM_NOEDIT);
+
     // ATOMIC END
 
 }
@@ -164,7 +178,19 @@ void StaticModel::UpdateBatches(const FrameInfo& frame)
 
     // ATOMIC BEGIN
     if (geometryDisabled_)
+    {
         UpdateBatchesHideGeometry();
+    }
+
+    if (lightmap_)
+    {
+        for (unsigned i = 0; i < batches_.Size(); ++i)
+        {
+            batches_[i].lightmapTextureID_ = lightmapIndex_;
+            batches_[i].geometryType_ = GEOM_STATIC_NOINSTANCING;
+            batches_[i].lightmapTilingOffset_ = &lightmapTilingOffset_;
+        }
+    }
     // ATOMIC END
 
 }

+ 23 - 0
Source/Atomic/Graphics/StaticModel.h

@@ -122,6 +122,21 @@ public:
     void SetGeometryEnabledAttr(const VariantVector& value);
     const VariantVector& GetGeometryEnabledAttr() const;
 
+    bool GetLightmap() const { return lightmap_; }
+    void SetLightmap(bool lightmap) { lightmap_ = lightmap; }
+
+    float GetLightmapScale() const { return lightmapScale_; }
+    void SetLightmapScale(float scale) { lightmapScale_ = scale; }
+
+    unsigned GetLightmapSize() const { return lightmapSize_; }
+    void SetLightmapSize(unsigned size) { lightmapSize_ = size; }
+
+    unsigned GetLightmapIndex() const { return lightmapIndex_; }
+    void SetLightmapIndex(unsigned idx) { lightmapIndex_ = idx; }
+
+    const Vector4& GetLightmapTilingOffset() const { return lightmapTilingOffset_; }
+    void SetLightmapTilingOffset(Vector4 tilingOffset) { lightmapTilingOffset_ = tilingOffset; }
+
     // ATOMIC END
 
 protected:
@@ -155,6 +170,14 @@ protected:
     mutable VariantVector geometryEnabled_;
     /// true if any geometry has been disabled
     mutable bool geometryDisabled_;
+
+    bool lightmap_;
+    float lightmapScale_;
+    unsigned lightmapSize_;
+
+    unsigned lightmapIndex_;
+    Vector4 lightmapTilingOffset_;
+
     // ATOMIC END
 
 private:

+ 1 - 1
Source/Atomic/IPC/IPC.h

@@ -89,7 +89,7 @@ private:
 
     Vector<SharedPtr<IPCBroker> > brokers_;
 
-    // valid on child
+    // valid on child process
     SharedPtr<IPCWorker> worker_;
 
 #ifdef ATOMIC_PLATFORM_WINDOWS

+ 12 - 0
Source/Atomic/IPC/IPCEvents.h

@@ -60,5 +60,17 @@ ATOMIC_EVENT(E_IPCMESSAGE, IPCMessage)
     ATOMIC_PARAM(P_VALUE, Value);  // int
 }
 
+ATOMIC_EVENT(E_IPCCMD, IPCCmd)
+{
+    ATOMIC_PARAM(P_COMMAND, Command); // string
+    ATOMIC_PARAM(P_ID, ID); // unsigned
+}
+
+ATOMIC_EVENT(E_IPCCMDRESULT, IPCCmdResult)
+{
+    ATOMIC_PARAM(P_COMMAND, Command); // string
+    ATOMIC_PARAM(P_ID, ID); // unsigned
+}
+
 
 }

+ 8 - 1
Source/Atomic/IPC/IPCServer.cpp

@@ -69,6 +69,9 @@ namespace Atomic
         SubscribeToEvent(E_UPDATE, ATOMIC_HANDLER(IPCServer, HandleUpdate));
 
         SubscribeToEvent(serverBroker_, E_IPCCMDRESULT, ATOMIC_HANDLER(IPCServer, HandleIPCCmdResult));
+
+        OnIPCWorkerStarted();
+
     }
 
     void IPCServer::HandleIPCWorkerExit(StringHash eventType, VariantMap& eventData)
@@ -77,6 +80,8 @@ namespace Atomic
         {
             serverBroker_ = 0;
             brokerEnabled_ = false;
+
+            OnIPCWorkerExited();
         }
     }
 
@@ -93,6 +98,9 @@ namespace Atomic
 
         SendEvent("IPCServerLog", serverLogData);
 
+
+        OnIPCWorkerLog(eventData[P_LEVEL].GetInt(), eventData[P_MESSAGE].GetString());
+
     }
 
     bool IPCServer::StartInternal(const String& exec, const Vector<String>& args)
@@ -177,7 +185,6 @@ namespace Atomic
 
     }
 
-
     unsigned IPCServer::QueueCommand(IPCResultHandler* handler, const VariantMap& cmdMap)
     {
         IPCCommand cmd;

+ 7 - 12
Source/Atomic/IPC/IPCServer.h

@@ -27,18 +27,6 @@
 
 namespace Atomic
 {
-    ATOMIC_EVENT(E_IPCCMD, IPCCmd)
-    {
-        ATOMIC_PARAM(P_COMMAND, Command); // string
-        ATOMIC_PARAM(P_ID, ID); // unsigned
-    }
-
-    ATOMIC_EVENT(E_IPCCMDRESULT, IPCCmdResult)
-    {
-        ATOMIC_PARAM(P_COMMAND, Command); // string
-        ATOMIC_PARAM(P_ID, ID); // unsigned
-    }
-
     class IPCBroker;
 
     /// IPCResultHandler
@@ -72,10 +60,17 @@ namespace Atomic
         virtual bool Start() = 0;
 
         unsigned QueueCommand(IPCResultHandler* handler, const VariantMap& cmdMap);
+
         bool GetBrokerEnabled() const;
 
+        IPCBroker* GetServerBroker() const { return serverBroker_; }
+
     protected:
 
+        virtual void OnIPCWorkerStarted() {}
+        virtual void OnIPCWorkerLog(int level, const String& message) {}
+        virtual void OnIPCWorkerExited() {}
+
         bool StartInternal(const String& exec, const Vector<String>& args);
 
     private:

+ 10 - 0
Source/Atomic/Math/Random.cpp

@@ -58,4 +58,14 @@ float RandStandardNormal()
     return val;
 }
 
+// ATOMIC BEGIN
+
+float RandZeroOne()
+{
+    static float invRAND_MAX = 1.0f / RAND_MAX;
+    return float(rand()) * invRAND_MAX;
+}
+
+// ATOMIC END
+
 }

+ 5 - 0
Source/Atomic/Math/Random.h

@@ -36,4 +36,9 @@ ATOMIC_API int Rand();
 /// Return a standard normal distributed number.
 ATOMIC_API float RandStandardNormal();
 
+// ATOMIC BEGIN
+/// Returns a random number between 0 and 1
+ATOMIC_API float RandZeroOne();
+// ATOMIC END
+
 }

+ 49 - 0
Source/Atomic/Math/Vector3.h

@@ -411,6 +411,39 @@ public:
 
         return *this * (1.0f - t) + rhs * t;
     }
+
+    static void GetRandomInSphere( Vector3& result, const Vector3& center, float radius )
+    {
+        Vector3 dir;
+        GetRandomDirection(dir);
+        result = center + dir * radius;
+    }
+
+    static void GetRandomDirection( Vector3& result )
+    {
+        float  len;
+
+        do
+        {
+           result.x_ = (RandZeroOne() * 2.0f - 1.0f);
+           result.y_ = (RandZeroOne() * 2.0f - 1.0f);
+           result.z_ = (RandZeroOne() * 2.0f - 1.0f);
+           len   = result.Length();
+
+        } while( len > 1.0f );
+
+        result /= len;
+    }
+
+    static void GetRandomHemisphereDirection( Vector3& result, const Vector3& normal )
+    {
+        GetRandomDirection(result);
+
+        if( result.DotProduct(normal) < 0 ) {
+            result = -result;
+        }
+    }
+
     // ATOMIC END
     
     /// Return float data.
@@ -497,4 +530,20 @@ inline IntVector3 VectorMax(const IntVector3& lhs, const IntVector3& rhs) { retu
 /// Return a random value from [0, 1) from 3-vector seed.
 inline float StableRandom(const Vector3& seed) { return StableRandom(Vector2(StableRandom(Vector2(seed.x_, seed.y_)), seed.z_)); }
 
+// ATOMIC BEGIN
+
+inline float AreaOfTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2)
+  {
+
+    float a = (v0 - v1).Length();
+    float b = (v1 - v2).Length();
+    float c = (v2 - v0).Length();
+
+    float s = (a + b + c) * 0.5f;
+
+    return (float) Sqrt<float>(s * (s-a) * (s-b) * (s-c));
+  }
+
+// ATOMIC END
+
 }

+ 98 - 0
Source/Atomic/Scene/Scene.cpp

@@ -46,6 +46,10 @@
 #include "../DebugNew.h"
 
 // ATOMIC BEGIN
+#include "../Graphics/Graphics.h"
+#include "../Graphics/Texture2D.h"
+#include "../Graphics/StaticModel.h"
+#include "../IO/FileSystem.h"
 #include "PrefabComponent.h"
 // ATOMIC END
 
@@ -1281,6 +1285,13 @@ void Scene::FinishLoading(Deserializer* source)
         fileName_ = source->GetName();
         checksum_ = source->GetChecksum();
     }
+
+    // ATOMIC BEGIN
+    if (fileName_.Length())
+    {
+        LoadLightmaps();
+    }
+    // ATOMIC END
 }
 
 void Scene::FinishSaving(Serializer* dest) const
@@ -1547,4 +1558,91 @@ void RegisterSceneLibrary(Context* context)
     // ATOMIC END
 }
 
+// ATOMIC BEGIN
+void Scene::LoadLightmaps(bool reload)
+{
+    // If we're running headless, don't load lightmap textures
+    if (!GetSubsystem<Graphics>())
+    {
+        return;
+    }
+
+    if (lightmaps_.Size() && !reload)
+    {
+        return;
+    }
+
+    lightmaps_.Clear();
+
+    PODVector<StaticModel*> staticModels;
+    GetComponents<StaticModel>(staticModels, true);
+
+    int maxLightMap = -1;
+
+    for (int i = 0; i < (int) staticModels.Size(); i++)
+    {
+        StaticModel* staticModel = staticModels[i];
+
+        if (!staticModel->GetLightmap())
+            continue;
+
+        int lightmapIndex = (int) staticModel->GetLightmapIndex();
+
+        if (lightmapIndex > maxLightMap)
+        {
+            maxLightMap = lightmapIndex;
+        }
+    }
+
+    if (maxLightMap < 0)
+        return;
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    String sceneName = Atomic::GetFileName(GetFileName());
+    String lightmapFolder = ToString("AtomicGlow/Scenes/%s/Lightmaps/", sceneName.CString());
+
+    for (int i = 0; i < maxLightMap + 1; i++)
+    {
+        String textureName = ToString("%sLightmap%i.png", lightmapFolder.CString(), i);
+        Texture2D* texture = cache->GetResource<Texture2D>(textureName);
+
+        if (!texture)
+        {
+            ATOMIC_LOGWARNINGF("Scene::PreloadLightmaps() - Unable to load texture %s", textureName.CString());
+            lightmaps_.Push(SharedPtr<Texture2D>((Texture2D*)0));
+            continue;
+        }
+
+        // FILTER_NEAREST is good for testing lightmap, without bilinear artifacts
+        // texture->SetFilterMode(FILTER_NEAREST);
+        texture->SetNumLevels(1); // No mipmaps
+
+        texture->SetAddressMode(COORD_U, ADDRESS_CLAMP);
+        texture->SetAddressMode(COORD_V, ADDRESS_CLAMP);
+
+        lightmaps_.Push(SharedPtr<Texture2D>(texture));
+    }
+
+}
+
+void Scene::SetLightmapTexture(unsigned id)
+{
+    // Store graphics subsystem into static variable for speed
+    // NOTE: If Atomic needs to support changing graphics subsystem on the fly
+    // this will need to be changed
+    static Graphics* graphics = GetSubsystem<Graphics>();
+
+    if (id >= lightmaps_.Size())
+    {
+        graphics->SetTexture(TU_EMISSIVE, 0);
+        return;
+    }
+
+    graphics->SetTexture(TU_EMISSIVE, lightmaps_[id]);
+
+}
+
+// ATOMIC END
+
 }

+ 13 - 0
Source/Atomic/Scene/Scene.h

@@ -35,6 +35,10 @@ namespace Atomic
 class File;
 class PackageFile;
 
+// ATOMIC BEGIN
+class Texture2D;
+// ATOMIC END
+
 static const unsigned FIRST_REPLICATED_ID = 0x1;
 static const unsigned LAST_REPLICATED_ID = 0xffffff;
 static const unsigned FIRST_LOCAL_ID = 0x01000000;
@@ -256,6 +260,11 @@ public:
     /// Mark a node dirty in scene replication states. The node does not need to have own replication state yet.
     void MarkReplicationDirty(Node* node);
 
+    // ATOMIC BEGIN
+    void LoadLightmaps(bool reload = false);
+    void SetLightmapTexture(unsigned id);
+    // ATOMIC END
+
 private:
     /// Handle the logic update event to update the scene, if active.
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
@@ -276,6 +285,10 @@ private:
     /// Preload resources from a JSON scene or object prefab file.
     void PreloadResourcesJSON(const JSONValue& value);
 
+    // ATOMIC BEGIN
+    Vector<SharedPtr<Texture2D>> lightmaps_;
+    // ATOMIC END
+
     /// Replicated scene nodes by ID.
     HashMap<unsigned, Node*> replicatedNodes_;
     /// Local scene nodes by ID.

+ 4 - 0
Source/AtomicApp/IPCClientApp.h

@@ -44,6 +44,10 @@ namespace Atomic
 
         bool Initialize(const Vector<String>& arguments);
 
+        bool GetBrokerActive() const { return brokerActive_; }
+
+        IPC* GetIPC() const { return ipc_; }
+
     private:
 
         void HandleLogMessage(StringHash eventType, VariantMap& eventData);

+ 15 - 1
Source/AtomicEditor/Application/AEEditorApp.cpp

@@ -39,6 +39,13 @@
 
 #include "../Components/EditorComponents.h"
 
+#ifdef ATOMIC_GLOW
+
+#include <AtomicGlow/GlowService/GlowService.h>
+using namespace AtomicGlow;
+
+#endif
+
 #include "AEEditorPrefs.h"
 #include "AEEditorApp.h"
 
@@ -147,8 +154,15 @@ namespace AtomicEditor
 
         context_->RegisterSubsystem(new EditorMode(context_));
         context_->RegisterSubsystem(new NETBuildSystem(context_));
-        context_->RegisterSubsystem(new EditorNETService(context_));        
+        context_->RegisterSubsystem(new EditorNETService(context_));
 
+#ifdef ATOMIC_GLOW
+        SharedPtr<GlowService> glowService(new GlowService(context_));
+        if (glowService->Start())
+        {
+            context_->RegisterSubsystem(glowService);
+        }
+#endif
         AppBase::Start();
 
         vm_->SetModuleSearchPaths("AtomicEditor/JavaScript;AtomicEditor/EditorScripts;AtomicEditor/EditorScripts/AtomicEditor");

+ 2 - 0
Source/AtomicEditor/Components/EditorComponents.cpp

@@ -24,6 +24,7 @@
 
 #include "EditorComponent.h"
 #include "CubemapGenerator.h"
+#include "GlowComponent.h"
 
 namespace AtomicEditor
 {
@@ -32,6 +33,7 @@ void RegisterEditorComponentLibrary(Atomic::Context* context)
 {
     EditorComponent::RegisterObject(context);
     CubemapGenerator::RegisterObject(context);
+    GlowComponent::RegisterObject(context);
 }
 
 }

+ 156 - 0
Source/AtomicEditor/Components/GlowComponent.cpp

@@ -0,0 +1,156 @@
+//
+// Copyright (c) 2014-2017 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Core/Context.h>
+
+#include <ToolCore/ToolSystem.h>
+#include <ToolCore/Project/Project.h>
+
+#include <AtomicGlow/GlowService/GlowService.h>
+#include "GlowComponent.h"
+
+using namespace ToolCore;
+using namespace AtomicGlow;
+
+namespace AtomicEditor
+{
+
+GlowComponent::GlowComponent(Context *context) : EditorComponent(context)
+{
+    GlowSettings glowSettings;
+    glowSettings.SetDefaults();
+    SetFromGlowSettings(glowSettings);
+}
+
+GlowComponent::~GlowComponent()
+{
+
+}
+
+void GlowComponent::SetFromGlowSettings(const GlowSettings& settings)
+{
+
+    lexelDensity_ = settings.lexelDensity_;
+
+    giEnabled_ = settings.giEnabled_;
+    giGranularity_ = settings.giGranularity_;
+    giMaxBounces_ = settings.giMaxBounces_;
+
+    aoEnabled_ = settings.aoEnabled_;
+    aoDepth_ = settings.aoDepth_;
+    nsamples_ = settings.nsamples_;
+    aoMin_ = settings.aoMin_;
+    aoMultiply_ = settings.aoMultiply_;
+
+}
+
+void GlowComponent::CopyToGlowSettings(GlowSettings& settings) const
+{
+    settings.lexelDensity_ = lexelDensity_ ;
+    settings.sceneLexelDensityScale_ = 1.0f;
+
+    settings.giEnabled_= giEnabled_;
+    settings.giGranularity_ = giGranularity_;
+    settings.giMaxBounces_ = giMaxBounces_;
+
+    settings.aoEnabled_ = aoEnabled_;
+    settings.aoDepth_ = aoDepth_;
+    settings.nsamples_ = nsamples_;
+    settings.aoMin_ = aoMin_;
+    settings.aoMultiply_ = aoMultiply_;
+}
+
+bool GlowComponent::Bake()
+{
+
+    GlowService* glowService = GetSubsystem<GlowService>();
+
+    if (!glowService)
+    {
+        ATOMIC_LOGERROR("GlowComponent::Bake() - Unable ot get glow service");
+        return false;
+    }
+
+    SubscribeToEvent(E_ATOMICGLOWSERVICEBAKERESULT, ATOMIC_HANDLER(GlowComponent, HandleAtomicGlowServiceBakeResult));
+    SubscribeToEvent(E_ATOMICGLOWSERVICELOGEVENT, ATOMIC_HANDLER(GlowComponent, HandleAtomicGlowServiceLogEvent));
+    SubscribeToEvent(E_ATOMICGLOWBAKECANCEL, ATOMIC_HANDLER(GlowComponent, HandleAtomicGlowBakeCancel));
+
+    GlowSettings settings;
+    CopyToGlowSettings(settings);
+    settings.Validate();
+    SetFromGlowSettings(settings);
+
+    ToolSystem* toolSystem = GetSubsystem<ToolSystem>();
+    if (!toolSystem)
+        return false;
+
+    Project* project = toolSystem->GetProject();
+    if (!project)
+        return false;
+
+    return glowService->Bake(project->GetProjectPath(), GetScene(), settings);
+}
+
+void GlowComponent::HandleAtomicGlowBakeCancel(StringHash eventType, VariantMap& eventData)
+{
+    GetSubsystem<GlowService>()->CancelBake();
+}
+
+void GlowComponent::HandleAtomicGlowServiceBakeResult(StringHash eventType, VariantMap& eventData)
+{
+    using namespace AtomicGlowServiceBakeResult;
+
+    // convert to a glow component event, which contains the same fields
+    SendEvent(E_ATOMICGLOWBAKERESULT, eventData);
+
+}
+
+void GlowComponent::HandleAtomicGlowServiceLogEvent(StringHash eventType, VariantMap& eventData)
+{
+    using namespace AtomicGlowServiceLogEvent;
+
+    // convert to a glow component event, which contains the same fields
+    SendEvent(E_ATOMICGLOWLOGEVENT, eventData);
+
+}
+
+void GlowComponent::RegisterObject(Context* context)
+{
+    context->RegisterFactory<GlowComponent>();
+
+    ATOMIC_ATTRIBUTE("Lexel Density", float, lexelDensity_, 0.1f, AM_FILE);
+
+    ATOMIC_ATTRIBUTE("GI Enabled", bool, giEnabled_, false, AM_FILE);
+    ATOMIC_ATTRIBUTE("GI Granularity", int, giGranularity_, 16, AM_FILE);
+    ATOMIC_ATTRIBUTE("GI Max Cycles", int, giMaxBounces_, 3, AM_FILE);
+
+    ATOMIC_ATTRIBUTE("AO Enabled", bool, aoEnabled_, false, AM_FILE);
+    ATOMIC_ATTRIBUTE("AO Depth", float, aoDepth_, 0.25f, AM_FILE);
+    ATOMIC_ATTRIBUTE("AO Samples", int, nsamples_, 64, AM_FILE);
+    ATOMIC_ATTRIBUTE("AO Min", float, aoMin_, 0.45f, AM_FILE);
+    ATOMIC_ATTRIBUTE("AO Multiply", float, aoMultiply_, 1.0f, AM_FILE);
+
+}
+
+
+}

+ 102 - 0
Source/AtomicEditor/Components/GlowComponent.h

@@ -0,0 +1,102 @@
+//
+// Copyright (c) 2014-2017 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+
+#include <AtomicGlow/Common/GlowSettings.h>
+#include <AtomicGlow/GlowService/GlowServiceEvents.h>
+
+#include "EditorComponent.h"
+
+using namespace Atomic;
+using namespace AtomicGlow;
+
+namespace AtomicEditor
+{
+
+// These glow events are here pretty much for editor access
+// due to Glow not currently having a scripting module
+ATOMIC_EVENT(E_ATOMICGLOWBAKECANCEL, AtomicGlowBakeCancel)
+{
+}
+
+ATOMIC_EVENT(E_ATOMICGLOWBAKERESULT, AtomicGlowBakeResult)
+{
+    ATOMIC_PARAM(P_SUCCESS, Success);    // bool
+    ATOMIC_PARAM(P_RESULT, Result);    // String
+}
+
+ATOMIC_EVENT(E_ATOMICGLOWLOGEVENT, AtomicGlowLogEvent)
+{
+    ATOMIC_PARAM(P_LEVEL, Level);    // bool
+    ATOMIC_PARAM(P_MESSAGE, Message);    // String
+}
+
+class GlowComponent : public EditorComponent
+{
+    ATOMIC_OBJECT(GlowComponent, EditorComponent)
+
+public:
+    /// Construct.
+    GlowComponent(Context* context);
+
+    /// Destruct.
+    virtual ~GlowComponent();
+
+    bool Bake();
+
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+protected:
+
+private:
+
+    void HandleAtomicGlowBakeCancel(StringHash eventType, VariantMap& eventData);
+
+    void HandleAtomicGlowServiceBakeResult(StringHash eventType, VariantMap& eventData);
+    void HandleAtomicGlowServiceLogEvent(StringHash eventType, VariantMap& eventData);
+
+    void SetFromGlowSettings(const GlowSettings& settings);
+
+    void CopyToGlowSettings(GlowSettings& settings) const;
+
+    // global scalar
+    float lexelDensity_;
+
+    // global illumination
+    bool giEnabled_;
+    int giGranularity_;
+    int giMaxBounces_;
+
+    // ambient occlusion
+    bool aoEnabled_;
+    float aoDepth_;
+    unsigned nsamples_;
+    float aoMin_;
+    float aoMultiply_;
+
+};
+
+
+}

+ 314 - 0
Source/AtomicGlow/Atlas/MeshLightmapUVGen.cpp

@@ -0,0 +1,314 @@
+
+#include <ThirdParty/thekla/thekla_atlas.h>
+
+#include <Atomic/IO/Log.h>
+
+#include "ModelPacker.h"
+#include "MeshLightmapUVGen.h"
+
+
+namespace AtomicGlow
+{
+
+
+MeshLightmapUVGen::MeshLightmapUVGen(Context* context, Model* model, const Settings& settings) : Object(context),
+    model_(model),
+    modelPacker_(new ModelPacker(context)),
+    settings_(settings),
+    tOutputMesh_(0),
+    tInputMesh_(0)
+{
+
+
+}
+
+MeshLightmapUVGen::~MeshLightmapUVGen()
+{
+
+}
+
+inline void MeshLightmapUVGen::EmitVertex(PODVector<MPVertex>& vertices, unsigned& index, const MPVertex& vertex)
+{
+    index = 0;
+
+    for (unsigned i = 0; i < vertices.Size(); i++)
+    {
+        if (vertices[i] == vertex)
+        {
+            index = i;
+            return;
+        }
+    }
+
+    index = vertices.Size();
+    vertices.Push(vertex);
+}
+
+void MeshLightmapUVGen::WriteLightmapUVCoords()
+{
+    
+    //Thekla::atlas_write_debug_textures(tOutputMesh_, tInputMesh_, ToString("/Users/jenge/Desktop/%s_lmWorldSpaceTexture.png", modelName.CString()).CString() ,
+    //                                                              ToString("/Users/jenge/Desktop/%s_lmNormalTexture.png", modelName.CString()).CString() );
+
+    Vector<PODVector<MPVertex>> geoVerts;
+    Vector<PODVector<unsigned>> geoIndices;
+
+    geoVerts.Resize(curLOD_->mpGeometry_.Size());
+    geoIndices.Resize(curLOD_->mpGeometry_.Size());
+
+    float uscale = 1.f / tOutputMesh_->atlas_width;
+    float vscale = 1.f / tOutputMesh_->atlas_height;
+
+    for (unsigned i = 0; i < tOutputMesh_->index_count/3; i++)
+    {
+        unsigned v0 = (unsigned) tOutputMesh_->index_array[i * 3];
+        unsigned v1 = (unsigned) tOutputMesh_->index_array[i * 3 + 1];
+        unsigned v2 = (unsigned) tOutputMesh_->index_array[i * 3 + 2];
+
+        Thekla::Atlas_Output_Vertex& tv0 = tOutputMesh_->vertex_array[v0];
+        Thekla::Atlas_Output_Vertex& tv1 = tOutputMesh_->vertex_array[v1];
+        Thekla::Atlas_Output_Vertex& tv2 = tOutputMesh_->vertex_array[v2];
+
+        LMVertex& lv0 = lmVertices_[tv0.xref];
+        LMVertex& lv1 = lmVertices_[tv1.xref];
+        LMVertex& lv2 = lmVertices_[tv2.xref];
+
+        unsigned geometryIdx = lv0.geometryIdx_;
+
+        // check for mixed geometry in triangle
+        if (geometryIdx != lv1.geometryIdx_ || geometryIdx != lv2.geometryIdx_)
+        {
+            assert(0);
+        }
+
+        MPGeometry* mpGeo = curLOD_->mpGeometry_[geometryIdx];
+
+        PODVector<MPVertex>& verts = geoVerts[geometryIdx];
+        PODVector<unsigned>& indices = geoIndices[geometryIdx];
+
+        unsigned ovindices[3];
+        Vector2 uvs[3];
+
+        uvs[0] = Vector2(tv0.uv[0], tv0.uv[1]);
+        uvs[1] = Vector2(tv1.uv[0], tv1.uv[1]);
+        uvs[2] = Vector2(tv2.uv[0], tv2.uv[1]);
+
+        ovindices[0] = lv0.originalVertex_;
+        ovindices[1] = lv1.originalVertex_;
+        ovindices[2] = lv2.originalVertex_;
+
+        Vector2 center(uvs[0]);
+        center += uvs[1];
+        center += uvs[2];
+        center /= 3.0f;
+
+        unsigned index;
+        for (unsigned j = 0; j < 3; j++)
+        {
+            Vector2 uv = uvs[j];
+
+            /*
+            uv -= center;
+            uv *= 0.98f;
+            uv += center;
+
+            uv.x_ = Clamp<float>(uv.x_, 2, tOutputMesh_->atlas_width - 2);
+            uv.y_ = Clamp<float>(uv.y_, 2, tOutputMesh_->atlas_height - 2);
+            */
+
+            uv.x_ *= uscale;
+            uv.y_ *= vscale;
+
+            MPVertex mpv = mpGeo->vertices_[ovindices[j]];
+            mpv.uv1_ = uv;
+            EmitVertex(verts, index, mpv);
+            indices.Push(index);
+        }
+    }
+
+    for (unsigned i = 0; i < curLOD_->mpGeometry_.Size(); i++)
+    {
+        MPGeometry* mpGeo = curLOD_->mpGeometry_[i];
+
+        mpGeo->vertices_ = geoVerts[i];
+        mpGeo->indices_ = new unsigned[geoIndices[i].Size()];
+        memcpy(&mpGeo->indices_[0], &geoIndices[i][0], sizeof(unsigned) * geoIndices[i].Size());
+        mpGeo->numIndices_ = geoIndices[i].Size();
+
+        // Check whether we need to add UV1 semantic
+
+        PODVector<VertexElement> nElements;
+
+        unsigned texCoordCount = 0;
+        for (unsigned j = 0; j < mpGeo->elements_.Size(); j++)
+        {
+            VertexElement element = mpGeo->elements_[j];
+
+            if (element.type_ == TYPE_VECTOR2 && element.semantic_ == SEM_TEXCOORD)
+                texCoordCount++;
+        }
+
+        if (texCoordCount == 0)
+        {
+            // We don't have a valid UV set in UV0
+            // FIX ME: This doesn't currently work
+            mpGeo->elements_.Push(VertexElement(TYPE_VECTOR2, SEM_TEXCOORD, 0));
+            mpGeo->elements_.Push(VertexElement(TYPE_VECTOR2, SEM_TEXCOORD, 1));
+        }
+        else if (texCoordCount == 1)
+        {
+            bool added = false;
+            for (unsigned j = 0; j < mpGeo->elements_.Size(); j++)
+            {
+                VertexElement element = mpGeo->elements_[j];
+
+                nElements.Push(element);
+
+                if ( (element.type_ == TYPE_VECTOR2 && element.semantic_ == SEM_TEXCOORD) || (!added && j == (mpGeo->elements_.Size() - 1) ) )
+                {
+                    added = true;
+                    VertexElement element(TYPE_VECTOR2, SEM_TEXCOORD, 1);
+                    nElements.Push(element);
+                }
+
+            }
+
+            mpGeo->elements_ = nElements;
+        }
+
+    }
+
+}
+
+bool MeshLightmapUVGen::Generate()
+{
+    if (model_.Null())
+        return false;
+
+    if (!modelPacker_->Unpack(model_))
+    {
+        return false;
+    }
+
+    for (unsigned i = 0; i < modelPacker_->lodLevels_.Size(); i++)
+    {
+
+        curLOD_ = modelPacker_->lodLevels_[i];
+
+        // combine all LOD vertices/indices
+
+        unsigned totalVertices = 0;
+        unsigned totalIndices = 0;
+        for (unsigned j = 0; j < curLOD_->mpGeometry_.Size(); j++)
+        {
+            MPGeometry* geo = curLOD_->mpGeometry_[j];
+            totalVertices += geo->vertices_.Size();
+            totalIndices += geo->numIndices_;
+        }
+
+        // Setup thekla input mesh
+        tInputMesh_ = new Thekla::Atlas_Input_Mesh();
+
+        // Allocate vertex arrays
+        lmVertices_ = new LMVertex[totalVertices];
+
+        tInputMesh_->vertex_count = totalVertices;
+        tInputMesh_->vertex_array = new Thekla::Atlas_Input_Vertex[tInputMesh_->vertex_count];
+
+        tInputMesh_->face_count = totalIndices / 3;
+        tInputMesh_->face_array = new Thekla::Atlas_Input_Face[tInputMesh_->face_count];
+
+        unsigned vCount = 0;
+        unsigned fCount = 0;
+
+        for (unsigned j = 0; j < curLOD_->mpGeometry_.Size(); j++)
+        {
+            MPGeometry* geo = curLOD_->mpGeometry_[j];
+
+            unsigned vertexStart = vCount;
+
+            for (unsigned k = 0; k < geo->vertices_.Size(); k++, vCount++)
+            {
+                const MPVertex& mpv = geo->vertices_[k];
+
+                LMVertex& lmv = lmVertices_[vCount];
+                Thekla::Atlas_Input_Vertex& tv = tInputMesh_->vertex_array[vCount];
+
+                lmv.geometry_ = geo;
+                lmv.geometryIdx_ = j;
+                lmv.originalVertex_ = k;
+
+                tv.position[0] = mpv.position_.x_;
+                tv.position[1] = mpv.position_.y_;
+                tv.position[2] = mpv.position_.z_;
+
+                tv.normal[0] = mpv.normal_.x_;
+                tv.normal[1] = mpv.normal_.y_;
+                tv.normal[2] = mpv.normal_.z_;
+
+                tv.uv[0] = mpv.uv0_.x_;
+                tv.uv[1] = mpv.uv0_.y_;
+
+                // this appears unused in thekla atlas?
+                tv.first_colocal = vCount;
+
+            }
+
+            for (unsigned k = 0; k < geo->numIndices_/3; k++, fCount++)
+            {
+                Thekla::Atlas_Input_Face& tface = tInputMesh_->face_array[fCount];
+
+                tface.vertex_index[0] = (int) (geo->indices_[k * 3] + vertexStart);
+                tface.vertex_index[1] = (int) (geo->indices_[k * 3 + 1] + vertexStart);
+                tface.vertex_index[2] = (int) (geo->indices_[k * 3 + 2] + vertexStart);
+
+                if (tface.vertex_index[0] > totalVertices || tface.vertex_index[1] > totalVertices || tface.vertex_index[2] > totalVertices)
+                {
+                    ATOMIC_LOGERROR("Vertex overflow");
+                    return false;
+                }
+
+                tface.material_index = j;
+
+            }
+
+        }
+
+        Thekla::Atlas_Options atlasOptions;
+        atlas_set_default_options(&atlasOptions);
+
+        // disable brute force packing quality, as it has a number of notes about performance
+        // and it is turned off in Thekla example in repo as well.  I am also seeing some meshes
+        // having problems packing with it and hanging on import
+        atlasOptions.packer_options.witness.packing_quality = 1;
+
+        atlasOptions.packer_options.witness.texel_area = 8;
+        atlasOptions.packer_options.witness.conservative = true;
+
+        Thekla::Atlas_Error error = Thekla::Atlas_Error_Success;
+
+        tOutputMesh_ = atlas_generate(tInputMesh_, &atlasOptions, &error);
+
+        if (tOutputMesh_)
+        {
+            WriteLightmapUVCoords();
+        }
+
+        delete [] tInputMesh_->vertex_array;
+        delete [] tInputMesh_->face_array;
+        delete tInputMesh_;
+        tInputMesh_ = 0;
+
+        atlas_free(tOutputMesh_);
+        tOutputMesh_ = 0;
+
+    }
+
+    // update model
+    modelPacker_->Pack();
+
+    return true;
+
+}
+
+}

+ 70 - 0
Source/AtomicGlow/Atlas/MeshLightmapUVGen.h

@@ -0,0 +1,70 @@
+
+#pragma once
+
+#include "ModelPacker.h"
+
+using namespace Atomic;
+
+namespace Thekla
+{
+
+struct Atlas_Output_Mesh;
+struct Atlas_Input_Mesh;
+
+}
+
+namespace AtomicGlow
+{
+
+class ModelPacker;
+
+class MeshLightmapUVGen : public Object
+{
+    ATOMIC_OBJECT(MeshLightmapUVGen, Object)
+
+public:
+
+    struct Settings
+    {
+        bool genChartID_;
+
+        Settings()
+        {
+            genChartID_ = false;
+        }
+
+    };
+
+    MeshLightmapUVGen(Context* context, Model* model, const Settings& settings);
+    virtual ~MeshLightmapUVGen();
+
+    bool Generate();
+
+private:
+
+    inline void EmitVertex(PODVector<MPVertex>& vertices, unsigned& index, const MPVertex& vertex);
+
+    void WriteLightmapUVCoords();
+
+    struct LMVertex
+    {
+        MPGeometry* geometry_;
+        unsigned geometryIdx_;
+        unsigned originalVertex_;
+    };
+
+    SharedPtr<Model> model_;
+    SharedPtr<ModelPacker> modelPacker_;
+
+    SharedPtr<MPLODLevel> curLOD_;
+
+    SharedArrayPtr<LMVertex> lmVertices_;
+
+    Settings settings_;
+
+    Thekla::Atlas_Output_Mesh* tOutputMesh_;
+    Thekla::Atlas_Input_Mesh* tInputMesh_;
+
+};
+
+}

+ 532 - 0
Source/AtomicGlow/Atlas/ModelPacker.cpp

@@ -0,0 +1,532 @@
+
+#include <Atomic/Graphics/IndexBuffer.h>
+#include <Atomic/Graphics/VertexBuffer.h>
+
+#include "ModelPacker.h"
+
+namespace AtomicGlow
+{
+
+ModelPacker::ModelPacker(Context* context) : Object(context)
+{
+
+}
+
+ModelPacker::~ModelPacker()
+{
+
+}
+
+/// Get the total vertex and index counts of all LOD geometry
+void MPLODLevel::GetTotalCounts(unsigned& totalVertices, unsigned& totalIndices) const
+{
+    totalVertices = 0;
+    totalIndices = 0;
+
+    for (unsigned i = 0; i < mpGeometry_.Size(); i++)
+    {
+        MPGeometry* mpGeo = mpGeometry_[i];
+
+        totalVertices += mpGeo->vertices_.Size();
+        totalIndices += mpGeo->numIndices_;
+    }
+
+}
+
+/// Returns true if all LOD geometry contains element
+bool MPLODLevel::HasElement(VertexElementType type, VertexElementSemantic semantic, unsigned char index) const
+{
+    if (!mpGeometry_.Size())
+        return false;
+
+    for (unsigned i = 0; i < mpGeometry_.Size(); i++)
+    {
+        MPGeometry* mpGeo = mpGeometry_[i];
+
+        if (!VertexBuffer::HasElement(mpGeo->elements_, type, semantic, index))
+            return false;
+
+    }
+
+    return true;
+
+}
+
+
+bool ModelPacker::Pack()
+{
+
+    if (model_.Null())
+    {
+        SetError("No model to pack");
+        return false;
+    }
+
+    for (unsigned i = 0; i < lodLevels_.Size(); i++)
+    {
+        MPLODLevel* lodLevel = lodLevels_[i];
+
+        if (!lodLevel->mpGeometry_.Size())
+            continue;
+
+        unsigned totalVertices = 0;
+        unsigned totalIndices = 0;
+
+        for (unsigned j = 0; j < lodLevel->mpGeometry_.Size(); j++)
+        {
+            totalVertices += lodLevel->mpGeometry_[j]->vertices_.Size();
+            totalIndices += lodLevel->mpGeometry_[j]->numIndices_;
+        }
+
+        bool combineBuffers = true;
+
+        // Check if buffers can be combined (same vertex element mask, under 65535 vertices)
+        const PODVector<VertexElement>& elements = lodLevel->mpGeometry_[0]->elements_;
+
+        for (unsigned j = 1; j < lodLevel->mpGeometry_.Size(); j++)
+        {
+            if (elements != lodLevel->mpGeometry_[j]->elements_)
+            {
+                combineBuffers = false;
+                break;
+            }
+
+        }
+
+        // Check if keeping separate buffers allows to avoid 32-bit indices
+        if (combineBuffers && totalVertices > 65535)
+        {
+            bool allUnder65k = true;
+
+            for (unsigned j = 0; j < lodLevel->mpGeometry_.Size(); j++)
+            {
+                if (lodLevel->mpGeometry_[j]->vertices_.Size() > 65535)
+                {
+                    allUnder65k = false;
+                    break;
+                }
+            }
+
+            if (allUnder65k == true)
+                combineBuffers = false;
+        }
+
+        SharedPtr<IndexBuffer> ib;
+        SharedPtr<VertexBuffer> vb;
+        Vector<SharedPtr<VertexBuffer> > vbVector;
+        Vector<SharedPtr<IndexBuffer> > ibVector;
+        unsigned startVertexOffset = 0;
+        unsigned startIndexOffset = 0;
+
+        for (unsigned j = 0; j < lodLevel->mpGeometry_.Size(); j++)
+        {
+
+            MPGeometry* mpGeo = lodLevel->mpGeometry_[j];
+
+            bool largeIndices;
+
+            if (combineBuffers)
+                largeIndices = totalIndices > 65535;
+            else
+                largeIndices = mpGeo->vertices_.Size() > 65535;
+
+            // Create new buffers if necessary
+            if (!combineBuffers || vbVector.Empty())
+            {
+                vb = new VertexBuffer(context_);
+                ib = new IndexBuffer(context_);
+
+                vb->SetShadowed(true);
+                ib->SetShadowed(true);
+
+                if (combineBuffers)
+                {
+                    ib->SetSize(totalIndices, largeIndices);
+                    vb->SetSize(totalVertices, mpGeo->elements_);
+                }
+                else
+                {
+                    ib->SetSize(mpGeo->numIndices_, largeIndices);
+                    vb->SetSize(mpGeo->vertices_.Size(), mpGeo->elements_);
+                }
+
+                vbVector.Push(vb);
+                ibVector.Push(ib);
+                startVertexOffset = 0;
+                startIndexOffset = 0;
+            }
+
+            unsigned char* vertexData = vb->GetShadowData();
+            unsigned char* indexData = ib->GetShadowData();
+
+            assert(vertexData);
+            assert(indexData);
+
+            // Build the index data
+            if (!largeIndices)
+            {
+                unsigned short* dest = (unsigned short*)indexData + startIndexOffset;
+
+                for (unsigned k = 0; k < mpGeo->numIndices_; k++)
+                {
+                    *dest++ = (unsigned short) (mpGeo->indices_[k] + startVertexOffset);
+                }
+            }
+            else
+            {
+                unsigned* dest = (unsigned*)indexData + startIndexOffset;
+
+                for (unsigned k = 0; k < mpGeo->numIndices_; k++)
+                {
+                    *dest++ = mpGeo->indices_[k] + startVertexOffset;
+                }
+
+            }
+
+            // Build vertex data
+            float* vertexDest = (float*)((unsigned char*)vertexData + startVertexOffset * vb->GetVertexSize());
+
+            for (unsigned k = 0; k < mpGeo->vertices_.Size(); k++)
+            {
+                const MPVertex& vertex = mpGeo->vertices_[k];
+
+                unsigned texCoordCount = 0;
+
+                const PODVector<VertexElement>& elements = vb->GetElements();
+
+                for (unsigned x = 0; x < elements.Size(); x++)
+                {
+                    VertexElement element = elements[x];
+
+                    if (element.type_ == TYPE_VECTOR3 && element.semantic_ == SEM_POSITION)
+                    {
+                        *vertexDest++ = vertex.position_.x_;
+                        *vertexDest++ = vertex.position_.y_;
+                        *vertexDest++ = vertex.position_.z_;
+                    }
+                    if (element.type_ == TYPE_VECTOR3 && element.semantic_ == SEM_NORMAL)
+                    {
+                        *vertexDest++ = vertex.normal_.x_;
+                        *vertexDest++ = vertex.normal_.y_;
+                        *vertexDest++ = vertex.normal_.z_;
+                    }
+                    if (element.type_ == TYPE_UBYTE4 && element.semantic_ == SEM_COLOR)
+                    {
+                        *((unsigned*)vertexDest) = vertex.color_;
+                        vertexDest++;
+                    }
+                    if (element.type_ == TYPE_VECTOR2 && element.semantic_ == SEM_TEXCOORD)
+                    {
+                        if (texCoordCount == 0)
+                        {
+                            *vertexDest++ = vertex.uv0_.x_;
+                            *vertexDest++ = vertex.uv0_.y_;
+                        }
+                        else
+                        {
+
+                            *vertexDest++ = vertex.uv1_.x_;
+                            *vertexDest++ = vertex.uv1_.y_;
+                        }
+
+                        texCoordCount++;
+                    }
+                    if (element.type_ == TYPE_VECTOR4 && element.semantic_ == SEM_TANGENT)
+                    {
+                        *vertexDest++ = vertex.tangent_.x_;
+                        *vertexDest++ = vertex.tangent_.y_;
+                        *vertexDest++ = vertex.tangent_.z_;
+                        *vertexDest++ = vertex.tangent_.w_;
+                    }
+
+                }
+
+            }
+
+            // Update geometry, note that this assumes geometry center, etc are consistent with original
+            Geometry* geom = mpGeo->geometry_;
+
+            geom->SetIndexBuffer(ib);
+            geom->SetVertexBuffer(0, vb);
+            geom->SetDrawRange(TRIANGLE_LIST, startIndexOffset, mpGeo->numIndices_, true);
+
+            startVertexOffset += mpGeo->vertices_.Size();
+            startIndexOffset += mpGeo->numIndices_;
+        }
+
+        // update model
+        PODVector<unsigned> emptyMorphRange;
+        model_->SetVertexBuffers(vbVector, emptyMorphRange, emptyMorphRange);
+        model_->SetIndexBuffers(ibVector);
+
+    }
+
+    return true;
+}
+
+bool ModelPacker::Unpack(Model* model)
+{
+
+    model_ = model;
+
+    unsigned maxLOD = 0;
+
+    for (unsigned i = 0; i < model_->GetNumGeometries(); i++)
+    {
+        unsigned numLOD = model_->GetNumGeometryLodLevels(i);
+
+        if (numLOD > maxLOD)
+            maxLOD = numLOD;
+    }
+
+    if (!maxLOD)
+    {
+        SetError("No LOD in model");
+        return false;
+    }
+
+    for (unsigned i = 0; i < maxLOD; i++)
+    {
+        if (!UnpackLODLevel(i))
+            return false;
+    }
+
+    return true;
+}
+
+bool ModelPacker::UnpackLODLevel(unsigned level)
+{
+    PODVector<Geometry*> lodGeo;
+
+    for (unsigned i = 0; i < model_->GetNumGeometries(); i++)
+    {
+        Geometry * geo = model_->GetGeometry(i, level);
+
+        if (geo)
+            lodGeo.Push(geo);
+    }
+
+    if (!level && !lodGeo.Size())
+    {
+        SetError("No geometry in LOD 0 for model");
+        return false;
+    }
+
+    if (!lodGeo.Size())
+        return true;
+
+    SharedPtr<MPLODLevel> lodLevel (new MPLODLevel());
+
+    for (unsigned i = 0; i < lodGeo.Size(); i++)
+    {
+        if (!UnpackGeometry(lodLevel, lodGeo[i]))
+            return false;
+    }
+
+    lodLevel->level_ = level;
+
+    lodLevels_.Push(lodLevel);
+
+    return true;
+
+}
+
+bool ModelPacker::UnpackGeometry(MPLODLevel *level, Geometry* geometry)
+{
+    SharedPtr<MPGeometry> mpGeo(new MPGeometry());
+
+    mpGeo->geometry_ = geometry;
+
+    // We only support vertex buffer operations on vertex buffer 0
+    // TODO: should it be an error if > 1 vertex buffer in geo since we might be destructive to index buffers?
+
+    const unsigned char* indexData = 0;
+    unsigned indexSize = 0;
+
+    unsigned vertexSize = 0;
+    const unsigned char* vertexData = 0;
+
+    const PODVector<VertexElement>* elements = 0;
+
+    geometry->GetRawData(vertexData, vertexSize, indexData, indexSize, elements);
+
+    if (!indexData || !indexSize || !vertexData || !vertexSize || !elements)
+    {
+        SetError("ModelPacker::UnpackGeometry - Failed to get raw data for geometry");
+        return false;
+    }
+
+    // VERTEX DATA
+
+    mpGeo->elements_ = *elements;
+
+    const unsigned char* positionData = 0;
+    const unsigned char* normalData = 0;
+    const unsigned char* tangentData = 0;
+    const unsigned char* colorData = 0;
+    const unsigned char* uv0Data = 0;
+    const unsigned char* uv1Data = 0;
+
+    unsigned vertexStart = geometry->GetVertexStart();
+    unsigned vertexCount = geometry->GetVertexCount();
+
+    vertexData += vertexStart * vertexSize;
+
+    for (unsigned i = 0; i < elements->Size(); i++)
+    {
+        VertexElement element = elements->At(i);
+
+        if (element.type_ == TYPE_VECTOR3 && element.semantic_ == SEM_POSITION)
+        {
+            positionData = vertexData + element.offset_;
+        }
+        else if (element.type_ == TYPE_VECTOR3 && element.semantic_ == SEM_NORMAL)
+        {
+            normalData = vertexData + element.offset_;
+        }
+        else if (element.type_ == TYPE_UBYTE4 && element.semantic_ == SEM_COLOR)
+        {
+            colorData = vertexData + element.offset_;
+        }
+        else if (element.type_ == TYPE_VECTOR4 && element.semantic_ == SEM_TANGENT)
+        {
+            tangentData = vertexData + element.offset_;
+        }
+        else if (element.type_ == TYPE_VECTOR2 && element.semantic_ == SEM_TEXCOORD)
+        {
+            if (!uv0Data)
+            {
+                uv0Data = vertexData + element.offset_;
+            }
+            else
+            {
+                uv1Data = vertexData + element.offset_;
+            }
+        }
+    }
+
+    if (!positionData)
+    {
+        SetError("Geometry has no position data");
+        return false;
+    }
+
+    mpGeo->vertices_.Resize(vertexCount);
+
+    MPVertex* v = &mpGeo->vertices_[0];
+
+    v->Clear();
+
+    for (unsigned i = 0; i < vertexCount; i++, v++)
+    {
+        float* fp = (float *) positionData;
+
+        v->position_.x_ = fp[0];
+        v->position_.y_ = fp[1];
+        v->position_.z_ = fp[2];
+
+        positionData += vertexSize;
+
+        if (normalData)
+        {
+            fp = (float *) normalData;
+
+            v->normal_.x_ = fp[0];
+            v->normal_.y_ = fp[1];
+            v->normal_.z_ = fp[2];
+
+            normalData += vertexSize;
+        }
+
+        if (tangentData)
+        {
+            fp = (float *) tangentData;
+
+            v->tangent_.x_ = fp[0];
+            v->tangent_.y_ = fp[1];
+            v->tangent_.z_ = fp[2];
+            v->tangent_.w_ = fp[3];
+
+            tangentData += vertexSize;
+        }
+
+        if (uv0Data)
+        {
+            fp = (float *) uv0Data;
+
+            v->uv0_.x_ = fp[0];
+            v->uv0_.y_ = fp[1];
+
+            uv0Data += vertexSize;
+        }
+
+        if (uv1Data)
+        {
+            fp = (float *) uv1Data;
+
+            v->uv1_.x_ = fp[0];
+            v->uv1_.y_ = fp[1];
+
+            uv1Data += vertexSize;
+        }
+
+        if (colorData)
+        {
+            v->color_ = *((unsigned *)colorData);
+            colorData += vertexSize;
+        }
+
+    }
+
+    // INDICES
+
+    unsigned indexStart = geometry->GetIndexStart();
+    unsigned indexCount = geometry->GetIndexCount();
+
+    // source indices converted to unsigned value
+    PODVector<unsigned> geoIndices;
+
+    if (indexSize == sizeof(unsigned short))
+    {
+        // 16-bit indices
+        const unsigned short* indices = ((const unsigned short*)indexData) + indexStart;
+        const unsigned short* indicesEnd = indices + indexCount;
+
+        while (indices < indicesEnd)
+        {
+            unsigned idx = (unsigned) *indices++;
+
+            if (idx >= vertexStart && idx < (vertexStart + vertexCount))
+            {
+                geoIndices.Push(idx - vertexStart);
+            }
+        }
+    }
+    else
+    {
+        // 32-bit indices
+        const unsigned* indices = ((const unsigned*)indexData) + indexStart;
+        const unsigned* indicesEnd = indices + indexCount;
+
+        while (indices < indicesEnd)
+        {
+            unsigned idx = *indices++;
+
+            if (idx >= vertexStart && idx < (vertexStart + vertexCount))
+            {
+                geoIndices.Push(idx - vertexStart);
+            }
+        }
+
+    }
+
+    mpGeo->indices_ = new unsigned[geoIndices.Size()];
+    mpGeo->numIndices_ = geoIndices.Size();
+    memcpy(&mpGeo->indices_[0], &geoIndices[0], sizeof(unsigned) * geoIndices.Size());
+
+    level->mpGeometry_.Push(mpGeo);
+
+    return true;
+}
+
+}

+ 109 - 0
Source/AtomicGlow/Atlas/ModelPacker.h

@@ -0,0 +1,109 @@
+#pragma once
+
+#include <Atomic/Graphics/IndexBuffer.h>
+#include <Atomic/Graphics/VertexBuffer.h>
+#include <Atomic/Graphics/Geometry.h>
+#include <Atomic/Graphics/Model.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class MPGeometry;
+
+struct MPVertex
+{
+    Vector3 position_;
+    Vector3 normal_;
+    Vector4 tangent_;
+    unsigned color_;
+    Vector2 uv0_;
+    Vector2 uv1_;
+
+    void Clear()
+    {
+        position_ =  Vector3::ZERO;
+        normal_ = Vector3::ZERO;
+        tangent_ = Vector4::ZERO;
+        uv0_ = Vector2::ZERO;
+        uv1_ = Vector2::ZERO;
+        color_ = 0;
+    }
+
+    bool operator==(const MPVertex& rhs)
+    {
+        return ( position_ == rhs.position_ &&
+                 normal_ == rhs.normal_ &&
+                 tangent_ == rhs.tangent_ &&
+                 color_ == rhs.color_ &&
+                 uv0_ == rhs.uv0_ &&
+                 uv1_ == rhs.uv1_ );
+
+    }
+};
+
+class MPGeometry : public RefCounted
+{
+    ATOMIC_REFCOUNTED(MPGeometry)
+
+public:
+
+    SharedPtr<Geometry> geometry_;
+    PODVector<MPVertex> vertices_;
+    PODVector<VertexElement> elements_;
+    SharedArrayPtr<unsigned> indices_;
+    unsigned numIndices_;
+
+};
+
+class MPLODLevel : public RefCounted
+{
+    ATOMIC_REFCOUNTED(MPLODLevel)
+
+public:
+
+    /// Get the total vertex and index counts of all LOD geometry
+    void GetTotalCounts(unsigned& totalVertices, unsigned& totalIndices) const;
+
+    /// Returns true if all LOD geometry contains element
+    bool HasElement(VertexElementType type, VertexElementSemantic semantic, unsigned char index = 0) const;
+
+    Vector<SharedPtr<MPGeometry>> mpGeometry_;
+
+    unsigned level_;
+};
+
+/// Model packer/unpacker from/to packed representation (destructive!)
+class ModelPacker : public Object
+{
+    ATOMIC_OBJECT(ModelPacker, Object)
+
+public:
+
+    ModelPacker(Context* context);
+    virtual ~ModelPacker();
+
+    bool Unpack(Model* model);
+
+    bool Pack();
+
+    const String& GetErrorText() const { return errorText_; }
+
+    Vector<SharedPtr<MPLODLevel>> lodLevels_;
+
+private:
+
+    void SetError(const String& errorText) { errorText_ = errorText; }
+
+    String errorText_;
+
+    bool UnpackLODLevel(unsigned level);
+
+    bool UnpackGeometry(MPLODLevel* level, Geometry* geometry);
+
+    SharedPtr<Model> model_;
+
+};
+
+}

+ 46 - 0
Source/AtomicGlow/CMakeLists.txt

@@ -0,0 +1,46 @@
+include_directories ( ${CMAKE_SOURCE_DIR}/Source/ThirdParty )
+include_directories ( ${CMAKE_SOURCE_DIR}/Source/ThirdParty/embree/include )
+
+add_definitions(-DEMBREE_STATIC_LIB=1)
+
+file (GLOB GLOW_COMMON_SOURCE Common/*.cpp Common/*.h)
+file (GLOB GLOW_KERNEL_SOURCE Kernel/*.cpp Kernel/*.h)
+file (GLOB GLOW_ATLAS_SOURCE Atlas/*.cpp Atlas/*.h)
+file (GLOB GLOW_SERVICE_SOURCE GlowService/*.cpp GlowService/*.h)
+
+add_library(AtomicGlowLib ${GLOW_COMMON_SOURCE} ${GLOW_KERNEL_SOURCE} ${GLOW_ATLAS_SOURCE} ${GLOW_SERVICE_SOURCE} )
+
+target_link_libraries(AtomicGlowLib ToolCore Poco embree)
+
+target_compile_definitions(AtomicGlowLib PUBLIC -DATOMIC_GLOW=1)
+
+add_dependencies(AtomicApp AtomicToolCheckScripts)
+
+if (WIN32)
+if (NOT ATOMIC_GLOW_WINDOWS_SUBSYSTEM)
+    add_definitions(-DATOMIC_WIN32_CONSOLE)
+endif()
+endif()
+
+
+file (GLOB GLOW_APPLICATION_SOURCE GlowApplication/*.cpp GlowApplication/*.h)
+
+add_executable(AtomicGlow ${GLOW_APPLICATION_SOURCE})
+
+target_link_libraries(AtomicGlow AtomicApp AtomicJS AtomicNETScript AtomicGlowLib)
+
+if (WIN32)
+    target_link_libraries(AtomicGlow Iphlpapi Wldap32)
+    if (ATOMIC_GLOW_WINDOWS_SUBSYSTEM)
+        set_target_properties(AtomicGlow PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS")
+    endif()
+endif()
+
+
+# Copy AtomicGlow to Artifacts
+add_custom_command( TARGET AtomicGlow POST_BUILD
+                    COMMAND "${CMAKE_COMMAND}"
+                    ARGS -E make_directory \"${ATOMIC_SOURCE_DIR}/Artifacts/Build/AtomicGlow\"
+                    COMMAND "${CMAKE_COMMAND}"
+                    ARGS -E copy_if_different \"$<TARGET_FILE:AtomicGlow>\" \"${ATOMIC_SOURCE_DIR}/Artifacts/Build/AtomicGlow/\"
+                    COMMENT "Copying AtomicGlow to Build Artifacts" )

+ 40 - 0
Source/AtomicGlow/Common/GlowEvents.h

@@ -0,0 +1,40 @@
+//
+// Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+// Glow process -> Glow Service event
+ATOMIC_EVENT(E_ATOMICGLOWRESULT, AtomicGlowResult)
+{
+    ATOMIC_PARAM(P_SUCCESS, Success);    // bool
+    ATOMIC_PARAM(P_RESULT, Result);    // String
+    ATOMIC_PARAM(P_BAKEDATA, BakeData);  // VectorBuffer
+}
+
+}

+ 30 - 0
Source/AtomicGlow/Common/GlowSettings.cpp

@@ -0,0 +1,30 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "GlowSettings.h"
+
+namespace AtomicGlow
+{
+
+GlowSettings GlobalGlowSettings;
+
+}

+ 241 - 0
Source/AtomicGlow/Common/GlowSettings.h

@@ -0,0 +1,241 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Math/MathDefs.h>
+#include <Atomic/Container/Str.h>
+#include <Atomic/IO/VectorBuffer.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+
+{
+    enum GlowPreset
+    {
+        GLOW_PRESET_FAST_LOW_QUALITY,
+        GLOW_PRESET_MEDIUM_QUALITY,
+        GLOW_PRESET_HIGH_QUALITY,
+        GLOW_PRESET_SLOW_EXTREME_QUALITY
+    };
+
+    enum GlowOutputFormat
+    {
+        GLOW_OUTPUT_PNG,
+        GLOW_OUTPUT_DDS
+    };
+
+    struct GlowSettings
+    {
+        int lightmapSize_;
+        GlowOutputFormat outputFormat_;
+
+        // global scalar
+        float lexelDensity_;
+
+        float sceneLexelDensityScale_;
+
+        // global illumination
+        bool giEnabled_;
+        int giGranularity_;
+        int giMaxBounces_;
+
+        // ambient occlusion
+        bool aoEnabled_;
+        float aoDepth_;
+        unsigned nsamples_;
+        float aoMin_;
+        float aoMultiply_;
+
+        // NEW GI
+
+        // Number of photon passes.
+        int photonPassCount_;
+        // Maximum photon tracing depth (number of light bounces).
+        int photonBounceCount_;
+        // The minimum energy that photon should have to continue tracing.
+        float photonEnergyThreshold_;
+        // The reflected light maximum distance. All intersections above this value will be ignored.
+        float photonMaxDistance_;
+
+        // Number of final gather samples.
+        int finalGatherSamples_;
+        // Maximum distance to gather photons at.
+        float finalGatherDistance_;
+        // A radius of circle in which samples are gathered from photon map.
+        int finalGatherRadius_;
+
+        GlowSettings()
+        {
+            SetDefaults();
+            Validate();
+        }
+
+        void Pack(VectorBuffer& buffer) const
+        {
+            buffer.WriteInt(lightmapSize_);
+            buffer.WriteUInt((unsigned) outputFormat_);
+
+            buffer.WriteFloat(lexelDensity_);
+            buffer.WriteFloat(sceneLexelDensityScale_);
+
+            buffer.WriteBool(giEnabled_);
+            buffer.WriteInt(giGranularity_);
+            buffer.WriteInt(giMaxBounces_);
+
+            buffer.WriteBool(aoEnabled_);
+            buffer.WriteFloat(aoDepth_);
+            buffer.WriteUInt(nsamples_);
+            buffer.WriteFloat(aoDepth_);
+            buffer.WriteFloat(aoMin_);
+            buffer.WriteFloat(aoMultiply_);
+        }
+
+        void Unpack(VectorBuffer& buffer)
+        {
+            lightmapSize_ = buffer.ReadInt();
+            outputFormat_ = (GlowOutputFormat) buffer.ReadUInt();
+
+            lexelDensity_ = buffer.ReadFloat();
+            sceneLexelDensityScale_ = buffer.ReadFloat();
+
+            giEnabled_ = buffer.ReadBool();
+            giGranularity_ = buffer.ReadInt();
+            giMaxBounces_ = buffer.ReadInt();
+
+            aoEnabled_ = buffer.ReadBool();
+            aoDepth_ = buffer.ReadFloat();
+            nsamples_ = buffer.ReadUInt( );
+            aoDepth_= buffer.ReadFloat();
+            aoMin_ = buffer.ReadFloat();
+            aoMultiply_ = buffer.ReadFloat();
+        }
+
+        void Validate()
+        {
+            // always use 2048 for lightmap size
+            lightmapSize_ = 2048;
+
+            sceneLexelDensityScale_ = Atomic::Clamp<float>(sceneLexelDensityScale_, 0.01f, 1.0f);
+
+            lexelDensity_ = Atomic::Clamp<float>(lexelDensity_, 0.01f, 1.0f);
+            nsamples_ = Atomic::Clamp<unsigned>(nsamples_, 16, 256);
+
+            giMaxBounces_ = Atomic::Clamp<int>(giMaxBounces_, 0, 8);
+            giGranularity_ = Atomic::Clamp<int>(giGranularity_, 4, 16);
+
+            aoDepth_ = Atomic::Clamp<float>(aoDepth_, 0.01f, 10.0f);
+            aoMin_ = Atomic::Clamp<float>(aoMin_, 0.0f, 0.95f);
+            aoMultiply_ = Atomic::Clamp<float>(aoMultiply_, 0.01f, 100.0f);
+        }
+
+        void SetDefaults(GlowPreset preset = GLOW_PRESET_FAST_LOW_QUALITY)
+        {
+            // common settings
+
+            // lightmap size
+            lightmapSize_ = 2048;
+
+            giMaxBounces_ = 3;
+
+            sceneLexelDensityScale_ = 0.35f;
+
+            // TODO: Factor in DDS scene lighting loader, which have tested
+            // and minimal artifacts with significant runtime memory savings
+            outputFormat_ = GLOW_OUTPUT_PNG;
+
+            aoEnabled_ = false;
+            aoDepth_ = 0.25f;
+            aoMin_ = 0.45f;
+            aoMultiply_ = 1.0f;
+
+            switch (preset)
+            {
+                case GLOW_PRESET_FAST_LOW_QUALITY:
+                    lexelDensity_ = 0.16f;
+                    nsamples_ = 16;
+                    photonPassCount_          = 64;
+                    photonBounceCount_        = 4;
+                    photonEnergyThreshold_    = 0.05f;
+                    photonMaxDistance_        = 10;
+
+                    finalGatherSamples_       = 1024;
+                    finalGatherDistance_      = 50;
+                    finalGatherRadius_        = 7;
+                    break;
+
+                case GLOW_PRESET_MEDIUM_QUALITY:
+                    lexelDensity_ = 0.32f;
+                    nsamples_ = 64;
+                    giEnabled_ = true;
+                    giGranularity_ = 8;
+
+                    photonPassCount_          = 16;
+                    photonBounceCount_        = 3;
+                    photonEnergyThreshold_    = 0.05f;
+                    photonMaxDistance_        = 10;
+
+                    finalGatherSamples_       = 64;
+                    finalGatherDistance_      = 50;
+                    finalGatherRadius_        = 7;
+                    break;
+
+                case GLOW_PRESET_HIGH_QUALITY:
+                    lexelDensity_ = 0.5f;                    
+                    giEnabled_ = true;
+                    nsamples_ = 256;
+                    giGranularity_ = 8;
+
+                    photonPassCount_          = 32;
+                    photonBounceCount_        = 3;
+                    photonEnergyThreshold_    = 0.05f;
+                    photonMaxDistance_        = 10;
+
+                    finalGatherSamples_       = 128;
+                    finalGatherDistance_      = 50;
+                    finalGatherRadius_        = 7;
+                    break;
+
+                case GLOW_PRESET_SLOW_EXTREME_QUALITY:
+                    lexelDensity_ = 0.65f;
+                    nsamples_ = 256;
+                    giEnabled_ = true;
+                    giGranularity_ = 8;
+
+                    photonPassCount_          = 64;
+                    photonBounceCount_        = 4;
+                    photonEnergyThreshold_    = 0.05f;
+                    photonMaxDistance_        = 10;
+
+                    finalGatherSamples_       = 1024;
+                    finalGatherDistance_      = 50;
+                    finalGatherRadius_        = 7;
+                    break;
+            }
+        }
+
+    };
+
+    extern GlowSettings GlobalGlowSettings;
+
+}

+ 339 - 0
Source/AtomicGlow/GlowApplication/AtomicGlowApp.cpp

@@ -0,0 +1,339 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/Core/CoreEvents.h>
+#include <Atomic/Core/WorkQueue.h>
+#include <Atomic/Engine/EngineDefs.h>
+#include <Atomic/Engine/Engine.h>
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/IO/Log.h>
+#include <Atomic/IO/IOEvents.h>
+
+#include <Atomic/Resource/ResourceMapRouter.h>
+#include <Atomic/IPC/IPCEvents.h>
+
+#include <ToolCore/ToolSystem.h>
+#include <ToolCore/ToolEnvironment.h>
+
+#include <AtomicGlow/Common/GlowSettings.h>
+#include <AtomicGlow/Common/GlowEvents.h>
+#include <AtomicGlow/Kernel/BakeModel.h>
+#include <AtomicGlow/Kernel/BakeMaterial.h>
+#include <AtomicGlow/Kernel/SceneBaker.h>
+
+#include "AtomicGlowApp.h"
+
+using namespace ToolCore;
+
+#ifdef ATOMIC_PLATFORM_OSX
+#include <unistd.h>
+#endif
+
+#ifdef ATOMIC_PLATFORM_WINDOWS
+#include <stdio.h>
+#endif
+
+
+ATOMIC_DEFINE_APPLICATION_MAIN(AtomicGlow::AtomicGlowApp);
+
+namespace AtomicGlow
+{
+
+    AtomicGlowApp::AtomicGlowApp(Context* context) :
+        IPCClientApp(context)
+    {
+
+    }
+
+    AtomicGlowApp::~AtomicGlowApp()
+    {
+
+    }
+
+    void AtomicGlowApp::HandleIPCCmd(StringHash eventType, VariantMap& eventData)
+    {
+
+        using namespace IPCCmd;
+
+        IPC* ipc = GetSubsystem<IPC>();
+
+        String cmd = eventData[P_COMMAND].GetString().ToLower();
+        unsigned id = eventData[P_ID].GetUInt();
+
+        VariantMap result;
+        result[P_COMMAND] = cmd;
+        result[P_ID] = id;
+
+        if (cmd == "bake")
+        {
+            timer_.Reset();
+
+            String sceneName = eventData["scenename"].GetString();
+
+            // settings
+            VectorBuffer settingsBuffer = eventData["settings"].GetVectorBuffer();
+            GlobalGlowSettings.Unpack(settingsBuffer);
+
+            ATOMIC_LOGINFOF("AtomicGlow baking scene: %s", sceneName.CString());
+
+            sceneBaker_->SetStandaloneMode(false);
+            sceneBaker_->LoadScene(sceneName);
+
+            using namespace IPCCmdResult;
+            result["result"] = "success";
+
+            ipc->SendEventToBroker(E_IPCCMDRESULT, result);
+
+        }
+
+        if (cmd == "quit")
+        {
+            ATOMIC_LOGINFO("AtomicGlow quit received, exiting");
+            GetSubsystem<WorkQueue>()->TerminateThreads();
+            exitCode_ = EXIT_SUCCESS;
+            engine_->Exit();
+        }
+
+    }
+
+    void AtomicGlowApp::HandleLogMessage(StringHash eventType, VariantMap& eventData)
+    {
+        using namespace LogMessage;
+
+        if (GetBrokerActive())
+        {
+
+            if (!GetIPC())
+                return;
+
+            VariantMap logEvent;
+            logEvent[IPCWorkerLog::P_LEVEL] = eventData[P_LEVEL].GetInt();
+            logEvent[IPCWorkerLog::P_MESSAGE] = eventData[P_MESSAGE].GetString();
+            GetIPC()->SendEventToBroker(E_IPCWORKERLOG, logEvent);
+        }
+
+    }
+
+
+    void AtomicGlowApp::HandleUpdate(StringHash eventType, VariantMap& eventData)
+    {
+        // if no scene has been loaded, return
+        if (!sceneBaker_->GetScene())
+        {
+            return;
+        }
+
+        if (!GetSubsystem<WorkQueue>()->IsCompleted(M_MAX_UNSIGNED))
+        {
+            return;
+        }
+
+        // if we're done, exit in standalone mode, or send IPC event if
+        // running off Glow service
+        if (sceneBaker_->GetCurrentLightMode() == GLOW_LIGHTMODE_COMPLETE)
+        {
+            String resultString = ToString("Scene lit in %i seconds", (int) (timer_.GetMSec(false) / 1000.0f));
+
+            ATOMIC_LOGINFO(resultString);
+
+            UnsubscribeFromEvent(E_UPDATE);
+
+            sceneBaker_->GenerateLightmaps();
+
+            if (sceneBaker_->GetStandaloneMode())
+            {
+                // TODO: write scene file/lightmaps in standalone mode
+
+                exitCode_ = EXIT_SUCCESS;
+                engine_->Exit();
+                return;
+            }
+            else
+            {
+                using namespace AtomicGlowResult;
+                VariantMap eventData;
+                eventData[P_SUCCESS] = true;
+                eventData[P_RESULT] = resultString;
+                eventData[P_BAKEDATA] = sceneBaker_->GetBakeData();
+                GetIPC()->SendEventToBroker(E_ATOMICGLOWRESULT, eventData);
+            }
+
+            return;
+        }
+
+        if (sceneBaker_->GetCurrentLightMode() == GLOW_LIGHTMODE_UNDEFINED)
+        {
+
+            // light mode will either move to direct or complete, depending on work to do
+            sceneBaker_->Light(GLOW_LIGHTMODE_DIRECT);
+            return;
+        }
+
+        if (sceneBaker_->GetCurrentLightMode() == GLOW_LIGHTMODE_DIRECT)
+        {
+            sceneBaker_->LightFinishCycle();
+
+            // light mode will either move to indirect or complete, depending on work to do
+            sceneBaker_->Light(GLOW_LIGHTMODE_INDIRECT);
+            return;
+        }
+
+        if (sceneBaker_->GetCurrentLightMode() == GLOW_LIGHTMODE_INDIRECT)
+        {
+            sceneBaker_->LightFinishCycle();
+
+            // light mode will either move to indirect or complete, depending on work to do
+            sceneBaker_->Light(GLOW_LIGHTMODE_INDIRECT);
+            return;
+        }
+
+    }
+
+    void AtomicGlowApp::Start()
+    {
+        // IMPORTANT!!!
+        // This needs to be refactored, see // ATOMIC GLOW HACK in AssetDatabase.cpp
+        // SharedPtr<ResourceMapRouter> router(new ResourceMapRouter(context_, "__atomic_ResourceCacheMap.json"));
+        // IMPORTANT!!!
+
+        if (exitCode_)
+            return;
+
+        if (!engine_->Initialize(engineParameters_))
+        {
+            ErrorExit();
+            return;
+        }
+
+        context_->RegisterSubsystem(new BakeMaterialCache(context_));
+        context_->RegisterSubsystem(new BakeModelCache(context_));
+
+        SubscribeToEvent(E_UPDATE, ATOMIC_HANDLER(AtomicGlowApp, HandleUpdate));
+        SubscribeToEvent(E_LOGMESSAGE, ATOMIC_HANDLER(AtomicGlowApp, HandleLogMessage));
+        SubscribeToEvent(E_IPCMESSAGE, ATOMIC_HANDLER(AtomicGlowApp, HandleUpdate));
+        SubscribeToEvent(E_IPCCMD, ATOMIC_HANDLER(AtomicGlowApp, HandleIPCCmd));
+
+        bool ipc = IPCClientApp::Initialize(arguments_);
+
+        ToolSystem* tsystem = GetSubsystem<ToolSystem>();
+
+        // Load project, in read only mode
+        if (!tsystem->LoadProject(projectPath_, true))
+        {
+            ErrorExit(ToString("Unable to load project %s", projectPath_.CString()));
+            return;
+        }
+
+        sceneBaker_ = new SceneBaker(context_, projectPath_);
+
+        if (!ipc)
+        {
+            if (!sceneBaker_->LoadScene(scenePath_))
+            {
+                String message = scenePath_.Length() ? "AtomicGlowApp::Start() - standalone mode unable to load scene: " + scenePath_:
+                                                       "AtomicGlowApp::Start() - standalone mode scene not specified";
+
+                ErrorExit(message);
+            }
+        }
+
+    }
+
+    void AtomicGlowApp::Setup()
+    {
+        // AtomicGlow is always headless
+        engineParameters_["Headless"] = true;
+
+        FileSystem* fileSystem = GetSubsystem<FileSystem>();
+
+        ToolSystem* tsystem = new ToolSystem(context_);
+        context_->RegisterSubsystem(tsystem);
+
+        ToolEnvironment* env = new ToolEnvironment(context_);
+        context_->RegisterSubsystem(env);
+
+        // Initialize the ToolEnvironment
+        if (!env->Initialize(true))
+        {
+            ErrorExit("Unable to initialize tool environment");
+            return;
+        }
+
+        for (unsigned i = 0; i < arguments_.Size(); ++i)
+        {
+            if (arguments_[i].Length() > 1)
+            {
+                String argument = arguments_[i].ToLower();
+                String value = i + 1 < arguments_.Size() ? arguments_[i + 1] : String::EMPTY;
+
+                if (argument == "--project" && value.Length())
+                {
+                    if (GetExtension(value) == ".atomic")
+                    {
+                        value = GetPath(value);
+                    }
+
+                    if (!fileSystem->DirExists(value))
+                    {
+                        ErrorExit(ToString("%s project path does not exist", value.CString()));
+                        return;
+                    }
+
+                    projectPath_ = AddTrailingSlash(value);
+
+                }
+                else if (argument == "--scene" && value.Length())
+                {
+                    scenePath_ = value;
+
+                }
+
+            }
+        }
+
+        if (!projectPath_.Length())
+        {
+            ErrorExit(ToString("%s project path not specified"));
+            return;
+        }
+
+        engineParameters_.InsertNew("LogName", fileSystem->GetAppPreferencesDir("AtomicEditor", "Logs") + "AtomicGlow.log");
+
+    #ifdef ATOMIC_DEV_BUILD
+        engineParameters_["ResourcePrefixPaths"] = env->GetRootSourceDir() + "/Resources/";
+        engineParameters_["ResourcePaths"] = ToString("CoreData;EditorData;%sResources;%sCache", projectPath_.CString(), projectPath_.CString());
+    #endif
+
+        // TODO: change to using new workerthreadcount command line arg
+        // which exposed in editor GlowComponent UI
+        // also, check how the number of threads affects light times
+        // engineParameters_[EP_WORKER_THREADS_COUNT] = 8;
+
+        IPCClientApp::Setup();
+
+    }
+
+
+}
+
+

+ 66 - 0
Source/AtomicGlow/GlowApplication/AtomicGlowApp.h

@@ -0,0 +1,66 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Core/Timer.h>
+#include <AtomicApp/IPCClientApp.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+    class SceneBaker;
+
+
+    class AtomicGlowApp : public IPCClientApp
+    {
+        ATOMIC_OBJECT(AtomicGlowApp, IPCClientApp)
+
+    public:
+        /// Construct.
+        AtomicGlowApp(Context* context);
+        virtual ~AtomicGlowApp();
+
+    private:
+
+        void HandleUpdate(StringHash eventType, VariantMap& eventData);
+        void HandleIPCCmd(StringHash eventType, VariantMap& eventData);
+        void HandleLogMessage(StringHash eventType, VariantMap& eventData);
+
+
+        /// Setup before engine initialization.
+        virtual void Setup();
+
+        virtual void Start();
+
+        Timer timer_;
+
+        String projectPath_;
+        String scenePath_;
+
+        SharedPtr<SceneBaker> sceneBaker_;
+
+    };
+
+}

+ 192 - 0
Source/AtomicGlow/GlowService/GlowProcess.cpp

@@ -0,0 +1,192 @@
+//
+// Copyright (c) 2014-2017 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Core/Context.h>
+#include <Atomic/IPC/IPCBroker.h>
+#include <Atomic/IPC/IPCEvents.h>
+
+#include "../Common/GlowEvents.h"
+
+#include "GlowService.h"
+#include "GlowServiceEvents.h"
+#include "GlowProcess.h"
+
+namespace AtomicGlow
+{
+
+
+// Atomic Glow Process
+GlowProcess::GlowProcess(Context* context) : IPCServer(context),
+    exitCalled_(false),
+    resultHandler_(new GlowProcessResultHandler(context, this))
+{
+
+}
+
+GlowProcess::~GlowProcess()
+{
+
+}
+
+void GlowProcess::OnIPCWorkerExited()
+{
+    GetSubsystem<GlowService>()->OnGlowProcessExited();
+}
+
+void GlowProcess::OnIPCWorkerLog(int level, const String& message)
+{
+    using namespace AtomicGlowServiceLogEvent;
+
+    VariantMap eventData;
+    eventData[P_LEVEL] = level;
+    eventData[P_MESSAGE] = message;
+
+    SendEvent(E_ATOMICGLOWSERVICELOGEVENT, eventData);
+}
+
+bool GlowProcess::Start(const String &glowBinaryPath, const StringVector &baseArgs)
+{
+    if (!IPCServer::StartInternal(glowBinaryPath, baseArgs))
+    {
+        return false;
+    }
+
+    SubscribeToEvent(GetServerBroker(), E_ATOMICGLOWRESULT, ATOMIC_HANDLER(GlowProcess, HandleAtomicGlowResult));
+
+    return true;
+}
+
+void GlowProcess::HandleAtomicGlowResult(StringHash eventType, VariantMap& eventData)
+{
+    GlowService* glowService = GetSubsystem<GlowService>();
+
+    using namespace AtomicGlowResult;
+
+    const String& result = eventData[P_RESULT].GetString();
+
+    if (!eventData[P_SUCCESS].GetBool())
+    {
+        glowService->OnBakeError(result);
+        return;
+    }
+
+    Variant variant;
+
+    if (!eventData.TryGetValue(P_BAKEDATA, variant) || variant.GetType() != VAR_BUFFER)
+    {
+        glowService->OnBakeError("GlowProcess::HandleResult() - Unable to get bake data");
+        return;
+    }
+
+    VectorBuffer bakeData = variant.GetVectorBuffer();
+
+    glowService->ProcessBakeData(bakeData);
+
+    glowService->OnBakeSuccess();
+
+}
+
+unsigned GlowProcess::QueueCommand(const VariantMap& cmdMap)
+{
+    return IPCServer::QueueCommand(resultHandler_, cmdMap);
+}
+
+void GlowProcess::Exit()
+{
+    if (exitCalled_)
+        return;
+
+    exitCalled_ = true;
+
+    using namespace IPCCmd;
+    VariantMap cmdMap;
+    cmdMap[P_COMMAND] = "quit";
+    QueueCommand(cmdMap);
+}
+
+void GlowProcess::HandleResult(unsigned cmdID, const VariantMap& cmdResult)
+{
+    using namespace IPCCmdResult;
+
+    GlowService* glowService = GetSubsystem<GlowService>();
+
+    Variant variant;
+    String cmd;
+    String result;
+
+    if (!cmdResult.TryGetValue(P_COMMAND, variant) || variant.GetType() != VAR_STRING)
+    {
+        ATOMIC_LOGERROR("GlowProcess::HandleResult() - Unable to process result, command key missing");
+        return;
+    }
+
+    cmd = variant.GetString();
+
+    if (!cmdResult.TryGetValue("result", variant) || variant.GetType() != VAR_STRING)
+    {
+        ATOMIC_LOGERROR("GlowProcess::HandleResult() - Unable to process result, command key missing");
+        return;
+    }
+
+    result = variant.GetString();
+
+    if (cmd == "bake")
+    {
+        if (result != "success")
+        {
+            glowService->OnBakeError(result);
+            return;
+        }
+    }
+
+}
+
+// Result Handler
+
+GlowProcessResultHandler::GlowProcessResultHandler(Context* context, GlowProcess* process) :
+    IPCResultHandler(context),
+    process_(process)
+{
+
+}
+
+GlowProcessResultHandler::~GlowProcessResultHandler()
+{
+
+}
+
+void GlowProcessResultHandler::HandleResult(unsigned cmdID, const VariantMap& cmdResult)
+{
+    if (!process_)
+    {
+        ATOMIC_LOGWARNING("lowProcessResultHandler::HandleResult() - called without current Glow process");
+        return;
+    }
+
+    process_->HandleResult(cmdID, cmdResult);
+
+}
+
+
+}
+

+ 88 - 0
Source/AtomicGlow/GlowService/GlowProcess.h

@@ -0,0 +1,88 @@
+//
+// Copyright (c) 2014-2017 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/IPC/IPCServer.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class GlowProcessResultHandler;
+
+class GlowProcess : public IPCServer
+{
+    friend class GlowProcessResultHandler;
+
+    ATOMIC_OBJECT(GlowProcess, IPCServer)
+
+public:
+
+    /// Construct.
+    GlowProcess(Context* context);
+    /// Destruct.
+    virtual ~GlowProcess();
+
+    bool Start() { return false; }
+
+    bool Start(const String& glowBinaryPath, const StringVector& baseArgs);
+
+    void Exit();
+
+    bool GetExitCalled() const { return exitCalled_; }
+
+    unsigned QueueCommand(const VariantMap& cmdMap);
+
+private:
+
+    void OnIPCWorkerLog(int level, const String& message);
+    void OnIPCWorkerExited();
+
+    void HandleResult(unsigned cmdID, const VariantMap& cmdResult);
+
+    void HandleAtomicGlowResult(StringHash eventType, VariantMap& eventData);
+
+    bool exitCalled_;
+
+    SharedPtr<GlowProcessResultHandler> resultHandler_;
+
+};
+
+class GlowProcessResultHandler : public IPCResultHandler
+{
+    ATOMIC_OBJECT(GlowProcessResultHandler, IPCResultHandler)
+
+public:
+    /// Construct.
+    GlowProcessResultHandler(Context* context, GlowProcess* process);
+    /// Destruct.
+    virtual ~GlowProcessResultHandler();
+
+    void HandleResult(unsigned cmdID, const VariantMap& cmdResult);
+
+private:    
+
+    WeakPtr<GlowProcess> process_;
+
+};
+
+}

+ 320 - 0
Source/AtomicGlow/GlowService/GlowService.cpp

@@ -0,0 +1,320 @@
+//
+// Copyright (c) 2014-2017 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/IPC/IPCEvents.h>
+#include <Atomic/Graphics/StaticModel.h>
+
+#include "GlowProcess.h"
+#include "GlowServiceEvents.h"
+#include "GlowService.h"
+
+namespace AtomicGlow
+{
+
+GlowService::GlowService(Context* context) : Object(context),
+    bakeCanceled_(false)
+{
+
+}
+
+GlowService::~GlowService()
+{
+
+}
+
+void GlowService::OnGlowProcessExited()
+{
+    if (glowProcess_.NotNull())
+    {
+        if (!glowProcess_->GetExitCalled() || bakeCanceled_)
+        {
+            using namespace AtomicGlowServiceBakeResult;
+
+            VariantMap eventData;
+            eventData[P_RESULT] = bakeCanceled_ ? "Bake canceled" : "GlowService::OnGlowProcessExited() - Glow process exited unexpectedly";
+            eventData[P_SUCCESS] = false;
+            SendEvent(E_ATOMICGLOWSERVICEBAKERESULT, eventData);
+        }
+    }
+
+    if (scene_)
+    {
+        scene_->LoadLightmaps(true);
+    }
+
+    projectPath_ = String::EMPTY;
+    glowProcess_ = 0;
+    scene_ = 0;
+
+}
+
+void GlowService::OnBakeError(const String& result)
+{
+    ATOMIC_LOGERRORF("%s", result.CString());
+    glowProcess_->Exit();
+
+    using namespace AtomicGlowServiceBakeResult;
+
+    VariantMap eventData;
+    eventData[P_RESULT] = result;
+    eventData[P_SUCCESS] = false;
+
+    SendEvent(E_ATOMICGLOWSERVICEBAKERESULT, eventData);
+}
+
+void GlowService::OnBakeSuccess()
+{
+    glowProcess_->Exit();
+
+    using namespace AtomicGlowServiceBakeResult;
+
+    VariantMap eventData;
+    eventData[P_RESULT] = "success";
+    eventData[P_SUCCESS] = true;    
+
+    SendEvent(E_ATOMICGLOWSERVICEBAKERESULT, eventData);
+
+}
+
+void GlowService::ProcessBakeData(VectorBuffer& bakeData)
+{
+    if (!scene_)
+    {
+        ATOMIC_LOGERROR("GlowService::ProcessBakeData() - called with null scene");
+        return;
+    }
+
+    unsigned count = bakeData.ReadUInt();
+
+    PODVector<Node*> children;
+
+    scene_->GetChildrenWithComponent<StaticModel>(children, true);
+
+    for (unsigned i = 0; i < count; i++)
+    {
+        Node* node = 0;
+        StaticModel* staticModel = 0;
+
+        unsigned nodeID = bakeData.ReadUInt();
+        unsigned staticModelID = bakeData.ReadUInt();
+        unsigned lightMask = bakeData.ReadUInt();
+        unsigned lightmapIndex = bakeData.ReadUInt();
+        Vector4 tilingOffset = bakeData.ReadVector4();
+
+        for (unsigned j = 0; j < children.Size(); j++)
+        {
+            if (children[j]->GetID() == nodeID)
+            {
+                node = children[j];
+                staticModel = node->GetComponent<StaticModel>();
+
+                if (!staticModel || staticModel->GetID() != staticModelID)
+                {
+                    ATOMIC_LOGERROR("GlowService::ProcessBakeData() - mismatched node <-> static model ID");
+                    return;
+                }
+
+                break;
+            }
+
+        }
+
+        if (!node)
+        {
+            ATOMIC_LOGERROR("GlowService::ProcessBakeData() - unable to find node ID");
+            return;
+        }
+
+        staticModel->SetLightMask(lightMask);
+        staticModel->SetLightmapIndex(lightmapIndex);
+        staticModel->SetLightmapTilingOffset(tilingOffset);
+
+    }
+
+}
+
+bool GlowService::Bake(const String& projectPath, Scene* scene, const GlowSettings& settings)
+{
+    if (!scene)
+    {
+        ATOMIC_LOGERROR("GlowService::Bake() - Called with null scene");
+        return false;
+    }
+
+    String sceneName = scene->GetFileName();
+
+    if (!sceneName.Length())
+    {
+        ATOMIC_LOGERROR("GlowService::Bake() - Called with unnamed scene");
+        return false;
+    }
+
+    if (!projectPath.Length())
+    {
+        ATOMIC_LOGERROR("GlowService::Bake() - zero length projectPath");
+        return false;
+    }
+
+
+    if (glowProcess_.NotNull())
+    {
+        ATOMIC_LOGERROR("GlowService::Bake() - Called with existing glow process");
+        return false;
+    }
+
+    if (!glowBinaryPath_.Length())
+    {
+        ATOMIC_LOGERROR("GlowService::Bake() - Called with empty glowBinaryPath_");
+        return false;
+    }
+
+
+    bakeCanceled_ = false;
+    result_.Clear();
+
+    glowProcess_ = new GlowProcess(context_);
+
+    projectPath_ = projectPath;
+
+    StringVector args;
+    args.Push("--project");
+    args.Push(projectPath_);
+    args += glowBaseArgs_;
+
+    if (!glowProcess_->Start(glowBinaryPath_, args))
+    {
+        ATOMIC_LOGERRORF("GlowService::Bake() - Glow process failed to start: %s", glowBinaryPath_.CString());
+        return false;
+    }
+
+    scene_ = scene;
+    projectPath_ = projectPath;
+
+    using namespace IPCCmd;
+    VariantMap cmdMap;
+    cmdMap[P_COMMAND] = "bake";
+    cmdMap["scenename"] = sceneName;
+
+    // settings
+    VectorBuffer settingsBuffer;
+    settings.Pack(settingsBuffer);
+    cmdMap["settings"] = settingsBuffer;
+
+    glowProcess_->QueueCommand(cmdMap);
+
+    return true;
+
+}
+
+void GlowService::CancelBake()
+{
+
+    if (glowProcess_.Null())
+    {
+        ATOMIC_LOGERROR("GlowService::CancelBake() - Called without existing glow process");
+        return;
+    }
+
+    bakeCanceled_ = true;
+    glowProcess_->Exit();
+
+}
+
+bool GlowService::LocateServiceExecutable()
+{
+
+    glowBinaryPath_ = String::EMPTY;
+    glowBaseArgs_.Clear();
+
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+
+#ifdef ATOMIC_DEV_BUILD
+
+    String rootSourceDir = ATOMIC_ROOT_SOURCE_DIR;
+    rootSourceDir = AddTrailingSlash(rootSourceDir);
+
+    glowBinaryPath_ = rootSourceDir + "Artifacts/Build/AtomicGlow/AtomicGlow";
+
+#else
+
+#ifdef ATOMIC_PLATFORM_OSX
+    String resourcesDir = GetPath(RemoveTrailingSlash(fileSystem->GetProgramDir())) + "Resources/";
+#else
+    String resourcesDir = fileSystem->GetProgramDir() + "Resources/";
+#endif
+
+    glowBinaryPath_ = resourcesDir + "ToolData/AtomicGlow/AtomicGlow";
+
+#endif
+
+#ifdef ATOMIC_PLATFORM_WINDOWS
+
+    glowBinaryPath_ += ".exe";
+
+#endif
+
+    if (!fileSystem->FileExists(glowBinaryPath_))
+    {
+        ATOMIC_LOGERRORF("AtomicGlow binary not found: %s", glowBinaryPath_.CString());
+
+        glowBinaryPath_.Clear();
+        glowBaseArgs_.Clear();
+
+        return false;
+    }
+
+    return true;
+}
+
+bool GlowService::GetServiceExecutable(String& execPath, Vector<String>& baseArgs) const
+{
+    execPath.Clear();
+    baseArgs.Clear();
+
+    if (!glowBinaryPath_.Length())
+    {
+        return false;
+    }
+
+    execPath = glowBinaryPath_;
+    baseArgs = glowBaseArgs_;
+
+    return true;
+}
+
+bool GlowService::Start()
+{
+    if (!LocateServiceExecutable())
+    {
+        ATOMIC_LOGERROR("GlowService::Start() - Unable to start AtomicGlow service");
+        return false;
+    }
+
+    return true;
+
+}
+
+
+
+}

+ 80 - 0
Source/AtomicGlow/GlowService/GlowService.h

@@ -0,0 +1,80 @@
+//
+// Copyright (c) 2014-2017 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+#include <Atomic/Scene/Scene.h>
+
+#include <AtomicGlow/Common/GlowSettings.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class GlowProcess;
+
+class GlowService : public Object
+{
+    friend class GlowProcess;
+
+    ATOMIC_OBJECT(GlowService, Object)
+
+public:
+    /// Construct.
+    GlowService(Context* context);
+    /// Destruct.
+    virtual ~GlowService();
+
+    bool Start();
+
+    bool Bake(const String& projectPath, Scene* scene, const GlowSettings& settings);
+
+    void CancelBake();
+
+    bool GetServiceExecutable(String& execPath, Vector<String>& baseArgs) const;
+
+private:
+
+    bool LocateServiceExecutable();
+
+    void OnBakeError(const String& result);
+    void OnBakeSuccess();
+
+    void ProcessBakeData(VectorBuffer& bakeData);
+
+    void OnGlowProcessExited();
+
+    String glowBinaryPath_;
+    StringVector glowBaseArgs_;
+
+    WeakPtr<Scene> scene_;
+    SharedPtr<GlowProcess> glowProcess_;
+
+    String result_;
+    bool bakeCanceled_;
+    String projectPath_;
+
+};
+
+}

+ 44 - 0
Source/AtomicGlow/GlowService/GlowServiceEvents.h

@@ -0,0 +1,44 @@
+//
+// Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+ATOMIC_EVENT(E_ATOMICGLOWSERVICEBAKERESULT, AtomicGlowServiceBakeResult)
+{
+    ATOMIC_PARAM(P_SUCCESS, Success);    // bool
+    ATOMIC_PARAM(P_RESULT, Result);    // String
+}
+
+ATOMIC_EVENT(E_ATOMICGLOWSERVICELOGEVENT, AtomicGlowServiceLogEvent)
+{
+    ATOMIC_PARAM(P_LEVEL, Level);    // bool
+    ATOMIC_PARAM(P_MESSAGE, Message);    // String
+}
+
+}

+ 662 - 0
Source/AtomicGlow/Kernel/BakeLight.cpp

@@ -0,0 +1,662 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Portions Copyright (c) 2015 Dmitry Sovetov
+// Copyright 2009-2017 Intel Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Graphics/Zone.h>
+
+#include <AtomicGlow/Common/GlowSettings.h>
+
+#include "EmbreeScene.h"
+
+#include "LightRay.h"
+#include "BakeLight.h"
+#include "BakeMesh.h"
+#include "SceneBaker.h"
+
+namespace AtomicGlow
+{
+
+BakeLight::BakeLight(Context* context, SceneBaker* sceneBaker) : BakeNode(context, sceneBaker),
+    color_(Color::WHITE),
+    intensity_( 0.0f ),
+    castsShadow_( false )
+{
+}
+
+BakeLight::~BakeLight()
+{
+}
+
+LightCutoff* BakeLight::GetCutoffModel( void ) const
+{
+    return cutoffModel_;
+}
+
+void BakeLight::SetCutoffModel( LightCutoff* value )
+{
+    cutoffModel_ = value;
+}
+
+LightInfluence* BakeLight::GetInfluenceModel( void ) const
+{
+    return influenceModel_;
+}
+
+void BakeLight::SetInfluenceModel( LightInfluence* value )
+{
+    influenceModel_ = value;
+}
+
+LightVertexGenerator* BakeLight::GetVertexGenerator( void ) const
+{
+    return vertexGenerator_;
+}
+
+void BakeLight::SetVertexGenerator( LightVertexGenerator* value )
+{
+    vertexGenerator_ = value;
+}
+
+LightAttenuation* BakeLight::GetAttenuationModel( void ) const
+{
+    return attenuationModel_;
+}
+
+void BakeLight::SetAttenuationModel( LightAttenuation* value )
+{
+    attenuationModel_ = value;
+}
+
+PhotonEmitter* BakeLight::GetPhotonEmitter( void ) const
+{
+    return photonEmitter_;
+}
+
+void BakeLight::SetPhotonEmitter( PhotonEmitter* value )
+{
+    photonEmitter_ = value;
+}
+
+const Vector3& BakeLight::GetPosition( void ) const
+{
+    return position_;
+}
+
+void BakeLight::SetPosition( const Vector3& value )
+{
+    position_ = value;
+}
+
+const Color& BakeLight::GetColor( void ) const
+{
+    return color_;
+}
+
+void BakeLight::SetColor( const Color& value )
+{
+    color_ = value;
+}
+
+float BakeLight::GetIntensity( void ) const
+{
+    return intensity_;
+}
+
+void BakeLight::SetIntensity( float value )
+{
+    intensity_ = value;
+}
+
+bool BakeLight::GetCastsShadow( void ) const
+{
+    return castsShadow_;
+}
+
+void BakeLight::SetCastsShadow( bool value )
+{
+    castsShadow_ = value;
+}
+
+BakeLight* BakeLight::CreateZoneLight( SceneBaker* baker, const Color& color)
+{
+    BakeLight* light = new BakeLight(baker->GetContext(), baker);
+    light->SetCastsShadow( false );
+    light->SetColor( color );
+    light->SetIntensity(1.0f);
+
+    if (GlobalGlowSettings.aoEnabled_)
+    {
+        light->SetInfluenceModel( new AmbientOcclusionInfluence( light ) );
+    }
+
+    return light;
+}
+
+BakeLight* BakeLight::CreatePointLight( SceneBaker* baker, const Vector3& position, float radius, const Color& color, float intensity, bool castsShadow )
+{
+    BakeLight* light = new BakeLight(baker->GetContext(), baker);
+
+    light->SetInfluenceModel( new LightInfluence( light ) );
+    light->SetAttenuationModel( new LinearLightAttenuation( light, radius ) );
+    light->SetCutoffModel( new LightCutoff( light ) );
+    light->SetPhotonEmitter( new PhotonEmitter( light ) );
+
+    light->SetCastsShadow( castsShadow );
+    light->SetPosition( position );
+    light->SetColor( color );
+    light->SetIntensity( intensity );
+
+    return light;
+}
+
+BakeLight* BakeLight::CreateSpotLight( SceneBaker* baker, const Vector3& position, const Vector3& direction, float cutoff, float radius, const Color& color, float intensity, bool castsShadow )
+{
+    BakeLight* light = new BakeLight(baker->GetContext(), baker);
+
+    light->SetInfluenceModel( new LightInfluence( light ) );
+    light->SetAttenuationModel( new LinearLightAttenuation( light, radius ) );
+    light->SetCutoffModel( new LightSpotCutoff( light, direction, cutoff, 1.0f ) );
+    light->SetPhotonEmitter( new PhotonEmitter( light ) );
+
+    light->SetCastsShadow( castsShadow );
+    light->SetPosition( position );
+    light->SetColor( color );
+    light->SetIntensity( intensity );
+
+    return light;
+}
+
+BakeLight* BakeLight::CreateDirectionalLight( SceneBaker* baker, const Vector3& direction, const Color& color, float intensity, bool castsShadow )
+{
+    BakeLight* light = new BakeLight(baker->GetContext(), baker);
+
+    light->SetInfluenceModel( new DirectionalLightInfluence( light, direction ) );
+    light->SetPhotonEmitter( new DirectionalPhotonEmitter( light, direction ) );
+    light->SetCastsShadow( castsShadow );
+    light->SetColor( color );
+    light->SetIntensity( intensity );
+
+    return light;
+}
+
+BakeLight* BakeLight::CreateAreaLight( SceneBaker* baker, BakeMesh* bakeMesh, const Vector3& position, const Color& color, float intensity, bool castsShadow )
+{
+    BakeLight* light = new BakeLight(baker->GetContext(), baker);
+
+    light->SetInfluenceModel( new LightInfluence( light ) );
+    const BoundingBox bbox = bakeMesh->GetBoundingBox();
+    float volume = 7.25f; /*(bbox.max_.x_ - bbox.min_.x_) * (bbox.max_.y_ - bbox.min_.y_) * (bbox.max_.z_ - bbox.min_.z_)*/;
+    light->SetAttenuationModel( new LinearLightAttenuation( light, volume ) );
+    light->SetPhotonEmitter( new PhotonEmitter( light ) );
+    light->SetCutoffModel( new LightCutoff( light ) );
+    light->SetVertexGenerator( new FaceLightVertexGenerator( bakeMesh, true, 0 ) );
+    light->SetCastsShadow( castsShadow );
+    light->SetPosition( position );
+    light->SetColor( color );
+    light->SetIntensity( intensity );
+
+    light->GetVertexGenerator()->Generate();
+
+    return light;
+}
+
+// ------------------------------------------------------- LightInfluence --------------------------------------------------------- //
+
+LightInfluence::LightInfluence( const BakeLight* light ) :
+    light_( light )
+{
+
+}
+
+float LightInfluence::Calculate(LightRay* lightRay, const Vector3& light, float& distance ) const
+{
+    LightRay::SamplePoint& source = lightRay->samplePoint_;
+
+    Vector3 direction = light - source.position;
+    distance = direction.Length();
+    direction.Normalize();
+
+    // ** Calculate Lambert's cosine law intensity
+    float intensity = GetLambert( direction, source.normal );
+
+    if( intensity <= 0.001f ) {
+        return 0.0f;
+    }
+
+    // ** Cast shadow to point
+    if( light_->GetCastsShadow() )
+    {
+        LightRay::SamplePoint& source = lightRay->samplePoint_;
+
+        // clean this mess up
+        RTCScene scene = source.bakeMesh->GetSceneBaker()->GetEmbreeScene()->GetRTCScene();
+
+        lightRay->SetupRay(source.position, direction, .001f, distance);
+
+        rtcOccluded(scene, lightRay->rtcRay_);
+
+        // obstructed?  TODO: glass, etc
+        if (lightRay->rtcRay_.geomID != RTC_INVALID_GEOMETRY_ID)
+        {
+            return 0.0f;
+        }
+    }
+
+    return intensity;
+}
+
+float LightInfluence::GetLambert( const Vector3& direction, const Vector3& normal )
+{
+    float dp = direction.DotProduct(normal);
+    return dp < 0.0f ? 0.0f : dp;
+}
+
+// --------------------------------------------------- DirectionalLightInfluence -------------------------------------------------- //
+
+DirectionalLightInfluence::DirectionalLightInfluence( const BakeLight* light, const Vector3& direction )
+    : LightInfluence( light ), direction_( direction )
+{
+
+}
+
+float DirectionalLightInfluence::Calculate(LightRay* lightRay, const Vector3& light, float& distance ) const
+{
+    LightRay::SamplePoint& source = lightRay->samplePoint_;
+
+    float intensity = GetLambert( -direction_, source.normal );
+
+    if( intensity <= 0.001f )
+    {
+        return 0.0f;
+    }
+
+    // clean this mess up
+    RTCScene scene = source.bakeMesh->GetSceneBaker()->GetEmbreeScene()->GetRTCScene();
+
+    lightRay->SetupRay(source.position, -direction_, .001f, LIGHT_LARGE_DISTANCE);
+
+    rtcOccluded(scene, lightRay->rtcRay_);
+
+    // obstructed?  TODO: glass, etc
+    if (lightRay->rtcRay_.geomID != RTC_INVALID_GEOMETRY_ID)
+    {
+        return 0.0f;
+    }
+
+    return intensity;
+}
+
+// --------------------------------------------------- AmbientOcclusionInfluence -------------------------------------------------- //
+
+AmbientOcclusionInfluence::AmbientOcclusionInfluence( const BakeLight* light )
+    : LightInfluence( light )
+{
+
+}
+
+float AmbientOcclusionInfluence::Calculate(LightRay* lightRay, const Vector3& light, float& distance ) const
+{
+
+    LightRay::SamplePoint& source = lightRay->samplePoint_;
+
+    if (source.normal == Vector3::ZERO)
+        return 1.0f;
+
+    // clean this mess up
+    RTCScene scene = source.bakeMesh->GetSceneBaker()->GetEmbreeScene()->GetRTCScene();
+
+    const Color& color = light_->GetColor();
+
+    Vector3 rad(color.r_, color.g_, color.b_);
+
+    if (!GlobalGlowSettings.aoEnabled_)
+    {
+        return 1.0f;
+    }
+
+    // TODO: AO using ray packets/streams
+
+    RTCRay& ray = lightRay->rtcRay_;
+
+    unsigned nsamples = GlobalGlowSettings.nsamples_;
+
+    // this needs to be based on model/scale likely?
+    float aoDepth = GlobalGlowSettings.aoDepth_;
+
+    // smallest percent of ao value to use
+    float aoMin = GlobalGlowSettings.aoMin_;
+
+    // brightness control
+    float multiply = GlobalGlowSettings.aoMultiply_;
+
+    // Shoot rays through the differential hemisphere.
+    int nhits = 0;
+    float avgDepth = 0.0f;
+    for (unsigned nsamp = 0; nsamp < nsamples; nsamp++)
+    {
+        Vector3 rayDir;
+        Vector3::GetRandomHemisphereDirection(rayDir, source.normal);
+
+        float dotp = source.normal.x_ * rayDir.x_ +
+                source.normal.y_ * rayDir.y_ +
+                source.normal.z_ * rayDir.z_;
+
+        if (dotp < 0.1f)
+        {
+            continue;
+        }
+
+        float variance = 0.0f;//nsamples <= 32 ? 0.0f : aoDepth * ((float) rand() / (float) RAND_MAX) * 0.25f;
+
+        float depth = aoDepth + variance;
+
+        lightRay->SetupRay(source.position, rayDir, .001f, depth);
+
+        rtcOccluded(scene, ray);
+
+        if (ray.geomID != RTC_INVALID_GEOMETRY_ID)
+        {
+            avgDepth += Min<float>(ray.tfar, aoDepth);
+            nhits++;
+        }
+    }
+
+    if (nhits)// && (nsamples <= 32 ? true : nhits > 4))
+    {
+        avgDepth /= float(nhits);
+        avgDepth /= aoDepth;
+
+        avgDepth = Clamp<float>(avgDepth, 0.1f, 1.0f) * 100.0f;
+        avgDepth *= avgDepth;
+        float ao = avgDepth / 10000.0f;
+
+        ao = aoMin + ao/2.0f;
+        ao *= multiply;
+        ao = Clamp<float>(ao, aoMin, 1.0f);
+
+        return ao;
+    }
+
+    return 1.0f;
+}
+
+// --------------------------------------------------------- LightCutoff ---------------------------------------------------------- //
+
+LightCutoff::LightCutoff( const BakeLight* light ) : light_( light )
+{
+
+}
+
+float LightCutoff::Calculate( const Vector3& point ) const
+{
+    return 1.0f;
+}
+
+float LightCutoff::GetCutoffForDirection( const Vector3& direction ) const
+{
+    return 1.0f;
+}
+
+// ------------------------------------------------------- LightSpotCutoff -------------------------------------------------------- //
+
+LightSpotCutoff::LightSpotCutoff( const BakeLight* light, const Vector3& direction, float cutoff, float exponent ) :
+    LightCutoff( light ), direction_( direction ), cutoff_( cutoff ), exponent_( exponent )
+{
+
+}
+
+float LightSpotCutoff::Calculate( const Vector3& point ) const
+{
+    Vector3 dir = point - light_->GetPosition();
+    dir.Normalize();
+    return GetCutoffForDirection( dir );
+}
+
+float LightSpotCutoff::GetCutoffForDirection( const Vector3& direction ) const
+{
+    float value = direction.DotProduct(direction_);
+
+    if( value <= cutoff_ ) {
+        return 0.0f;
+    }
+
+    value = (1.0f - (1.0f - value) * 1.0f / (1.0f - cutoff_));
+
+    if( fabs( 1.0f - exponent_ ) > 0.01f ) {
+        value = powf( value, exponent_ );
+    }
+
+    return value;
+}
+
+// ------------------------------------------------------- LightAttenuation ------------------------------------------------------- //
+
+LightAttenuation::LightAttenuation( const BakeLight* light ) : light_( light )
+{
+
+}
+
+// ---------------------------------------------------- LinearLightAttenuation ---------------------------------------------------- //
+
+
+LinearLightAttenuation::LinearLightAttenuation( const BakeLight* light, float radius, float constant, float linear, float quadratic )
+    : LightAttenuation( light ), radius_( radius ), constant_( constant ), linear_( linear ), quadratic_( quadratic )
+{
+
+}
+
+
+float LinearLightAttenuation::Calculate( float distance ) const
+{
+    if (fabs(radius_) < 0.01f)
+        return 0.0f;
+
+    float r = distance / radius_;
+    return Max<float>( 1.0f / (1.0f + constant_ + linear_ * r + quadratic_ * r * r), 0.0f );
+}
+
+// -------------------------------------------------------- PhotonEmitter --------------------------------------------------------- //
+
+PhotonEmitter::PhotonEmitter( const BakeLight* light ) :
+    light_( light )
+{
+
+}
+
+int PhotonEmitter::GetPhotonCount( void ) const
+{
+    return static_cast<int>( light_->GetIntensity() * 25000 );
+}
+
+void PhotonEmitter::Emit( SceneBaker* sceneBaker, Vector3& position, Vector3& direction ) const
+{
+    position  = light_->GetPosition();
+    Vector3::GetRandomDirection(direction);
+
+}
+
+// --------------------------------------------------- DirectinalPhotonEmitter ---------------------------------------------------- //
+
+DirectionalPhotonEmitter::DirectionalPhotonEmitter( const BakeLight* light, const Vector3& direction ) :
+    PhotonEmitter( light ), direction_( direction )
+{
+    plane_.Define(direction.Normalized(), light->GetPosition()); //  = Plane::calculate( direction, light->position() );
+}
+
+void DirectionalPhotonEmitter::Emit( SceneBaker* sceneBaker, Vector3& position, Vector3& direction ) const
+{
+    const BoundingBox& bounds = sceneBaker->GetSceneBounds();
+
+    Vector3 randomPoint(Lerp<float>(bounds.min_.x_, bounds.max_.x_, RandZeroOne()),
+                        Lerp<float>(bounds.min_.y_, bounds.max_.y_, RandZeroOne()),
+                        Lerp<float>(bounds.min_.z_, bounds.max_.z_, RandZeroOne()));
+
+
+    position  = plane_.Project(randomPoint) - direction_ * ( LIGHT_LARGE_DISTANCE * 0.5f);
+    direction = direction_;
+}
+
+// ---------------------------------------------------- LightVertexGenerator ------------------------------------------------------ //
+
+
+LightVertexGenerator::LightVertexGenerator( BakeMesh* bakeMesh ) : mesh_( bakeMesh )
+{
+
+}
+
+unsigned LightVertexGenerator::GetVertexCount( void ) const
+{
+    return vertices_.Size();
+}
+
+
+const LightVertexVector& LightVertexGenerator::GetVertices( void ) const
+{
+    return vertices_;
+}
+
+
+void LightVertexGenerator::Clear( void )
+{
+    vertices_.Clear();
+}
+
+
+void LightVertexGenerator::Generate( void )
+{
+    Clear();
+
+    unsigned numVertices;
+    const SharedArrayPtr<BakeMesh::MMVertex>& vertices = mesh_->GetVertices(numVertices);
+
+    LightVertex lightVertex;
+
+    for( unsigned i = 0; i < numVertices; i++ )
+    {
+        lightVertex.position_ = vertices[i].position_;
+        lightVertex.normal_   = vertices[i].normal_;
+
+        Push( lightVertex ) ;
+    }
+}
+
+
+void LightVertexGenerator::Push(const LightVertex &vertex )
+{
+    vertices_.Push( vertex );
+}
+
+// ------------------------------------------------- FaceLightVertexGenerator --------------------------------------------------- //
+
+FaceLightVertexGenerator::FaceLightVertexGenerator( BakeMesh* bakeMesh, bool excludeVertices, int maxSubdivisions )
+    : LightVertexGenerator( bakeMesh ),
+      excludeVertices_( excludeVertices ),
+      maxSubdivisions_( maxSubdivisions )
+{
+
+}
+
+void FaceLightVertexGenerator::Generate( void )
+{
+    Clear();
+
+    if( !excludeVertices_ ) {
+        LightVertexGenerator::Generate();
+    }
+
+    unsigned numVertices;
+    const SharedArrayPtr<BakeMesh::MMVertex>& vertices = mesh_->GetVertices(numVertices);
+
+    unsigned numTriangles;
+    const SharedArrayPtr<BakeMesh::MMTriangle>& triangles = mesh_->GetTriangles(numTriangles);
+
+    for( unsigned i = 0; i < numTriangles; i++ )
+    {
+        const BakeMesh::MMTriangle& mtri = triangles[i];
+
+        LightVertex verts[3];
+
+        for (int j = 0; j < 3; j++)
+        {
+            verts[j].position_ = vertices[mtri.indices_[j]].position_;
+            verts[j].normal_ = vertices[mtri.indices_[j]].normal_;
+        }
+
+        LightTriangle triangle(verts[0], verts[1], verts[2]);
+
+        GenerateFromTriangle( triangle, 0 );
+    }
+}
+
+void FaceLightVertexGenerator::GenerateFromTriangle( const LightTriangle& triangle, int subdivision )
+{
+    // Generate light vertex from triangle centroid
+    Push( triangle.GetCentroid() );
+
+    // The maximum subdivisions exceeded
+    if( subdivision >= maxSubdivisions_ ) {
+        return;
+    }
+
+    //  Tesselate a triangle
+    LightTriangle center, corners[3];
+    triangle.Tesselate( center, corners );
+
+    // Process corner triangles
+    for( int i = 0; i < 3; i++ )
+    {
+        GenerateFromTriangle( corners[i], subdivision + 1 );
+    }
+}
+
+// ---------------------------------------------- Triangle ---------------------------------------------- //
+
+LightTriangle::LightTriangle( const LightVertex& a, const LightVertex& b, const LightVertex& c ) :
+    a_( a ),
+    b_( b ),
+    c_( c )
+{
+    centroid_.position_ = (a_.position_ + b_.position_ + c_.position_)  / 3.0f;
+    centroid_.normal_   = (a_.normal_   + b_.normal_   + c_.normal_)    / 3.0f;
+    centroid_.normal_.Normalize();
+}
+
+const LightVertex& LightTriangle::GetCentroid( void ) const
+{
+    return centroid_;
+}
+
+void LightTriangle::Tesselate( LightTriangle& center, LightTriangle triangles[3] ) const
+{
+    LightVertex xA = LightVertex::Interpolate( a_, b_, 0.5f );
+    LightVertex xB = LightVertex::Interpolate( b_, c_, 0.5f );
+    LightVertex xC = LightVertex::Interpolate( c_, a_, 0.5f );
+
+    triangles[0] = LightTriangle( a_, xA,  xC  );
+    triangles[1] = LightTriangle( xA,  b_, xB  );
+    triangles[2] = LightTriangle( xC,  xB,  c_ );
+    center       = LightTriangle( xA,  xB,  xC  );
+}
+
+
+}

+ 502 - 0
Source/AtomicGlow/Kernel/BakeLight.h

@@ -0,0 +1,502 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Portions Copyright (c) 2015 Dmitry Sovetov
+// Portions Copyright 2009-2017 Intel Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Core/Mutex.h>
+#include <Atomic/Math/Plane.h>
+#include "BakeNode.h"
+
+namespace Atomic
+{
+class Light;
+class Zone;
+}
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class LightRay;
+class BakeMesh;
+class BakeLight;
+class LightVertexGenerator;
+
+
+/// Light cuttoff is used for configuring the light source influence direction (omni, directional, spotlight).
+/// LightCutoff class is a base for all cutoff models and represents an omni-directional light.
+class LightCutoff : public RefCounted
+{
+    ATOMIC_REFCOUNTED(LightCutoff)
+
+public:
+
+    /// Constructs a LightCutoff instance.
+    LightCutoff( const BakeLight* light );
+    virtual             ~LightCutoff( void ) {}
+
+    /// Calculates a light influence to a given point.
+    virtual float       Calculate( const Vector3& point ) const;
+
+    /// Calculates a light cutoff for direction.
+    virtual float       GetCutoffForDirection( const Vector3& direction ) const;
+
+protected:
+
+    /// Parent light instance.
+    const BakeLight*        light_;
+};
+
+/// LightSpotCutoff is used for spot lights.
+class LightSpotCutoff : public LightCutoff
+{
+
+    ATOMIC_REFCOUNTED(LightSpotCutoff)
+
+public:
+
+    // Constructs a LightSpotCutoff instance
+    // param light Parent light source.
+    // param direction Spot light direction.
+    // param cutoff The cutoff value represents the maximum angle between the light direction and the light to pixel vector.
+    // param exponent The cutoff exponent.
+    LightSpotCutoff( const BakeLight* light, const Vector3& direction, float cutoff, float exponent );
+    virtual ~LightSpotCutoff( void ) {}
+
+    /// Calculates a light influence for a spot light.
+    virtual float Calculate( const Vector3& point ) const;
+
+    /// Calculates a light cutoff for direction.
+    virtual float GetCutoffForDirection( const Vector3& direction ) const;
+
+private:
+
+    /// Light direction.
+    Vector3 direction_;
+    /// Light cutoff.
+    float cutoff_;
+    /// Light spot exponent.
+    float exponent_;
+};
+
+/// Light distance attenuation model.
+class LightAttenuation : public RefCounted
+{
+    ATOMIC_REFCOUNTED(LightAttenuation)
+
+public:
+
+    /// Constructs a LightAttenuation instance.
+    LightAttenuation( const BakeLight* light );
+    virtual ~LightAttenuation( void ) {}
+
+    /// Calculates light attenuation by distance.
+    /// An attenuation factor is a value in range [0;1], where 0 means
+    /// that light is attenuated to zero, and 1 means no attenuation at all.
+    virtual float Calculate( float distance ) const { return 0.0f; }
+
+protected:
+
+    /// Parent light source.
+    const BakeLight* light_;
+};
+
+/// Linear light attenuation model.
+class LinearLightAttenuation : public LightAttenuation
+{
+
+    ATOMIC_REFCOUNTED(LinearLightAttenuation)
+
+public:
+    /// Constructs a LinearLightAttenuation instance.
+    LinearLightAttenuation( const BakeLight* light, float radius, float constant = 0.0f, float linear = 0.0f, float quadratic = 25.0f );
+
+    virtual float Calculate( float distance ) const;
+
+private:
+
+    // Light influence radius.
+    float radius_;
+    // Constant light attenuation factor.
+    float constant_;
+    // Linear light attenuation factor.
+    float linear_;
+    // Quadratic light attenuation factor.
+    float quadratic_;
+};
+
+/// Light to point influence model.
+class LightInfluence : public RefCounted
+{
+
+    ATOMIC_REFCOUNTED(LightInfluence)
+
+    public:
+
+        /// Constructs a LightInfluence instance.
+        LightInfluence( const BakeLight* light );
+
+    virtual ~LightInfluence( void ) {}
+
+    /// Calculates omni light influence to a given point.
+    virtual float Calculate(LightRay* lightRay, const Vector3 &light, float& distance ) const;
+
+    /// Calculates a light influence by a Lambert's cosine law.
+    static float GetLambert( const Vector3& direction, const Vector3& normal );
+
+protected:
+
+    /// Parent light instance.
+    const BakeLight* light_;
+};
+
+///  Directional light influence model.
+class DirectionalLightInfluence : public LightInfluence
+{
+    ATOMIC_REFCOUNTED(DirectionalLightInfluence)
+
+    public:
+
+        /// Constructs a DirectionalLightInfluence instance.
+        DirectionalLightInfluence( const BakeLight* light, const Vector3& direction );
+
+    /// Calculates a directional light influence.
+    virtual float Calculate( LightRay* lightRay, const Vector3& light, float& distance ) const;
+
+private:
+
+    /// Light source direction.
+    Vector3 direction_;
+};
+
+///  Ambient occlusion influence model.
+class AmbientOcclusionInfluence : public LightInfluence
+{
+    ATOMIC_REFCOUNTED(AmbientOcclusionInfluence)
+
+    public:
+
+    /// Constructs a AmbientOcclusionInfluence instance.
+    AmbientOcclusionInfluence( const BakeLight* light);
+
+    /// Calculates a ambient occlusion light influence.
+    virtual float Calculate( LightRay* lightRay, const Vector3& light, float& distance ) const;
+
+private:
+
+};
+
+
+
+/// Photon emitter is used to determine an amount of photons to be emitted by a given light.
+class PhotonEmitter : public RefCounted
+{
+    ATOMIC_REFCOUNTED(PhotonEmitter)
+
+public:
+
+    // Constructs a new PhotonEmitter instance.
+    PhotonEmitter( const BakeLight* light );
+    virtual ~PhotonEmitter( void ) {}
+
+    // Calculates an amount of photons to be emitted.
+    virtual int GetPhotonCount( void ) const;
+
+    // Emits a new photon.
+    virtual void Emit( SceneBaker* scene, Vector3& position, Vector3& direction ) const;
+
+protected:
+
+    // Parent light source.
+    const BakeLight* light_;
+};
+
+/// Directional photon emitter emits photons with a given direction.
+class DirectionalPhotonEmitter : public PhotonEmitter
+{
+
+    ATOMIC_REFCOUNTED(DirectionalPhotonEmitter)
+
+    public:
+
+    // Constructs a new DirectionalPhotonEmitter instance.
+    // param light Parent light source.
+    // param direction Emission direction.
+    DirectionalPhotonEmitter( const BakeLight* light, const Vector3& direction );
+
+    /// Emits a new photon.
+    virtual void Emit( SceneBaker* scene, Vector3& position, Vector3& direction ) const;
+
+private:
+
+    /// Emission direction.
+    Vector3 direction_;
+
+    /// Emission plane.
+    Plane plane_;
+};
+
+
+class BakeLight : public BakeNode
+{
+    ATOMIC_OBJECT(BakeLight, BakeNode)
+
+    public:
+
+    BakeLight(Context* context, SceneBaker *sceneBaker);
+    virtual ~BakeLight();
+
+    /*
+    virtual void SetLight(Atomic::Light* light) = 0;
+    virtual void Light(LightRay* ray) = 0;
+    */
+
+    // Returns an light influence model.
+    LightInfluence* GetInfluenceModel( void ) const;
+
+    // Sets an light influence model.
+    void SetInfluenceModel( LightInfluence* value );
+
+    // Returns an attenuation model.
+    LightAttenuation* GetAttenuationModel( void ) const;
+
+    // Sets an attenuation model.
+    void SetAttenuationModel( LightAttenuation* value );
+
+    // Returns a light vertex generator.
+    LightVertexGenerator* GetVertexGenerator( void ) const;
+
+    // Sets an light vertex generator.
+    void SetVertexGenerator( LightVertexGenerator* value );
+
+    // Returns an cutoff model.
+    LightCutoff* GetCutoffModel( void ) const;
+
+    // Sets an cutoff model.
+    void SetCutoffModel( LightCutoff* value );
+
+    // Returns a photon emitter.
+    PhotonEmitter* GetPhotonEmitter( void ) const;
+
+    // Sets a photon emitter.
+    // By default each light has a photon emitter, light sources with no photon emitter
+    // set won't make any influence to a global illumination.
+    void SetPhotonEmitter( PhotonEmitter* value );
+
+    // Returns a light position.
+    const Vector3& GetPosition( void ) const;
+
+    // Sets a light position.
+    void SetPosition( const Vector3& value );
+
+    // Returns a light color.
+    const Color& GetColor( void ) const;
+
+    // Sets a light color.
+    void SetColor( const Color& value );
+
+    // Returns a light intensity.
+    float GetIntensity( void ) const;
+
+    // Sets a light intensity.
+    void SetIntensity( float value );
+
+    // Returns true if this light casts shadow otherwise false.
+    bool GetCastsShadow( void ) const;
+
+    // Sets a castShadow flag.
+    void SetCastsShadow( bool value );
+
+    // Direct Lighting
+
+    void DirectLight(LightRay* lightRay);
+
+    // Creates a Zone light instance.
+    static BakeLight* CreateZoneLight( SceneBaker* baker, const Color& color = Color::WHITE );
+
+    // Creates a point light instance.
+    static BakeLight* CreatePointLight( SceneBaker* baker, const Vector3& position, float radius, const Color& color = Color::WHITE, float intensity = 1.0f, bool castsShadow = true );
+
+    // Creates a spot light instance.
+    static BakeLight* CreateSpotLight( SceneBaker* baker, const Vector3& position, const Vector3& direction, float cutoff, float radius, const Color& color = Color::WHITE, float intensity = 1.0f, bool castsShadow = true );
+
+    // Creates a directional light instance.
+    static BakeLight* CreateDirectionalLight( SceneBaker* baker, const Vector3& direction, const Color& color = Color::WHITE, float intensity = 1.0f, bool castsShadow = true );
+
+    // Creates an area light instance.
+    static BakeLight* CreateAreaLight( SceneBaker* baker, BakeMesh* mesh, const Vector3& position, const Color& color = Color::WHITE, float intensity = 1.0f, bool castsShadow = true );
+
+private:
+
+    // Light position.
+    Vector3 position_;
+
+    // Light color.
+    Color color_;
+
+    // Light intensity.
+    float intensity_;
+
+    // Casts shadow flag.
+    bool castsShadow_;
+
+    // Light cutoff model.
+    SharedPtr<LightCutoff> cutoffModel_;
+
+    // Light attenuation model.
+    SharedPtr<LightAttenuation>  attenuationModel_;
+
+    // Light influence model.
+    SharedPtr<LightInfluence> influenceModel_;
+
+    // Light source photon emitter.
+    SharedPtr<PhotonEmitter> photonEmitter_;
+
+    // Light vertex sampler.
+    SharedPtr<LightVertexGenerator> vertexGenerator_;
+
+};
+
+// A light vertex is a single light point used with mesh light sources.
+struct LightVertex
+{
+    // Point position.
+    Vector3 position_;
+    // Point normal.
+    Vector3  normal_;
+
+    static LightVertex Interpolate( const LightVertex& a, const LightVertex& b, float scalar )
+    {
+        LightVertex result;
+
+        result.position_ = a.position_ * scalar + b.position_ * (1.0f - scalar);
+
+        result.normal_ = a.normal_ * scalar + b.normal_ * (1.0f - scalar);
+        result.normal_.Normalize();
+
+        return result;
+    }
+};
+
+// A helper class to tesselate faces.
+class LightTriangle
+{
+public:
+
+    /// Constructs a Triangle instance
+    LightTriangle( void ) {}
+
+    /// Constructs a Triangle instance from three vertices.
+    LightTriangle( const LightVertex& a, const LightVertex& b, const LightVertex& c );
+
+    /// Returns a triangle centroid.
+    const LightVertex&   GetCentroid( void ) const;
+
+    /// Tesselates a triangle. Splits this triangle into 4 smaller ones.
+    /// param center Output center triangle.
+    /// param triangles Three triangles on corners.
+    void Tesselate( LightTriangle& center, LightTriangle triangles[3] ) const;
+
+private:
+
+    // Triangle vertices
+    LightVertex a_, b_, c_;
+
+    // Triangle centroid.
+    LightVertex centroid_;
+};
+
+
+typedef PODVector<LightVertex> LightVertexVector;
+
+/// A LightVertexGenerator used to generate a set of light points for mesh light sources.
+class LightVertexGenerator : public RefCounted
+{
+    ATOMIC_REFCOUNTED(LightVertexGenerator)
+
+    public:
+
+    /// Constructs a LightVertexGenerator instance.
+    LightVertexGenerator( BakeMesh* bakeMesh );
+    virtual ~LightVertexGenerator( void ) {}
+
+    /// Returns vector of light vertices.
+    const LightVertexVector& GetVertices( void ) const;
+
+    /// Generates a set of light vertices based on mesh vertices.
+    virtual void Generate( void );
+
+    /// Clears a previously generated data.
+    void Clear( void );
+
+    /// Returns a total number of light vertices.
+    unsigned GetVertexCount( void ) const;
+
+protected:
+
+    /// Adds a new light vertex.
+    void Push( const LightVertex& vertex );
+
+protected:
+
+    // Source mesh.
+    WeakPtr<BakeMesh> mesh_;
+
+    // Set of generated light vertices.
+    LightVertexVector vertices_;
+};
+
+/// A FaceLightVertexGenerator generates a set of vertices based on mesh vertices & face centroids.
+/// Face based generator can also perform a mesh tesselation.
+class FaceLightVertexGenerator : public LightVertexGenerator
+{
+    ATOMIC_REFCOUNTED(FaceLightVertexGenerator)
+
+    public:
+
+    /// Constructs a FaceLightVertexGenerator instance.
+    /// param mesh Source light mesh.
+    /// param excludeVertices The flag indicating that we should skip mesh vertices.
+    /// param maxSubdivisions Maximum subdivision steps per mesh face (0 means no tesselation).
+    FaceLightVertexGenerator( BakeMesh* bakeMesh, bool excludeVertices = true, int maxSubdivisions = 0 );
+
+    /// Generates a set of light vertices.
+    virtual void Generate( void );
+
+private:
+
+    /// Generates a set of light vertices from triangle.
+    virtual void GenerateFromTriangle( const LightTriangle& triangle, int subdivision );
+
+private:
+
+    /// The flag indicating that we should skip mesh vertices.
+    bool excludeVertices_;
+
+    /// Max subdivision depth.
+    int  maxSubdivisions_;
+};
+
+
+}

+ 167 - 0
Source/AtomicGlow/Kernel/BakeMaterial.cpp

@@ -0,0 +1,167 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Resource/ResourceCache.h>
+#include <Atomic/Resource/XMLFile.h>
+#include <Atomic/Graphics/Material.h>
+
+#include "BakeMaterial.h"
+
+namespace AtomicGlow
+{
+
+
+BakeMaterialCache::BakeMaterialCache(Context* context) : Object(context)
+{
+}
+
+BakeMaterialCache::~BakeMaterialCache()
+{
+
+}
+
+BakeMaterial* BakeMaterialCache::GetBakeMaterial(Material *material)
+{
+    if (!material)
+        return 0;
+
+    SharedPtr<BakeMaterial> bakeMaterial;
+
+    if (bakeCache_.TryGetValue(material->GetName(), bakeMaterial))
+    {
+        return bakeMaterial;
+    }
+
+    bakeMaterial = new BakeMaterial(context_);
+
+    if (!bakeMaterial->LoadMaterial(material))
+    {
+        return 0;
+    }
+
+    bakeCache_[material->GetName()] = bakeMaterial;
+
+    return bakeMaterial;
+
+}
+
+BakeMaterial::BakeMaterial(Context* context) : Object(context),
+    occlusionMasked_(false)
+{
+    uoffset_ = Vector4(1.0f, 0.0f, 0.0f, 0.0f);
+    voffset_ = Vector4(0.0f, 1.0f, 0.0f, 0.0f);
+}
+
+BakeMaterial::~BakeMaterial()
+{
+
+}
+
+Variant BakeMaterial::ParseShaderParameterValue(const String& value)
+{
+    String valueTrimmed = value.Trimmed();
+    if (valueTrimmed.Length() && IsAlpha((unsigned)valueTrimmed[0]))
+        return Variant(ToBool(valueTrimmed));
+    else
+        return ToVectorVariant(valueTrimmed);
+}
+
+bool BakeMaterial::LoadMaterial(Material *material)
+{
+    material_ = material;
+
+    ATOMIC_LOGINFOF("Material: %s", material->GetName().CString());
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    XMLFile* xmlFile = cache->GetResource<XMLFile>(material->GetName());
+
+    if (!xmlFile)
+        return false;
+
+    XMLElement rootElem = xmlFile->GetRoot();
+
+    XMLElement techniqueElem = rootElem.GetChild("technique");
+
+    if (techniqueElem)
+    {
+        String name = techniqueElem.GetAttribute("name").ToLower();
+
+        // TODO: better way of setting/detecting occlusion masked materials
+        if (name.Contains("diffalpha") || name.Contains("difflightmapalpha"))
+        {
+            occlusionMasked_ = true;
+        }
+    }
+
+    // techniques
+
+    // textures
+    XMLElement textureElem = rootElem.GetChild("texture");
+    while (textureElem)
+    {
+        String unit = textureElem.GetAttribute("unit").ToLower();
+
+        if (unit == "diffuse")
+        {
+            String name = textureElem.GetAttribute("name");
+
+            diffuseTexture_ = cache->GetResource<Image>(name);
+
+            if (diffuseTexture_.Null())
+                return false;
+
+            ATOMIC_LOGINFOF("diffuse: %s %ux%u", name.CString(), diffuseTexture_->GetWidth(), diffuseTexture_->GetHeight());
+        }
+
+        textureElem = textureElem.GetNext("texture");
+    }
+
+    // Shader parameters
+
+    XMLElement parameterElem = rootElem.GetChild("parameter");
+    while (parameterElem)
+    {
+        String name = parameterElem.GetAttribute("name").ToLower();
+
+        Variant value;
+
+        if (!parameterElem.HasAttribute("type"))
+            value = ParseShaderParameterValue(parameterElem.GetAttribute("value"));
+        else
+            value = Variant(parameterElem.GetAttribute("type"), parameterElem.GetAttribute("value"));
+
+        if (name == "uoffset")
+            uoffset_ = value.GetVector4();
+
+        else if (name == "voffset")
+            voffset_ = value.GetVector4();
+
+        parameterElem = parameterElem.GetNext("parameter");
+    }
+
+
+    return true;
+}
+
+
+}

+ 92 - 0
Source/AtomicGlow/Kernel/BakeMaterial.h

@@ -0,0 +1,92 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Resource/Image.h>
+#include <Atomic/Graphics/Material.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class BakeMaterial : public Object
+{
+    ATOMIC_OBJECT(BakeMaterial, Object)
+
+    public:
+
+    BakeMaterial(Context* context);
+    virtual ~BakeMaterial();
+
+    bool LoadMaterial(Material* material);
+
+    Material* GetMaterial() { return material_; }
+
+    Image* GetDiffuseTexture() const { return diffuseTexture_; }
+
+    bool GetOcclusionMasked() const { return occlusionMasked_; }
+
+    const Vector4& GetUOffset() const { return uoffset_; }
+    const Vector4& GetVOffset() const { return voffset_; }
+
+private:
+
+    Variant ParseShaderParameterValue(const String& value);
+
+    SharedPtr<Material> material_;
+
+    // parameters which are loaded out of material xml
+    // as we don't load material in headless graphics mode
+
+    SharedPtr<Image> diffuseTexture_;
+
+    bool occlusionMasked_;
+
+    Vector4 uoffset_;
+    Vector4 voffset_;
+
+};
+
+class BakeMaterialCache : public Object
+{
+    friend class BakeMaterial;
+
+    ATOMIC_OBJECT(BakeMaterialCache, Object)
+
+    public:
+
+    BakeMaterialCache(Context* context);
+    virtual ~BakeMaterialCache();
+
+    BakeMaterial* GetBakeMaterial(Material* material);
+
+private:
+
+    /// Material->GetName -> BakeMaterial
+    HashMap<StringHash, SharedPtr<BakeMaterial>> bakeCache_;
+
+};
+
+
+
+}

+ 611 - 0
Source/AtomicGlow/Kernel/BakeMesh.cpp

@@ -0,0 +1,611 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "EmbreeScene.h"
+
+#include <Atomic/Core/WorkQueue.h>
+#include <Atomic/IO/Log.h>
+#include <Atomic/Graphics/Zone.h>
+
+#include <AtomicGlow/Common/GlowSettings.h>
+
+#include "Raster.h"
+#include "LightRay.h"
+#include "SceneBaker.h"
+#include "RadianceMap.h"
+#include "BakeLight.h"
+#include "BakeMesh.h"
+
+namespace AtomicGlow
+{
+
+BakeMesh::BakeMesh(Context* context, SceneBaker *sceneBaker) : BakeNode(context, sceneBaker),
+    numVertices_(0),
+    numTriangles_(0),
+    radianceHeight_(0),
+    radianceWidth_(0),
+    embreeGeomID_(RTC_INVALID_GEOMETRY_ID),
+    numWorkItems_(0),
+    ambientColor_(Color::BLACK)
+{
+
+}
+
+BakeMesh::~BakeMesh()
+{
+
+}
+
+void BakeMesh::Pack(unsigned lightmapIdx, Vector4 tilingOffset)
+{
+    if (radianceMap_.NotNull())
+    {
+        radianceMap_->packed_ = true;
+    }
+
+    // modify the in memory scene, which will either become data for the editor
+    // or in standalone mode, saved out the scene
+    if (staticModel_)
+    {
+        staticModel_->SetLightMask(0);
+        staticModel_->SetLightmapIndex(lightmapIdx);
+        staticModel_->SetLightmapTilingOffset(tilingOffset);
+    }
+
+}
+
+
+bool BakeMesh::FillLexelsCallback(void* param, int x, int y, const Vector3& barycentric,const Vector3& dx, const Vector3& dy, float coverage)
+{
+    return ((ShaderData*) param)->bakeMesh_->LightPixel((ShaderData*) param, x, y, barycentric, dx, dy, coverage);
+}
+
+bool BakeMesh::LightPixel(ShaderData* shaderData, int x, int y, const Vector3& barycentric,const Vector3& dx, const Vector3& dy, float coverage)
+{
+
+    if (x >= radianceWidth_ || y >= radianceHeight_)
+        return true;
+
+    meshMutex_.Acquire();
+    bool accept = radiancePassAccept_[y * radianceWidth_ + x];
+
+    // check whether we've already lit this pixel
+    if (accept)
+    {
+        meshMutex_.Release();
+        return true;
+    }
+
+    radiancePassAccept_[y * radianceWidth_ + x] = true;
+
+    meshMutex_.Release();
+
+    MMTriangle* tri = &triangles_[shaderData->triangleIdx_];
+    MMVertex* verts[3];
+
+    verts[0] = &vertices_[tri->indices_[0]];
+    verts[1] = &vertices_[tri->indices_[1]];
+    verts[2] = &vertices_[tri->indices_[2]];
+
+    LightRay ray;
+    LightRay::SamplePoint& sample = ray.samplePoint_;
+
+    sample.bakeMesh = this;
+
+    sample.triangle = shaderData->triangleIdx_;
+    sample.barycentric = barycentric;
+
+    sample.radianceX = x;
+    sample.radianceY = y;
+
+    sample.position = verts[0]->position_ * barycentric.x_ +
+                      verts[1]->position_ * barycentric.y_ +
+                      verts[2]->position_ * barycentric.z_;
+
+    sample.normal = verts[0]->normal_ * barycentric.x_ +
+                    verts[1]->normal_ * barycentric.y_ +
+                    verts[2]->normal_ * barycentric.z_;
+
+    sample.uv0  = verts[0]->uv0_ * barycentric.x_ +
+                  verts[1]->uv0_ * barycentric.y_ +
+                  verts[2]->uv0_ * barycentric.z_;
+
+    sample.uv1  = verts[0]->uv1_ * barycentric.x_ +
+                  verts[1]->uv1_ * barycentric.y_ +
+                  verts[2]->uv1_ * barycentric.z_;
+
+
+    sceneBaker_->TraceRay(&ray, bakeLights_);
+
+    return true;
+}
+
+void BakeMesh::LightTrianglesWork(const WorkItem* item, unsigned threadIndex)
+{
+    ShaderData shaderData;
+    BakeMesh* bakeMesh = shaderData.bakeMesh_ = (BakeMesh*) item->aux_;
+
+    shaderData.lightMode_ = bakeMesh->GetSceneBaker()->GetCurrentLightMode();
+    Vector2 triUV1[3];
+
+    Vector2 extents(bakeMesh->radianceWidth_, bakeMesh->radianceHeight_);
+
+    MMTriangle* start = reinterpret_cast<MMTriangle*>(item->start_);
+    MMTriangle* end = reinterpret_cast<MMTriangle*>(item->end_);
+
+    while (start <= end)
+    {
+        MMTriangle* tri = start;
+
+        shaderData.triangleIdx_ = tri - &bakeMesh->triangles_[0];
+
+        start++;
+
+        for (unsigned j = 0; j < 3; j++)
+        {
+            unsigned idx = tri->indices_[j];
+
+            triUV1[j] = bakeMesh->vertices_[idx].uv1_;
+            triUV1[j].x_ *= float(bakeMesh->radianceWidth_);
+            triUV1[j].y_ *= float(bakeMesh->radianceHeight_);
+
+            triUV1[j].x_ = Clamp<float>(triUV1[j].x_, 0.0f, bakeMesh->radianceWidth_);
+            triUV1[j].y_ = Clamp<float>(triUV1[j].y_, 0.0f, bakeMesh->radianceHeight_);
+        }
+
+        Raster::DrawTriangle(true, extents, false, triUV1, FillLexelsCallback, &shaderData );
+
+    }
+
+}
+
+
+void BakeMesh::ContributeRadiance(const LightRay* lightRay, const Vector3& radiance, GlowLightMode lightMode)
+{
+    MutexLock lock(meshMutex_);
+
+    const LightRay::SamplePoint& source = lightRay->samplePoint_;
+
+    unsigned radX = source.radianceX;
+    unsigned radY = source.radianceY;
+
+    const Vector3& v = radiance_[radY * radianceWidth_ + radX];
+
+    radianceTriIndices_[radY * radianceWidth_ + radX]  = source.triangle;
+
+    if (v.x_ < 0.0f)
+    {
+        radiance_[radY * radianceWidth_ + radX] = radiance;
+    }
+    else
+    {
+        radiance_[radY * radianceWidth_ + radX] += radiance;
+    }
+
+}
+
+void BakeMesh::GenerateRadianceMap()
+{
+    if (radianceMap_.NotNull())
+        return;
+
+    radianceMap_ = new RadianceMap(context_, this);
+}
+
+
+void BakeMesh::Light(GlowLightMode mode)
+{
+    if (mode == GLOW_LIGHTMODE_INDIRECT && !GlobalGlowSettings.giEnabled_)
+        return;
+
+    if (!GetLightmap() || !radianceWidth_ || !radianceHeight_ || !numTriangles_)
+        return;
+
+    bakeLights_.Clear();
+    sceneBaker_->QueryLights(boundingBox_, bakeLights_);
+
+    // for all triangles
+
+    for (unsigned i = 0; i < radianceWidth_ * radianceHeight_; i++)
+    {
+        radiancePassAccept_[i] = false;
+    }
+
+    WorkQueue* queue = GetSubsystem<WorkQueue>();
+
+    unsigned numTrianglePerItem = numTriangles_ / 4 ? numTriangles_ / 4 : numTriangles_;
+
+    unsigned curIdx = 0;
+    numWorkItems_ = 0;
+
+    while (true)
+    {
+        SharedPtr<WorkItem> item = queue->GetFreeItem();
+        item->priority_ = M_MAX_UNSIGNED;
+        item->workFunction_ = LightTrianglesWork;
+        item->aux_ = this;
+
+        item->start_ = &triangles_[curIdx];
+
+        unsigned endIdx = curIdx + numTrianglePerItem;
+
+        if (endIdx < numTriangles_)
+        {
+            item->end_ = &triangles_[endIdx];
+        }
+        else
+        {
+            item->end_ = &triangles_[numTriangles_ - 1];
+        }
+
+        item->sendEvent_ = true;
+
+        queue->AddWorkItem(item);
+
+        numWorkItems_++;
+
+        if (item->end_ == &triangles_[numTriangles_ - 1])
+            break;
+
+        curIdx += numTrianglePerItem + 1;
+    }
+
+
+}
+
+static unsigned CalcLightMapSize(unsigned sz)
+{
+    // highest multiple of 8, note rasterizer requires a multiple of 8!
+    sz = (sz + 8) & ~7;
+
+    if (sz > 512 && !IsPowerOfTwo(sz))
+    {
+        sz = NextPowerOfTwo(sz)/2;
+    }
+
+    sz = Clamp<unsigned>(sz, 32, GlobalGlowSettings.lightmapSize_);
+
+    return sz;
+
+}
+
+void BakeMesh::Preprocess()
+{
+
+    RTCScene scene = sceneBaker_->GetEmbreeScene()->GetRTCScene();
+
+    if (staticModel_ && staticModel_->GetZone())
+    {
+        ambientColor_ = staticModel_->GetZone()->GetAmbientColor();
+    }
+
+    if (staticModel_ && staticModel_->GetCastShadows())
+    {
+        // Create the embree mesh
+        embreeGeomID_ = rtcNewTriangleMesh(scene, RTC_GEOMETRY_STATIC, numTriangles_, numVertices_);
+        rtcSetUserData(scene, embreeGeomID_, this);
+        rtcSetOcclusionFilterFunction(scene, embreeGeomID_, OcclusionFilter);
+        rtcSetIntersectionFilterFunction(scene, embreeGeomID_, IntersectFilter);
+
+        // Populate vertices
+
+        float* vertices = (float*) rtcMapBuffer(scene, embreeGeomID_, RTC_VERTEX_BUFFER);
+
+        MMVertex* vIn = &vertices_[0];
+
+        for (unsigned i = 0; i < numVertices_; i++, vIn++)
+        {
+            *vertices++ = vIn->position_.x_;
+            *vertices++ = vIn->position_.y_;
+            *vertices++ = vIn->position_.z_;
+
+            // Note that RTC_VERTEX_BUFFER is 16 byte aligned, thus extra component
+            // which isn't used, though we'll initialize it
+            *vertices++ = 0.0f;
+        }
+
+        rtcUnmapBuffer(scene, embreeGeomID_, RTC_VERTEX_BUFFER);
+
+        uint32_t* triangles = (uint32_t*) rtcMapBuffer(scene, embreeGeomID_, RTC_INDEX_BUFFER);
+
+        for (size_t i = 0; i < numTriangles_; i++)
+        {
+            MMTriangle* tri = &triangles_[i];
+
+            *triangles++ = tri->indices_[0];
+            *triangles++ = tri->indices_[1];
+            *triangles++ = tri->indices_[2];
+        }
+
+        rtcUnmapBuffer(scene, embreeGeomID_, RTC_INDEX_BUFFER);
+
+        sceneBaker_->GetEmbreeScene()->RegisterBakeMesh(embreeGeomID_, this);
+    }
+
+    float lmScale = staticModel_->GetLightmapScale();
+
+    // if we aren't lightmapped, we just contribute to occlusion
+    if (!GetLightmap() || lmScale <= 0.0f)
+        return;
+
+    unsigned lmSize = staticModel_->GetLightmapSize();
+
+    if (!lmSize)
+    {
+        float totalarea = 0.0f;
+
+        for (unsigned i = 0; i < numTriangles_; i++)
+        {
+            MMTriangle* tri = &triangles_[i];
+
+            MMVertex* v0 = &vertices_[tri->indices_[0]];
+            MMVertex* v1 = &vertices_[tri->indices_[1]];
+            MMVertex* v2 = &vertices_[tri->indices_[2]];
+
+            totalarea += AreaOfTriangle(v0->position_,
+                                        v1->position_,
+                                        v2->position_);
+        }
+
+        totalarea = Clamp<float>(totalarea, 1, 64.0f);
+
+        lmSize = CalcLightMapSize(totalarea * 64.0f * lmScale * GlobalGlowSettings.lexelDensity_ * GlobalGlowSettings.sceneLexelDensityScale_);
+
+    }
+
+    if (lmSize < 32)
+        lmSize = 32;
+
+    if (lmSize > 2048)
+        lmSize = 2048;
+
+    radianceWidth_ = lmSize;
+    radianceHeight_ = lmSize;
+
+    // init radiance
+    radiance_ = new Vector3[radianceWidth_ * radianceHeight_];
+    radianceTriIndices_ = new int[radianceWidth_ * radianceHeight_];
+    radiancePassAccept_ = new bool[radianceWidth_ * radianceHeight_];
+
+    Vector3 v(-1, -1, -1);
+    for (unsigned i = 0; i < radianceWidth_ * radianceHeight_; i++)
+    {
+        radiance_[i] = v;
+        radianceTriIndices_[i] = -1;
+        radiancePassAccept_[i] = false;
+    }
+
+    // If GI is enabled, setup bounce metrics
+    if (GlobalGlowSettings.giEnabled_)
+    {
+        // granularity setting?
+        int pwidth = Min<int>(32, radianceWidth_ / 4);
+        int pheight = Min<int>(32, radianceHeight_ / 4);
+
+        photonMap_ = new PhotonMap(this, pwidth, pheight);
+    }
+}
+
+
+bool BakeMesh::SetStaticModel(StaticModel* staticModel)
+{
+    if (!staticModel || !staticModel->GetNode())
+        return false;
+
+    staticModel_ = staticModel;
+    node_ = staticModel_->GetNode();
+
+    bakeModel_ = GetSubsystem<BakeModelCache>()->GetBakeModel(staticModel_->GetModel());
+
+    if (bakeModel_.Null())
+    {
+        return false;
+    }
+
+    ModelPacker* modelPacker = bakeModel_->GetModelPacker();
+
+    if (modelPacker->lodLevels_.Size() < 1)
+    {
+        return false;
+    }
+
+    MPLODLevel* lodLevel = modelPacker->lodLevels_[0];
+
+    // LOD must have LM coords
+
+    if (!lodLevel->HasElement(TYPE_VECTOR2, SEM_TEXCOORD, 1))
+    {
+        return false;
+    }
+
+    // materials
+
+    if (staticModel_->GetNumGeometries() != lodLevel->mpGeometry_.Size())
+    {
+        ATOMIC_LOGERROR("BakeMesh::Preprocess() - Geometry mismatch");
+        return false;
+    }
+
+    for (unsigned i = 0; i < staticModel_->GetNumGeometries(); i++)
+    {
+        BakeMaterial* bakeMaterial = GetSubsystem<BakeMaterialCache>()->GetBakeMaterial(staticModel_->GetMaterial(i));
+        bakeMaterials_.Push(bakeMaterial);
+    }
+
+    // allocate
+
+    numVertices_ = 0;
+    unsigned totalIndices = 0;
+
+    lodLevel->GetTotalCounts(numVertices_, totalIndices);
+
+    if (!numVertices_ || ! totalIndices)
+    {
+        return false;
+    }
+
+    numTriangles_ = totalIndices/3;
+
+    vertices_ = new MMVertex[numVertices_];
+    triangles_ = new MMTriangle[numTriangles_];
+
+    MMVertex* vOut = &vertices_[0];
+    MMTriangle* tri = &triangles_[0];
+
+    unsigned vertexStart = 0;
+    unsigned indexStart = 0;
+
+    const Matrix3x4& wtransform = node_->GetWorldTransform();
+
+    for (unsigned i = 0; i < lodLevel->mpGeometry_.Size(); i++)
+    {
+        MPGeometry* mpGeo = lodLevel->mpGeometry_[i];
+
+        // Setup Vertices
+
+        MPVertex* vIn = &mpGeo->vertices_[0];
+
+        for (unsigned j = 0; j < mpGeo->vertices_.Size(); j++)
+        {
+            vOut->position_ = wtransform * vIn->position_;
+            vOut->normal_ = wtransform.Rotation() * vIn->normal_;
+            vOut->uv0_ = vIn->uv0_;
+            vOut->uv1_ = vIn->uv1_;
+
+            boundingBox_.Merge(vOut->position_);
+
+            vOut++;
+            vIn++;
+        }
+
+        // Setup Triangles
+
+        for (unsigned j = 0; j < mpGeo->numIndices_; j+=3, tri++)
+        {
+            tri->materialIndex_ = i;
+            tri->indices_[0] = mpGeo->indices_[j] + vertexStart;
+            tri->indices_[1] = mpGeo->indices_[j + 1] + vertexStart;
+            tri->indices_[2] = mpGeo->indices_[j + 2] + vertexStart;
+
+            tri->normal_ = vertices_[tri->indices_[0]].normal_;
+            tri->normal_ += vertices_[tri->indices_[1]].normal_;
+            tri->normal_ += vertices_[tri->indices_[2]].normal_;
+            tri->normal_ /= 3.0f;
+            tri->normal_.Normalize();
+
+        }
+
+        indexStart  += mpGeo->numIndices_;
+        vertexStart += mpGeo->vertices_.Size();
+    }
+
+    return true;
+
+}
+
+void BakeMesh::GetST(int triIndex, int channel, const Vector3& barycentric, Vector2& st) const
+{
+    if (triIndex < 0 || triIndex >= numTriangles_)
+        return;
+
+    const MMTriangle* tri = &triangles_[triIndex];
+
+    const Vector2& st0 = channel == 0 ? vertices_[tri->indices_[0]].uv0_ : vertices_[tri->indices_[0]].uv1_;
+    const Vector2& st1 = channel == 0 ? vertices_[tri->indices_[1]].uv0_ : vertices_[tri->indices_[1]].uv1_;
+    const Vector2& st2 = channel == 0 ? vertices_[tri->indices_[2]].uv0_ : vertices_[tri->indices_[2]].uv1_;
+
+    st = barycentric.z_*st0 + barycentric.x_*st1 + barycentric.y_*st2;
+
+}
+
+bool BakeMesh::GetUV0Color(int triIndex, const Vector3& barycentric, Color& colorOut) const
+{
+    colorOut = Color::BLACK;
+
+    if (triIndex < 0 || triIndex >= numTriangles_)
+        return false;
+
+    const MMTriangle* tri = &triangles_[triIndex];
+    const BakeMaterial* material = bakeMaterials_[tri->materialIndex_];
+    const Image* diffuse = material->GetDiffuseTexture();
+
+    if (!diffuse)
+    {
+        return false;
+    }
+
+    Vector2 st;
+
+    GetST(triIndex, 0, barycentric, st);
+
+    int x = diffuse->GetWidth() * st.x_;
+    int y = diffuse->GetHeight() * st.y_;
+
+    if (x < 0)
+        x = diffuse->GetWidth() + x;
+
+    if (y < 0)
+        y = diffuse->GetWidth() + y;
+
+    colorOut = diffuse->GetPixel(x, y);
+
+    return true;
+}
+
+void BakeMesh::IntersectFilter(void* ptr, RTCRay& ray)
+{
+    CommonFilter(static_cast<BakeMesh*>(ptr), ray);
+}
+
+void BakeMesh::OcclusionFilter(void* ptr, RTCRay& ray)
+{
+    CommonFilter(static_cast<BakeMesh*>(ptr), ray);
+}
+
+bool BakeMesh::CommonFilter(const BakeMesh* bakeMesh, RTCRay& ray)
+{
+    if (ray.primID >= (unsigned) bakeMesh->numTriangles_)
+        return false;
+
+    const MMTriangle* tri = &bakeMesh->triangles_[ray.primID];
+    const BakeMaterial* material = bakeMesh->bakeMaterials_[tri->materialIndex_];
+
+    if (!material)
+        return false;
+
+    if (!material->GetOcclusionMasked())
+        return false;
+
+    Color color;
+    if (bakeMesh->GetUV0Color(ray.primID, Vector3(ray.u, ray.v, 1.0f-ray.u-ray.v), color))
+    {
+        if (color.a_ < 0.5f)
+        {
+            ray.geomID = RTC_INVALID_GEOMETRY_ID;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
+}

+ 222 - 0
Source/AtomicGlow/Kernel/BakeMesh.h

@@ -0,0 +1,222 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Embree.h"
+
+#include <Atomic/Core/Thread.h>
+#include <Atomic/Core/Mutex.h>
+#include <Atomic/Graphics/StaticModel.h>
+
+#include "BakeMaterial.h"
+#include "BakeModel.h"
+#include "BakeNode.h"
+#include "RadianceMap.h"
+#include "Photons.h"
+
+namespace AtomicGlow
+{
+
+using namespace Atomic;
+
+class LightRay;
+class SceneBaker;
+class BakeLight;
+class BakeMesh;
+class BounceBakeLight;
+
+class BakeMesh : public BakeNode
+{
+    friend class BounceBakeLight;
+
+    ATOMIC_OBJECT(BakeMesh, BakeNode)
+
+    public:
+
+    struct MMVertex
+    {
+        Vector3 position_;
+        Vector3 normal_;
+        Vector2 uv0_;
+        Vector2 uv1_;
+    };
+
+    struct MMTriangle
+    {
+        // index into bakeMaterials_ vector
+        unsigned materialIndex_;
+
+        Vector3 normal_;
+
+        // index into vertices_ array
+        unsigned indices_[3];
+    };
+
+    struct MMSample
+    {
+        BakeMesh* bakeMesh;
+        unsigned triangle;
+
+        // coords in radiance_
+        unsigned radianceX;
+        unsigned radianceY;
+
+        Vector3 position;
+        Vector3 normal;
+        Vector2 uv0;
+        Vector2 uv1;
+    };
+
+
+    BakeMesh(Context* context, SceneBaker* sceneBaker);
+    virtual ~BakeMesh();
+
+    bool SetStaticModel(StaticModel* staticModel);
+
+    /// Get world space bounding box
+    const BoundingBox& GetBoundingBox() const { return boundingBox_; }    
+    const SharedArrayPtr<MMVertex>& GetVertices(unsigned& numVertices) const { numVertices = numVertices_; return vertices_; }
+    const SharedArrayPtr<MMTriangle>& GetTriangles(unsigned& numTriangles) const { numTriangles = numTriangles_; return triangles_; }
+
+    void Preprocess();
+
+    void Light(GlowLightMode mode);
+
+    bool GetLightmap() const { return staticModel_.Null() ? false : staticModel_->GetLightmap(); }
+
+    void ContributeRadiance(const LightRay* lightRay, const Vector3 &radiance, GlowLightMode lightMode = GLOW_LIGHTMODE_DIRECT);
+
+    int GetRadianceWidth() const { return radianceWidth_; }
+    int GetRadianceHeight() const { return radianceHeight_; }
+
+    BounceBakeLight* GenerateBounceBakeLight();
+    void GenerateRadianceMap();
+
+    inline bool GetRadiance(int x, int y, Vector3& rad, int& triIndex) const
+    {
+        rad = Vector3::ZERO;
+        triIndex = -1;
+
+        if (x < 0 || x >= radianceWidth_)
+            return false;
+
+        if (y < 0 || y >= radianceHeight_)
+            return false;
+
+        rad = radiance_[y * radianceWidth_ + x];
+
+        triIndex = radianceTriIndices_[y * radianceWidth_ + x];
+
+        if (triIndex < 0 || rad.x_ < 0.0f)
+            return false;
+
+        return true;
+    }
+
+    unsigned GetGeomID() const { return embreeGeomID_; }
+
+    inline const MMTriangle* GetTriangle(unsigned index) const
+    {
+        if (index >= numTriangles_)
+            return 0;
+
+        return &triangles_[index];
+    }
+
+    SharedPtr<RadianceMap> GetRadianceMap() const { return radianceMap_; }
+
+    StaticModel* GetStaticModel() const { return staticModel_; }
+
+    void Pack(unsigned lightmapIdx, Vector4 tilingOffset);
+
+    bool GetUV0Color(int triIndex, const Vector3 &barycentric, Color& colorOut) const;
+
+    void GetST(int triIndex, int channel, const Vector3& barycentric, Vector2& st) const;
+
+    const Color& GetAmbientColor() const { return ambientColor_; }
+
+    PhotonMap* GetPhotonMap() const { return photonMap_; }
+
+    void SetPhotonMap(PhotonMap* photonMap) { photonMap_ = photonMap; }
+
+private:
+
+    struct ShaderData
+    {
+        GlowLightMode lightMode_;
+        BakeMesh* bakeMesh_;
+        unsigned triangleIdx_;
+    };
+
+    static void LightTrianglesWork(const WorkItem* item, unsigned threadIndex);
+    static bool FillLexelsCallback(void* param, int x, int y, const Vector3& barycentric,const Vector3& dx, const Vector3& dy, float coverage);
+
+    static bool CommonFilter(const BakeMesh* bakeMesh, RTCRay& ray);
+    static void OcclusionFilter(void* ptr, RTCRay& ray);
+    static void IntersectFilter(void* ptr, RTCRay& ray);
+
+    bool LightPixel(ShaderData* shaderData, int x, int y, const Vector3& barycentric,const Vector3& dx, const Vector3& dy, float coverage);
+
+    // mesh geometry, in world space
+
+    BoundingBox boundingBox_;
+
+    SharedArrayPtr<MMVertex> vertices_;
+    unsigned numVertices_;
+    SharedArrayPtr<MMTriangle> triangles_;
+    unsigned numTriangles_;
+
+    SharedPtr<StaticModel> staticModel_;
+
+    // resources
+    PODVector<BakeMaterial*> bakeMaterials_;
+    SharedPtr<BakeModel> bakeModel_;    
+
+    // lights affecting this mesh
+    PODVector<BakeLight*> bakeLights_;
+
+    SharedPtr<RadianceMap> radianceMap_;
+
+    // can be accessed from multiple threads
+    SharedArrayPtr<Vector3> radiance_;
+    SharedArrayPtr<bool> radiancePassAccept_;
+    // radiance -> triangle contributor
+    SharedArrayPtr<int> radianceTriIndices_;
+
+    SharedPtr<PhotonMap> photonMap_;
+
+    unsigned radianceHeight_;
+    unsigned radianceWidth_;
+
+    unsigned embreeGeomID_;
+
+    // multithreading
+
+    Mutex meshMutex_;
+    unsigned numWorkItems_;
+
+    Color ambientColor_;
+
+};
+
+
+}

+ 97 - 0
Source/AtomicGlow/Kernel/BakeModel.cpp

@@ -0,0 +1,97 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Resource/ResourceCache.h>
+
+#include "BakeModel.h"
+
+namespace AtomicGlow
+{
+
+
+BakeModelCache::BakeModelCache(Context* context) : Object(context)
+{
+
+}
+
+BakeModelCache::~BakeModelCache()
+{
+
+}
+
+BakeModel* BakeModelCache::GetBakeModel(Model *model)
+{
+    if (!model)
+        return 0;
+
+    SharedPtr<BakeModel> bakeModel;
+
+    if (bakeCache_.TryGetValue(model->GetName(), bakeModel))
+    {
+        return bakeModel;
+    }
+
+    bakeModel = new BakeModel(context_);
+
+    if (!bakeModel->LoadModel(model))
+    {
+        return 0;
+    }
+
+    bakeCache_[model->GetName()] = bakeModel;
+
+    return bakeModel;
+
+}
+
+BakeModel::BakeModel(Context* context) : Object(context)
+{
+
+}
+
+BakeModel::~BakeModel()
+{
+
+}
+
+bool BakeModel::LoadModel(Model *model)
+{
+
+    modelPacker_ = new ModelPacker(context_);
+
+    ATOMIC_LOGINFOF("Unpacking model: %s", model->GetName().CString());
+
+    if (!modelPacker_->Unpack(model))
+        return false;
+
+    /*
+    for (unsigned i = 0; i < modelPacker_->lodLevels_.Size(); i++)
+    {
+        GenerateLODLevelAOMap(modelPacker_->lodLevels_[i]);
+    }
+    */
+
+    return true;
+}
+
+
+}

+ 68 - 0
Source/AtomicGlow/Kernel/BakeModel.h

@@ -0,0 +1,68 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <AtomicGlow/Atlas/ModelPacker.h>
+
+namespace AtomicGlow
+{
+
+class BakeModel : public Object
+{
+    ATOMIC_OBJECT(BakeModel, Object)
+
+    public:
+
+    BakeModel(Context* context);
+    virtual ~BakeModel();
+
+    bool LoadModel(Model* model);
+
+    ModelPacker* GetModelPacker() { return modelPacker_; }
+
+private:
+
+    SharedPtr<ModelPacker> modelPacker_;
+
+};
+
+class BakeModelCache : public Object
+{
+    ATOMIC_OBJECT(BakeModelCache, Object)
+
+    public:
+
+    BakeModelCache(Context* context);
+    virtual ~BakeModelCache();
+
+    BakeModel* GetBakeModel(Model* model);
+
+private:
+
+    /// Model->GetName -> BakeModel
+    HashMap<StringHash, SharedPtr<BakeModel>> bakeCache_;
+
+};
+
+
+
+}

+ 41 - 0
Source/AtomicGlow/Kernel/BakeNode.cpp

@@ -0,0 +1,41 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "SceneBaker.h"
+
+#include "BakeNode.h"
+
+
+namespace AtomicGlow
+{
+
+
+BakeNode::BakeNode(Context* context, SceneBaker* sceneBaker) : Object(context),
+    sceneBaker_(sceneBaker)
+{
+}
+
+BakeNode::~BakeNode()
+{
+
+}
+
+}

+ 58 - 0
Source/AtomicGlow/Kernel/BakeNode.h

@@ -0,0 +1,58 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Scene/Node.h>
+
+#include "GlowTypes.h"
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class SceneBaker;
+
+class BakeNode : public Object
+{
+    ATOMIC_OBJECT(BakeNode, Object)
+
+public:
+
+    BakeNode(Context* context, SceneBaker* sceneBaker);
+    virtual ~BakeNode();
+
+    SceneBaker* GetSceneBaker() { return sceneBaker_; }
+    Node* GetNode() const { return node_; }
+
+protected:
+
+    // scene graph
+    SharedPtr<Node> node_;
+
+    WeakPtr<SceneBaker> sceneBaker_;
+
+private:
+
+};
+
+}

+ 26 - 0
Source/AtomicGlow/Kernel/Embree.h

@@ -0,0 +1,26 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <embree2/rtcore.h>
+#include <embree2/rtcore_ray.h>
+

+ 91 - 0
Source/AtomicGlow/Kernel/EmbreeScene.cpp

@@ -0,0 +1,91 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <xmmintrin.h>
+#include <pmmintrin.h>
+#include <cmath>
+#include <cfloat>
+
+#include "Embree.h"
+
+#include <Atomic/IO/Log.h>
+
+#include "BakeMesh.h"
+#include "EmbreeScene.h"
+
+// TODO: Configurable for fast/final bakes?
+
+/*
+enum RTCSceneFlags
+{
+  // dynamic type flags
+  RTC_SCENE_STATIC     = (0 << 0),    //!< specifies static scene
+  RTC_SCENE_DYNAMIC    = (1 << 0),    //!< specifies dynamic scene
+
+  // acceleration structure flags
+  RTC_SCENE_COMPACT    = (1 << 8),    //!< use memory conservative data structures
+  RTC_SCENE_COHERENT   = (1 << 9),    //!< optimize data structures for coherent rays
+  RTC_SCENE_INCOHERENT = (1 << 10),    //!< optimize data structures for in-coherent rays (enabled by default)
+  RTC_SCENE_HIGH_QUALITY = (1 << 11),  //!< create higher quality data structures
+
+  // traversal algorithm flags
+  RTC_SCENE_ROBUST     = (1 << 16)     //!< use more robust traversal algorithms
+};
+
+*/
+
+namespace AtomicGlow
+{
+
+static void RTCErrorCallback(const RTCError code, const char* str)
+{
+    ATOMIC_LOGERRORF("RTC Error %d: %s", code, str);
+}
+
+
+EmbreeScene::EmbreeScene(Context* context) : Object(context),
+    rtcDevice_(0),
+    rtcScene_(0)
+{
+
+    // Intel says to do this, so we're doing it.
+    _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
+    _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
+
+    // Create the embree device and scene.
+    rtcDevice_ = rtcNewDevice(NULL);
+
+    rtcDeviceSetErrorFunction(rtcDevice_, RTCErrorCallback);
+
+    rtcScene_ = rtcDeviceNewScene(rtcDevice_, RTC_SCENE_STATIC, RTC_INTERSECT1);
+}
+
+EmbreeScene::~EmbreeScene()
+{
+
+}
+
+void EmbreeScene::Commit()
+{
+    rtcCommit(rtcScene_);
+}
+
+}

+ 72 - 0
Source/AtomicGlow/Kernel/EmbreeScene.h

@@ -0,0 +1,72 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Embree.h"
+
+#include <Atomic/Core/Object.h>
+
+namespace AtomicGlow
+{
+
+using namespace Atomic;
+
+class EmbreeScenePrivate;
+class BakeMesh;
+
+class EmbreeScene : public Object
+{
+    ATOMIC_OBJECT(EmbreeScene, Object)
+
+    public:
+
+    EmbreeScene(Context* context);
+    virtual ~EmbreeScene();    
+
+    void Commit();
+
+    RTCScene GetRTCScene() const { return rtcScene_; }
+
+    void RegisterBakeMesh(unsigned geomID, BakeMesh* mesh)
+    {
+        meshMapLookup_[geomID] = mesh;
+    }
+
+    BakeMesh* GetBakeMesh(unsigned geomID) const
+    {
+        return meshMapLookup_[geomID] ? meshMapLookup_[geomID]->Get() : 0;
+    }
+
+
+private:
+
+    // embree geomID -> MeshMap
+    HashMap<unsigned, WeakPtr<BakeMesh>> meshMapLookup_;
+
+    RTCDevice rtcDevice_;
+    RTCScene rtcScene_;
+
+
+};
+
+
+}

+ 43 - 0
Source/AtomicGlow/Kernel/GlowTypes.h

@@ -0,0 +1,43 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Math/Vector3.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+enum GlowLightMode
+{
+    GLOW_LIGHTMODE_UNDEFINED,
+    GLOW_LIGHTMODE_AMBIENT,
+    GLOW_LIGHTMODE_DIRECT,
+    GLOW_LIGHTMODE_INDIRECT,
+    GLOW_LIGHTMODE_COMPLETE
+};
+
+const float LIGHT_LARGE_DISTANCE = 65535.0f;
+const float LIGHT_ANGLE_EPSILON = 0.001f;
+
+}

+ 42 - 0
Source/AtomicGlow/Kernel/LightMap.cpp

@@ -0,0 +1,42 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/Resource/Image.h>
+
+#include "LightMap.h"
+
+namespace AtomicGlow
+{
+
+LightMap::LightMap(Context* context, int width, int height) : Object(context),
+  id_(0)
+{
+    image_ = new Image(context_);
+    image_->SetSize(width, height, 3);
+    image_->Clear(Color::BLACK);
+}
+
+LightMap::~LightMap()
+{
+
+}
+
+}

+ 64 - 0
Source/AtomicGlow/Kernel/LightMap.h

@@ -0,0 +1,64 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+#include <AtomicGlow/Common/GlowSettings.h>
+
+namespace Atomic
+{
+
+class Image;
+
+}
+
+namespace AtomicGlow
+{
+
+using namespace Atomic;
+
+class LightMap : public Object
+{
+    ATOMIC_OBJECT(LightMap, Object)
+
+    public:
+
+    LightMap(Context* context, int width = GlobalGlowSettings.lightmapSize_, int height = GlobalGlowSettings.lightmapSize_);
+    virtual ~LightMap();
+
+    unsigned GetID() const { return id_; }
+    void SetID(unsigned id) { id_ = id; }
+
+    Image* GetImage() const { return image_; }
+    void SetImage(Image* image) { image_ = image; }
+
+private:
+
+    unsigned id_;
+
+    SharedPtr<Image> image_;
+
+};
+
+
+}

+ 357 - 0
Source/AtomicGlow/Kernel/LightMapPacker.cpp

@@ -0,0 +1,357 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <ThirdParty/STB/stb_rect_pack.h>
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/Resource/Image.h>
+
+#include "BakeMesh.h"
+#include "LightMapPacker.h"
+
+namespace AtomicGlow
+{
+
+LightMapPacker::LightMapPacker(Context* context) : Object(context)
+{
+
+}
+
+LightMapPacker::~LightMapPacker()
+{
+
+}
+
+void LightMapPacker::AddRadianceMap(RadianceMap* radianceMap)
+{
+    if (!radianceMap)
+        return;
+
+    radMaps_.Push(SharedPtr<RadianceMap>(radianceMap));
+
+}
+
+
+bool LightMapPacker::TryAddRadianceMap(RadianceMap* radMap)
+{
+
+    if (radMap->packed_)
+        return false;
+
+    const int numnodes = GlobalGlowSettings.lightmapSize_;
+
+    SharedArrayPtr<unsigned char> nodes(new unsigned char[sizeof(stbrp_node) * numnodes]);
+    SharedArrayPtr<unsigned char> rects(new unsigned char[sizeof(stbrp_rect) * (workingSet_.Size() + 1)]);
+
+    stbrp_context rectContext;
+
+    stbrp_init_target (&rectContext, numnodes, numnodes, (stbrp_node *) nodes.Get(), numnodes);
+    stbrp_rect* rect = (stbrp_rect*) rects.Get();
+
+    // add working set, we do this brute force for best results
+    for (unsigned i = 0; i < workingSet_.Size(); i++)
+    {
+        RadianceMap* wmap = workingSet_[i];
+
+        rect->id = (int) i;
+        rect->w = wmap->GetWidth() + LIGHTMAP_PADDING * 2;
+        rect->h = wmap->GetHeight() + LIGHTMAP_PADDING * 2;
+
+        rect++;
+    }
+
+    rect->id = (int) workingSet_.Size();
+    rect->w = radMap->GetWidth() + LIGHTMAP_PADDING * 2;
+    rect->h = radMap->GetHeight() + LIGHTMAP_PADDING * 2;
+
+
+    if (!stbrp_pack_rects (&rectContext, (stbrp_rect *)rects.Get(), workingSet_.Size() + 1))
+    {
+        return false;
+    }
+
+    return true;
+
+}
+
+void LightMapPacker::DilatedBlit(const Image* srcImage, Image* destImage, const IntRect& destRect)
+{
+    for (int y = 0; y < srcImage->GetHeight(); y++)
+    {
+        for (int x = 0; x < srcImage->GetWidth(); x++)
+        {
+            destImage->SetPixelInt(destRect.left_ + x + LIGHTMAP_PADDING,
+                                   destRect.top_ + y + LIGHTMAP_PADDING,
+                                   srcImage->GetPixelInt(x, y));
+        }
+    }
+
+    // dilate top and bottom
+    for (int x = 0; x < srcImage->GetWidth(); x++)
+    {
+        for (int i = 0; i < LIGHTMAP_PADDING; i++)
+        {
+            destImage->SetPixelInt(destRect.left_ + x + LIGHTMAP_PADDING,
+                                   destRect.top_ + i,
+                                   srcImage->GetPixelInt(x, 0));
+
+            destImage->SetPixelInt(destRect.left_ + x + LIGHTMAP_PADDING,
+                                   destRect.bottom_ - i,
+                                   srcImage->GetPixelInt(x, srcImage->GetHeight() - 1));
+        }
+
+    }
+
+    // dilate left and right
+    for (int y = 0; y < srcImage->GetHeight(); y++)
+    {
+        for (int i = 0; i < LIGHTMAP_PADDING; i++)
+        {
+            destImage->SetPixelInt(destRect.left_ + i,
+                                   destRect.top_ + y + LIGHTMAP_PADDING,
+                                   srcImage->GetPixelInt(0, y));
+
+            destImage->SetPixelInt(destRect.right_ - i,
+                                   destRect.top_ + y + LIGHTMAP_PADDING,
+                                   srcImage->GetPixelInt(srcImage->GetWidth() - 1, y));
+        }
+
+    }
+
+    // dilate corners
+    for (int y = LIGHTMAP_PADDING - 1; y >= 0 ; y--)
+    {
+        for (int x = LIGHTMAP_PADDING - 1; x >= 0 ; x--)
+        {
+
+            // upper left
+            unsigned pixel = destImage->GetPixelInt(destRect.left_ + x + 1, destRect.top_ + y + 1);
+            //pixel = Color::BLUE.ToUInt();
+            destImage->SetPixelInt(destRect.left_ + x, destRect.top_ + y, pixel);
+
+            // upper right
+            pixel = destImage->GetPixelInt(destRect.right_ - x - 1, destRect.top_ + y + 1);
+            //pixel = Color::RED.ToUInt();
+            destImage->SetPixelInt(destRect.right_ - x, destRect.top_ + y, pixel);
+
+            // lower left
+            pixel = destImage->GetPixelInt(destRect.left_ + x + 1, destRect.bottom_ - y - 1);
+            //pixel = Color::GREEN.ToUInt();
+            destImage->SetPixelInt(destRect.left_ + x, destRect.bottom_ - y, pixel);
+
+            // lower right
+            pixel = destImage->GetPixelInt(destRect.right_ - x - 1, destRect.bottom_ - y - 1);
+            //pixel = Color::MAGENTA.ToUInt();
+            destImage->SetPixelInt(destRect.right_ - x, destRect.bottom_ - y, pixel);
+        }
+    }
+
+}
+
+void LightMapPacker::EmitLightmap(unsigned lightMapID)
+{
+    int width = GlobalGlowSettings.lightmapSize_;
+    int height = GlobalGlowSettings.lightmapSize_;
+
+    // see note in stbrp_init_target docs
+    int numnodes = width;
+
+    SharedArrayPtr<unsigned char> nodes(new unsigned char[sizeof(stbrp_node) * numnodes]);
+    SharedArrayPtr<unsigned char> rects(new unsigned char[sizeof(stbrp_rect) * workingSet_.Size()]);
+
+    stbrp_context rectContext;
+
+    stbrp_init_target (&rectContext, width, height, (stbrp_node *) nodes.Get(), numnodes);
+    stbrp_rect* rect = (stbrp_rect*) rects.Get();
+
+    for (unsigned i = 0; i < workingSet_.Size(); i++)
+    {
+        RadianceMap* radMap = workingSet_[i];
+
+        rect->id = (int) i;
+        rect->w = radMap->GetWidth() + LIGHTMAP_PADDING * 2;
+        rect->h = radMap->GetHeight() + LIGHTMAP_PADDING * 2;
+
+        rect++;
+    }
+
+    if (!stbrp_pack_rects (&rectContext, (stbrp_rect *)rects.Get(), workingSet_.Size()))
+    {
+        ATOMIC_LOGERROR("SceneBaker::Light() - not all rects packed");
+        return;
+    }
+
+    SharedPtr<Image> image(new Image(context_));
+    image->SetSize(width, height, 3);
+    image->Clear(Color::CYAN);
+
+    rect = (stbrp_rect*) rects.Get();
+
+    for (unsigned i = 0; i < workingSet_.Size(); i++)
+    {
+        RadianceMap* radMap = workingSet_[i];
+
+        if (!rect->was_packed)
+        {
+            ATOMIC_LOGERROR("LightMapPacker::Light() - skipping unpacked lightmap");
+            continue;
+        }
+
+        DilatedBlit(radMap->image_, image, IntRect(rect->x, rect->y, rect->x + rect->w - 1, rect->y + rect->h - 1));
+
+        radMap->bakeMesh_->Pack(lightMapID, Vector4(float(radMap->image_->GetWidth())/float(width),
+                                                    float(radMap->image_->GetHeight())/float(height),
+                                                    float(rect->x + LIGHTMAP_PADDING)/float(width),
+                                                    float(rect->y + LIGHTMAP_PADDING)/float(height)));
+
+        rect++;
+    }
+
+    // dilate left and right maximum extents
+    for (int i = 0; i < height; i++)
+    {
+        image->SetPixelInt(width -1, i, image->GetPixelInt(0, i));
+    }
+
+    for (int i = 0; i < width; i++)
+    {
+        image->SetPixelInt(i, height - 1, image->GetPixelInt(i, 0));
+    }
+
+    SharedPtr<LightMap> lightmap(new LightMap(context_));
+    lightMaps_.Push(lightmap);
+
+    lightmap->SetID(lightMapID);
+    lightmap->SetImage(image);
+
+    workingSet_.Clear();
+
+}
+
+bool LightMapPacker::SaveLightmaps(const String &projectPath, const String &scenePath)
+{    
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+
+    for (unsigned i = 0; i < lightMaps_.Size(); i++)
+    {
+        LightMap* lightmap = lightMaps_[i];
+
+        const char* format = GlobalGlowSettings.outputFormat_ == GLOW_OUTPUT_PNG ? "png" : "dds";
+
+        // Note: 2 scenes with the same name in project will collide for lightmap storage
+        // this shouldn't be a general issue, and will be addressed once lightmaps are processed
+        // to Cache with GUID
+        String sceneName = GetFileName(scenePath);
+
+        String folder = ToString("%sResources/AtomicGlow/Scenes/%s/Lightmaps/",  projectPath.CString(), sceneName.CString());
+
+        if (!fileSystem->DirExists(folder))
+        {
+            fileSystem->CreateDirsRecursive(folder);
+        }
+
+        if (!fileSystem->DirExists(folder))
+        {
+            ATOMIC_LOGERRORF("LightMapPacker::SaveLightmaps - Unable to create folder: %s", folder.CString());
+            return false;
+        }
+
+        String filename = ToString("%sLightmap%u.%s", folder.CString(), lightmap->GetID(), format);
+
+        ATOMIC_LOGINFOF("Saving Lightmap: %s", filename.CString());
+
+        GlobalGlowSettings.outputFormat_ == GLOW_OUTPUT_PNG ? lightmap->GetImage()->SavePNG(filename) : lightmap->GetImage()->SaveDDS(filename);
+
+    }
+
+    return true;
+
+}
+
+static bool CompareRadianceMap(RadianceMap* lhs, RadianceMap* rhs)
+{
+    int lhsWeight = lhs->GetWidth();
+    lhsWeight += lhs->GetHeight();
+
+    int rhsWeight = rhs->GetWidth();
+    rhsWeight += rhs->GetHeight();
+
+    // sort from biggest to smallest
+    return lhsWeight > rhsWeight;
+}
+
+
+void LightMapPacker::Pack()
+{
+    unsigned lightmapID = 0;
+
+    SharedPtr<LightMap> lightmap;
+
+    Sort(radMaps_.Begin(), radMaps_.End(), CompareRadianceMap);
+
+    for (unsigned i = 0; i < radMaps_.Size(); i++)
+    {
+        RadianceMap* radMap = radMaps_[i];
+
+        if (radMap->packed_)
+            continue;
+
+        if (radMap->GetWidth() >= GlobalGlowSettings.lightmapSize_ || radMap->GetHeight() >= GlobalGlowSettings.lightmapSize_)
+        {
+            lightmap = new LightMap(context_);
+            lightMaps_.Push(lightmap);
+
+            lightmap->SetID(lightmapID);
+            lightmap->SetImage(radMap->image_);
+
+            radMap->bakeMesh_->Pack(lightmapID, Vector4(1.0f, 1.0f, 0.0f, 0.0f));
+
+            lightmapID++;
+
+            continue;
+        }
+
+        workingSet_.Push(radMap);
+
+        for (unsigned j = 0; j < radMaps_.Size(); j++)
+        {
+            if (i == j)
+                continue;
+
+            RadianceMap* otherMap = radMaps_[j];
+
+            if (TryAddRadianceMap(otherMap))
+            {
+                workingSet_.Push(otherMap);
+            }
+
+        }
+
+        EmitLightmap(lightmapID++);
+        workingSet_.Clear();
+
+    }
+
+}
+
+
+}

+ 75 - 0
Source/AtomicGlow/Kernel/LightMapPacker.h

@@ -0,0 +1,75 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+#include "BakeMesh.h"
+#include "LightMap.h"
+
+namespace Atomic
+{
+
+class Image;
+
+}
+
+namespace AtomicGlow
+{
+
+using namespace Atomic;
+
+// on 4 pixel boundry to prevent bilinear bleeds and for DDS 4x4 compression
+static const int LIGHTMAP_PADDING = 4;
+
+class LightMapPacker : public Object
+{
+    ATOMIC_OBJECT(LightMapPacker, Object)
+
+    public:
+
+    LightMapPacker(Context* context);
+    virtual ~LightMapPacker();
+
+    void AddRadianceMap(RadianceMap* radianceMap);
+
+    void Pack();
+
+    bool SaveLightmaps(const String& projectPath, const String& scenePath);
+
+private:
+
+    /// Attempts to add a radiance map to current working set
+    bool TryAddRadianceMap(RadianceMap* radMap);
+    void DilatedBlit(const Image* srcImage, Image* destImage, const IntRect& destRect);
+    void EmitLightmap(unsigned lightMapID);
+
+    Vector<SharedPtr<RadianceMap>> radMaps_;
+    Vector<SharedPtr<LightMap>> lightMaps_;
+
+
+    PODVector<RadianceMap*> workingSet_;
+
+};
+
+
+}

+ 73 - 0
Source/AtomicGlow/Kernel/LightRay.cpp

@@ -0,0 +1,73 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright 2009-2017 Intel Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <xmmintrin.h>
+#include <pmmintrin.h>
+#include <cmath>
+#include <cfloat>
+
+#include "LightRay.h"
+
+namespace AtomicGlow
+{
+
+LightRay::LightRay()
+{
+
+}
+
+LightRay::~LightRay()
+{
+
+}
+
+void LightRay::ClearHit()
+{
+    rtcRay_.geomID = RTC_INVALID_GEOMETRY_ID;
+    rtcRay_.primID = RTC_INVALID_GEOMETRY_ID;
+    rtcRay_.instID = RTC_INVALID_GEOMETRY_ID;
+    rtcRay_.u = 0.0f;
+    rtcRay_.v = 0.0f;
+    rtcRay_.Ng[0] = rtcRay_.Ng[1] = rtcRay_.Ng[2] = 0.0f;
+}
+
+
+void LightRay::SetupRay(const Vector3& origin, const Vector3& dir, float tNear, float tFar)
+{
+    rtcRay_.org[0] = origin.x_;
+    rtcRay_.org[1] = origin.y_;
+    rtcRay_.org[2] = origin.z_;
+
+    rtcRay_.dir[0] = dir.x_;
+    rtcRay_.dir[1] = dir.y_;
+    rtcRay_.dir[2] = dir.z_;
+
+    rtcRay_.tnear = tNear;
+    rtcRay_.tfar = tFar;
+
+    rtcRay_.mask = 0xFFFFFFFF;
+    rtcRay_.time = 0.0f;
+
+    ClearHit();
+}
+
+}

+ 85 - 0
Source/AtomicGlow/Kernel/LightRay.h

@@ -0,0 +1,85 @@
+
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright 2009-2017 Intel Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Embree.h"
+
+#include "Atomic/Math/Vector3.h"
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class BakeMesh;
+
+class LightRay
+{
+
+public:
+
+    LightRay();
+    ~LightRay();
+
+    struct SamplePoint
+    {
+        SamplePoint()
+        {
+            Clear();
+        }
+
+        void Clear()
+        {
+            bakeMesh = 0;
+            triangle = 0;
+            radianceX = 0;
+            radianceY = 0;
+            position = normal = Vector3::ZERO;
+            uv0 = uv0 = Vector2::ZERO;
+        }
+
+        BakeMesh* bakeMesh;
+        unsigned triangle;
+
+        // coords in radiance_
+        unsigned radianceX;
+        unsigned radianceY;
+
+        Vector3 barycentric;
+        Vector3 position;
+        Vector3 normal;
+        Vector2 uv0;
+        Vector2 uv1;
+    };
+
+    void SetupRay(const Vector3& origin, const Vector3& dir, float tNear = 0.001f, float tFar = 99999.0f);
+
+    void ClearHit();
+
+    RTCRay rtcRay_;
+    SamplePoint samplePoint_;
+
+};
+
+}

+ 341 - 0
Source/AtomicGlow/Kernel/Photons.cpp

@@ -0,0 +1,341 @@
+//
+// Copyright (c) 2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2015 Dmitry Sovetov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Atomic/Core/StringUtils.h>
+
+#include "EmbreeScene.h"
+#include "SceneBaker.h"
+#include "Photons.h"
+#include "BakeMesh.h"
+
+namespace AtomicGlow
+{
+
+
+PhotonMap::PhotonMap(BakeMesh* bakeMesh, int width, int height) :
+    bakeMesh_(bakeMesh),
+    width_(width),
+    height_(height)
+{
+
+    if (width_ > 0 && height_ > 0)
+    {
+        photons_ = new Photon[width_ * height_];
+
+        for (int i = 0; i < width_ * height_; i++)
+        {
+            photons_[i].Reset();
+        }
+    }
+
+}
+
+PhotonMap::~PhotonMap()
+{
+
+}
+
+void PhotonMap::SavePng(const String& filename) const
+{
+    Image image(bakeMesh_->GetContext());
+
+    image.SetSize(width_, height_, 3);
+    image.Clear(Color::BLACK);
+
+    for( int y = 0; y < height_; y++ )
+    {
+        for( int x = 0; x < width_; x++ )
+        {
+            const Photon& photon = photons_[y * width_ + x];
+
+            int count;
+            Color gathered = Color::BLACK;
+
+            for (count = 0; count < PHOTON_TRI_MAX; count++)
+            {
+                if (photon.tris_[count] == PHOTON_TRI_INVALID)
+                    break;
+
+                gathered += photon.gathered_[count];
+
+            }
+
+            if (!count)
+                continue;
+
+            gathered.r_ *= (1.0f / float(count));
+            gathered.g_ *= (1.0f / float(count));
+            gathered.b_ *= (1.0f / float(count));
+            gathered.a_ *= (1.0f / float(count));
+
+            image.SetPixel(x, y, gathered);
+        }
+
+    }
+
+    image.SavePNG(filename);
+
+}
+
+void PhotonMap::Gather(int radius )
+{
+    for( int y = 0; y < height_; y++ )
+    {
+        for( int x = 0; x < width_; x++ )
+        {
+            Photon& photon = photons_[y * width_ + x];
+
+            for (int i = 0; i < PHOTON_TRI_MAX; i++)
+            {
+                if (photon.tris_[i] == PHOTON_TRI_INVALID)
+                    break;
+
+                photon.gathered_[i] = Gather(photon.tris_[i], x, y, radius);
+            }
+        }
+    }
+
+    /*
+    if (true)
+    {
+        String filename = ToString("/Users/jenge/Temp/PhotonMap%i.png", bakeMesh_->GetGeomID());
+        SavePng(filename);
+    }
+    */
+
+}
+
+Color PhotonMap::Gather( int triIndex, int x, int y, int radius ) const
+{
+    Color color = Color::BLACK;
+    int photons = 0;
+
+    for( int j = y - radius; j <= y + radius; j++ )
+    {
+        for( int i = x - radius; i <= x + radius; i++ )
+        {
+            if( i < 0 || j < 0 || i >= width_ || j >= height_ )
+            {
+                continue;
+            }
+
+            const Photon& photon = photons_[j * width_ + i];
+
+            int idx = photon.MapTriIndex(triIndex);
+
+            if (idx == PHOTON_TRI_INVALID)
+            {
+                continue;
+            }
+
+            float distance = sqrtf( static_cast<float>( (x - i) * (x - i) + (y - j) * (y - j) ) );
+
+            if( distance > radius ) {
+                continue;
+            }
+
+            if (photon.photons_[idx])
+            {
+                color += photon.color_[idx];
+                photons += photon.photons_[idx];
+            }
+        }
+    }
+
+    if( photons == 0 )
+    {
+        return Color::BLACK;
+    }
+
+    float divisor = 1.0f / static_cast<float>( photons );
+
+    color.r_ *= divisor;
+    color.g_ *= divisor;
+    color.b_ *= divisor;
+    color.a_ *= divisor;
+
+    return color;
+}
+
+
+Photons::Photons( SceneBaker* sceneBaker, int passCount, int maxDepth, float energyThreshold, float maxDistance )
+    : sceneBaker_( SharedPtr<SceneBaker>(sceneBaker) ),
+      passCount_( passCount ),
+      maxDepth_( maxDepth ),
+      energyThreshold_( energyThreshold ),
+      maxDistance_( maxDistance ),
+      photonCount_( 0 )
+{
+
+}
+
+int Photons::Emit( const Vector<SharedPtr<BakeLight>>& bakeLights )
+{
+    // TODO: depending on performance, photon emission may need to be threaded
+
+    for( int j = 0; j < passCount_; j++ )
+    {
+        Vector<SharedPtr<BakeLight>>::ConstIterator itr = bakeLights.Begin();
+
+        while (itr != bakeLights.End())
+        {
+            BakeLight* bakeLight = *itr;
+
+            if( !bakeLight->GetPhotonEmitter() )
+            {
+                itr++;
+                continue;
+            }
+
+            EmitPhotons( bakeLight );
+
+            itr++;
+        }
+    }
+
+    return photonCount_;
+}
+
+void Photons::EmitPhotons( const BakeLight* light )
+{
+    PhotonEmitter* emitter = light->GetPhotonEmitter();
+    Vector3 direction;
+    Vector3 position;
+
+    for( int i = 0, n = emitter->GetPhotonCount(); i < n; i++ )
+    {
+        // Emit photon
+        emitter->Emit( sceneBaker_, position, direction );
+
+        // Calculate light cutoff
+        float cut = 1.0f;
+
+        if( const LightCutoff* cutoff = light->GetCutoffModel() )
+        {
+            cut = cutoff->GetCutoffForDirection( direction );
+        }
+
+        if( cut <= 0.0f )
+        {
+            continue;
+        }
+
+        Trace( light->GetAttenuationModel(), position, direction, light->GetColor() * light->GetIntensity() * cut, 0 );
+    }
+
+}
+
+void Photons::Trace(const LightAttenuation* attenuation, const Vector3& position, const Vector3& direction, const Color& color, int depth , unsigned lastGeomID, unsigned lastPrimID)
+{
+    // Maximum depth or energy threshold exceeded
+    if( depth > maxDepth_ || color.Luma() < energyThreshold_ )
+    {
+        return;
+    }
+
+    // clean this mess up
+    RTCScene scene = sceneBaker_->GetEmbreeScene()->GetRTCScene();
+
+    float maxDist = attenuation ? maxDistance_ : LIGHT_LARGE_DISTANCE;
+
+    lightRay_.SetupRay(position, direction, .001f, maxDist);
+
+    RTCRay& ray = lightRay_.rtcRay_;
+
+    rtcIntersect(scene, ray);
+
+    // The photon didn't hit anything
+    if (ray.geomID == RTC_INVALID_GEOMETRY_ID)
+    {
+        return;
+    }
+
+    // self hit
+    if (ray.geomID == lastGeomID && ray.primID == lastPrimID)
+    {
+        return;
+    }
+
+    Vector3 hitPosition = position + (direction * ray.tfar);
+
+    Vector3 hitNormal(ray.Ng[0], ray.Ng[1], ray.Ng[2]);
+    hitNormal.Normalize();
+
+    // energy attenuation after a photon has passed the traced segment
+    float att = 1.0f;
+    if( attenuation )
+    {
+        att = attenuation->Calculate( (position - hitPosition).Length() );
+    }
+
+    // energy after reflection
+    float influence = LightInfluence::GetLambert( direction, hitNormal ) * att;
+
+    if (influence <= 0.0f)
+    {
+        return;
+    }
+
+    BakeMesh* bakeMesh = sceneBaker_->GetEmbreeScene()->GetBakeMesh(ray.geomID);
+
+    Color hitColor;
+    Vector3 bary(ray.u, ray.v, 1.0f-ray.u-ray.v);
+
+    // TODO: alpha mask support?
+    if (!bakeMesh || !bakeMesh->GetUV0Color(ray.primID, bary, hitColor))
+    {
+        return;
+    }
+
+    // final photon color
+    hitColor.r_ *= (color.r_ * influence);
+    hitColor.g_ *= (color.g_ * influence);
+    hitColor.b_ *= (color.b_ * influence);
+
+    Vector2 st;
+    bakeMesh->GetST(ray.primID, 1, bary, st);
+
+    // store photon energy
+    Store( bakeMesh->GetPhotonMap(), (int) ray.primID, hitColor, st );
+
+    // keep tracing
+    Vector3 dir;
+    Vector3::GetRandomHemisphereDirection(dir, hitNormal);
+    Trace( attenuation, hitPosition, dir, hitColor, depth + 1, ray.geomID, ray.primID );
+}
+
+void Photons::Store(PhotonMap* photonmap, int triIdx, const Color& color, const Vector2& uv )
+{
+    if( !photonmap ) {
+        return;
+    }
+
+    if (!photonmap->AddPhoton(triIdx, uv, color))
+    {
+        return;
+    }
+
+    photonCount_++;
+}
+
+}

+ 265 - 0
Source/AtomicGlow/Kernel/Photons.h

@@ -0,0 +1,265 @@
+//
+// Copyright (c) 2017, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2015 Dmitry Sovetov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Container/RefCounted.h>
+#include <Atomic/Container/ArrayPtr.h>
+
+#include "LightRay.h"
+#include "BakeLight.h"
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class SceneBaker;
+
+const int PHOTON_TRI_MAX = 8;
+const int PHOTON_TRI_INVALID = -1;
+
+class PhotonMap : public RefCounted
+{
+    ATOMIC_REFCOUNTED(PhotonMap)
+
+public:
+
+    struct Photon
+    {
+      int tris_[PHOTON_TRI_MAX];
+      Color gathered_[PHOTON_TRI_MAX];
+      Color color_[PHOTON_TRI_MAX];
+      int photons_[PHOTON_TRI_MAX];
+
+      int MapTriIndex(int triIndex) const
+      {
+          for (int i = 0; i < PHOTON_TRI_MAX; i++)
+          {
+              if (tris_[i] == PHOTON_TRI_INVALID)
+                  break;
+
+              if (tris_[i] == triIndex)
+                  return i;
+          }
+
+          return PHOTON_TRI_INVALID;
+
+      }
+
+      void Reset()
+      {
+          for (int i = 0; i < PHOTON_TRI_MAX; i++)
+          {
+              tris_[i] = PHOTON_TRI_INVALID;
+              color_[i] = gathered_[i] = Color::BLACK;
+              photons_[i] = 0;
+          }
+      }
+
+    };
+
+    PhotonMap(BakeMesh* bakeMesh, int width, int height);
+    virtual ~PhotonMap();
+
+    // Allocates a triangle, always check return, in readOnly mode won't allocate new tri
+    bool GetPhoton(int triIndex, const Vector2& uv, Color& color, int& photons, Color& gathered, bool readOnly = false)
+    {
+        color = Color::BLACK;
+        gathered = Color::BLACK;
+        photons = 0;
+
+        int x = static_cast<int>( uv.x_ * (width_  - 1) );
+        int y = static_cast<int>( uv.y_ * (height_ - 1) );
+
+        if (x < 0 || x >= width_)
+            return false;
+
+        if (y < 0 || y >= height_)
+            return false;
+
+        Photon* photon = &photons_[y * width_ + x];
+
+        int idx = photon->MapTriIndex(triIndex);
+
+        if (idx == PHOTON_TRI_INVALID)
+        {
+            if (readOnly)
+            {
+                return false;
+            }
+
+            for ( int i = 0; i < PHOTON_TRI_MAX; i++ )
+            {
+                if (photon->tris_[i] == PHOTON_TRI_INVALID)
+                {
+                    idx = photon->tris_[i] = triIndex;
+                    break;
+                }
+            }
+
+        }
+
+        if (idx == PHOTON_TRI_INVALID)
+        {
+            // out of tri storage
+            return false;
+        }
+
+        color = photon->color_[idx];
+        gathered = photon->gathered_[idx];
+        photons = photon->photons_[idx];
+
+        return true;
+
+    }
+
+    // Adds a photon
+    bool AddPhoton(int triIndex, const Vector2& uv, const Color& color)
+    {
+
+        if (color.r_ < 0.01f && color.g_ < 0.01f && color.b_ < 0.01f)
+        {
+            // filter out tiny adds
+            return false;
+        }
+
+        int x = static_cast<int>( uv.x_ * (width_  - 1) );
+        int y = static_cast<int>( uv.y_ * (height_ - 1) );
+
+        if (x < 0 || x >= width_)
+            return false;
+
+        if (y < 0 || y >= height_)
+            return false;
+
+        Photon* photon = &photons_[y * width_ + x];
+
+        int idx = photon->MapTriIndex(triIndex);
+
+        if (idx == PHOTON_TRI_INVALID)
+        {
+            for ( int i = 0; i < PHOTON_TRI_MAX; i++ )
+            {
+                if (photon->tris_[i] == PHOTON_TRI_INVALID)
+                {
+                    idx = i;
+                    photon->tris_[i] = triIndex;
+                    break;
+                }
+            }
+        }
+
+        if (idx == PHOTON_TRI_INVALID)
+        {
+            // out of tri storage
+            return false;
+        }
+
+        photon->color_[idx] += color;
+        photon->photons_[idx]++;
+
+        return true;
+
+    }
+
+    void SavePng(const String& filename) const;
+
+    void Gather( int radius );
+
+    Color Gather( int triIndex, int x, int y, int radius ) const;
+
+private:
+
+    int width_;
+    int height_;
+
+    WeakPtr<BakeMesh> bakeMesh_;
+    SharedArrayPtr<Photon> photons_;
+
+};
+
+class Photons : public RefCounted
+{
+
+    ATOMIC_REFCOUNTED(Photon)
+
+public:
+
+    // Constructs a Photons instance.
+    // param scene Scene to be baked.
+    // param passCount Amount of photon passes.
+    // param maxDepth Maximum photon tracing depth (number of light bounces).
+    // param energyThreshold The minimum energy that photon should have to continue tracing.
+    // param maxDistance The reflected light maximum distance. All intersections above this value will be ignored.
+    Photons( SceneBaker* sceneBaker, int passCount, int maxDepth, float energyThreshold, float maxDistance );
+
+    // Emits photons from all scene lights, returning number of photons emitted
+    virtual int Emit( const Vector<SharedPtr<BakeLight>>& bakeLights );
+
+private:
+
+    // Emits photons from a given light.
+    void EmitPhotons( const BakeLight* light );
+
+    // Traces a photon path for a given depth.
+    /*
+     Traces a photon path for a given depth. Each time the photon bounces the
+     reflected light is stored to a photon map.
+
+     \param attenuation Light attenuation model.
+     \param position Photon's start position.
+     \param direction Photon's direction.
+     \param color Photon's color.
+     \param energy Photon's energy.
+     \param depth Current trace depth.
+     */
+    void Trace(const LightAttenuation* attenuation, const Vector3& position, const Vector3& direction, const Color& color, int depth, unsigned lastGeomID = RTC_INVALID_GEOMETRY_ID, unsigned lastPrimID = RTC_INVALID_GEOMETRY_ID );
+
+    //Stores a photon bounce.
+    void Store( PhotonMap* photonmap, int triIdx, const Color& color, const Vector2& uv );
+
+private:
+
+    // Parent scene.
+    SharedPtr<SceneBaker> sceneBaker_;
+
+    // Amount of photon passes, at each pass there will be emitted N photons.
+    int passCount_;
+
+    // Maximum tracing depth.
+    int maxDepth_;
+
+    // Photon energy threshold.
+    float energyThreshold_;
+
+    // Maximum intersection distance
+    float maxDistance_;
+
+    // Total amount of photons stored.
+    int photonCount_;
+
+    LightRay lightRay_;
+};
+
+}

+ 339 - 0
Source/AtomicGlow/Kernel/RadianceMap.cpp

@@ -0,0 +1,339 @@
+
+#include "LightMap.h"
+#include "BakeMesh.h"
+#include "RadianceMap.h"
+
+namespace AtomicGlow
+{
+
+RadianceMap::RadianceMap(Context* context, BakeMesh* bakeMesh) : Object(context),
+    bakeMesh_(bakeMesh),    
+    packed_(false)
+{
+    int width = bakeMesh->GetRadianceWidth();
+    int height = bakeMesh->GetRadianceHeight();
+
+    image_ = new Image(context_);
+    image_->SetSize(width, height, 2, 3);
+
+    Vector3 rad;
+    int triIndex;
+    Color c;
+
+    for (unsigned y = 0; y <height; y++)
+    {
+        for (unsigned x = 0; x < width; x++)
+        {
+            if (!bakeMesh->GetRadiance(x, y, rad, triIndex))
+            {
+                image_->SetPixel(x, y, 0, Color::MAGENTA);
+                image_->SetPixel(x, y, 1, Color::BLACK);
+                continue;
+            }
+
+            if (rad.Length() > 3.0f)
+            {
+                rad.Normalize();
+                rad *= 3.0f;
+            }
+
+            c.r_ = rad.x_;
+            c.g_ = rad.y_;
+            c.b_ = rad.z_;
+
+            image_->SetPixel(x, y, c);
+            // mark as a valid pixel
+            image_->SetPixel(x, y, 1, Color::RED);
+        }
+    }
+
+    // blur before fill
+    Blur();
+
+    const int maxDist = 7;
+    int d = 1;
+    while (FillInvalidPixels(d) && d <= maxDist)
+    {
+        d++;
+    }
+
+    //Downsample();
+
+}
+
+bool RadianceMap::CheckValidPixel(int x, int y, Color &color)
+{
+
+    if (x < 0 || x >= image_->GetWidth())
+        return false;
+
+    if (y < 0 || y >= image_->GetHeight())
+        return false;
+
+    color = image_->GetPixel(x, y, 1);
+
+    if (color == Color::BLACK)
+        return false;
+
+    color = image_->GetPixel(x, y, 0);
+
+    return true;
+}
+
+bool RadianceMap::FillInvalidPixels(int searchDistance)
+{
+    bool result = false;
+
+    PODVector<Vector2> coords;
+
+    // left
+    coords.Push(Vector2(-searchDistance, 0));
+    // right
+    coords.Push(Vector2(searchDistance, 0));
+
+    // down
+    coords.Push(Vector2(0, searchDistance));
+    // up
+    coords.Push(Vector2(0, -searchDistance));
+
+    coords.Push(Vector2(-searchDistance, -searchDistance));
+    coords.Push(Vector2(searchDistance, searchDistance));
+    coords.Push(Vector2(-searchDistance, searchDistance));
+    coords.Push(Vector2(searchDistance, -searchDistance));
+
+
+    int width = image_->GetWidth();
+    int height = image_->GetHeight();
+
+    for (int x = 0; x < width; x++)
+    {
+        for (int y = 0; y < height; y++)
+        {
+            Color c;
+            HashMap<int, PODVector<Color>> colors;
+
+            if (!CheckValidPixel(x, y , c))
+            {
+                // we have an unitialized pixel, search for an initialized neighbor
+                for (unsigned i = 0; i< coords.Size(); i++)
+                {
+                    const Vector2& coord = coords[i];
+
+                    if (CheckValidPixel(x + coord.x_, y + coord.y_, c))
+                    {
+                        Vector3 rad;
+                        int triIndex;
+
+                        bakeMesh_->GetRadiance(x + coord.x_, y + coord.y_, rad, triIndex);
+
+                        // triIndex can be -1, for a previously filled pixel
+                        colors[triIndex].Push(c);
+                    }
+                }
+
+                if (colors.Size())
+                {
+                    result = true;
+
+                    HashMap<int, PODVector<Color>>::ConstIterator itr = colors.Begin();
+                    int bestTri = -2;
+                    int bestCount = 0;
+                    while (itr != colors.End())
+                    {
+                        // only consider the previous fill colors, if we don't have any
+                        // valid tri colors
+                        if (itr->first_ < 0)
+                        {
+                            itr++;
+                            continue;
+                        }
+
+                        if (itr->second_.Size() > bestCount)
+                        {
+                            bestCount = itr->second_.Size();
+                            bestTri = itr->first_;
+                        }
+
+                        itr++;
+                    }
+
+                    if (bestTri < 0)
+                    {
+                        if (!colors.Contains(-1))
+                        {
+                            // shouldn't happen
+                            continue;
+                        }
+
+                        // use the previous fill as we don't have a valid tri
+                        bestTri = -1;
+
+                    }
+
+                    const PODVector<Color>& triColors = colors[bestTri];
+
+                    c = Color::BLACK;
+                    for (unsigned i = 0; i < triColors.Size(); i++)
+                    {
+                        c += triColors[i];
+                    }
+
+                    c.r_ /= triColors.Size();
+                    c.g_ /= triColors.Size();
+                    c.b_ /= triColors.Size();
+
+                    image_->SetPixel(x, y, c);
+                    image_->SetPixel(x, y, 1, Color::RED);
+                }
+            }
+        }
+    }
+
+    return result;
+}
+
+
+void RadianceMap::Blur()
+{
+    int width = image_->GetWidth();
+    int height = image_->GetHeight();
+
+    SharedPtr<Image> target(new Image(context_));
+    target->SetSize(width, height, 2, 3);
+    target->SetData(image_->GetData());
+
+    Color color;
+    float validPixels;
+    int minK, maxK, minL, maxL;
+    for (int i = 0; i < width; ++i)
+    {
+        for (int j = 0; j < height; ++j)
+        {
+
+            Vector3 rad;
+            int destTriIndex;
+
+            if (!bakeMesh_->GetRadiance(i, j, rad, destTriIndex))
+                continue;
+
+            color = Color::BLACK;
+            validPixels = 0;
+
+            minK = i - 1;
+            if (minK < 0)
+                minK = 0;
+            maxK = i + 1;
+            if (maxK >= width)
+                maxK = width - 1;
+            minL = j - 1;
+            if (minL < 0)
+                minL = 0;
+            maxL = j + 1;
+            if (maxL >= height)
+                maxL = height - 1;
+
+            for (int k = minK; k <= maxK; ++k)
+            {
+                for (int l = minL - 1; l < maxL; ++l)
+                {
+
+                    int tindex;
+                    if (!bakeMesh_->GetRadiance(i, j, rad, tindex))
+                        continue;
+
+                    if (tindex != destTriIndex)
+                        continue;
+
+                    Color c;
+                    if (!CheckValidPixel(k, l, c))
+                        continue;
+
+                    color += c;
+                    ++validPixels;
+                }
+            }
+
+            if (validPixels)
+            {
+                color.r_ /= validPixels;
+                color.g_ /= validPixels;
+                color.b_ /= validPixels;
+                target->SetPixel(i, j, color);
+            }
+        }
+    }
+
+    image_->SetData(target->GetData());
+}
+
+bool RadianceMap::Downsample()
+{
+    // Simple average downsample gives nice results
+
+    SharedPtr<Image> tmp(new Image(context_));
+    tmp->SetSize(image_->GetWidth()/2, image_->GetHeight()/2, 3);
+    tmp->Clear(Color::BLACK);
+
+    for (int y = 0; y < tmp->GetHeight(); y++)
+    {
+        for (int x = 0; x < tmp->GetWidth(); x++)
+        {
+
+            int validColors = 0;
+
+            Color c = Color::BLACK;
+
+            Color tc = image_->GetPixel(x * 2, y * 2);
+            if (tc != Color::BLACK)
+            {
+                c += tc;
+                validColors++;
+            }
+
+            tc = image_->GetPixel(x * 2 + 1, y * 2);
+            if (tc != Color::BLACK)
+            {
+                c += tc;
+                validColors++;
+            }
+
+            tc = image_->GetPixel(x * 2, y * 2 + 1);
+            if (tc != Color::BLACK)
+            {
+                c += tc;
+                validColors++;
+            }
+
+            tc = image_->GetPixel(x * 2 + 1, y * 2 + 1);
+            if (tc != Color::BLACK)
+            {
+                c += tc;
+                validColors++;
+            }
+
+            if (!validColors)
+                continue;
+
+            c.r_ /= validColors;
+            c.g_ /= validColors;
+            c.b_ /= validColors;
+            c.a_ = 1.0f;
+            tmp->SetPixel(x, y, c);
+        }
+
+    }
+
+    image_->SetSize(tmp->GetWidth(), tmp->GetHeight(), 3);
+    image_->SetData(tmp->GetData());
+
+    return true;
+
+
+}
+
+RadianceMap::~RadianceMap()
+{
+
+}
+
+}

+ 60 - 0
Source/AtomicGlow/Kernel/RadianceMap.h

@@ -0,0 +1,60 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Resource/Image.h>
+
+
+namespace AtomicGlow
+{
+
+class BakeMesh;
+
+using namespace Atomic;
+
+class RadianceMap : public Object
+{
+    ATOMIC_OBJECT(RadianceMap, Object)
+
+    public:
+
+    RadianceMap(Context* context, BakeMesh* bakeMesh);
+    virtual ~RadianceMap();
+
+    int GetWidth() const { return image_.Null() ? 0 : image_->GetWidth(); }
+    int GetHeight() const { return image_.Null() ? 0 : image_->GetHeight(); }
+
+    SharedPtr<BakeMesh> bakeMesh_;
+    SharedPtr<Image> image_;
+    bool packed_;
+
+private:
+
+    bool CheckValidPixel(int x, int y, Color& color);
+    bool FillInvalidPixels(int searchDistance = 1);
+    void Blur();
+    bool Downsample();
+
+};
+
+
+}

+ 708 - 0
Source/AtomicGlow/Kernel/Raster.cpp

@@ -0,0 +1,708 @@
+
+#include "Raster.h"
+
+#ifdef ATOMIC_PLATFORM_WINDOWS
+#include <float.h>
+#endif
+
+namespace AtomicGlow
+{
+
+namespace
+{
+
+/// Return the maximum of the two arguments. For floating point values, it returns the second value if the first is NaN.
+template <typename T>
+inline const T & _max(const T & a, const T & b)
+{
+    return (b < a) ? a : b;
+}
+
+/// Return the maximum of the three arguments.
+template <typename T>
+inline const T & _max3(const T & a, const T & b, const T & c)
+{
+    return _max(a, _max(b, c));
+}
+
+/// Return the minimum of two values.
+template <typename T>
+inline const T & _min(const T & a, const T & b)
+{
+    return (a < b) ? a : b;
+}
+
+/// Return the maximum of the three arguments.
+template <typename T>
+inline const T & _min3(const T & a, const T & b, const T & c)
+{
+    return _min(a, _min(b, c));
+}
+
+/// Clamp between two values.
+template <typename T>
+inline const T & _clamp(const T & x, const T & a, const T & b)
+{
+    return _min(_max(x, a), b);
+}
+
+inline bool isFinite(const float f)
+{
+
+#ifdef ATOMIC_PLATFORM_WINDOWS
+    return _finite(f) != 0;
+#endif
+
+#ifdef ATOMIC_PLATFORM_OSX
+    return isfinite(f);
+#endif
+
+#ifdef ATOMIC_PLATFORM_LINUX
+    return finitef(f);
+#endif
+
+}
+
+static inline float delta(float bot, float top, float ih)
+{
+    return (bot - top) * ih;
+}
+
+static inline Vector2 delta(const Vector2& bot, const Vector2& top, float ih)
+{
+    return (bot - top) * ih;
+}
+
+static inline Vector3 delta(const Vector3& bot, const Vector3& top, float ih)
+{
+    return (bot - top) * ih;
+}
+
+// @@ The implementation in nvmath.h should be equivalent.
+static inline int iround(float f)
+{
+    // @@ Optimize this.
+    return int(floorf(f+0.5f));
+    //return int(round(f));
+    //return int(f);
+}
+
+
+class ClippedTriangle
+{
+public:
+    ClippedTriangle(const Vector2& a, const Vector2& b, const Vector2& c)
+    {
+        m_numVertices = 3;
+        m_activeVertexBuffer = 0;
+
+        m_verticesA[0]=a;
+        m_verticesA[1]=b;
+        m_verticesA[2]=c;
+
+        m_vertexBuffers[0] = m_verticesA;
+        m_vertexBuffers[1] = m_verticesB;
+    }
+
+    unsigned vertexCount()
+    {
+        return m_numVertices;
+    }
+
+    const Vector2 * vertices()
+    {
+        return m_vertexBuffers[m_activeVertexBuffer];
+    }
+
+    inline void clipHorizontalPlane(float offset, float clipdirection)
+    {
+        Vector2 * v  = m_vertexBuffers[m_activeVertexBuffer];
+        m_activeVertexBuffer ^= 1;
+        Vector2 * v2 = m_vertexBuffers[m_activeVertexBuffer];
+
+        v[m_numVertices] = v[0];
+
+        float dy2,   dy1 = offset - v[0].y_;
+        int   dy2in, dy1in = clipdirection*dy1 >= 0;
+        unsigned  p=0;
+
+        for (unsigned k=0; k<m_numVertices; k++)
+        {
+            dy2   = offset - v[k+1].y_;
+            dy2in = clipdirection*dy2 >= 0;
+
+            if (dy1in) v2[p++] = v[k];
+
+            if ( dy1in + dy2in == 1 ) // not both in/out
+            {
+                float dx = v[k+1].x_ - v[k].x_;
+                float dy = v[k+1].y_ - v[k].y_;
+                v2[p++] = Vector2(v[k].x_ + dy1*(dx/dy), offset);
+            }
+
+            dy1 = dy2; dy1in = dy2in;
+        }
+        m_numVertices = p;
+
+        //for (uint k=0; k<m_numVertices; k++) printf("(%f, %f)\n", v2[k].x, v2[k].y); printf("\n");
+    }
+
+    inline void clipVerticalPlane(float offset, float clipdirection )
+    {
+        Vector2 * v  = m_vertexBuffers[m_activeVertexBuffer];
+        m_activeVertexBuffer ^= 1;
+        Vector2 * v2 = m_vertexBuffers[m_activeVertexBuffer];
+
+        v[m_numVertices] = v[0];
+
+        float dx2,   dx1   = offset - v[0].x_;
+        int   dx2in, dx1in = clipdirection*dx1 >= 0;
+        unsigned  p=0;
+
+        for (unsigned k=0; k<m_numVertices; k++)
+        {
+            dx2 = offset - v[k+1].x_;
+            dx2in = clipdirection*dx2 >= 0;
+
+            if (dx1in) v2[p++] = v[k];
+
+            if ( dx1in + dx2in == 1 ) // not both in/out
+            {
+                float dx = v[k+1].x_ - v[k].x_;
+                float dy = v[k+1].y_ - v[k].y_;
+                v2[p++] = Vector2(offset, v[k].y_ + dx1*(dy/dx));
+            }
+
+            dx1 = dx2; dx1in = dx2in;
+        }
+        m_numVertices = p;
+
+        //for (uint k=0; k<m_numVertices; k++) printf("(%f, %f)\n", v2[k].x, v2[k].y); printf("\n");
+    }
+
+    void computeAreaCentroid()
+    {
+        Vector2 * v  = m_vertexBuffers[m_activeVertexBuffer];
+        v[m_numVertices] = v[0];
+
+        m_area = 0;
+        float centroidx=0, centroidy=0;
+        for (unsigned k=0; k<m_numVertices; k++)
+        {
+            // http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/
+            float f = v[k].x_*v[k+1].y_ - v[k+1].x_*v[k].y_;
+            m_area += f;
+            centroidx += f * (v[k].x_ + v[k+1].x_);
+            centroidy += f * (v[k].y_ + v[k+1].y_);
+        }
+        m_area = 0.5f * fabs(m_area);
+        if (m_area==0) {
+            m_centroid = Vector2(0.0f, 0.0f);
+        } else {
+            m_centroid = Vector2(centroidx/(6*m_area), centroidy/(6*m_area));
+        }
+    }
+
+    void clipAABox(float x0, float y0, float x1, float y1)
+    {
+        clipVerticalPlane  ( x0, -1);
+        clipHorizontalPlane( y0, -1);
+        clipVerticalPlane  ( x1,  1);
+        clipHorizontalPlane( y1,  1);
+
+        computeAreaCentroid();
+    }
+
+    Vector2 centroid()
+    {
+        return m_centroid;
+    }
+
+    float area()
+    {
+        return m_area;
+    }
+
+private:
+    Vector2 m_verticesA[7+1];
+    Vector2 m_verticesB[7+1];
+    Vector2 * m_vertexBuffers[2];
+    unsigned    m_numVertices;
+    unsigned    m_activeVertexBuffer;
+    float   m_area;
+    Vector2 m_centroid;
+};
+
+
+/// A triangle vertex.
+struct Vertex
+{
+    Vector2 pos;	// Position.
+    Vector3 tex;	// Texcoord. (Barycentric coordinate)
+};
+
+
+/// A triangle for rasterization.
+struct Triangle
+{
+    Triangle(const Vector2& v0, const Vector2& v1, const Vector2& v2, const Vector3& t0, const Vector3& t1, const Vector3& t2);
+
+    bool computeDeltas();
+
+    bool draw(const Vector2& extents, bool enableScissors, RasterSamplingCallback cb, void *param);
+    bool drawAA(const Vector2& extents, bool enableScissors, RasterSamplingCallback cb, void *param);
+
+    void flipBackface();
+    void computeUnitInwardNormals();
+
+    // Vertices.
+    Vector2 v1, v2, v3;
+    Vector2 n1, n2, n3; // unit inward normals
+    Vector3 t1, t2, t3;
+
+    // Deltas.
+    Vector3 dx, dy;
+
+    float sign;
+    bool valid;
+};
+
+
+/// Triangle ctor.
+Triangle::Triangle(const Vector2& v0, const Vector2& v1, const Vector2& v2, const Vector3& t0, const Vector3& t1, const Vector3& t2)
+{
+    // Init vertices.
+    this->v1 = v0;
+    this->v2 = v2;
+    this->v3 = v1;
+
+    // Set barycentric coordinates.
+    this->t1 = t0;
+    this->t2 = t2;
+    this->t3 = t1;
+
+    // make sure every triangle is front facing.
+    flipBackface();
+
+    // Compute deltas.
+    valid = computeDeltas();
+
+    computeUnitInwardNormals();
+}
+
+
+/// Compute texture space deltas.
+/// This method takes two edge vectors that form a basis, determines the
+/// coordinates of the canonic vectors in that basis, and computes the
+/// texture gradient that corresponds to those vectors.
+bool Triangle::computeDeltas()
+{
+    Vector2 e0 = v3 - v1;
+    Vector2 e1 = v2 - v1;
+
+    Vector3 de0 = t3 - t1;
+    Vector3 de1 = t2 - t1;
+
+    float denom = 1.0f / (e0.y_ * e1.x_ - e1.y_ * e0.x_);
+    if (!isFinite(denom)) {
+        return false;
+    }
+
+    float lambda1 = - e1.y_ * denom;
+    float lambda2 = e0.y_ * denom;
+    float lambda3 = e1.x_ * denom;
+    float lambda4 = - e0.x_ * denom;
+
+    dx = de0 * lambda1 + de1 * lambda2;
+    dy = de0 * lambda3 + de1 * lambda4;
+
+    return true;
+}
+
+// compute unit inward normals for each edge.
+void Triangle::computeUnitInwardNormals()
+{
+    n1 = v1 - v2; n1 = Vector2(-n1.y_, n1.x_); n1 = n1 * (1.0f/sqrtf(n1.x_*n1.x_ + n1.y_*n1.y_));
+    n2 = v2 - v3; n2 = Vector2(-n2.y_, n2.x_); n2 = n2 * (1.0f/sqrtf(n2.x_*n2.x_ + n2.y_*n2.y_));
+    n3 = v3 - v1; n3 = Vector2(-n3.y_, n3.x_); n3 = n3 * (1.0f/sqrtf(n3.x_*n3.x_ + n3.y_*n3.y_));
+}
+
+void Triangle::flipBackface()
+{
+    // check if triangle is backfacing, if so, swap two vertices
+    if ( ((v3.x_-v1.x_)*(v2.y_-v1.y_) - (v3.y_-v1.y_)*(v2.x_-v1.x_)) < 0 ) {
+        Vector2 hv=v1; v1=v2; v2=hv; // swap pos
+        Vector3 ht=t1; t1=t2; t2=ht; // swap tex
+    }
+}
+
+bool Triangle::draw(const Vector2 & extents, bool enableScissors, RasterSamplingCallback cb, void * param)
+{
+    // 28.4 fixed-point coordinates
+    const int Y1 = iround(16.0f * v1.y_);
+    const int Y2 = iround(16.0f * v2.y_);
+    const int Y3 = iround(16.0f * v3.y_);
+
+    const int X1 = iround(16.0f * v1.x_);
+    const int X2 = iround(16.0f * v2.x_);
+    const int X3 = iround(16.0f * v3.x_);
+
+    // Deltas
+    const int DX12 = X1 - X2;
+    const int DX23 = X2 - X3;
+    const int DX31 = X3 - X1;
+
+    const int DY12 = Y1 - Y2;
+    const int DY23 = Y2 - Y3;
+    const int DY31 = Y3 - Y1;
+
+    // Fixed-point deltas
+    const int FDX12 = DX12 << 4;
+    const int FDX23 = DX23 << 4;
+    const int FDX31 = DX31 << 4;
+
+    const int FDY12 = DY12 << 4;
+    const int FDY23 = DY23 << 4;
+    const int FDY31 = DY31 << 4;
+
+    int minx, miny, maxx, maxy;
+    if (enableScissors) {
+        int frustumX0 =  0 << 4;
+        int frustumY0 =  0 << 4;
+        int frustumX1 =  (int)extents.x_ << 4;
+        int frustumY1 =  (int)extents.y_ << 4;
+
+        // Bounding rectangle
+        minx = (_max(_min3(X1, X2, X3), frustumX0) + 0xF) >> 4;
+        miny = (_max(_min3(Y1, Y2, Y3), frustumY0) + 0xF) >> 4;
+        maxx = (_min(_max3(X1, X2, X3), frustumX1) + 0xF) >> 4;
+        maxy = (_min(_max3(Y1, Y2, Y3), frustumY1) + 0xF) >> 4;
+    }
+    else {
+        // Bounding rectangle
+        minx = (_min3(X1, X2, X3) + 0xF) >> 4;
+        miny = (_min3(Y1, Y2, Y3) + 0xF) >> 4;
+        maxx = (_max3(X1, X2, X3) + 0xF) >> 4;
+        maxy = (_max3(Y1, Y2, Y3) + 0xF) >> 4;
+    }
+
+    // Block size, standard 8x8 (must be power of two)
+    const int q = 8;
+
+    // @@ This won't work when minx,miny are negative. This code path is not used. Leaving as is for now.
+    assert(minx >= 0);
+    assert(miny >= 0);
+
+    // Start in corner of 8x8 block
+    minx &= ~(q - 1);
+    miny &= ~(q - 1);
+
+    // Half-edge constants
+    int C1 = DY12 * X1 - DX12 * Y1;
+    int C2 = DY23 * X2 - DX23 * Y2;
+    int C3 = DY31 * X3 - DX31 * Y3;
+
+    // Correct for fill convention
+    if(DY12 < 0 || (DY12 == 0 && DX12 > 0)) C1++;
+    if(DY23 < 0 || (DY23 == 0 && DX23 > 0)) C2++;
+    if(DY31 < 0 || (DY31 == 0 && DX31 > 0)) C3++;
+
+    // Loop through blocks
+    for(int y = miny; y < maxy; y += q)
+    {
+        for(int x = minx; x < maxx; x += q)
+        {
+            // Corners of block
+            int x0 = x << 4;
+            int x1 = (x + q - 1) << 4;
+            int y0 = y << 4;
+            int y1 = (y + q - 1) << 4;
+
+            // Evaluate half-space functions
+            bool a00 = C1 + DX12 * y0 - DY12 * x0 > 0;
+            bool a10 = C1 + DX12 * y0 - DY12 * x1 > 0;
+            bool a01 = C1 + DX12 * y1 - DY12 * x0 > 0;
+            bool a11 = C1 + DX12 * y1 - DY12 * x1 > 0;
+            int a = (a00 << 0) | (a10 << 1) | (a01 << 2) | (a11 << 3);
+
+            bool b00 = C2 + DX23 * y0 - DY23 * x0 > 0;
+            bool b10 = C2 + DX23 * y0 - DY23 * x1 > 0;
+            bool b01 = C2 + DX23 * y1 - DY23 * x0 > 0;
+            bool b11 = C2 + DX23 * y1 - DY23 * x1 > 0;
+            int b = (b00 << 0) | (b10 << 1) | (b01 << 2) | (b11 << 3);
+
+            bool c00 = C3 + DX31 * y0 - DY31 * x0 > 0;
+            bool c10 = C3 + DX31 * y0 - DY31 * x1 > 0;
+            bool c01 = C3 + DX31 * y1 - DY31 * x0 > 0;
+            bool c11 = C3 + DX31 * y1 - DY31 * x1 > 0;
+            int c = (c00 << 0) | (c10 << 1) | (c01 << 2) | (c11 << 3);
+
+            // Skip block when outside an edge
+            if(a == 0x0 || b == 0x0 || c == 0x0) continue;
+
+            // Accept whole block when totally covered
+            if(a == 0xF && b == 0xF && c == 0xF)
+            {
+                Vector3 texRow = t1 + dy*(y0 - v1.y_) + dx*(x0 - v1.x_);
+
+                for(int iy = y; iy < y + q; iy++)
+                {
+                    Vector3 tex = texRow;
+                    for(int ix = x; ix < x + q; ix++)
+                    {
+                        Vector3 tex2 = t1 + dx * (ix - v1.x_) + dy * (iy - v1.y_);
+                        if (!cb(param, ix, iy, tex2, dx, dy, 1.0)) {
+                            // early out.
+                            return false;
+                        }
+                        tex += dx;
+                    }
+                    texRow += dy;
+                }
+            }
+            else // Partially covered block
+            {
+                int CY1 = C1 + DX12 * y0 - DY12 * x0;
+                int CY2 = C2 + DX23 * y0 - DY23 * x0;
+                int CY3 = C3 + DX31 * y0 - DY31 * x0;
+                Vector3 texRow = t1 + dy*(y0 - v1.y_) + dx*(x0 - v1.x_);
+
+                for(int iy = y; iy < y + q; iy++)
+                {
+                    int CX1 = CY1;
+                    int CX2 = CY2;
+                    int CX3 = CY3;
+                    Vector3 tex = texRow;
+
+                    for(int ix = x; ix < x + q; ix++)
+                    {
+                        if(CX1 > 0 && CX2 > 0 && CX3 > 0)
+                        {
+                            Vector3 tex2 = t1 + dx * (ix - v1.x_) + dy * (iy - v1.y_);
+                            if (!cb(param, ix, iy, tex2, dx, dy, 1.0))
+                            {
+                                // early out.
+                                return false;
+                            }
+                        }
+
+                        CX1 -= FDY12;
+                        CX2 -= FDY23;
+                        CX3 -= FDY31;
+                        tex += dx;
+                    }
+
+                    CY1 += FDX12;
+                    CY2 += FDX23;
+                    CY3 += FDX31;
+                    texRow += dy;
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+
+#define PX_INSIDE    1.0f/sqrt(2.0f)
+#define PX_OUTSIDE  -1.0f/sqrt(2.0f)
+
+#define BK_SIZE 8
+#define BK_INSIDE   sqrt(BK_SIZE*BK_SIZE/2.0f)
+#define BK_OUTSIDE -sqrt(BK_SIZE*BK_SIZE/2.0f)
+
+// extents has to be multiple of BK_SIZE!!
+bool Triangle::drawAA(const Vector2& extents, bool enableScissors, RasterSamplingCallback cb, void * param)
+{
+    float minx, miny, maxx, maxy;
+    if (enableScissors) {
+        // Bounding rectangle
+        minx = floorf(_max(_min3(v1.x_, v2.x_, v3.x_), 0.0f));
+        miny = floorf(_max(_min3(v1.y_, v2.y_, v3.y_), 0.0f));
+        maxx = ceilf( _min(_max3(v1.x_, v2.x_, v3.x_), extents.x_-1.0f));
+        maxy = ceilf( _min(_max3(v1.y_, v2.y_, v3.y_), extents.y_-1.0f));
+    }
+    else {
+        // Bounding rectangle
+        minx = floorf(_min3(v1.x_, v2.x_, v3.x_));
+        miny = floorf(_min3(v1.y_, v2.y_, v3.y_));
+        maxx = ceilf( _max3(v1.x_, v2.x_, v3.x_));
+        maxy = ceilf( _max3(v1.y_, v2.y_, v3.y_));
+    }
+
+    // There's no reason to align the blocks to the viewport, instead we align them to the origin of the triangle bounds.
+    minx = floorf(minx);
+    miny = floorf(miny);
+    //minx = (float)(((int)minx) & (~((int)BK_SIZE - 1))); // align to blocksize (we don't need to worry about blocks partially out of viewport)
+    //miny = (float)(((int)miny) & (~((int)BK_SIZE - 1)));
+
+    minx += 0.5; miny +=0.5;  // sampling at texel centers!
+    maxx += 0.5; maxy +=0.5;
+
+    // Half-edge constants
+    float C1 = n1.x_ * (-v1.x_) + n1.y_ * (-v1.y_);
+    float C2 = n2.x_ * (-v2.x_) + n2.y_ * (-v2.y_);
+    float C3 = n3.x_ * (-v3.x_) + n3.y_ * (-v3.y_);
+
+    // Loop through blocks
+    for(float y0 = miny; y0 <= maxy; y0 += BK_SIZE)
+    {
+        for(float x0 = minx; x0 <= maxx; x0 += BK_SIZE)
+        {
+            // Corners of block
+            float xc = (x0 + (BK_SIZE-1)/2.0f);
+            float yc = (y0 + (BK_SIZE-1)/2.0f);
+
+            // Evaluate half-space functions
+            float aC = C1 + n1.x_ * xc + n1.y_ * yc;
+            float bC = C2 + n2.x_ * xc + n2.y_ * yc;
+            float cC = C3 + n3.x_ * xc + n3.y_ * yc;
+
+            // Skip block when outside an edge
+            if( (aC <= BK_OUTSIDE) || (bC <= BK_OUTSIDE) || (cC <= BK_OUTSIDE) ) continue;
+
+            // Accept whole block when totally covered
+            if( (aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE) )
+            {
+                Vector3 texRow = t1 + dy*(y0 - v1.y_) + dx*(x0 - v1.x_);
+
+                for (float y = y0; y < y0 + BK_SIZE; y++)
+                {
+                    Vector3 tex = texRow;
+                    for(float x = x0; x < x0 + BK_SIZE; x++)
+                    {
+                        if (!cb(param, (int)x, (int)y, tex, dx, dy, 1.0f))
+                        {
+                            return false;
+                        }
+                        tex += dx;
+                    }
+                    texRow += dy;
+                }
+            }
+            else // Partially covered block
+            {
+                float CY1 = C1 + n1.x_ * x0 + n1.y_ * y0;
+                float CY2 = C2 + n2.x_ * x0 + n2.y_ * y0;
+                float CY3 = C3 + n3.x_ * x0 + n3.y_ * y0;
+                Vector3 texRow = t1 + dy*(y0 - v1.y_) + dx*(x0 - v1.x_);
+
+                for(float y = y0; y < y0 + BK_SIZE; y++)
+                {
+                    float CX1 = CY1;
+                    float CX2 = CY2;
+                    float CX3 = CY3;
+                    Vector3 tex = texRow;
+
+                    for (float x = x0; x < x0 + BK_SIZE; x++)
+                    {
+                        if (CX1 >= PX_INSIDE && CX2 >= PX_INSIDE && CX3 >= PX_INSIDE)
+                        {
+                            // pixel completely covered
+                            Vector3 tex = t1 + dx * (x - v1.x_) + dy * (y - v1.y_);
+                            if (!cb(param, (int)x, (int)y, tex, dx, dy, 1.0f))
+                            {
+                                return false;
+                            }
+                        }
+                        else if ((CX1 >= PX_OUTSIDE) && (CX2 >= PX_OUTSIDE) && (CX3 >= PX_OUTSIDE))
+                        {
+                            // triangle partially covers pixel. do clipping.
+                            ClippedTriangle ct(v1-Vector2(x,y), v2-Vector2(x,y), v3-Vector2(x,y));
+                            ct.clipAABox(-0.5, -0.5, 0.5, 0.5);
+                            Vector2 centroid = ct.centroid();
+                            float area = ct.area();
+                            if (area > 0.0f)
+                            {
+                                Vector3 texCent = tex - dx*centroid.x_ - dy*centroid.y_;
+                                //nvCheck(texCent.x_ >= -0.1f && texCent.x_ <= 1.1f); // @@ Centroid is not very exact...
+                                //nvCheck(texCent.y_ >= -0.1f && texCent.y_ <= 1.1f);
+                                //nvCheck(texCent.z >= -0.1f && texCent.z <= 1.1f);
+                                //Vector3 texCent2 = t1 + dx * (x - v1.x_) + dy * (y - v1.y_);
+                                if (!cb(param, (int)x, (int)y, texCent, dx, dy, area))
+                                {
+                                    return false;
+                                }
+                            }
+                        }
+
+                        CX1 += n1.x_;
+                        CX2 += n2.x_;
+                        CX3 += n3.x_;
+                        tex += dx;
+                    }
+
+                    CY1 += n1.y_;
+                    CY2 += n2.y_;
+                    CY3 += n3.y_;
+                    texRow += dy;
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+} // namespace
+
+
+/// Process the given triangle.
+bool Raster::DrawTriangle(bool antialias, const Vector2& extents, bool enableScissors, const Vector2 v[3], RasterSamplingCallback cb, void* param)
+{
+    Triangle tri(v[0], v[1], v[2], Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1));
+
+    if (tri.valid) {
+        if (antialias) {
+            return tri.drawAA(extents, enableScissors, cb, param);
+        } else {
+            return tri.draw(extents, enableScissors, cb, param);
+        }
+    }
+
+    return true;
+}
+
+inline float triangleArea(const Vector2& v1, const Vector2& v2, const Vector2& v3)
+{
+    return 0.5f * (v3.x_ * v1.y_ + v1.x_ * v2.y_ + v2.x_ * v3.y_ - v2.x_ * v1.y_ - v3.x_ * v2.y_ - v1.x_ * v3.y_);
+}
+
+/// Process the given quad.
+bool Raster::DrawQuad(bool antialias, const Vector2& extents, bool enableScissors, const Vector2 v[4], RasterSamplingCallback cb, void * param)
+{
+    bool sign0 = triangleArea(v[0], v[1], v[2]) > 0.0f;
+    bool sign1 = triangleArea(v[0], v[2], v[3]) > 0.0f;
+
+    // Divide the quad into two non overlapping triangles.
+    if (sign0 == sign1) {
+        Triangle tri0(v[0], v[1], v[2], Vector3(0,0,0), Vector3(1,0,0), Vector3(1,1,0));
+        Triangle tri1(v[0], v[2], v[3], Vector3(0,0,0), Vector3(1,1,0), Vector3(0,1,0));
+
+        if (tri0.valid && tri1.valid) {
+            if (antialias) {
+                return tri0.drawAA(extents, enableScissors, cb, param) && tri1.drawAA(extents, enableScissors, cb, param);
+            } else {
+                return tri0.draw(extents, enableScissors, cb, param) && tri1.draw(extents, enableScissors, cb, param);
+            }
+        }
+    }
+    else
+    {
+        Triangle tri0(v[0], v[1], v[3], Vector3(0,0,0), Vector3(1,0,0), Vector3(0,1,0));
+        Triangle tri1(v[1], v[2], v[3], Vector3(1,0,0), Vector3(1,1,0), Vector3(0,1,0));
+
+        if (tri0.valid && tri1.valid) {
+            if (antialias) {
+                return tri0.drawAA(extents, enableScissors, cb, param) && tri1.drawAA(extents, enableScissors, cb, param);
+            } else {
+                return tri0.draw(extents, enableScissors, cb, param) && tri1.draw(extents, enableScissors, cb, param);
+            }
+        }
+    }
+
+    return true;
+}
+
+}

+ 28 - 0
Source/AtomicGlow/Kernel/Raster.h

@@ -0,0 +1,28 @@
+
+#pragma once
+
+#include <Atomic/Math/Vector2.h>
+#include <Atomic/Math/Vector3.h>
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+/// A callback to sample the environment. Return false to terminate rasterization.
+typedef bool (*RasterSamplingCallback)(void* param, int x, int y, const Vector3& barycentric,const Vector3& dx, const Vector3& dy, float coverage);
+
+class Raster
+{
+public:
+
+    // Process the given triangle. Returns false if rasterization was interrupted by the callback.
+    static bool DrawTriangle(bool antialias, const Vector2& extents, bool enableScissors, const Vector2 v[3], RasterSamplingCallback cb, void* param);
+
+    // Process the given quad. Returns false if rasterization was interrupted by the callback.
+    static bool DrawQuad(bool antialias, const Vector2& extents, bool enableScissors, const Vector2 v[4], RasterSamplingCallback cb, void* param);
+
+};
+
+
+}

+ 703 - 0
Source/AtomicGlow/Kernel/SceneBaker.cpp

@@ -0,0 +1,703 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <xmmintrin.h>
+#include <pmmintrin.h>
+#include <cmath>
+#include <cfloat>
+
+#include <ThirdParty/STB/stb_rect_pack.h>
+
+#include <Atomic/Core/WorkQueue.h>
+#include <Atomic/IO/Log.h>
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/Resource/ResourceCache.h>
+#include <Atomic/Graphics/Zone.h>
+#include <Atomic/Graphics/Light.h>
+#include <Atomic/Graphics/StaticModel.h>
+
+#include "LightRay.h"
+#include "BakeModel.h"
+#include "BakeMesh.h"
+#include "BakeLight.h"
+#include "EmbreeScene.h"
+#include "LightMapPacker.h"
+#include "SceneBaker.h"
+#include "Photons.h"
+
+namespace AtomicGlow
+{
+
+SceneBaker::SceneBaker(Context* context, const String &projectPath) : Object(context),
+    currentLightMode_(GLOW_LIGHTMODE_UNDEFINED),
+    currentGIPass_(0),
+    projectPath_(AddTrailingSlash(projectPath)),
+    standaloneMode_(true)
+{
+    embreeScene_ = new EmbreeScene(context_);
+}
+
+SceneBaker::~SceneBaker()
+{
+
+}
+
+bool SceneBaker::SaveLitScene()
+{
+    if (!standaloneMode_)
+    {
+        ATOMIC_LOGERROR("SceneBaker::SaveLitScene() - only supported in standalone mode");
+        return false;
+    }
+
+    String sceneFilename = AddTrailingSlash(projectPath_) + "Resources/" + scene_->GetFileName();
+
+    File saveFile(context_, sceneFilename, FILE_WRITE);
+    return scene_->SaveXML(saveFile);
+}
+
+bool SceneBaker::WriteBakeData(VectorBuffer& buffer)
+{
+    buffer.Clear();
+
+    // protocol is very simple right now, can easily be expanded
+
+    buffer.WriteUInt(bakeMeshes_.Size());
+
+    for (unsigned i = 0; i < bakeMeshes_.Size(); i++)
+    {
+        BakeMesh* bakeMesh = bakeMeshes_[i];
+        Node* node = bakeMesh->GetNode();
+        StaticModel* staticModel = bakeMesh->GetStaticModel();
+
+        buffer.WriteUInt(node->GetID());
+        buffer.WriteUInt(staticModel->GetID());
+        buffer.WriteUInt(staticModel->GetLightMask());
+        buffer.WriteUInt(staticModel->GetLightmapIndex());
+        buffer.WriteVector4(staticModel->GetLightmapTilingOffset());
+    }
+
+    return true;
+}
+
+bool SceneBaker::GenerateLightmaps()
+{
+    ATOMIC_LOGINFO("Generating Lightmaps");
+
+    for (unsigned i = 0; i < bakeMeshes_.Size(); i++)
+    {
+        BakeMesh* mesh = bakeMeshes_[i];
+        mesh->GenerateRadianceMap();
+    }
+
+    SharedPtr<LightMapPacker> packer(new LightMapPacker(context_));
+
+    for (unsigned i = 0; i < bakeMeshes_.Size(); i++)
+    {
+        BakeMesh* mesh = bakeMeshes_[i];
+
+        SharedPtr<RadianceMap> radianceMap = mesh->GetRadianceMap();
+
+        if (radianceMap.NotNull())
+        {
+            packer->AddRadianceMap(radianceMap);
+        }
+
+    }
+
+    packer->Pack();
+
+    if (!packer->SaveLightmaps(projectPath_, scene_->GetFileName()))
+    {
+        return false;
+    }
+
+    if (standaloneMode_)
+    {
+        if (!SaveLitScene())
+            return false;
+    }
+
+    return WriteBakeData(bakeData_);
+
+}
+
+void SceneBaker::TraceRay(LightRay* lightRay, const PODVector<BakeLight*>& bakeLights)
+{
+    if (currentLightMode_ == GLOW_LIGHTMODE_DIRECT)
+    {
+        DirectLight(lightRay, bakeLights);
+    }
+    else
+    {
+        IndirectLight(lightRay);
+    }
+
+}
+
+bool SceneBaker::LightDirect()
+{
+    // Direct Lighting
+    currentLightMode_ = GLOW_LIGHTMODE_DIRECT;
+
+    if (!bakeMeshes_.Size())
+    {
+        ATOMIC_LOGINFO("SceneBaker::LightDirect() - No bake meshes found");
+        bakeLights_.Clear();
+        return false;
+    }
+
+    for (unsigned i = 0; i < bakeMeshes_.Size(); i++)
+    {
+        BakeMesh* mesh = bakeMeshes_[i];
+        mesh->Light(GLOW_LIGHTMODE_DIRECT);
+    }
+
+    return true;
+
+}
+
+void SceneBaker::LightDirectFinish()
+{
+
+}
+
+bool SceneBaker::EmitPhotons()
+{
+    Photons photons(this, GlobalGlowSettings.photonPassCount_, GlobalGlowSettings.photonBounceCount_,
+                          GlobalGlowSettings.photonEnergyThreshold_, GlobalGlowSettings.photonMaxDistance_);
+
+    int numPhotons = photons.Emit(bakeLights_);
+
+    ATOMIC_LOGINFOF("SceneBaker::EmitPhotons() - %i photons emitted", numPhotons);
+
+    if (!numPhotons)
+    {
+        return false;
+    }
+
+
+    for( unsigned i = 0; i < bakeMeshes_.Size(); i++ )
+    {
+        if( PhotonMap* photons = bakeMeshes_[i]->GetPhotonMap() )
+        {
+            photons->Gather( GlobalGlowSettings.finalGatherRadius_ );
+        }
+    }
+
+    return true;
+}
+
+bool SceneBaker::LightGI()
+{
+    // Indirect Lighting
+    currentLightMode_ = GLOW_LIGHTMODE_INDIRECT;
+
+    // We currently only need one GI pass
+    if (!GlobalGlowSettings.giEnabled_ || currentGIPass_ >= 1)
+    {
+        return false;
+    }
+
+    ATOMIC_LOGINFOF("GI Pass #%i of %i", currentGIPass_ + 1, 1);
+
+    bool photons = EmitPhotons();
+
+    if (!photons)
+        return false;
+
+    for (unsigned i = 0; i < bakeMeshes_.Size(); i++)
+    {
+        BakeMesh* mesh = bakeMeshes_[i];
+        mesh->Light(GLOW_LIGHTMODE_INDIRECT);
+    }
+
+    return true;
+
+}
+
+void SceneBaker::LightGIFinish()
+{
+    currentGIPass_++;
+}
+
+
+bool SceneBaker::Light(const GlowLightMode lightMode)
+{
+    if (lightMode == GLOW_LIGHTMODE_DIRECT)
+    {
+        if (!LightDirect())
+        {
+            currentLightMode_ = GLOW_LIGHTMODE_COMPLETE;
+            ATOMIC_LOGINFO("Cycle: Direct Lighting - no work to be done");
+            return false;
+        }
+
+        ATOMIC_LOGINFO("Cycle: Direct Lighting");
+
+        return true;
+    }
+
+    if (lightMode == GLOW_LIGHTMODE_INDIRECT)
+    {
+        if (!LightGI())
+        {
+            currentLightMode_ = GLOW_LIGHTMODE_COMPLETE;
+
+            // We currently only need one GI pass
+            if (GlobalGlowSettings.giEnabled_ && currentGIPass_ == 0)
+            {
+                ATOMIC_LOGINFO("Cycle: GI - no work to be done");
+            }
+
+            return false;
+        }
+
+        return true;
+    }
+
+
+    return true;
+}
+
+void SceneBaker::LightFinishCycle()
+{
+    if (currentLightMode_ == GLOW_LIGHTMODE_DIRECT)
+    {
+        LightDirectFinish();
+    }
+
+    if (currentLightMode_ == GLOW_LIGHTMODE_INDIRECT)
+    {
+        LightGIFinish();
+    }
+
+}
+
+void SceneBaker::QueryLights(const BoundingBox& bbox, PODVector<BakeLight*>& lights)
+{
+    lights.Clear();
+
+    for (unsigned i = 0; i < bakeLights_.Size(); i++)
+    {
+
+        // TODO: filter on zone, range, groups
+        lights.Push(bakeLights_[i]);
+
+    }
+
+}
+
+bool SceneBaker::Preprocess()
+{
+    Vector<SharedPtr<BakeMesh>>::Iterator itr = bakeMeshes_.Begin();
+
+    while (itr != bakeMeshes_.End())
+    {
+        (*itr)->Preprocess();
+        itr++;
+    }
+
+    embreeScene_->Commit();
+
+    return true;
+}
+
+bool SceneBaker::LoadScene(const String& filename)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    SharedPtr<File> file = cache->GetFile(filename);
+
+    if (!file || !file->IsOpen())
+    {
+        return false;
+    }
+
+    scene_ = new Scene(context_);
+
+    if (!scene_->LoadXML(*file))
+    {
+        scene_ = 0;
+        return false;
+    }
+
+    // IMPORTANT!, if scene updates are enabled
+    // the Octree component will add work queue items
+    // and will call WorkQueue->Complete(), see Octree::HandleRenderUpdate
+    scene_->SetUpdateEnabled(false);
+
+    // Zones
+    PODVector<Node*> zoneNodes;
+    PODVector<Zone*> zones;
+    scene_->GetChildrenWithComponent<Zone>(zoneNodes, true);
+
+    for (unsigned i = 0; i < zoneNodes.Size(); i++)
+    {        
+        Zone* zone = zoneNodes[i]->GetComponent<Zone>();
+
+        if (!zone->GetNode()->IsEnabled() || !zone->IsEnabled())
+            continue;
+
+        zones.Push(zone);;
+
+        BakeLight* bakeLight = BakeLight::CreateZoneLight(this, zone->GetAmbientColor());
+        bakeLights_.Push(SharedPtr<BakeLight>(bakeLight));
+    }
+
+    // Lights
+    PODVector<Node*> lightNodes;
+    scene_->GetChildrenWithComponent<Atomic::Light>(lightNodes, true);
+
+    for (unsigned i = 0; i < lightNodes.Size(); i++)
+    {
+        BakeLight* bakeLight = 0;
+        Node* lightNode = lightNodes[i];
+        Atomic::Light* light = lightNode->GetComponent<Atomic::Light>();
+
+        if (!lightNode->IsEnabled() || !light->IsEnabled())
+            continue;
+
+        if (light->GetLightType() == LIGHT_DIRECTIONAL)
+        {
+            bakeLight = BakeLight::CreateDirectionalLight(this, lightNode->GetDirection(), light->GetColor(), 1.0f, light->GetCastShadows());
+        }
+        else if (light->GetLightType() == LIGHT_POINT)
+        {
+            bakeLight = BakeLight::CreatePointLight(this, lightNode->GetWorldPosition(), light->GetRange(), light->GetColor(), 1.0f, light->GetCastShadows());
+        }
+
+        if (bakeLight)
+        {
+            bakeLights_.Push(SharedPtr<BakeLight>(bakeLight));
+        }
+
+    }
+
+    // Static Models
+    PODVector<StaticModel*> staticModels;
+    scene_->GetComponents<StaticModel>(staticModels, true);
+
+    for (unsigned i = 0; i < staticModels.Size(); i++)
+    {
+        StaticModel* staticModel = staticModels[i];
+
+        if (!staticModel->GetNode()->IsEnabled() || !staticModel->IsEnabled())
+            continue;
+
+        const BoundingBox& modelWBox = staticModel->GetWorldBoundingBox();
+
+        sceneBounds_.Merge(modelWBox);
+
+        Vector3 center = modelWBox.Center();
+        int bestPriority = M_MIN_INT;
+        Zone* newZone = 0;
+
+        for (PODVector<Zone*>::Iterator i = zones.Begin(); i != zones.End(); ++i)
+        {
+            Zone* zone = *i;
+            int priority = zone->GetPriority();
+            if (priority > bestPriority && (staticModel->GetZoneMask() & zone->GetZoneMask()) && zone->IsInside(center))
+            {
+                newZone = zone;
+                bestPriority = priority;
+            }
+        }
+
+        staticModel->SetZone(newZone, false);
+
+        if (staticModel->GetModel() && (staticModel->GetLightmap() || staticModel->GetCastShadows()))
+        {
+            Model* model = staticModel->GetModel();
+
+            for (unsigned i = 0; i < model->GetNumGeometries(); i++)
+            {
+                Geometry* geo = model->GetGeometry(i, 0);
+
+                if (!geo)
+                {
+                    ATOMIC_LOGERRORF("SceneBaker::LoadScene - model without geometry: %s", model->GetName().CString());
+                    return false;
+                }
+
+                const unsigned char* indexData = 0;
+                unsigned indexSize = 0;
+                unsigned vertexSize = 0;
+                const unsigned char* vertexData = 0;
+                const PODVector<VertexElement>* elements = 0;
+
+                geo->GetRawData(vertexData, vertexSize, indexData, indexSize, elements);
+
+                if (!indexData || !indexSize || !vertexData || !vertexSize || !elements)
+                {
+                    ATOMIC_LOGERRORF("SceneBaker::LoadScene - Unable to inspect geometry elements: %s",  model->GetName().CString());
+                    return false;
+                }
+
+                int texcoords = 0;
+                for (unsigned i = 0; i < elements->Size(); i++)
+                {
+                    const VertexElement& element = elements->At(i);
+
+                    if (element.type_ == TYPE_VECTOR2 && element.semantic_ == SEM_TEXCOORD)
+                    {
+                        texcoords++;
+                    }
+                }
+
+                if (texcoords < 2)
+                {
+                    ATOMIC_LOGERRORF("SceneBaker::LoadScene - Model without lightmap UV set, skipping: %s",  model->GetName().CString());
+                    continue;
+                }
+
+            }
+
+            SharedPtr<BakeMesh> meshMap (new BakeMesh(context_, this));
+            meshMap->SetStaticModel(staticModel);
+            bakeMeshes_.Push(meshMap);
+
+            if (staticModel->GetNode()->GetName() == "Plane")
+            {
+                // NOTE: photo emitter should probably be using the vertex generator not staticModel world position
+                BakeLight* bakeLight = BakeLight::CreateAreaLight(this, meshMap, staticModel->GetNode()->GetWorldPosition(), Color::WHITE);
+                if (bakeLight)
+                {
+                    bakeLights_.Push(SharedPtr<BakeLight>(bakeLight));
+                }
+
+            }
+        }
+
+    }
+
+    return Preprocess();
+}
+
+// DIRECT LIGHT
+
+void SceneBaker::DirectLight( LightRay* lightRay, const PODVector<BakeLight*>& bakeLights )
+{
+    for (unsigned i = 0; i < bakeLights.Size(); i++)
+    {
+        BakeLight* bakeLight = bakeLights[i];
+
+        Color influence;
+
+        if (bakeLight->GetVertexGenerator())
+        {
+            influence = DirectLightFromPointSet( lightRay, bakeLight );
+        }
+        else
+        {
+            influence = DirectLightFromPoint( lightRay, bakeLight );
+        }
+
+        if (influence.r_ || influence.g_ || influence.b_ )
+        {
+            lightRay->samplePoint_.bakeMesh->ContributeRadiance(lightRay, influence.ToVector3());
+        }
+
+    }
+}
+
+Color SceneBaker::DirectLightFromPoint( LightRay* lightRay, const BakeLight* light ) const
+{
+    float influence = DirectLightInfluenceFromPoint( lightRay, light->GetPosition(), light );
+
+    if( influence > 0.0f )
+    {
+        return light->GetColor() * light->GetIntensity() * influence;
+    }
+
+    return Color::BLACK;
+}
+
+Color SceneBaker::DirectLightFromPointSet( LightRay* lightRay, const BakeLight* light ) const
+{
+    LightVertexGenerator* vertexGenerator = light->GetVertexGenerator();
+
+    if (!vertexGenerator)
+    {
+        // this should not happen
+        ATOMIC_LOGERROR("SceneBaker::DirectLightFromPointSet - called without vertex generator");
+        return Color::BLACK;
+    }
+
+    // No light vertices generated - just exit
+    if( !vertexGenerator->GetVertexCount())
+    {
+        return Color::BLACK;
+    }
+
+    const LightVertexVector& vertices = vertexGenerator->GetVertices();
+    Color color = Color::BLACK;
+
+    for( unsigned i = 0, n = vertexGenerator->GetVertexCount(); i < n; i++ )
+    {
+        const LightVertex&  vertex = vertices[i];
+        float influence = DirectLightInfluenceFromPoint( lightRay, vertex.position_ /*+ light->GetPosition()*/, light );
+
+        // ** We have a non-zero light influence - add a light color to final result
+        if( influence > 0.0f )
+        {
+            color += light->GetColor() * light->GetIntensity() * influence;
+        }
+    }
+
+    color = color * ( 1.0f / static_cast<float>( vertexGenerator->GetVertexCount()));
+
+    return color;
+}
+
+// ** DirectLight::influenceFromPoint
+float SceneBaker::DirectLightInfluenceFromPoint(LightRay* lightRay, const Vector3 &point, const BakeLight* light ) const
+{
+    float inf       = 1.0f;
+    float att       = 1.0f;
+    float cut       = 1.0f;
+    float distance  = 0.0f;
+
+    // Calculate light influence.
+    if( const LightInfluence* influence = light->GetInfluenceModel() )
+    {
+        inf = influence->Calculate( lightRay, point, distance );
+    }
+
+    // Calculate light cutoff.
+    if( const LightCutoff* cutoff = light->GetCutoffModel() )
+    {
+        cut = cutoff->Calculate( lightRay->samplePoint_.position);
+    }
+
+    // Calculate light attenuation
+    if( const LightAttenuation* attenuation = light->GetAttenuationModel() )
+    {
+        att = attenuation->Calculate( distance );
+    }
+
+    // Return final influence
+    return inf * att * cut;
+}
+
+// INDIRECT LIGHT
+
+void SceneBaker::IndirectLight( LightRay* lightRay)
+{
+    BakeMesh* bakeMesh = 0;
+    LightRay::SamplePoint& source = lightRay->samplePoint_;
+
+    Vector3 gathered;
+
+    int nsamples = GlobalGlowSettings.finalGatherSamples_;
+    float maxDistance = GlobalGlowSettings.finalGatherDistance_; // , settings.m_finalGatherRadius, settings.m_skyColor, settings.m_ambientColor
+
+    int hits = 0;
+    for( int k = 0; k <nsamples; k++ )
+    {
+        Vector3 dir;
+        Vector3::GetRandomHemisphereDirection(dir, source.normal);
+
+        float influence = Max<float>( source.normal.DotProduct(dir), 0.0f );
+
+        if (influence > 1.0f)
+        {
+            // ATOMIC_LOGINFO("This shouldn't happen");
+        }
+
+        RTCRay& ray = lightRay->rtcRay_;
+        lightRay->SetupRay(source.position, dir, .001f, maxDistance);
+
+        rtcIntersect(GetEmbreeScene()->GetRTCScene(), ray);
+
+        bool skyEnabled = true;
+
+        if (ray.geomID == RTC_INVALID_GEOMETRY_ID)
+        {
+            if (skyEnabled)
+            {
+                Color skyColor(130.0f/255.0f, 209.0f/255.0f, 207.0f/255.0f);
+                skyColor = Color::WHITE * 0.15f;
+                gathered += (skyColor * influence).ToVector3();// + ambientColor;
+                hits++;
+            }
+
+            continue;
+        }               
+
+        bakeMesh = GetEmbreeScene()->GetBakeMesh(ray.geomID);
+
+        if (!bakeMesh)
+        {
+            continue;
+        }
+
+        if (bakeMesh == source.bakeMesh && ray.primID == source.triangle)
+        {
+            // do not self light
+            continue;
+        }
+
+        const BakeMesh::MMTriangle* tri = bakeMesh->GetTriangle(ray.primID);
+
+        // TODO: interpolate normal, if artifacting
+        if( dir.DotProduct(tri->normal_) >= 0.0f )
+        {
+            continue;
+        }
+
+        PhotonMap* photonMap = bakeMesh->GetPhotonMap();
+
+        if (!photonMap)
+        {
+            continue;
+        }
+
+        Vector3 bary(ray.u, ray.v, 1.0f-ray.u-ray.v);
+        Vector2 st;
+        bakeMesh->GetST(ray.primID, 1, bary, st);
+
+        int photons;
+        Color pcolor;
+        Color gcolor;
+
+        if (!photonMap->GetPhoton( (int) ray.primID, st, pcolor, photons, gcolor, true))
+        {
+            continue;
+        }
+
+        hits++;
+        gathered += gcolor.ToVector3() * influence;// + ambientColor;
+
+    }
+
+    if (!hits)
+        return;
+
+    gathered /= static_cast<float>( nsamples );
+
+    if (gathered.x_ >= 0.01f || gathered.y_ >= 0.01f || gathered.z_ >= 0.01f )
+    {
+        source.bakeMesh->ContributeRadiance(lightRay, gathered, GLOW_LIGHTMODE_INDIRECT);
+    }
+
+}
+
+}

+ 125 - 0
Source/AtomicGlow/Kernel/SceneBaker.h

@@ -0,0 +1,125 @@
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <Atomic/Scene/Scene.h>
+#include "GlowTypes.h"
+
+using namespace Atomic;
+
+namespace AtomicGlow
+{
+
+class LightRay;
+class BakeMesh;
+class BakeLight;
+class EmbreeScene;
+
+class SceneBaker : public Object
+{
+    ATOMIC_OBJECT(SceneBaker, Object)
+
+    public:
+
+    SceneBaker(Context* context, const String& projectPath);
+    virtual ~SceneBaker();    
+
+    bool Light(const GlowLightMode lightMode);
+    void LightFinishCycle();
+
+    bool LoadScene(const String& filename);
+
+    bool GenerateLightmaps();
+
+    const VectorBuffer& GetBakeData() const { return bakeData_; }
+
+    GlowLightMode GetCurrentLightMode() const { return currentLightMode_; }
+
+    void QueryLights(const BoundingBox& bbox, PODVector<BakeLight*>& lights);
+
+    void TraceRay(LightRay* lightRay, const PODVector<AtomicGlow::BakeLight *>& bakeLights);
+
+    Scene* GetScene() const { return scene_; }
+
+    EmbreeScene* GetEmbreeScene() const { return embreeScene_; }
+
+    int GetCurrentGIBounce() const {return currentGIPass_; }
+
+    bool WriteBakeData(VectorBuffer& buffer);
+
+    bool GetStandaloneMode() const { return standaloneMode_; }
+
+    void SetStandaloneMode(bool standaloneMode) { standaloneMode_ = standaloneMode; }
+
+    const BoundingBox& GetSceneBounds() const { return sceneBounds_; }
+
+private:
+
+    //void FilterLightmap(Image* lightmap);
+    //void EmitLightmap(int lightMapIndex);
+    //bool TryAddStaticModelBaker(StaticModelBaker *bakeModel);
+
+    bool Preprocess();
+
+    /// process direct (and ao/ambient) lighting, returns true if there is work to be done
+    bool LightDirect();
+    void LightDirectFinish();
+
+    /// process a GI cycle, returns true if there is work to be done
+    bool LightGI();
+    void LightGIFinish();
+
+    bool SaveLitScene();
+
+    // Direct lighting
+
+    void DirectLight( LightRay* lightRay, const PODVector<BakeLight*>& bakeLights);
+    Color DirectLightFromPoint( LightRay* lightRay, const BakeLight* light ) const;
+    Color DirectLightFromPointSet( LightRay* lightRay, const BakeLight* light ) const;
+    float DirectLightInfluenceFromPoint(LightRay* lightRay, const Vector3 &point, const BakeLight *light ) const;
+
+    // New GI
+
+    bool EmitPhotons();
+    void IndirectLight(LightRay* lightRay);
+
+    SharedPtr<Scene> scene_;
+    SharedPtr<EmbreeScene> embreeScene_;
+
+    Vector<SharedPtr<BakeMesh>> bakeMeshes_;
+
+    Vector<SharedPtr<BakeLight>> bakeLights_;
+
+    VectorBuffer bakeData_;
+
+    GlowLightMode currentLightMode_;
+
+    int currentGIPass_;
+
+    String projectPath_;
+    bool standaloneMode_;
+
+    BoundingBox sceneBounds_;
+
+};
+
+}

+ 46 - 34
Source/AtomicTool/AtomicTool.cpp

@@ -62,6 +62,19 @@ void AtomicTool::Setup()
     if (arguments.Contains("-toolbootstrap"))
         ToolEnvironment::SetBootstrapping();
 
+    ToolSystem* tsystem = new ToolSystem(context_);
+    context_->RegisterSubsystem(tsystem);
+
+    ToolEnvironment* env = new ToolEnvironment(context_);
+    context_->RegisterSubsystem(env);
+
+    // Initialize the ToolEnvironment
+    if (!env->Initialize(true))
+    {
+        ErrorExit("Unable to initialize tool environment");
+        return;
+    }
+
     engineParameters_["Headless"] = true;
     engineParameters_["LogLevel"] = LOG_INFO;
 
@@ -84,8 +97,32 @@ void AtomicTool::Setup()
         }
     }
 
+    SharedPtr<CommandParser> parser(new CommandParser(context_));
+
+    command_ = parser->Parse(arguments);
+    if (command_.Null())
+    {
+        String error = "No command found";
+
+        if (parser->GetErrorMessage().Length())
+            error = parser->GetErrorMessage();
+
+        ErrorExit(error);
+        return;
+    }
+
     // no default resources, AtomicTool may be run outside of source tree
     engineParameters_["ResourcePaths"] = "";
+
+    if (command_->RequiresProjectLoad())
+    {
+
+#ifdef ATOMIC_DEV_BUILD
+        engineParameters_["ResourcePrefixPaths"] = env->GetRootSourceDir() + "/Resources/";
+        engineParameters_["ResourcePaths"] = ToString("CoreData");
+#endif
+    }
+
 }
 
 void AtomicTool::HandleCommandFinished(StringHash eventType, VariantMap& eventData)
@@ -182,19 +219,6 @@ void AtomicTool::Start()
 
     const Vector<String>& arguments = GetArguments();
 
-    ToolSystem* tsystem = new ToolSystem(context_);
-    context_->RegisterSubsystem(tsystem);
-
-    ToolEnvironment* env = new ToolEnvironment(context_);
-    context_->RegisterSubsystem(env);
-
-    // Initialize the ToolEnvironment
-    if (!env->Initialize(true))
-    {
-        ErrorExit("Unable to initialize tool environment");
-        return;
-    }
-
     if (activationKey_.Length())
     {
         DoActivation();
@@ -207,29 +231,19 @@ void AtomicTool::Start()
 
     BuildSystem* buildSystem = GetSubsystem<BuildSystem>();
 
-    SharedPtr<CommandParser> parser(new CommandParser(context_));
-
-    SharedPtr<Command> cmd(parser->Parse(arguments));
-    if (!cmd)
-    {
-        String error = "No command found";
-
-        if (parser->GetErrorMessage().Length())
-            error = parser->GetErrorMessage();
-
-        ErrorExit(error);
-        return;
-    }
-
-    if (cmd->RequiresProjectLoad())
+    if (command_->RequiresProjectLoad())
     {
-        if (!cmd->LoadProject())
+        if (!command_->LoadProject())
         {
-            ErrorExit(ToString("Failed to load project: %s", cmd->GetProjectPath().CString()));
+            ErrorExit(ToString("Failed to load project: %s", command_->GetProjectPath().CString()));
             return;
         }
 
-        String projectPath = cmd->GetProjectPath();
+        String projectPath = command_->GetProjectPath();
+
+        ResourceCache* cache = GetSubsystem<ResourceCache>();
+        cache->AddResourceDir(ToString("%sResources", projectPath.CString()));
+        cache->AddResourceDir(ToString("%sCache", projectPath.CString()));
 
         // Set the build path
         String buildFolder = projectPath + "/" + "Build";
@@ -249,10 +263,8 @@ void AtomicTool::Start()
 
     }
 
-    command_ = cmd;
-
     // BEGIN LICENSE MANAGEMENT
-    if (cmd->RequiresLicenseValidation())
+    if (command_->RequiresLicenseValidation())
     {
         GetSubsystem<LicenseSystem>()->Initialize();
     }

+ 4 - 0
Source/AtomicTool/CMakeLists.txt

@@ -8,6 +8,10 @@ add_executable(AtomicTool ${ATOMIC_TOOL_SOURCES})
 
 target_link_libraries(AtomicTool ToolCore AtomicNETScript Poco Atomic)
 
+if (ATOMIC_GLOW)
+    target_link_libraries(AtomicTool AtomicGlowLib)
+endif()
+
 if (WIN32)
     target_link_libraries(AtomicTool Iphlpapi Wldap32)
 endif()

+ 7 - 0
Source/CMakeLists.txt

@@ -18,6 +18,9 @@ if (WIN32)
     endif ()
 endif ()
 
+option(ATOMIC_GLOW "Build Atomic Glow" ON)
+option(ATOMIC_GLOW_WINDOWS_SUBSYSTEM "Enable WINDOWS subsystem, otherwise CONSOLE is used" ON)
+
 add_subdirectory(ThirdParty)
 add_subdirectory(Atomic)
 
@@ -37,6 +40,10 @@ if (ATOMIC_DOTNET)
 endif ()
 
 if (ATOMIC_DESKTOP)
+
+    if (ATOMIC_GLOW)
+        add_subdirectory(AtomicGlow)
+    endif ()
     if (ATOMIC_DOTNET OR ATOMIC_JAVASCRIPT)
         add_subdirectory(AtomicTool)
     endif ()

+ 4 - 0
Source/ThirdParty/CMakeLists.txt

@@ -70,6 +70,10 @@ if (NOT IOS AND NOT ANDROID AND NOT WEB)
     add_subdirectory(Poco)
     add_subdirectory(nativefiledialog)
     add_subdirectory(libsquish)
+    add_subdirectory(thekla)
+    if (ATOMIC_GLOW)
+        add_subdirectory(embree)
+    endif()
 endif ()
 
 if (ATOMIC_DATABASE_SQLITE)

+ 4 - 1
Source/ThirdParty/STB/stb_image_write.h

@@ -116,7 +116,9 @@ LICENSE
 #define INCLUDE_STB_IMAGE_WRITE_H
 
 // ATOMIC BEGIN
+#ifndef STBI_IMAGE_NO_ATOMIC
 #include "../../Atomic/Container/Str.h"
+#endif
 // ATOMIC END
 
 #ifdef __cplusplus
@@ -228,7 +230,8 @@ static void stbi__stdio_write(void *context, void *data, int size)
 static int stbi__start_write_file(stbi__write_context *s, const char *filename)
 {
    // Urho3D: proper UTF8 handling for Windows, requires Urho3D WString class
-#ifndef _WIN32
+   // ATOMIC: added STBI_IMAGE_NO_ATOMIC
+#ifndef _WIN32 || defined(STBI_IMAGE_NO_ATOMIC)
    FILE *f = fopen(filename, "wb");
 #else
     Atomic::WString wstr(filename);

+ 407 - 0
Source/ThirdParty/STB/stb_rect_pack.c

@@ -0,0 +1,407 @@
+#include "stb_rect_pack.h"
+
+#define STB_RECT_PACK_IMPLEMENTATION
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//     IMPLEMENTATION SECTION
+//
+
+#ifdef STB_RECT_PACK_IMPLEMENTATION
+#ifndef STBRP_SORT
+#include <stdlib.h>
+#define STBRP_SORT qsort
+#endif
+
+#ifndef STBRP_ASSERT
+#include <assert.h>
+#define STBRP_ASSERT assert
+#endif
+
+#ifdef _MSC_VER
+#define STBRP__NOTUSED(v)  (void)(v)
+#else
+#define STBRP__NOTUSED(v)  (void)sizeof(v)
+#endif
+
+enum
+{
+   STBRP__INIT_skyline = 1
+};
+
+STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
+{
+   switch (context->init_mode) {
+      case STBRP__INIT_skyline:
+         STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
+         context->heuristic = heuristic;
+         break;
+      default:
+         STBRP_ASSERT(0);
+   }
+}
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
+{
+   if (allow_out_of_mem)
+      // if it's ok to run out of memory, then don't bother aligning them;
+      // this gives better packing, but may fail due to OOM (even though
+      // the rectangles easily fit). @TODO a smarter approach would be to only
+      // quantize once we've hit OOM, then we could get rid of this parameter.
+      context->align = 1;
+   else {
+      // if it's not ok to run out of memory, then quantize the widths
+      // so that num_nodes is always enough nodes.
+      //
+      // I.e. num_nodes * align >= width
+      //                  align >= width / num_nodes
+      //                  align = ceil(width/num_nodes)
+
+      context->align = (context->width + context->num_nodes-1) / context->num_nodes;
+   }
+}
+
+STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
+{
+   int i;
+#ifndef STBRP_LARGE_RECTS
+   STBRP_ASSERT(width <= 0xffff && height <= 0xffff);
+#endif
+
+   for (i=0; i < num_nodes-1; ++i)
+      nodes[i].next = &nodes[i+1];
+   nodes[i].next = NULL;
+   context->init_mode = STBRP__INIT_skyline;
+   context->heuristic = STBRP_HEURISTIC_Skyline_default;
+   context->free_head = &nodes[0];
+   context->active_head = &context->extra[0];
+   context->width = width;
+   context->height = height;
+   context->num_nodes = num_nodes;
+   stbrp_setup_allow_out_of_mem(context, 0);
+
+   // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
+   context->extra[0].x = 0;
+   context->extra[0].y = 0;
+   context->extra[0].next = &context->extra[1];
+   context->extra[1].x = (stbrp_coord) width;
+#ifdef STBRP_LARGE_RECTS
+   context->extra[1].y = (1<<30);
+#else
+   context->extra[1].y = 65535;
+#endif
+   context->extra[1].next = NULL;
+}
+
+// find minimum y position if it starts at x1
+static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
+{
+   stbrp_node *node = first;
+   int x1 = x0 + width;
+   int min_y, visited_width, waste_area;
+
+   STBRP__NOTUSED(c);
+
+   STBRP_ASSERT(first->x <= x0);
+
+   #if 0
+   // skip in case we're past the node
+   while (node->next->x <= x0)
+      ++node;
+   #else
+   STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
+   #endif
+
+   STBRP_ASSERT(node->x <= x0);
+
+   min_y = 0;
+   waste_area = 0;
+   visited_width = 0;
+   while (node->x < x1) {
+      if (node->y > min_y) {
+         // raise min_y higher.
+         // we've accounted for all waste up to min_y,
+         // but we'll now add more waste for everything we've visted
+         waste_area += visited_width * (node->y - min_y);
+         min_y = node->y;
+         // the first time through, visited_width might be reduced
+         if (node->x < x0)
+            visited_width += node->next->x - x0;
+         else
+            visited_width += node->next->x - node->x;
+      } else {
+         // add waste area
+         int under_width = node->next->x - node->x;
+         if (under_width + visited_width > width)
+            under_width = width - visited_width;
+         waste_area += under_width * (min_y - node->y);
+         visited_width += under_width;
+      }
+      node = node->next;
+   }
+
+   *pwaste = waste_area;
+   return min_y;
+}
+
+typedef struct
+{
+   int x,y;
+   stbrp_node **prev_link;
+} stbrp__findresult;
+
+static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
+{
+   int best_waste = (1<<30), best_x, best_y = (1 << 30);
+   stbrp__findresult fr;
+   stbrp_node **prev, *node, *tail, **best = NULL;
+
+   // align to multiple of c->align
+   width = (width + c->align - 1);
+   width -= width % c->align;
+   STBRP_ASSERT(width % c->align == 0);
+
+   node = c->active_head;
+   prev = &c->active_head;
+   while (node->x + width <= c->width) {
+      int y,waste;
+      y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
+      if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
+         // bottom left
+         if (y < best_y) {
+            best_y = y;
+            best = prev;
+         }
+      } else {
+         // best-fit
+         if (y + height <= c->height) {
+            // can only use it if it first vertically
+            if (y < best_y || (y == best_y && waste < best_waste)) {
+               best_y = y;
+               best_waste = waste;
+               best = prev;
+            }
+         }
+      }
+      prev = &node->next;
+      node = node->next;
+   }
+
+   best_x = (best == NULL) ? 0 : (*best)->x;
+
+   // if doing best-fit (BF), we also have to try aligning right edge to each node position
+   //
+   // e.g, if fitting
+   //
+   //     ____________________
+   //    |____________________|
+   //
+   //            into
+   //
+   //   |                         |
+   //   |             ____________|
+   //   |____________|
+   //
+   // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
+   //
+   // This makes BF take about 2x the time
+
+   if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
+      tail = c->active_head;
+      node = c->active_head;
+      prev = &c->active_head;
+      // find first node that's admissible
+      while (tail->x < width)
+         tail = tail->next;
+      while (tail) {
+         int xpos = tail->x - width;
+         int y,waste;
+         STBRP_ASSERT(xpos >= 0);
+         // find the left position that matches this
+         while (node->next->x <= xpos) {
+            prev = &node->next;
+            node = node->next;
+         }
+         STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
+         y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
+         if (y + height < c->height) {
+            if (y <= best_y) {
+               if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
+                  best_x = xpos;
+                  STBRP_ASSERT(y <= best_y);
+                  best_y = y;
+                  best_waste = waste;
+                  best = prev;
+               }
+            }
+         }
+         tail = tail->next;
+      }
+   }
+
+   fr.prev_link = best;
+   fr.x = best_x;
+   fr.y = best_y;
+   return fr;
+}
+
+static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
+{
+   // find best position according to heuristic
+   stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
+   stbrp_node *node, *cur;
+
+   // bail if:
+   //    1. it failed
+   //    2. the best node doesn't fit (we don't always check this)
+   //    3. we're out of memory
+   if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
+      res.prev_link = NULL;
+      return res;
+   }
+
+   // on success, create new node
+   node = context->free_head;
+   node->x = (stbrp_coord) res.x;
+   node->y = (stbrp_coord) (res.y + height);
+
+   context->free_head = node->next;
+
+   // insert the new node into the right starting point, and
+   // let 'cur' point to the remaining nodes needing to be
+   // stiched back in
+
+   cur = *res.prev_link;
+   if (cur->x < res.x) {
+      // preserve the existing one, so start testing with the next one
+      stbrp_node *next = cur->next;
+      cur->next = node;
+      cur = next;
+   } else {
+      *res.prev_link = node;
+   }
+
+   // from here, traverse cur and free the nodes, until we get to one
+   // that shouldn't be freed
+   while (cur->next && cur->next->x <= res.x + width) {
+      stbrp_node *next = cur->next;
+      // move the current node to the free list
+      cur->next = context->free_head;
+      context->free_head = cur;
+      cur = next;
+   }
+
+   // stitch the list back in
+   node->next = cur;
+
+   if (cur->x < res.x + width)
+      cur->x = (stbrp_coord) (res.x + width);
+
+#ifdef _DEBUG
+   cur = context->active_head;
+   while (cur->x < context->width) {
+      STBRP_ASSERT(cur->x < cur->next->x);
+      cur = cur->next;
+   }
+   STBRP_ASSERT(cur->next == NULL);
+
+   {
+      stbrp_node *L1 = NULL, *L2 = NULL;
+      int count=0;
+      cur = context->active_head;
+      while (cur) {
+         L1 = cur;
+         cur = cur->next;
+         ++count;
+      }
+      cur = context->free_head;
+      while (cur) {
+         L2 = cur;
+         cur = cur->next;
+         ++count;
+      }
+      STBRP_ASSERT(count == context->num_nodes+2);
+   }
+#endif
+
+   return res;
+}
+
+static int rect_height_compare(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   if (p->h > q->h)
+      return -1;
+   if (p->h < q->h)
+      return  1;
+   return (p->w > q->w) ? -1 : (p->w < q->w);
+}
+
+static int rect_width_compare(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   if (p->w > q->w)
+      return -1;
+   if (p->w < q->w)
+      return  1;
+   return (p->h > q->h) ? -1 : (p->h < q->h);
+}
+
+static int rect_original_order(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
+}
+
+#ifdef STBRP_LARGE_RECTS
+#define STBRP__MAXVAL  0xffffffff
+#else
+#define STBRP__MAXVAL  0xffff
+#endif
+
+STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
+{
+   int i, all_rects_packed = 1;
+
+   // we use the 'was_packed' field internally to allow sorting/unsorting
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = i;
+      #ifndef STBRP_LARGE_RECTS
+      STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff);
+      #endif
+   }
+
+   // sort according to heuristic
+   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
+
+   for (i=0; i < num_rects; ++i) {
+      if (rects[i].w == 0 || rects[i].h == 0) {
+         rects[i].x = rects[i].y = 0;  // empty rect needs no space
+      } else {
+         stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
+         if (fr.prev_link) {
+            rects[i].x = (stbrp_coord) fr.x;
+            rects[i].y = (stbrp_coord) fr.y;
+         } else {
+            rects[i].x = rects[i].y = STBRP__MAXVAL;
+         }
+      }
+   }
+
+   // unsort
+   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
+
+   // set was_packed flags and all_rects_packed status
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
+      if (!rects[i].was_packed)
+         all_rects_packed = 0;
+   }
+
+   // return the all_rects_packed status
+   return all_rects_packed;
+}
+#endif

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio