diff --git a/Game/Addons/IncreasedTerrainDistanceMod.meta b/Game/Addons/IncreasedTerrainDistanceMod.meta
new file mode 100644
index 0000000000..09e2ce79e3
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: d1bb22dc120f023418e479dd233189f8
+folderAsset: yes
+timeCreated: 1445869505
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Prefabs.meta b/Game/Addons/IncreasedTerrainDistanceMod/Prefabs.meta
new file mode 100644
index 0000000000..bb32f486a3
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Prefabs.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 7a111c9817a5b1d41b07a921a0a7356f
+folderAsset: yes
+timeCreated: 1445869505
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Prefabs/IncreasedTerrainDistanceMod.prefab b/Game/Addons/IncreasedTerrainDistanceMod/Prefabs/IncreasedTerrainDistanceMod.prefab
new file mode 100644
index 0000000000..0a30ef5c9e
Binary files /dev/null and b/Game/Addons/IncreasedTerrainDistanceMod/Prefabs/IncreasedTerrainDistanceMod.prefab differ
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Prefabs/IncreasedTerrainDistanceMod.prefab.meta b/Game/Addons/IncreasedTerrainDistanceMod/Prefabs/IncreasedTerrainDistanceMod.prefab.meta
new file mode 100644
index 0000000000..b5b5fe4677
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Prefabs/IncreasedTerrainDistanceMod.prefab.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e3424d797238df4409df534a8905e097
+timeCreated: 1445869544
+licenseType: Free
+NativeFormatImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources.meta b/Game/Addons/IncreasedTerrainDistanceMod/Resources.meta
new file mode 100644
index 0000000000..286cde5eae
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Resources.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 04a1299935db67847b17a10d26d7f9c4
+folderAsset: yes
+timeCreated: 1445869505
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeX.bin b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeX.bin
new file mode 100644
index 0000000000..ca4d53b89d
Binary files /dev/null and b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeX.bin differ
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeX.bin.meta b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeX.bin.meta
new file mode 100644
index 0000000000..3fe772e3ff
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeX.bin.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a535ea164aa7901479f9f1cd9e8185c6
+timeCreated: 1445869506
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeY.bin b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeY.bin
new file mode 100644
index 0000000000..c10527d423
Binary files /dev/null and b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeY.bin differ
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeY.bin.meta b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeY.bin.meta
new file mode 100644
index 0000000000..0245cfe0b7
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeY.bin.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4d5ba253761494c4e8f9587c486f00ce
+timeCreated: 1445869506
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_in.bin b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_in.bin
new file mode 100644
index 0000000000..c21c217a38
Binary files /dev/null and b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_in.bin differ
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_in.bin.meta b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_in.bin.meta
new file mode 100644
index 0000000000..5cf2f7e4c4
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_in.bin.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 38ee638dec858ac4094a4de9166a6895
+timeCreated: 1445869505
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_out.bin b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_out.bin
new file mode 100644
index 0000000000..2e8f437854
Binary files /dev/null and b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_out.bin differ
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_out.bin.meta b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_out.bin.meta
new file mode 100644
index 0000000000..365652651c
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_out.bin.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1c66fb9de5dc60645941b3472af7f012
+timeCreated: 1445869505
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts.meta b/Game/Addons/IncreasedTerrainDistanceMod/Scripts.meta
new file mode 100644
index 0000000000..c43dfb6879
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 346bcd4eb2e136144a345eeaee454fce
+folderAsset: yes
+timeCreated: 1445869505
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraPositionFromMainCamera.cs b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraPositionFromMainCamera.cs
new file mode 100644
index 0000000000..7e3793aaf8
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraPositionFromMainCamera.cs
@@ -0,0 +1,30 @@
+//Increased Terrain Distance Mod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 1.54
+
+using UnityEngine;
+//using System;
+//using System.Collections;
+//using System.Collections.Generic;
+//using System.IO;
+//using DaggerfallConnect;
+//using DaggerfallConnect.Arena2;
+//using DaggerfallConnect.Utility;
+//using DaggerfallWorkshop;
+using DaggerfallWorkshop.Game;
+//using DaggerfallWorkshop.Utility;
+
+namespace ProjectIncreasedTerrainDistance
+{
+ public class CloneCameraPositionFromMainCamera : MonoBehaviour
+ {
+ void LateUpdate()
+ {
+ this.transform.position = Camera.main.transform.position;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraPositionFromMainCamera.cs.meta b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraPositionFromMainCamera.cs.meta
new file mode 100644
index 0000000000..318f25d78d
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraPositionFromMainCamera.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: a64bba0059451b44fb0617d7834a1106
+timeCreated: 1445869506
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraRotationFromMainCamera.cs b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraRotationFromMainCamera.cs
new file mode 100644
index 0000000000..15dd94d574
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraRotationFromMainCamera.cs
@@ -0,0 +1,30 @@
+//Increased Terrain Distance Mod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 1.54
+
+using UnityEngine;
+//using System;
+//using System.Collections;
+//using System.Collections.Generic;
+//using System.IO;
+//using DaggerfallConnect;
+//using DaggerfallConnect.Arena2;
+//using DaggerfallConnect.Utility;
+//using DaggerfallWorkshop;
+using DaggerfallWorkshop.Game;
+//using DaggerfallWorkshop.Utility;
+
+namespace ProjectIncreasedTerrainDistance
+{
+ public class CloneCameraRotationFromMainCamera : MonoBehaviour
+ {
+ void LateUpdate()
+ {
+ this.transform.rotation = Camera.main.transform.rotation;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraRotationFromMainCamera.cs.meta b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraRotationFromMainCamera.cs.meta
new file mode 100644
index 0000000000..f4622e94f1
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/CloneCameraRotationFromMainCamera.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 1c0005de01b61f1498f9d8a54abceb62
+timeCreated: 1445869506
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedTerrainSampler.cs b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedTerrainSampler.cs
new file mode 100644
index 0000000000..5a919616a5
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedTerrainSampler.cs
@@ -0,0 +1,138 @@
+//Increased Terrain Distance Mod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 1.54
+
+using UnityEngine;
+using System;
+using System.Collections;
+using DaggerfallConnect;
+using DaggerfallConnect.Arena2;
+using DaggerfallWorkshop;
+using DaggerfallWorkshop.Utility;
+using ProjectIncreasedTerrainDistance;
+
+//namespace DaggerfallWorkshop
+namespace ProjectIncreasedTerrainDistance
+{
+ ///
+ /// Default TerrainSampler for StreamingWorld.
+ ///
+ public class ImprovedTerrainSampler : TerrainSampler
+ {
+ // Scale factors for this sampler implementation
+ public const float baseHeightScale = 8f;
+ public const float noiseMapScale = 4f;
+ public const float extraNoiseScale = 3f;
+ public const float scaledOceanElevation = 3.4f * baseHeightScale;
+ public const float scaledBeachElevation = 5.0f * baseHeightScale;
+
+ // Max terrain height of this sampler implementation
+ public const float maxTerrainHeight = 26115f;
+
+ public ImprovedTerrainSampler()
+ {
+ HeightmapDimension = defaultHeightmapDimension;
+ MaxTerrainHeight = maxTerrainHeight;
+ OceanElevation = scaledOceanElevation;
+ BeachElevation = scaledBeachElevation;
+ }
+
+ public override void GenerateSamples(ref MapPixelData mapPixel)
+ {
+ DaggerfallUnity dfUnity = DaggerfallUnity.Instance;
+
+ // Create samples arrays
+ mapPixel.tilemapSamples = new TilemapSample[MapsFile.WorldMapTileDim, MapsFile.WorldMapTileDim];
+ mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];
+
+ // Divisor ensures continuous 0-1 range of tile samples
+ float div = (float)(HeightmapDimension - 1) / 3f;
+
+ // Read neighbouring height samples for this map pixel
+ int mx = mapPixel.mapPixelX;
+ int my = mapPixel.mapPixelY;
+ byte[,] shm = dfUnity.ContentReader.WoodsFileReader.GetHeightMapValuesRange(mx - 2, my - 2, 4);
+ byte[,] lhm = dfUnity.ContentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3);
+
+ float[,] multiplierValue = new float[4, 4];
+ for (int y = 0; y < 4; y++)
+ {
+ for (int x = 0; x < 4; x++)
+ {
+ int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue));
+ int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue));
+
+ multiplierValue[x, y] = ImprovedWorldTerrain.computeHeightMultiplier(mapPixelX, mapPixelY);
+ }
+ }
+
+ // Extract height samples for all chunks
+ float averageHeight = 0;
+ float maxHeight = float.MinValue;
+ float baseHeight, noiseHeight;
+ float x1, x2, x3, x4;
+ int dim = HeightmapDimension;
+ //mapPixel.heightmapSamples = new float[dim, dim];
+ for (int y = 0; y < dim; y++)
+ {
+ for (int x = 0; x < dim; x++)
+ {
+ float rx = (float)x / div;
+ float ry = (float)y / div;
+ int ix = Mathf.FloorToInt(rx);
+ int iy = Mathf.FloorToInt(ry);
+ float sfracx = (float)x / (float)(dim - 1);
+ float sfracy = (float)y / (float)(dim - 1);
+ float fracx = (float)(x - ix * div) / div;
+ float fracy = (float)(y - iy * div) / div;
+ float scaledHeight = 0;
+
+ // Bicubic sample small height map for base terrain elevation
+ x1 = TerrainHelper.CubicInterpolator(shm[0, 3] * multiplierValue[0, 3], shm[1, 3] * multiplierValue[1, 3], shm[2, 3] * multiplierValue[2, 3], shm[3, 3] * multiplierValue[3, 3], sfracx);
+ x2 = TerrainHelper.CubicInterpolator(shm[0, 2] * multiplierValue[0, 2], shm[1, 2] * multiplierValue[1, 2], shm[2, 2] * multiplierValue[2, 2], shm[3, 2] * multiplierValue[3, 2], sfracx);
+ x3 = TerrainHelper.CubicInterpolator(shm[0, 1] * multiplierValue[0, 1], shm[1, 1] * multiplierValue[1, 1], shm[2, 1] * multiplierValue[2, 1], shm[3, 1] * multiplierValue[3, 1], sfracx);
+ x4 = TerrainHelper.CubicInterpolator(shm[0, 0] * multiplierValue[0, 0], shm[1, 0] * multiplierValue[1, 0], shm[2, 0] * multiplierValue[2, 0], shm[3, 0] * multiplierValue[3, 0], sfracx);
+ baseHeight = TerrainHelper.CubicInterpolator(x1, x2, x3, x4, sfracy);
+ scaledHeight += baseHeight * baseHeightScale;
+
+ // Bicubic sample large height map for noise mask over terrain features
+ x1 = TerrainHelper.CubicInterpolator(lhm[ix, iy + 0], lhm[ix + 1, iy + 0], lhm[ix + 2, iy + 0], lhm[ix + 3, iy + 0], fracx);
+ x2 = TerrainHelper.CubicInterpolator(lhm[ix, iy + 1], lhm[ix + 1, iy + 1], lhm[ix + 2, iy + 1], lhm[ix + 3, iy + 1], fracx);
+ x3 = TerrainHelper.CubicInterpolator(lhm[ix, iy + 2], lhm[ix + 1, iy + 2], lhm[ix + 2, iy + 2], lhm[ix + 3, iy + 2], fracx);
+ x4 = TerrainHelper.CubicInterpolator(lhm[ix, iy + 3], lhm[ix + 1, iy + 3], lhm[ix + 2, iy + 3], lhm[ix + 3, iy + 3], fracx);
+ noiseHeight = TerrainHelper.CubicInterpolator(x1, x2, x3, x4, fracy);
+ scaledHeight += noiseHeight * noiseMapScale;
+
+ // Additional noise mask for small terrain features at ground level
+ int noisex = mapPixel.mapPixelX * (HeightmapDimension - 1) + x;
+ int noisey = (MapsFile.MaxMapPixelY - mapPixel.mapPixelY) * (HeightmapDimension - 1) + y;
+ float lowFreq = TerrainHelper.GetNoise(noisex, noisey, 0.3f, 0.5f, 0.5f, 1);
+ float highFreq = TerrainHelper.GetNoise(noisex, noisey, 0.9f, 0.5f, 0.5f, 1);
+ scaledHeight += (lowFreq * highFreq) * extraNoiseScale;
+
+ // Clamp lower values to ocean elevation
+ if (scaledHeight < scaledOceanElevation)
+ scaledHeight = scaledOceanElevation;
+
+ // Accumulate average height
+ averageHeight += scaledHeight;
+
+ // Get max height
+ if (scaledHeight > maxHeight)
+ maxHeight = scaledHeight;
+
+ // Set sample
+ float height = Mathf.Clamp01(scaledHeight / MaxTerrainHeight);
+ mapPixel.heightmapSamples[y, x] = height;
+ }
+ }
+
+ // Average and max heights are passed back for locations
+ mapPixel.averageHeight = (averageHeight /= (float)(dim * dim)) / MaxTerrainHeight;
+ mapPixel.maxHeight = maxHeight / MaxTerrainHeight;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedTerrainSampler.cs.meta b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedTerrainSampler.cs.meta
new file mode 100644
index 0000000000..e035575cf6
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedTerrainSampler.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 1da0c38b685540740a98f3aa69898f31
+timeCreated: 1445869506
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedWorldTerrain.cs b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedWorldTerrain.cs
new file mode 100644
index 0000000000..1f1af7215a
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedWorldTerrain.cs
@@ -0,0 +1,542 @@
+//Increased Terrain Distance Mod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 1.54
+
+//#define CREATE_PERSISTENT_LOCATION_RANGE_MAPS
+//#define CREATE_PERSISTENT_TREE_COVERAGE_MAP
+#define LOAD_TREE_COVERAGE_MAP
+
+using UnityEngine;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text; // for Encoding.UTF8
+using DaggerfallConnect;
+using DaggerfallConnect.Arena2;
+using DaggerfallConnect.Utility;
+using DaggerfallWorkshop;
+using DaggerfallWorkshop.Game;
+using DaggerfallWorkshop.Utility;
+
+namespace ProjectIncreasedTerrainDistance
+{
+ ///
+ ///
+ public static class ImprovedWorldTerrain
+ {
+ const string filepathInTreeCoverageMap = "./Assets/daggerfall-unity/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_in.bin";
+
+ const string filepathOutTreeCoverageMap = "./Assets/daggerfall-unity/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapTreeCoverage_out.bin";
+
+ const string filepathMapLocationRangeX = "./Assets/daggerfall-unity/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeX.bin";
+ const string filepathMapLocationRangeY = "./Assets/daggerfall-unity/Game/Addons/IncreasedTerrainDistanceMod/Resources/mapLocationRangeY.bin";
+
+ const float minDistanceFromWaterForExtraExaggeration = 3.0f; // when does exaggeration start in terms of how far does terrain have to be away from water
+ const float exaggerationFactorWaterDistance = 0.15f; // how strong is the distance from water incorporated into the multiplier
+ const float extraExaggerationFactorLocationDistance = 0.00075f; // how strong is the distance from locations incorporated into the multiplier
+ const float maxHeightsExaggerationMultiplier = 25.0f; // this directly affects maxTerrainHeight in TerrainHelper.cs: maxTerrainHeight should be maxHeightsExaggerationMultiplier * baseHeightScale * 128 + noiseMapScale * 128 + extraNoiseScale
+
+ // 2D distance transform image - squared distance to water pixels of the world map
+ private static float[] mapDistanceSquaredFromWater = null;
+
+ // 2D distance transform image - squared distance to world map pixels with location
+ private static float[] mapDistanceSquaredFromLocations = null;
+
+ // map of multiplier values
+ private static float[] mapMultipliers = null;
+
+ // map with location positions
+ private static byte[] mapLocations = null;
+
+ // map with tree coverage
+ private static byte[] mapTreeCoverage = null;
+
+ private static byte[] mapLocationRangeX = null;
+ private static byte[] mapLocationRangeY = null;
+
+ // indicates if improved terrain is initialized (InitImprovedWorldTerrain() function was called)
+ private static bool init = false;
+
+ public static bool IsInit { get { return init; } }
+
+ public static void Unload()
+ {
+ mapDistanceSquaredFromWater = null;
+ mapDistanceSquaredFromLocations = null;
+ mapMultipliers = null;
+ mapLocations = null;
+ mapTreeCoverage = null;
+ mapLocationRangeX = null;
+ mapLocationRangeY = null;
+
+ Resources.UnloadUnusedAssets();
+
+ System.GC.Collect();
+ }
+
+ ///
+ /// Gets or sets map with location positions
+ ///
+ public static byte[] MapLocations
+ {
+ get
+ {
+ return mapLocations;
+ }
+ set { mapLocations = value; }
+ }
+
+ public static byte[] MapTreeCoverage
+ {
+ get
+ {
+ return mapTreeCoverage;
+ }
+ set { mapTreeCoverage = value; }
+ }
+
+ public static byte[] MapLocationRangeX
+ {
+ get
+ {
+ return mapLocationRangeX;
+ }
+ set { mapLocationRangeX = value; }
+ }
+
+ public static byte[] MapLocationRangeY
+ {
+ get
+ {
+ return mapLocationRangeY;
+ }
+ set { mapLocationRangeY = value; }
+ }
+
+ ///
+ /// Gets or sets map with squared distance to water pixels.
+ ///
+ public static float[] MapDistanceSquaredFromWater
+ {
+ get {
+ return mapDistanceSquaredFromWater;
+ }
+ set { mapDistanceSquaredFromWater = value; }
+ }
+
+ ///
+ /// gets the distance to water for a given world map pixel.
+ ///
+ public static float getDistanceFromWater(int mapPixelX, int mapPixelY)
+ {
+ if (init)
+ {
+ return ((float)Math.Sqrt(mapDistanceSquaredFromWater[mapPixelY * WoodsFile.mapWidthValue + mapPixelX]));
+ }
+ else
+ {
+ DaggerfallUnity.LogMessage("ImprovedWorldTerrain not initialized.", true);
+ return (1.0f);
+ }
+ }
+
+ ///
+ /// computes the height multiplier for a given world map pixel.
+ ///
+ public static float computeHeightMultiplier(int mapPixelX, int mapPixelY)
+ {
+ if (init)
+ {
+ return (mapMultipliers[mapPixelY * WoodsFile.mapWidthValue + mapPixelX]);
+ }
+ else
+ {
+ return (1.0f);
+ }
+ }
+
+ ///
+ /// initializes resources (mapDistanceSquaredFromWater, mapDistanceSquaredFromLocations, mapMultipliers) and smoothes small height map
+ ///
+ public static void InitImprovedWorldTerrain(ContentReader contentReader)
+ {
+ if (!init)
+ {
+ #if CREATE_PERSISTENT_LOCATION_RANGE_MAPS
+ {
+ int width = WoodsFile.mapWidthValue;
+ int height = WoodsFile.mapHeightValue;
+
+ mapLocationRangeX = new byte[width * height];
+ mapLocationRangeY = new byte[width * height];
+
+ //int y = 204;
+ //int x = 718;
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ //MapPixelData MapData = TerrainHelper.GetMapPixelData(contentReader, x, y);
+ //if (MapData.hasLocation)
+ //{
+ // int locationRangeX = (int)MapData.locationRect.xMax - (int)MapData.locationRect.xMin;
+ // int locationRangeY = (int)MapData.locationRect.yMax - (int)MapData.locationRect.yMin;
+ //}
+
+ ContentReader.MapSummary mapSummary;
+ int regionIndex = -1, mapIndex = -1;
+ bool hasLocation = contentReader.HasLocation(x, y, out mapSummary);
+ if (hasLocation)
+ {
+ regionIndex = mapSummary.RegionIndex;
+ mapIndex = mapSummary.MapIndex;
+ DFLocation location = contentReader.MapFileReader.GetLocation(regionIndex, mapIndex);
+ byte locationRangeX = location.Exterior.ExteriorData.Width;
+ byte locationRangeY = location.Exterior.ExteriorData.Height;
+
+ mapLocationRangeX[y * width + x] = locationRangeX;
+ mapLocationRangeY[y * width + x] = locationRangeY;
+ }
+ }
+ }
+
+ // save to files
+ FileStream ostream;
+ ostream = new FileStream("./Assets/IncreasedTerrainDistance/Resources/mapLocationRangeX.bin", FileMode.Create, FileAccess.Write);
+ BinaryWriter writerMapLocationRangeX = new BinaryWriter(ostream, Encoding.UTF8);
+ writerMapLocationRangeX.Write(mapLocationRangeX, 0, width * height);
+ writerMapLocationRangeX.Close();
+ ostream.Close();
+
+ ostream = new FileStream("./Assets/IncreasedTerrainDistance/Resources/mapLocationRangeY.bin", FileMode.Create, FileAccess.Write);
+ BinaryWriter writerMapLocationRangeY = new BinaryWriter(ostream, Encoding.UTF8);
+ writerMapLocationRangeY.Write(mapLocationRangeY, 0, width * height);
+ writerMapLocationRangeY.Close();
+ ostream.Close();
+ }
+#else
+ {
+ int width = WoodsFile.mapWidthValue;
+ int height = WoodsFile.mapHeightValue;
+
+ mapLocationRangeX = new byte[width * height];
+ mapLocationRangeY = new byte[width * height];
+
+ FileStream istream;
+ istream = new FileStream(filepathMapLocationRangeX, FileMode.Open, FileAccess.Read);
+ BinaryReader readerMapLocationRangeX = new BinaryReader(istream, Encoding.UTF8);
+ readerMapLocationRangeX.Read(mapLocationRangeX, 0, width * height);
+ readerMapLocationRangeX.Close();
+ istream.Close();
+
+ istream = new FileStream(filepathMapLocationRangeY, FileMode.Open, FileAccess.Read);
+ BinaryReader readerMapLocationRangeY = new BinaryReader(istream, Encoding.UTF8);
+ readerMapLocationRangeY.Read(mapLocationRangeY, 0, width * height);
+ readerMapLocationRangeY.Close();
+ istream.Close();
+ }
+ #endif
+
+ if (mapDistanceSquaredFromWater == null)
+ {
+ byte[] heightMapArray = contentReader.WoodsFileReader.Buffer.Clone() as byte[];
+ int width = WoodsFile.mapWidthValue;
+ int height = WoodsFile.mapHeightValue;
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ if (heightMapArray[y * width + x] <= 2)
+ heightMapArray[y * width + x] = 1;
+ else
+ heightMapArray[y * width + x] = 0;
+ }
+ }
+ //now set image borders to "water" (this is a workaround to prevent mountains to become too high in north-east and south-east edge of map)
+ for (int y = 0; y < height; y++)
+ {
+ heightMapArray[y * width + 0] = 1;
+ heightMapArray[y * width + width - 1] = 1;
+ }
+ for (int x = 0; x < width; x++)
+ {
+ heightMapArray[0 * width + x] = 1;
+ heightMapArray[(height - 1) * width + x] = 1;
+ }
+
+ mapDistanceSquaredFromWater = imageDistanceTransform(heightMapArray, width, height, 1);
+
+ heightMapArray = null;
+ }
+
+ if (mapDistanceSquaredFromLocations == null)
+ {
+ int width = WoodsFile.mapWidthValue;
+ int height = WoodsFile.mapHeightValue;
+ mapLocations = new byte[width * height];
+
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ ContentReader.MapSummary summary;
+ if (contentReader.HasLocation(x + 1, height - 1 - y, out summary))
+ mapLocations[y * width + x] = 1;
+ else
+ mapLocations[y * width + x] = 0;
+ }
+ }
+ mapDistanceSquaredFromLocations = imageDistanceTransform(mapLocations, width, height, 1);
+ }
+
+
+
+ if (mapMultipliers == null)
+ {
+ int width = WoodsFile.mapWidthValue;
+ int height = WoodsFile.mapHeightValue;
+ mapMultipliers = new float[width * height];
+
+ // compute the multiplier and store it in mapMultipliers
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ float distanceFromWater = (float)Math.Sqrt(mapDistanceSquaredFromWater[y * WoodsFile.mapWidthValue + x]);
+ float distanceFromLocation = (float)Math.Sqrt(mapDistanceSquaredFromLocations[y * WoodsFile.mapWidthValue + x]);
+ float multiplierLocation = (distanceFromLocation * extraExaggerationFactorLocationDistance + 1.0f); // terrain distant from location gets extra exaggeration
+ if (distanceFromWater < minDistanceFromWaterForExtraExaggeration) // except if it is near water
+ multiplierLocation = 1.0f;
+ mapMultipliers[y * width + x] = (Math.Min(maxHeightsExaggerationMultiplier, multiplierLocation * Math.Max(1.0f, distanceFromWater * exaggerationFactorWaterDistance)));
+ }
+ }
+
+ // multipliedMap gets smoothed
+ float[] newmapMultipliers = mapMultipliers.Clone() as float[];
+ float[,] weights = { { 0.0625f, 0.125f, 0.0625f }, { 0.125f, 0.25f, 0.125f }, { 0.0625f, 0.125f, 0.0625f } };
+ for (int y = 1; y < height - 1; y++)
+ {
+ for (int x = 1; x < width - 1; x++)
+ {
+ if (mapDistanceSquaredFromLocations[y * width + x] <= 2) // at and around locations ( <= 2 ... only map pixels in 8-connected neighborhood (distanceFromLocationMaps stores squared distances...))
+ {
+ newmapMultipliers[y * width + x] =
+ weights[0, 0] * mapMultipliers[(y - 1) * width + (x - 1)] + weights[0, 1] * mapMultipliers[(y - 1) * width + (x)] + weights[0, 2] * mapMultipliers[(y - 1) * width + (x + 1)] +
+ weights[1, 0] * mapMultipliers[(y - 0) * width + (x - 1)] + weights[1, 1] * mapMultipliers[(y - 0) * width + (x)] + weights[1, 2] * mapMultipliers[(y - 0) * width + (x + 1)] +
+ weights[2, 0] * mapMultipliers[(y + 1) * width + (x - 1)] + weights[2, 1] * mapMultipliers[(y + 1) * width + (x)] + weights[2, 2] * mapMultipliers[(y + 1) * width + (x + 1)];
+ }
+ }
+ }
+ mapMultipliers = newmapMultipliers;
+
+ newmapMultipliers = null;
+ weights = null;
+ }
+
+ //the height map gets smoothed as well
+ {
+ int width = WoodsFile.mapWidthValue;
+ int height = WoodsFile.mapHeightValue;
+ byte[] heightMapBuffer = contentReader.WoodsFileReader.Buffer.Clone() as byte[];
+ int[,] intWeights = { { 1, 2, 1 }, { 2, 4, 2 }, { 1, 2, 1 } };
+ for (int y = 1; y < height - 1; y++)
+ {
+ for (int x = 1; x < width - 1; x++)
+ {
+ if (mapDistanceSquaredFromWater[y * width + x] > 0) // check if squared distance from water is greater than zero -> if it is no water pixel
+ {
+ int value =
+ intWeights[0, 0] * (int)heightMapBuffer[(y - 1) * width + (x - 1)] + intWeights[0, 1] * (int)heightMapBuffer[(y - 1) * width + (x)] + intWeights[0, 2] * (int)heightMapBuffer[(y - 1) * width + (x + 1)] +
+ intWeights[1, 0] * (int)heightMapBuffer[(y - 0) * width + (x - 1)] + intWeights[1, 1] * (int)heightMapBuffer[(y - 0) * width + (x)] + intWeights[1, 2] * (int)heightMapBuffer[(y - 0) * width + (x + 1)] +
+ intWeights[2, 0] * (int)heightMapBuffer[(y + 1) * width + (x - 1)] + intWeights[2, 1] * (int)heightMapBuffer[(y + 1) * width + (x)] + intWeights[2, 2] * (int)heightMapBuffer[(y + 1) * width + (x + 1)];
+
+ heightMapBuffer[y * width + x] = (byte)(value / 16);
+ }
+ }
+ }
+ contentReader.WoodsFileReader.Buffer = heightMapBuffer;
+
+ heightMapBuffer = null;
+ intWeights = null;
+ }
+
+ // build tree coverage map
+ if (mapTreeCoverage == null)
+ {
+ int width = WoodsFile.mapWidthValue;
+ int height = WoodsFile.mapHeightValue;
+ mapTreeCoverage = new byte[width * height];
+
+ #if LOAD_TREE_COVERAGE_MAP
+ {
+ float startTreeCoverageAtElevation = ImprovedTerrainSampler.baseHeightScale * 2.0f; // ImprovedTerrainSampler.scaledBeachElevation;
+ float minTreeCoverageSaturated = ImprovedTerrainSampler.baseHeightScale * 6.0f;
+ float maxTreeCoverageSaturated = ImprovedTerrainSampler.baseHeightScale * 60.0f;
+ float endTreeCoverageAtElevation = ImprovedTerrainSampler.baseHeightScale * 80.0f;
+ //float maxElevation = 0.0f;
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ int readIndex = (height - 1 - y) * width + x;
+ float w = 0.0f;
+
+ //float elevation = ((float)contentReader.WoodsFileReader.Buffer[(height - 1 - y) * width + x]) / 255.0f; // *mapMultipliers[index];
+ float elevation = ((float)contentReader.WoodsFileReader.Buffer[readIndex]) * mapMultipliers[readIndex];
+
+ //maxElevation = Math.Max(maxElevation, elevation);
+ if ((elevation > minTreeCoverageSaturated) && (elevation < maxTreeCoverageSaturated))
+ {
+ w = 1.0f;
+ }
+ else if ((elevation >= startTreeCoverageAtElevation) && (elevation <= minTreeCoverageSaturated))
+ {
+ w = (elevation - startTreeCoverageAtElevation) / (minTreeCoverageSaturated - startTreeCoverageAtElevation);
+ }
+ else if ((elevation >= maxTreeCoverageSaturated) && (elevation <= endTreeCoverageAtElevation))
+ {
+ w = 1.0f - ((elevation - maxTreeCoverageSaturated) / (endTreeCoverageAtElevation - maxTreeCoverageSaturated));
+ }
+
+ //w = 0.65f * w + 0.35f * Math.Min(6.0f, (float)Math.Sqrt(mapDistanceSquaredFromLocations[y * width + x])) / 6.0f;
+
+ mapTreeCoverage[(y) * width + x] = Convert.ToByte(w * 255.0f);
+
+ //if (elevation>0.05f)
+ // mapTreeCoverage[index] = Convert.ToByte(250); //w * 255.0f);
+ //else mapTreeCoverage[index] = Convert.ToByte(0);
+
+ //if (elevation >= startTreeCoverageAtElevation)
+ //{
+ // mapTreeCoverage[(y) * width + x] = Convert.ToByte(255.0f);
+ //} else{
+ // mapTreeCoverage[(y) * width + x] = Convert.ToByte(0.0f);
+ //}
+ }
+ }
+ }
+ #else
+ {
+ FileStream istream;
+ istream = new FileStream(filepathInTreeCoverageMap, FileMode.Open, FileAccess.Read);
+ BinaryReader readerMapTreeCoverage = new BinaryReader(istream, Encoding.UTF8);
+ readerMapTreeCoverage.Read(mapTreeCoverage, 0, width * height);
+ readerMapTreeCoverage.Close();
+ istream.Close();
+ }
+ #endif
+
+ #if CREATE_PERSISTENT_TREE_COVERAGE_MAP
+ {
+ FileStream ostream = new FileStream(filepathOutTreeCoverageMap, FileMode.Create, FileAccess.Write);
+ BinaryWriter writerMapTreeCoverage = new BinaryWriter(ostream, Encoding.UTF8);
+ writerMapTreeCoverage.Write(mapTreeCoverage, 0, width * height);
+ writerMapTreeCoverage.Close();
+ ostream.Close();
+ }
+ #endif
+ //Debug.Log(string.Format("max elevation: {0}", maxElevation));
+ }
+
+ init = true;
+ }
+ }
+
+ /* distance transform of image (will get binarized) using squared distance */
+ public static float[] imageDistanceTransform(byte[] imgIn, int width, int height, byte maskValue)
+ {
+ const float INF = 1E20f;
+ // allocate image and initialize
+ float[] imgOut = new float[width * height];
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ if (imgIn[y * width + x] == maskValue)
+ imgOut[y * width + x] = 0; // pixels with maskValue -> distance 0
+ else
+ imgOut[y * width + x] = INF; // set to infinite
+ }
+ }
+
+ distanceTransform2D(ref imgOut, width, height);
+ return imgOut;
+ }
+
+ /* euklidean distance transform (based on an implementation of Pedro Felzenszwalb (from paper Fast distance transform in C++ by Felzenszwalb and Huttenlocher))*/
+
+ /* distance transform of 1d function using squared distance */
+ private static float[] distanceTransform1D(ref float[] f, int n)
+ {
+ const float INF = 1E20f;
+
+ float[] d = new float[n];
+ int[] v = new int[n];
+ float[] z = new float[n + 1];
+ int k = 0;
+ v[0] = 0;
+ z[0] = -INF;
+ z[1] = +INF;
+ for (int q = 1; q <= n - 1; q++)
+ {
+ float s = ((f[q] + (q * q)) - (f[v[k]] + (v[k] * v[k]))) / (2 * q - 2 * v[k]);
+ while (s <= z[k])
+ {
+ k--;
+ s = ((f[q] + (q * q)) - (f[v[k]] + (v[k] * v[k]))) / (2 * q - 2 * v[k]);
+ }
+ k++;
+ v[k] = q;
+ z[k] = s;
+ z[k + 1] = +INF;
+ }
+
+ k = 0;
+ for (int q = 0; q <= n - 1; q++)
+ {
+ while (z[k + 1] < q)
+ k++;
+ d[q] = (q - v[k]) * (q - v[k]) + f[v[k]];
+ }
+
+ return d;
+ }
+ /* in-place 2D distance transform on float array using squared distance, float array must be initialized with 0 for maskValue-pixels and infinite otherwise*/
+ private static void distanceTransform2D(ref float[] img, int width, int height)
+ {
+ float[] f = new float[Math.Max(width, height)];
+
+ // transform along columns
+ for (int x = 0; x < width; x++)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ f[y] = img[y * width + x];
+ }
+ float[] d = distanceTransform1D(ref f, height);
+ for (int y = 0; y < height; y++)
+ {
+ img[y * width + x] = d[y];
+ }
+ }
+
+ // transform along rows
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ f[x] = img[y * width + x];
+ }
+ float[] d = distanceTransform1D(ref f, width);
+ for (int x = 0; x < width; x++)
+ {
+ img[y * width + x] = d[x];
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedWorldTerrain.cs.meta b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedWorldTerrain.cs.meta
new file mode 100644
index 0000000000..7b10b1572e
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/ImprovedWorldTerrain.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 8067275e055388547bd8736b7f7df08b
+timeCreated: 1445869506
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/IncreasedTerrainDistance.cs b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/IncreasedTerrainDistance.cs
new file mode 100644
index 0000000000..ba9f32708f
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/IncreasedTerrainDistance.cs
@@ -0,0 +1,994 @@
+//Increased Terrain Distance Mod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 1.54
+//Contributors: Lypyl
+
+// uncomment next line if enhanced sky mod by Lypyl is present
+#define ENHANCED_SKY_AVAILABLE
+
+using UnityEngine;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using DaggerfallConnect;
+using DaggerfallConnect.Arena2;
+using DaggerfallConnect.Utility;
+using DaggerfallWorkshop;
+using DaggerfallWorkshop.Game;
+using DaggerfallWorkshop.Utility;
+
+namespace ProjectIncreasedTerrainDistance
+{
+ ///
+ /// Manages a world terrain object built from the world height map for increased terrain/view distance
+ /// one main objective was to make everything work inside this script (and the shader for texturing) without a need for changes in any other files
+ /// this means as a consequence that initialization of resources of other scripts that are used inside the script needs to be finished before any other work can be done.
+ /// another objective was that render and loading performance should only be impacted as less as possible. I did some investigations and decided to use one big
+ /// unity terrain object for the whole world since it gave the best performance compared with splitting up into several terrains. The terrain has to be created only once
+ /// and only needs to be translated to match with the StreamingWorld component. Experiments were made in Unity 4.6, don't know if Unity 5.0 would make a difference.
+ /// furthermore when using unity terrain one can use unity's level of detail mechanism for geometry (if needed), and other benefits provided by unity’s terrain system.
+ /// a consequence is though that low-detailed terrain geometry would be also rendered at the position of the detailed terrain inside the distance from the player defined by TerrainDistance.
+ /// the IncreasedTerrainTilemap shader defined in file DaggerfallIncreasedTerrainTilemap.shader discards fragments inside the area containing the detailed terrain from StreamingWorld,
+ /// except the most outer ring of the detailed terrain blocks (TerrainDistance-1) - this is to prevent holes in the world, as a consequence in the most outer ring there can happen
+ /// intersections between detailed terrain and world terrain
+ /// to decrease the chance of intersecting geometry in the most outer ring the world map is translated down on the y-axis a bit
+ ///
+ public class IncreasedTerrainDistance : MonoBehaviour
+ {
+ #region Fields
+
+ // Streaming World Component
+ public StreamingWorld streamingWorld;
+
+ // Local player GPS for tracking player virtual position
+ public PlayerGPS playerGPS;
+
+ // WeatherManager is used for seasonal textures
+ public WeatherManager weatherManager;
+
+ //public RenderTexture renderTextureSky;
+
+ //[Range(0.0001f, 0.000001f)]
+ //public float blendFactor = 0.000015f;
+
+ [Range(0.0f, 300000.0f)]
+ public float blendStart = 120000.0f;
+
+ [Range(0.0f, 300000.0f)]
+ public float blendEnd = 145000.0f;
+
+ public float fogDensity = 0.000025f;
+
+
+ // is dfUnity ready?
+ bool isReady = false;
+
+ // the height values of the world height map used as input for unity terrain function SetHeights()
+ float[,] worldHeights = null;
+
+ int worldMapWidth = MapsFile.MaxMapPixelX - MapsFile.MinMapPixelX;
+ int worldMapHeight = MapsFile.MaxMapPixelY - MapsFile.MinMapPixelY;
+
+ // used to track changes of playerGPS x- resp. y-position on the world map (-> a change results in an update of the terrain object's translation)
+ int MapPixelX = -1;
+ int MapPixelY = -1;
+
+ // used to backup old center x- resp. y-position of sink-area to restore old values which are not sunk (sink area is used to decrease the chance of low-detail world height map geometry intersect detailed geometry in near distance defined by TerrainDistance)
+ int backupSinkAreaCenterPosX = -1;
+ int backupSinkAreaCenterPosY = -1;
+
+ // unity terrain object which will hold the low-detail world map geometry, set to null initially for lazy creation
+ GameObject worldTerrainGameObject = null;
+
+ // terrain material used for world texturing (will be changed when daggerFallLocation.currentSeason changes from previous setting)
+ Material terrainMaterial = null;
+
+ // map holds info for tiles of terrain: r-channel... climate index, gb... currently unused, a-channel... discard-rendering flag for shader
+ Color32[] terrainInfoTileMap = null;
+ int terrainInfoTileMapDim;
+
+ // texture for terrainInfoTileMap
+ Texture2D textureTerrainInfoTileMap = null;
+
+
+ ClimateSeason oldSeason = ClimateSeason.Summer;
+
+ Texture2D textureAtlasDesertSummer = null;
+ Texture2D textureAtlasWoodlandSummer = null;
+ Texture2D textureAtlasMountainSummer = null;
+ Texture2D textureAtlasSwampSummer = null;
+
+ Texture2D textureAtlasDesertWinter = null;
+ Texture2D textureAtlasWoodlandWinter = null;
+ Texture2D textureAtlasMountainWinter = null;
+ Texture2D textureAtlasSwampWinter = null;
+
+ Texture2D textureAtlasDesertRain = null;
+ Texture2D textureAtlasWoodlandRain = null;
+ Texture2D textureAtlasMountainRain = null;
+ Texture2D textureAtlasSwampRain = null;
+
+ // stacked near camera (used for near terrain from range 1000-15000) to prevent floating-point rendering precision problems for huge clipping ranges
+ Camera stackedNearCamera = null;
+
+ // stacked camera (used for far terrain) to prevent floating-point rendering precision problems for huge clipping ranges
+ Camera stackedCamera = null;
+
+ public Camera getFarTerrainCamera() { return stackedCamera; }
+ public Camera getStackedNearCamera() { return stackedNearCamera; }
+
+ #if ENHANCED_SKY_AVAILABLE
+ EnhancedSky.SkyManager skyMan = null;
+ bool sampleFogColorFromSky = false;
+ #endif
+
+ GameObject goRenderSkyboxToTexture = null;
+ Camera cameraRenderSkyboxToTexture = null;
+
+ const int renderTextureSkyWidth = 256;
+ const int renderTextureSkyHeight = 256;
+ const int renderTextureSkyDepth = 16;
+ const RenderTextureFormat renderTextureSkyFormat = RenderTextureFormat.ARGB32;
+ RenderTexture renderTextureSky = null;
+
+
+ // instance of dfUnity
+ DaggerfallUnity dfUnity;
+
+ #endregion
+
+ #region Properties
+
+ public bool IsReady { get { return ReadyCheck(); } }
+
+ #endregion
+
+ #region Unity
+
+ static string GetGameObjectPath(GameObject obj)
+ {
+ string path = "/" + obj.name;
+ while (obj.transform.parent != null)
+ {
+ obj = obj.transform.parent.gameObject;
+ path = "/" + obj.name + path;
+ }
+ return path;
+ }
+
+ void SetTerrainSampler()
+ {
+ DaggerfallUnity.Instance.TerrainSampler = new ImprovedTerrainSampler();
+ }
+
+ void Awake()
+ {
+ if (!DaggerfallUnity.Settings.Nystul_IncreasedTerrainDistance)
+ return;
+
+ dfUnity = DaggerfallUnity.Instance;
+
+ DaggerfallUnity.OnSetTerrainSampler += SetTerrainSampler;
+
+ ImprovedTerrainSampler improvedTerrainSampler = DaggerfallUnity.Instance.TerrainSampler as ImprovedTerrainSampler;
+ if (improvedTerrainSampler == null)
+ {
+ DaggerfallUnity.LogMessage("IncreasedTerrainDistance: TerrainSampler instance is not of type ImprovedTerrainSampler (use ITerrainSampler terrainSampler = new ImprovedTerrainSampler() in DaggerfallUnity.cs)", true);
+ }
+
+ if (!streamingWorld)
+ streamingWorld = GameObject.Find("StreamingWorld").GetComponent();
+ if (!streamingWorld)
+ {
+ DaggerfallUnity.LogMessage("IncreasedTerrainDistance: Missing StreamingWorld reference.", true);
+ if (Application.isEditor)
+ Debug.Break();
+ else
+ Application.Quit();
+ }
+
+ if (!playerGPS)
+ playerGPS = GameObject.FindGameObjectWithTag("Player").GetComponent();
+ if (!playerGPS)
+ {
+ DaggerfallUnity.LogMessage("IncreasedTerrainDistance: Missing PlayerGPS reference.", true);
+ if (Application.isEditor)
+ Debug.Break();
+ else
+ Application.Quit();
+ }
+
+ if (!weatherManager)
+ {
+ //weatherManager = GameObject.Find("WeatherManager").GetComponent();
+ WeatherManager[] weatherManagers = Resources.FindObjectsOfTypeAll();
+ foreach (WeatherManager currentWeatherManager in weatherManagers)
+ {
+ GameObject go = currentWeatherManager.gameObject;
+ string objectPathInHierarchy = GetGameObjectPath(go);
+ if (objectPathInHierarchy == "/Exterior/WeatherManager")
+ {
+ weatherManager = currentWeatherManager;
+ break;
+ }
+ }
+ }
+ if (!weatherManager)
+ {
+ DaggerfallUnity.LogMessage("IncreasedTerrainDistance: Missing WeatherManager reference.", true);
+ if (Application.isEditor)
+ Debug.Break();
+ else
+ Application.Quit();
+ }
+ }
+
+ void OnEnable()
+ {
+ if (!DaggerfallUnity.Settings.Nystul_IncreasedTerrainDistance)
+ return;
+
+ FloatingOrigin.OnPositionUpdate += WorldTerrainUpdatePosition;
+
+ StreamingWorld.OnReady += UpdateTerrainInfoTilemap; // important to do actions after TerrainHelper.DilateCoastalClimate() was called in StreamingWorld.ReadyCheck()
+
+ StreamingWorld.OnTeleportToCoordinates += UpdateWorldTerrain;
+
+ PlayerEnterExit.OnTransitionExterior += TransitionToExterior;
+ PlayerEnterExit.OnTransitionDungeonExterior += TransitionToExterior;
+ }
+
+ void OnDisable()
+ {
+ if (!DaggerfallUnity.Settings.Nystul_IncreasedTerrainDistance)
+ return;
+
+ FloatingOrigin.OnPositionUpdate -= WorldTerrainUpdatePosition;
+
+ StreamingWorld.OnReady -= UpdateTerrainInfoTilemap;
+
+ StreamingWorld.OnTeleportToCoordinates -= UpdateWorldTerrain;
+
+ PlayerEnterExit.OnTransitionExterior -= TransitionToExterior;
+ PlayerEnterExit.OnTransitionDungeonExterior -= TransitionToExterior;
+ }
+
+ void InitImprovedWorldTerrain()
+ {
+ // preprocess heights
+ ImprovedWorldTerrain.InitImprovedWorldTerrain(DaggerfallUnity.Instance.ContentReader);
+ }
+
+ void WorldTerrainUpdatePosition(Vector3 offset)
+ {
+ if (worldTerrainGameObject != null)
+ {
+ // do not forget to update shader parameters (needed for correct fragment discarding for terrain tiles of map pixels inside TerrainDistance-1 area (the detailed terrain))
+ Terrain terrain = worldTerrainGameObject.GetComponent();
+ terrain.materialTemplate.SetInt("_PlayerPosX", this.playerGPS.CurrentMapPixel.X);
+ terrain.materialTemplate.SetInt("_PlayerPosY", this.playerGPS.CurrentMapPixel.Y);
+
+ //Debug.Log("update from floating origin event");
+ updatePositionWorldTerrain(ref worldTerrainGameObject, offset);
+ }
+ }
+
+ void UpdateTerrainInfoTilemap()
+ {
+ InitImprovedWorldTerrain();
+
+ worldTerrainGameObject = generateWorldTerrain();
+
+ GameObject goExterior = null;
+
+ GameObject[] gameObjects = Resources.FindObjectsOfTypeAll();
+ foreach (GameObject go in gameObjects)
+ {
+ string objectPathInHierarchy = GetGameObjectPath(go);
+ if (objectPathInHierarchy == "/Exterior")
+ {
+ goExterior = go;
+ }
+ }
+
+ worldTerrainGameObject.transform.SetParent(goExterior.transform);
+
+ SetUpCameras();
+
+ Terrain terrain = worldTerrainGameObject.GetComponent();
+
+ int worldMapResolution = Math.Max(worldMapWidth, worldMapHeight);
+
+ int[] climateMap = new int[worldMapResolution * worldMapResolution];
+ for (int y = 0; y < worldMapHeight; y++)
+ {
+ for (int x = 0; x < worldMapWidth; x++)
+ {
+ // get climate record for this map pixel
+ int worldClimate = dfUnity.ContentReader.MapFileReader.GetClimateIndex(x, y);
+ climateMap[(worldMapHeight - 1 - y) * worldMapResolution + x] = worldClimate;
+ }
+ }
+
+ terrainInfoTileMapDim = terrain.terrainData.heightmapResolution - 1;
+
+ terrainInfoTileMap = new Color32[terrainInfoTileMapDim * terrainInfoTileMapDim];
+
+ // Assign tile data to tilemap
+ Color32 tileColor = new Color32(0, 0, 0, 0);
+ for (int y = 0; y < worldMapHeight; y++)
+ {
+ for (int x = 0; x < worldMapWidth; x++)
+ {
+ // Get sample tile data
+ int climateIndex = climateMap[y * worldMapWidth + x];
+
+ // get location data
+ //byte hasLocation = ImprovedWorldTerrain.MapLocations[y * worldMapWidth + x];
+
+ byte locationMapRangeX = 0;
+ byte locationMapRangeY = 0;
+ if (x < worldMapWidth - 1)
+ {
+ locationMapRangeX = ImprovedWorldTerrain.MapLocationRangeX[(500 - 1 - y) * worldMapWidth + x + 1];
+ locationMapRangeY = ImprovedWorldTerrain.MapLocationRangeY[(500 - 1 - y) * worldMapWidth + x + 1];
+ }
+
+ byte treeCoverage = ImprovedWorldTerrain.MapTreeCoverage[y * worldMapWidth + x];
+
+ // Assign to tileMap
+ tileColor.r = Convert.ToByte(climateIndex);
+ tileColor.g = treeCoverage; // hasLocation;
+ tileColor.b = locationMapRangeX;
+ tileColor.a = locationMapRangeY;
+ terrainInfoTileMap[y * terrainInfoTileMapDim + x] = tileColor;
+ }
+ }
+
+ textureTerrainInfoTileMap = new Texture2D(terrainInfoTileMapDim, terrainInfoTileMapDim, TextureFormat.RGBA32, false);
+ textureTerrainInfoTileMap.filterMode = FilterMode.Point;
+ textureTerrainInfoTileMap.wrapMode = TextureWrapMode.Clamp;
+
+ // Promote tileMap
+ textureTerrainInfoTileMap.SetPixels32(terrainInfoTileMap);
+ textureTerrainInfoTileMap.Apply(false);
+
+ terrainMaterial.SetTexture("_MainTex", textureTerrainInfoTileMap);
+ terrainMaterial.SetTexture("_TilemapTex", textureTerrainInfoTileMap);
+
+ terrainMaterial.SetInt("_TilemapDim", terrainInfoTileMapDim);
+
+ terrainMaterial.mainTexture = textureTerrainInfoTileMap;
+ }
+
+ void Start()
+ {
+ if (!DaggerfallUnity.Settings.Nystul_IncreasedTerrainDistance)
+ return;
+
+ if (worldTerrainGameObject == null) // lazy creation
+ {
+ if (!ReadyCheck())
+ return;
+
+ if (!dfUnity.MaterialReader.IsReady)
+ return;
+
+ int layerExtendedTerrain = LayerMask.NameToLayer("WorldTerrain");
+ if (layerExtendedTerrain == -1)
+ {
+ DaggerfallUnity.LogMessage("Layer with name \"WorldTerrain\" missing! Set it in Unity Editor under \"Edit/Project Settings/Tags and Layers!\"", true);
+ if (Application.isEditor)
+ Debug.Break();
+ else
+ Application.Quit();
+ }
+
+ Camera.main.farClipPlane = 1200.0f;
+
+ if (!stackedNearCamera)
+ {
+ GameObject goStackedNearCamera = new GameObject("stackedNearCamera");
+ stackedNearCamera = goStackedNearCamera.AddComponent();
+ stackedNearCamera.cullingMask = Camera.main.cullingMask;
+ stackedNearCamera.nearClipPlane = 980.0f;
+ stackedNearCamera.farClipPlane = 15000.0f;
+ stackedNearCamera.fieldOfView = Camera.main.fieldOfView;
+ stackedNearCamera.renderingPath = Camera.main.renderingPath;
+ stackedNearCamera.gameObject.AddComponent();
+ stackedNearCamera.gameObject.AddComponent();
+ stackedNearCamera.transform.SetParent(this.transform);
+ }
+
+
+
+ if (!stackedCamera)
+ {
+ GameObject goStackedCamera = new GameObject("stackedCamera");
+ stackedCamera = goStackedCamera.AddComponent();
+ stackedCamera.cullingMask = 1 << layerExtendedTerrain;
+ stackedCamera.nearClipPlane = 980.0f;
+ stackedCamera.farClipPlane = 300000.0f;
+ stackedCamera.fieldOfView = Camera.main.fieldOfView;
+ stackedCamera.renderingPath = Camera.main.renderingPath;
+ stackedCamera.gameObject.AddComponent();
+ stackedCamera.gameObject.AddComponent();
+ stackedCamera.transform.SetParent(this.transform);
+ }
+
+ if (!renderTextureSky)
+ {
+ renderTextureSky = new RenderTexture(renderTextureSkyWidth, renderTextureSkyHeight, renderTextureSkyDepth, renderTextureSkyFormat);
+ }
+
+ if (!goRenderSkyboxToTexture)
+ {
+ goRenderSkyboxToTexture = new GameObject("stackedCameraSkyboxRenderToTextureGeneric", typeof(Camera));
+ goRenderSkyboxToTexture.transform.SetParent(this.transform);
+ }
+
+ if (!cameraRenderSkyboxToTexture)
+ {
+ cameraRenderSkyboxToTexture = goRenderSkyboxToTexture.GetComponent();
+
+ goRenderSkyboxToTexture.AddComponent();
+ goRenderSkyboxToTexture.AddComponent();
+
+ cameraRenderSkyboxToTexture.clearFlags = CameraClearFlags.Skybox;
+ cameraRenderSkyboxToTexture.cullingMask = 0; // nothing
+ cameraRenderSkyboxToTexture.nearClipPlane = stackedCamera.nearClipPlane;
+ cameraRenderSkyboxToTexture.farClipPlane = stackedCamera.farClipPlane;
+ cameraRenderSkyboxToTexture.fieldOfView = stackedCamera.fieldOfView;
+ }
+ }
+ }
+
+ void OnDestroy()
+ {
+ ImprovedWorldTerrain.Unload();
+
+ worldHeights = null;
+ worldTerrainGameObject = null;
+ terrainMaterial = null;
+ terrainInfoTileMap = null;
+ textureTerrainInfoTileMap = null;
+
+ textureAtlasDesertSummer = null;
+ textureAtlasWoodlandSummer = null;
+ textureAtlasMountainSummer = null;
+ textureAtlasSwampSummer = null;
+
+ textureAtlasDesertWinter = null;
+ textureAtlasWoodlandWinter = null;
+ textureAtlasMountainWinter = null;
+ textureAtlasSwampWinter = null;
+
+ textureAtlasDesertRain = null;
+ textureAtlasWoodlandRain = null;
+ textureAtlasMountainRain = null;
+ textureAtlasSwampRain = null;
+
+ Resources.UnloadUnusedAssets();
+
+ System.GC.Collect();
+ }
+
+ void TransitionToExterior(PlayerEnterExit.TransitionEventArgs args)
+ {
+ SetUpCameras();
+ }
+
+ void SetUpCameras()
+ {
+ // set up camera stack - AFTER layer "WorldTerrain" has been assigned to worldTerrainGameObject (is done in function generateWorldTerrain())
+
+ Camera.main.clearFlags = CameraClearFlags.Depth;
+ stackedNearCamera.clearFlags = CameraClearFlags.Depth;
+ stackedCamera.clearFlags = CameraClearFlags.Depth;
+ stackedCamera.depth = 1; // rendered first
+ stackedNearCamera.depth = 2;
+ Camera.main.depth = 3; // renders over stacked camera
+
+ cameraRenderSkyboxToTexture.depth = -10; // make sure to render first
+ cameraRenderSkyboxToTexture.renderingPath = stackedCamera.renderingPath;
+
+ #if ENHANCED_SKY_AVAILABLE
+ //skyMan = GameObject.Find("EnhancedSkyController").GetComponent();
+ EnhancedSky.SkyManager[] skyManagers = Resources.FindObjectsOfTypeAll();
+ foreach (EnhancedSky.SkyManager currentSkyManager in skyManagers)
+ {
+ GameObject go = currentSkyManager.gameObject;
+ string objectPathInHierarchy = GetGameObjectPath(go);
+ if (objectPathInHierarchy == "/Exterior/EnhancedSkyController")
+ {
+ skyMan = currentSkyManager;
+ break;
+ }
+ }
+
+ if (skyMan)
+ {
+ sampleFogColorFromSky = true;
+ }
+ #endif
+
+ cameraRenderSkyboxToTexture.targetTexture = renderTextureSky;
+ }
+
+ void Update()
+ {
+ if (worldTerrainGameObject != null)
+ {
+ // TODO: make sure this block is not executed when in floating origin mode (otherwise position update is done twice)
+ // Handle moving to new map pixel or first-time init
+ DFPosition curMapPixel = playerGPS.CurrentMapPixel;
+ if (curMapPixel.X != MapPixelX ||
+ curMapPixel.Y != MapPixelY)
+ {
+ UpdateWorldTerrain(curMapPixel);
+ }
+
+ Terrain terrain = worldTerrainGameObject.GetComponent();
+ if (terrain)
+ {
+ /*
+ if (RenderSettings.fog == true)
+ {
+ if (RenderSettings.fogMode == FogMode.Linear)
+ terrainMaterial.SetInt("_FogMode", 1);
+ else if (RenderSettings.fogMode == FogMode.Exponential)
+ terrainMaterial.SetInt("_FogMode", 2);
+ else if (RenderSettings.fogMode == FogMode.ExponentialSquared)
+ terrainMaterial.SetInt("_FogMode", 3);
+ }
+ else
+ {
+ terrainMaterial.SetInt("_FogMode", 0);
+ }
+ */
+ terrainMaterial.SetInt("_FogMode", 2);
+ terrainMaterial.SetFloat("_FogDensity", fogDensity);
+
+
+ #if ENHANCED_SKY_AVAILABLE
+ if ((sampleFogColorFromSky == true) && (!skyMan.IsOvercast))
+ {
+ terrain.materialTemplate.SetFloat("_FogFromSkyTex", 1);
+ }
+ else
+ {
+ terrain.materialTemplate.SetFloat("_FogFromSkyTex", 0);
+ }
+ #endif
+
+ //terrain.materialTemplate.SetFloat("_BlendFactor", blendFactor);
+ terrain.materialTemplate.SetFloat("_BlendStart", blendStart);
+ terrain.materialTemplate.SetFloat("_BlendEnd", blendEnd);
+ }
+
+ updateSeasonalTextures(); // this is necessary since climate changes may occur after UpdateWorldTerrain() has been invoked, TODO: an event would be ideal to trigger updateSeasonalTextures() instead
+ }
+ }
+
+ void UpdateWorldTerrain()
+ {
+ UpdateWorldTerrain(playerGPS.CurrentMapPixel);
+ }
+
+ void UpdateWorldTerrain(DFPosition worldPos)
+ {
+ if (worldTerrainGameObject != null) // sometimes it can happen that this point is reached before worldTerrainGameObject was created, in such case we just skip
+ {
+ // do not forget to update shader parameters (needed for correct fragment discarding for terrain tiles of map pixels inside TerrainDistance-1 area (the detailed terrain))
+ Terrain terrain = worldTerrainGameObject.GetComponent();
+ terrain.materialTemplate.SetInt("_PlayerPosX", this.playerGPS.CurrentMapPixel.X);
+ terrain.materialTemplate.SetInt("_PlayerPosY", this.playerGPS.CurrentMapPixel.Y);
+
+ Vector3 offset = new Vector3(0.0f, 0.0f, 0.0f);
+ updatePositionWorldTerrain(ref worldTerrainGameObject, offset);
+
+ updateSeasonalTextures();
+
+ Resources.UnloadUnusedAssets();
+
+ //System.GC.Collect();
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void updateSeasonalTextures()
+ {
+ if (!weatherManager)
+ return;
+
+ ClimateSeason currentSeason;
+
+ // Get season and weather
+ if (dfUnity.WorldTime.Now.SeasonValue == DaggerfallDateTime.Seasons.Winter)
+ {
+ currentSeason = ClimateSeason.Winter;
+ }
+ else
+ {
+ currentSeason = ClimateSeason.Summer;
+
+ if (weatherManager.IsRaining)
+ {
+ currentSeason = ClimateSeason.Rain;
+ }
+ else if (weatherManager.IsSnowing) // should not happen (snow in summer would be weird...)
+ {
+ currentSeason = ClimateSeason.Winter;
+ }
+ }
+
+ if (currentSeason != oldSeason)
+ {
+ switch (currentSeason)
+ {
+ case ClimateSeason.Summer:
+ terrainMaterial.SetTexture("_TileAtlasTexDesert", textureAtlasDesertSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexWoodland", textureAtlasWoodlandSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexMountain", textureAtlasMountainSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexSwamp", textureAtlasSwampSummer);
+ terrainMaterial.SetInt("_TextureSetSeasonCode", 0);
+ break;
+ case ClimateSeason.Winter:
+ terrainMaterial.SetTexture("_TileAtlasTexDesert", textureAtlasDesertWinter);
+ terrainMaterial.SetTexture("_TileAtlasTexWoodland", textureAtlasWoodlandWinter);
+ terrainMaterial.SetTexture("_TileAtlasTexMountain", textureAtlasMountainWinter);
+ terrainMaterial.SetTexture("_TileAtlasTexSwamp", textureAtlasSwampWinter);
+ terrainMaterial.SetInt("_TextureSetSeasonCode", 1);
+ break;
+ case ClimateSeason.Rain:
+ terrainMaterial.SetTexture("_TileAtlasTexDesert", textureAtlasDesertRain);
+ terrainMaterial.SetTexture("_TileAtlasTexWoodland", textureAtlasWoodlandRain);
+ terrainMaterial.SetTexture("_TileAtlasTexMountain", textureAtlasMountainRain);
+ terrainMaterial.SetTexture("_TileAtlasTexSwamp", textureAtlasSwampRain);
+ terrainMaterial.SetInt("_TextureSetSeasonCode", 2);
+ break;
+ default:
+ terrainMaterial.SetTexture("_TileAtlasTexDesert", textureAtlasDesertSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexWoodland", textureAtlasWoodlandSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexMountain", textureAtlasMountainSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexSwamp", textureAtlasSwampSummer);
+ terrainMaterial.SetInt("_TextureSetSeasonCode", 0);
+ break;
+ }
+ oldSeason = currentSeason;
+ }
+ }
+
+ private void updatePositionWorldTerrain(ref GameObject terrainGameObject, Vector3 offset)
+ {
+ // reduce chance of geometry intersections of world terrain and the most outer ring of detailed terrain of the StreamingWorld component
+ float extraTranslationY = -10.0f; // -12.5f * streamingWorld.TerrainScale;
+
+ // world scale computed as in StreamingWorld.cs and DaggerfallTerrain.cs scripts
+ float scale = MapsFile.WorldMapTerrainDim * MeshReader.GlobalScale;
+
+ // get displacement in world map pixels
+ float xdif = + 1 - playerGPS.CurrentMapPixel.X;
+ float zdif = worldMapHeight - 1 - playerGPS.CurrentMapPixel.Y;
+
+ // world map level transform (for whole world map pixels)
+ Vector3 worldMapLevelTransform;
+ worldMapLevelTransform.x = xdif * scale;
+ worldMapLevelTransform.y = extraTranslationY;
+ worldMapLevelTransform.z = -zdif * scale;
+
+ // used location [693,225] and [573,27] for debugging the local translation
+
+ // local world level transform (for inter- world map pixels)
+ float localTransformX = 0.0f; // old obsolete computation formula was: (float)Math.Floor((playerGPS.transform.position.x) / scale) * scale; // (float)Math.Floor((cameraPos.x) / scale) * scale;
+ float localTransformZ = 0.0f; // old obsolete computation formula was: (float)Math.Floor((playerGPS.transform.position.z) / scale) * scale; // (float)Math.Floor((cameraPos.z) / scale) * scale;
+ float localTransformY = 0.0f;
+
+ localTransformX += streamingWorld.WorldCompensation.x;
+ localTransformZ += streamingWorld.WorldCompensation.z;
+ localTransformY += streamingWorld.WorldCompensation.y;
+
+ float remainderX;
+ if (offset.x != 0)
+ {
+ remainderX = playerGPS.transform.position.x - (float)Math.Floor((playerGPS.transform.position.x) / Math.Abs(offset.x)) * Math.Abs(offset.x);
+ }
+ else
+ {
+ remainderX = playerGPS.transform.position.x;
+ }
+
+ float remainderZ;
+ if (offset.z != 0)
+ {
+ remainderZ = playerGPS.transform.position.z - (float)Math.Floor((playerGPS.transform.position.z) / Math.Abs(offset.z)) * Math.Abs(offset.z);
+ }
+ else
+ {
+ remainderZ = playerGPS.transform.position.z;
+ }
+
+ //Debug.Log(string.Format("remainderX, remainderZ: {0}, {1}; playerGPS x,z: {2},{3}", remainderX, remainderZ, playerGPS.transform.position.x, playerGPS.transform.position.z));
+
+ localTransformX += (float)Math.Floor((-streamingWorld.WorldCompensation.x + remainderX) / scale) * scale;
+ localTransformZ += (float)Math.Floor((-streamingWorld.WorldCompensation.z + remainderZ) / scale) * scale;
+
+ // compute composite transform and apply it to terrain object
+ Vector3 finalTransform = new Vector3(worldMapLevelTransform.x + localTransformX, worldMapLevelTransform.y + localTransformY, worldMapLevelTransform.z + localTransformZ);
+ terrainGameObject.gameObject.transform.localPosition = finalTransform;
+
+
+
+ int TerrainDistance = streamingWorld.TerrainDistance;
+
+ // sinkHeight for world terrain height values inside TerrainDistance radius from player position
+ float sinkHeight = (100.0f * streamingWorld.TerrainScale) / DaggerfallUnity.Instance.TerrainSampler.MaxTerrainHeight;
+
+ Terrain terrain = terrainGameObject.GetComponent();
+
+ // restore (previously) decreased terrain height values in sink area
+ float[,] heightValues = new float[TerrainDistance * 2, TerrainDistance * 2]; // only TerrainDistance * 2 height values are affected, not TerrainDistance * 2 + 1 values
+
+ if ((backupSinkAreaCenterPosX != -1) && (backupSinkAreaCenterPosY != -1)) // no values needs to be restored on very first run (since nothing was decreased before...)
+ {
+ for (int y = -(TerrainDistance - 1); y <= TerrainDistance; y++)
+ {
+ for (int x = -(TerrainDistance - 1); x <= TerrainDistance; x++)
+ {
+ int xpos = backupSinkAreaCenterPosX + x + 1;
+ int ypos = (worldMapHeight - 1 - backupSinkAreaCenterPosY) + y + 1;
+ if ((xpos >= 0) && (xpos < worldMapWidth) && (ypos >= 0) && (ypos < worldMapHeight))
+ {
+ heightValues[y + TerrainDistance - 1, x + TerrainDistance - 1] = worldHeights[ypos, xpos];
+ }
+ }
+ }
+ terrain.terrainData.SetHeights(backupSinkAreaCenterPosX - TerrainDistance + 2, worldMapHeight - 1 - backupSinkAreaCenterPosY - TerrainDistance + 2, heightValues);
+ }
+
+ backupSinkAreaCenterPosX = -2 + playerGPS.CurrentMapPixel.X;
+ backupSinkAreaCenterPosY = +1 + playerGPS.CurrentMapPixel.Y;
+
+ // decrease terrain height values in sink area
+ for (int y = -(TerrainDistance - 1); y <= TerrainDistance; y++)
+ {
+ for (int x = -(TerrainDistance - 1); x <= TerrainDistance; x++)
+ {
+ int xpos = backupSinkAreaCenterPosX + x + 1;
+ int ypos = (worldMapHeight - 1 - backupSinkAreaCenterPosY) + y + 1;
+ if ((xpos >= 0) && (xpos < worldMapWidth) && (ypos >= 0) && (ypos < worldMapHeight))
+ {
+ heightValues[y + TerrainDistance - 1, x + TerrainDistance - 1] = worldHeights[ypos, xpos] - sinkHeight;
+ }
+ }
+ }
+ terrain.terrainData.SetHeights(backupSinkAreaCenterPosX - TerrainDistance + 2, worldMapHeight - 1 - backupSinkAreaCenterPosY - TerrainDistance + 2, heightValues);
+
+ heightValues = null;
+
+ if (worldTerrainGameObject != null) // sometimes it can happen that this point is reached before worldTerrainGameObject was created, in such case we just skip
+ {
+ // update water height (thanks Lypyl!!!):
+ Vector3 vecWaterHeight = new Vector3(0.0f, (DaggerfallUnity.Instance.TerrainSampler.OceanElevation + 1.0f) * streamingWorld.TerrainScale, 0.0f); // water height level on y-axis (+1.0f some coastlines are incorrect otherwise)
+ Vector3 vecWaterHeightTransformed = worldTerrainGameObject.transform.TransformPoint(vecWaterHeight); // transform to world coordinates
+ terrainMaterial.SetFloat("_WaterHeightTransformed", vecWaterHeightTransformed.y);
+ }
+
+ MapPixelX = playerGPS.CurrentMapPixel.X;
+ MapPixelY = playerGPS.CurrentMapPixel.Y;
+ }
+
+ private GameObject generateWorldTerrain()
+ {
+ // Create Unity Terrain game object
+ GameObject terrainGameObject = Terrain.CreateTerrainGameObject(null);
+ terrainGameObject.name = string.Format("WorldTerrain");
+
+ terrainGameObject.gameObject.transform.localPosition = Vector3.zero;
+
+ // assign terrainGameObject to layer "WorldTerrain" if available (used for rendering with secondary camera to prevent floating-point precision problems with huge clipping ranges)
+ int layerExtendedTerrain = LayerMask.NameToLayer("WorldTerrain");
+ if (layerExtendedTerrain != -1)
+ terrainGameObject.layer = layerExtendedTerrain;
+
+ int worldMapResolution = Math.Max(worldMapWidth, worldMapHeight);
+
+ if (worldHeights == null)
+ {
+ worldHeights = new float[worldMapResolution, worldMapResolution];
+ }
+
+ for (int y = 0; y < worldMapHeight; y++)
+ {
+ for (int x = 0; x < worldMapWidth; x++)
+ {
+ // get height data for this map pixel from world map and scale it to approximately match StreamingWorld's terrain heights
+ float sampleHeight = Convert.ToSingle(dfUnity.ContentReader.WoodsFileReader.GetHeightMapValue(x, y));
+
+ sampleHeight *= (ImprovedWorldTerrain.computeHeightMultiplier(x, y) * ImprovedTerrainSampler.baseHeightScale + ImprovedTerrainSampler.noiseMapScale);
+
+ // make ocean elevation the lower limit
+ if (sampleHeight < ImprovedTerrainSampler.scaledOceanElevation)
+ {
+ sampleHeight = ImprovedTerrainSampler.scaledOceanElevation;
+ }
+
+ // normalize with TerrainHelper.maxTerrainHeight
+ worldHeights[worldMapHeight - 1 - y, x] = Mathf.Clamp01(sampleHeight / ImprovedTerrainSampler.maxTerrainHeight);
+ }
+ }
+
+ // Basemap not used and is just pushed far away
+ const float basemapDistance = 1000000f;
+
+ // Ensure TerrainData is created
+ Terrain terrain = terrainGameObject.GetComponent();
+ if (terrain.terrainData == null)
+ {
+ // Setup terrain data
+ TerrainData terrainData = new TerrainData();
+ terrainData.name = "TerrainData";
+
+ // this is not really an assignment! you tell unity terrain what resolution you want for your heightmap and it will allocate resources and take the next power of 2 increased by 1 as heightmapResolution...
+ terrainData.heightmapResolution = worldMapResolution;
+
+ float heightmapResolution = terrainData.heightmapResolution;
+ // Calculate width and length of terrain in world units
+ float terrainSize = ((MapsFile.WorldMapTerrainDim * MeshReader.GlobalScale) * (heightmapResolution - 1.0f));
+
+
+ terrainData.size = new Vector3(terrainSize, ImprovedTerrainSampler.maxTerrainHeight, terrainSize);
+
+ //terrainData.size = new Vector3(terrainSize, TerrainHelper.maxTerrainHeight * TerrainScale * worldMapResolution, terrainSize);
+ terrainData.SetDetailResolution(worldMapResolution, 16);
+ terrainData.alphamapResolution = worldMapResolution;
+ terrainData.baseMapResolution = worldMapResolution;
+
+ // Apply terrain data
+ terrain.terrainData = terrainData;
+ terrain.basemapDistance = basemapDistance;
+ }
+
+ terrain.heightmapPixelError = 0; // 0 ... prevent unity terrain lod approach, set to higher values to enable it
+ //terrain.castShadows = true;
+
+ // Promote heights
+ Vector3 size = terrain.terrainData.size;
+ terrain.terrainData.size = new Vector3(size.x, ImprovedTerrainSampler.maxTerrainHeight * streamingWorld.TerrainScale, size.z);
+ terrain.terrainData.SetHeights(0, 0, worldHeights);
+
+
+ // update world terrain position - do this before terrainGameObject.transform invocation, so that object2world matrix is updated with correct values
+ Vector3 offset = new Vector3(0.0f, 0.0f, 0.0f);
+ updatePositionWorldTerrain(ref terrainGameObject, offset);
+
+ textureAtlasDesertSummer = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(2).albedoMap;
+ textureAtlasDesertSummer.filterMode = FilterMode.Point;
+
+ textureAtlasWoodlandSummer = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(302).albedoMap;
+ textureAtlasWoodlandSummer.filterMode = FilterMode.Point;
+
+ textureAtlasMountainSummer = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(102).albedoMap;
+ textureAtlasMountainSummer.filterMode = FilterMode.Point;
+
+ textureAtlasSwampSummer = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(402).albedoMap;
+ textureAtlasSwampSummer.filterMode = FilterMode.Point;
+
+ textureAtlasDesertWinter = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(3).albedoMap;
+ textureAtlasDesertWinter.filterMode = FilterMode.Point;
+
+ textureAtlasWoodlandWinter = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(303).albedoMap;
+ textureAtlasWoodlandWinter.filterMode = FilterMode.Point;
+
+ textureAtlasMountainWinter = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(103).albedoMap;
+ textureAtlasMountainWinter.filterMode = FilterMode.Point;
+
+ textureAtlasSwampWinter = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(403).albedoMap;
+ textureAtlasSwampWinter.filterMode = FilterMode.Point;
+
+ textureAtlasDesertRain = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(4).albedoMap;
+ textureAtlasDesertRain.filterMode = FilterMode.Point;
+
+ textureAtlasWoodlandRain = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(304).albedoMap;
+ textureAtlasWoodlandRain.filterMode = FilterMode.Point;
+
+ textureAtlasMountainRain = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(104).albedoMap;
+ textureAtlasMountainRain.filterMode = FilterMode.Point;
+
+ textureAtlasSwampRain = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(404).albedoMap;
+ textureAtlasSwampRain.filterMode = FilterMode.Point;
+
+ terrainMaterial = new Material(Shader.Find("Daggerfall/IncreasedTerrainTilemap"));
+ terrainMaterial.name = string.Format("world terrain material");
+
+ // Assign textures and parameters
+ terrainMaterial.SetTexture("_TileAtlasTexDesert", textureAtlasDesertSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexWoodland", textureAtlasWoodlandSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexMountain", textureAtlasMountainSummer);
+ terrainMaterial.SetTexture("_TileAtlasTexSwamp", textureAtlasSwampSummer);
+ //terrainMaterial.SetTexture("_TilemapTex", textureTerrainInfoTileMap);
+
+ terrainMaterial.SetInt("_TextureSetSeasonCode", 0);
+
+ updateSeasonalTextures(); // change seasonal textures if necessary
+
+ terrainMaterial.SetInt("_PlayerPosX", this.playerGPS.CurrentMapPixel.X);
+ terrainMaterial.SetInt("_PlayerPosY", this.playerGPS.CurrentMapPixel.Y);
+
+ terrainMaterial.SetInt("_TerrainDistance", streamingWorld.TerrainDistance - 1); // -1... allow the outer ring of of detailed terrain to intersect with far terrain (to prevent some holes)
+
+ Vector3 vecWaterHeight = new Vector3(0.0f, (ImprovedTerrainSampler.scaledOceanElevation + 1.0f) * streamingWorld.TerrainScale, 0.0f); // water height level on y-axis (+1.0f some coastlines are incorrect otherwise)
+ Vector3 vecWaterHeightTransformed = terrainGameObject.transform.TransformPoint(vecWaterHeight); // transform to world coordinates
+ terrainMaterial.SetFloat("_WaterHeightTransformed", vecWaterHeightTransformed.y);
+
+ terrainMaterial.SetTexture("_SkyTex", renderTextureSky);
+
+ //Texture2D myTex = Resources.Load("test_pattern_texture") as Texture2D;
+ //terrainMaterial.SetTexture("_SkyTex", myTex);
+
+ if (RenderSettings.fog == true)
+ {
+ if (RenderSettings.fogMode == FogMode.Linear)
+ terrainMaterial.SetInt("_FogMode", 1);
+ else if (RenderSettings.fogMode == FogMode.Exponential)
+ terrainMaterial.SetInt("_FogMode", 2);
+ else if (RenderSettings.fogMode == FogMode.ExponentialSquared)
+ terrainMaterial.SetInt("_FogMode", 3);
+ }
+ else
+ {
+ terrainMaterial.SetInt("_FogMode", 0);
+ }
+
+ terrainMaterial.SetFloat("_FogFromSkyTex", 0);
+
+ //terrainMaterial.SetFloat("_BlendFactor", blendFactor);
+ terrainMaterial.SetFloat("_BlendStart", blendStart);
+ terrainMaterial.SetFloat("_BlendEnd", blendEnd);
+
+ // Promote material
+ terrain.materialTemplate = terrainMaterial;
+ terrain.materialType = Terrain.MaterialType.Custom;
+
+ terrainGameObject.SetActive(true);
+
+ return (terrainGameObject);
+ }
+
+ #endregion
+
+ #region Startup/Shutdown Methods
+
+ private bool ReadyCheck()
+ {
+ if (isReady)
+ return true;
+
+ if (dfUnity == null)
+ {
+ dfUnity = DaggerfallUnity.Instance;
+ }
+
+ // Do nothing if DaggerfallUnity not ready
+ if (!dfUnity.IsReady)
+ {
+ DaggerfallUnity.LogMessage("ExtendedTerrainDistance: DaggerfallUnity component is not ready. Have you set your Arena2 path?");
+ return false;
+ }
+
+ // Raise ready flag
+ isReady = true;
+
+ return true;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/IncreasedTerrainDistance.cs.meta b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/IncreasedTerrainDistance.cs.meta
new file mode 100644
index 0000000000..a982387f50
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/IncreasedTerrainDistance.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: c73e2303837c8cb4e9bd0ee1c2fc291a
+timeCreated: 1445869506
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/RenderSkyboxWithoutSun.cs b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/RenderSkyboxWithoutSun.cs
new file mode 100644
index 0000000000..05257fc528
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/RenderSkyboxWithoutSun.cs
@@ -0,0 +1,44 @@
+//Increased Terrain Distance Mod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 1.54
+
+using UnityEngine;
+//using System;
+//using System.Collections;
+//using System.Collections.Generic;
+//using System.IO;
+//using DaggerfallConnect;
+//using DaggerfallConnect.Arena2;
+//using DaggerfallConnect.Utility;
+//using DaggerfallWorkshop;
+using DaggerfallWorkshop.Game;
+//using DaggerfallWorkshop.Utility;
+
+namespace ProjectIncreasedTerrainDistance
+{
+ public class RenderSkyboxWithoutSun : MonoBehaviour
+ {
+ float revertSunSize;
+
+ void OnPreRender()
+ {
+ if (RenderSettings.skybox)
+ {
+ revertSunSize = RenderSettings.skybox.GetFloat("_SunSize");
+ RenderSettings.skybox.SetFloat("_SunSize", 0.0f);
+ }
+ }
+
+ void OnPostRender()
+ {
+ if (RenderSettings.skybox)
+ {
+ RenderSettings.skybox.SetFloat("_SunSize", revertSunSize);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Scripts/RenderSkyboxWithoutSun.cs.meta b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/RenderSkyboxWithoutSun.cs.meta
new file mode 100644
index 0000000000..610c052fe5
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Scripts/RenderSkyboxWithoutSun.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 19364de74fc6bc949a025b4a396e0f62
+timeCreated: 1445869506
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Shaders.meta b/Game/Addons/IncreasedTerrainDistanceMod/Shaders.meta
new file mode 100644
index 0000000000..f42a4e92a6
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Shaders.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: e42a24cb28ee68d48a7c3dce5187f95a
+folderAsset: yes
+timeCreated: 1445869505
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Shaders/DaggerfallIncreasedTerrainTilemap.shader b/Game/Addons/IncreasedTerrainDistanceMod/Shaders/DaggerfallIncreasedTerrainTilemap.shader
new file mode 100644
index 0000000000..ca3eb67ab9
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Shaders/DaggerfallIncreasedTerrainTilemap.shader
@@ -0,0 +1,321 @@
+//Increased Terrain Distance Mod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 1.54
+
+Shader "Daggerfall/IncreasedTerrainTilemap" {
+ Properties {
+ // These params are required to stop terrain system throwing errors
+ // However we won't be using them as Unity likes to init these textures
+ // and will overwrite any assignments we already made
+ // TODO: Combine splat painting with tilemapping
+ [HideInInspector] _MainTex("BaseMap (RGB)", 2D) = "white" {}
+ [HideInInspector] _Control ("Control (RGBA)", 2D) = "red" {}
+ [HideInInspector] _SplatTex3("Layer 3 (A)", 2D) = "white" {}
+ [HideInInspector] _SplatTex2("Layer 2 (B)", 2D) = "white" {}
+ [HideInInspector] _SplatTex1("Layer 1 (G)", 2D) = "white" {}
+ [HideInInspector] _SplatTex0("Layer 0 (R)", 2D) = "white" {}
+
+ // These params are used for our shader
+ _TileAtlasTexDesert ("Tileset Atlas (RGB)", 2D) = "white" {}
+ _TileAtlasTexWoodland ("Tileset Atlas (RGB)", 2D) = "white" {}
+ _TileAtlasTexMountain ("Tileset Atlas (RGB)", 2D) = "white" {}
+ _TileAtlasTexSwamp ("Tileset Atlas (RGB)", 2D) = "white" {}
+ _SkyTex("Sky Texture", 2D) = "white" {}
+ _TilemapTex("Tilemap (R)", 2D) = "red" {}
+ _TilesetDim("Tileset Dimension (in tiles)", Int) = 16
+ _TilemapDim("Tilemap Dimension (in tiles)", Int) = 1000
+ _MaxIndex("Max Tileset Index", Int) = 255
+ _AtlasSize("Atlas Size (in pixels)", Float) = 2048.0
+ _GutterSize("Gutter Size (in pixels)", Float) = 32.0
+ _PlayerPosX("Player Position in X-direction on world map", Int) = 0
+ _PlayerPosY("Player Position in X-direction on world map", Int) = 0
+ _TerrainDistance("Terrain Distance", Int) = 3
+ _WaterHeightTransformed("water level on y-axis in world coordinates", Float) = 58.9
+ _TextureSetSeasonCode("specifies which seasonal/weather texture set is used (0...Summer, 1...Winter/Snow, 2...Rain)", Int) = 0
+ _BlendStart("blend start distance for blending distant terrain into skybox", Float) = 15000.0
+ _BlendEnd("blend end distance for blending distant terrain into skybox", Float) = 200000.0
+ _FogMode("Fog Mode", Int) = 1
+ _FogDensity("Fog Density", Float) = 0.01
+ _FogFromSkyTex("specifies if fog color should be derived from sky texture or not", Int) = 0
+ }
+ SubShader {
+ Tags { "RenderType"="Opaque" }
+ LOD 200
+
+ // extra pass that renders to depth buffer only (world terrain is semi-transparent) - important for correct blending
+ Pass {
+ ZWrite On
+ ColorMask 0
+ }
+
+ CGPROGRAM
+ #pragma target 3.0
+ #pragma surface surf Lambert alpha:fade keepalpha finalcolor:fcolor noforwardadd
+ #pragma glsl
+
+ #define PI 3.1416f
+
+ sampler2D _TileAtlasTexDesert;
+ sampler2D _TileAtlasTexWoodland;
+ sampler2D _TileAtlasTexMountain;
+ sampler2D _TileAtlasTexSwamp;
+ sampler2D _SkyTex;
+ sampler2D _TilemapTex;
+ int _TilesetDim;
+ int _TilemapDim;
+ int _MaxIndex;
+ float _AtlasSize;
+ float _GutterSize;
+ float _WaterHeightTransformed;
+ int _TerrainDistance;
+ int _PlayerPosX;
+ int _PlayerPosY;
+ int _TextureSetSeasonCode;
+ float _BlendStart;
+ float _BlendEnd;
+
+ int _FogMode;
+ float _FogDensity;
+ int _FogFromSkyTex;
+
+ struct Input
+ {
+ float2 uv_MainTex;
+ float3 worldPos; // interpolated vertex positions used for correct coast line texturing
+ float3 worldNormal; // interpolated vertex normals used for texturing terrain based on terrain slope
+ float4 screenPos;
+ };
+
+ void fcolor (Input IN, SurfaceOutput o, inout fixed4 color)
+ {
+ float dist = distance(IN.worldPos.xz, _WorldSpaceCameraPos.xz); //max(abs(IN.worldPos.x - _WorldSpaceCameraPos.x), abs(IN.worldPos.z - _WorldSpaceCameraPos.z));
+
+ float blendFacTerrain = 1.0f;
+
+ /*
+ if (_FogMode == 1) // linear
+ {
+ blendFacTerrain = max(0.0f, min(1.0f, dist * unity_FogParams.z + unity_FogParams.w));
+ }
+ if (_FogMode == 2) // exp
+ {
+ // factor = exp(-density*z)
+ float fogFac = 0.0;
+ fogFac = unity_FogParams.y * dist;
+ blendFacTerrain = exp2(-fogFac);
+
+ }
+ if (_FogMode == 3) // exp2
+ {
+ // factor = exp(-(density*z)^2)
+ float fogFac = 0.0;
+ fogFac = unity_FogParams.x * dist;
+ blendFacTerrain = exp2(-fogFac*fogFac);
+ }
+ */
+
+ // factor = exp(-density*z)
+ float fogFac = _FogDensity * dist;
+ blendFacTerrain = exp2(-fogFac);
+
+ const float fadeRange = _BlendEnd - _BlendStart + 1.0f;
+ float alphaFadeAmount = max(0.0f, min(1.0f, (_BlendEnd - dist) / fadeRange));
+
+ if (_FogFromSkyTex == 1)
+ {
+ float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
+ color.rgb = blendFacTerrain * color.rgb + (1.0f - blendFacTerrain) * tex2D(_SkyTex, screenUV).rgb;
+ }
+ else
+ {
+ color.rgb = blendFacTerrain * color.rgb + (1.0f - blendFacTerrain) * unity_FogColor.rgb;
+ }
+ color.a = alphaFadeAmount;
+ }
+
+ half4 getColorByTextureAtlasIndex(Input IN, uniform sampler2D textureAtlas, uint index)
+ {
+ const float textureCrispness = 3.5f; // defines how crisp textures of extended terrain are (higher values result in more crispness)
+ const float textureCrispnessDiminishingFactor = 0.075f; // defines how fast crispness of textures diminishes with more distance from the player (the camera)
+ const float distanceAttenuation = 0.001; // used to attenuated computed distance
+
+ float dist = max(abs(IN.worldPos.x - _WorldSpaceCameraPos.x), abs(IN.worldPos.z - _WorldSpaceCameraPos.z));
+ dist = floor(dist*distanceAttenuation);
+
+ uint xpos = index % _TilesetDim;
+ uint ypos = index / _TilesetDim;
+ float2 uv = float2(xpos, ypos) / _TilesetDim;
+
+ // Offset to fragment position inside tile
+ float xoffset;
+ float yoffset;
+ // changed offset computation so that tile texture repeats over tile
+ xoffset = frac(IN.uv_MainTex.x * _TilemapDim * 1/(max(1,dist * textureCrispnessDiminishingFactor)) * textureCrispness ) / _GutterSize;
+ yoffset = frac(IN.uv_MainTex.y * _TilemapDim * 1/(max(1,dist * textureCrispnessDiminishingFactor)) * textureCrispness ) / _GutterSize;
+
+ uv += float2(xoffset, yoffset) + _GutterSize / _AtlasSize;
+
+ // Sample based on gradient and set output
+ float2 uvr = IN.uv_MainTex * ((float)_TilemapDim / _GutterSize);
+ half4 c = tex2Dgrad(textureAtlas, uv, ddx(uvr), ddy(uvr));
+ return(c);
+ }
+
+ void surf (Input IN, inout SurfaceOutput o)
+ {
+ const float limitAngleDirtTexture = 12.5f * PI / 180.0f; // tile will get dirt texture assigned if angles definied by surface normal and up-vector is larger than this value (and not larger than limitAngleStoneTexture)
+ const float limitAngleStoneTexture = 20.5f * PI / 180.0f; // tile will get stone texture assigned if angles definied by surface normal and up-vector is larger than this value
+
+ half4 c; // output color value
+
+ half4 c_g; // color value from grass texture
+ half4 c_d; // color value from dirt texture
+ half4 c_s; // color value from stone texture
+
+ float weightGrass = 1.0f;
+ float weightDirt = 0.0f;
+ float weightStone = 0.0f;
+
+ float4 terrainTileInfo = tex2D(_TilemapTex, IN.uv_MainTex).rgba;
+
+ int mapPixelX = IN.uv_MainTex.x*_TilemapDim;
+ int mapPixelY = 499 - IN.uv_MainTex.y*_TilemapDim;
+
+ // fragment discarding inside area spanned by _TerrainDistance
+ if ((abs(mapPixelX+1-_PlayerPosX)<=_TerrainDistance)&&(abs(mapPixelY+1-_PlayerPosY)<=_TerrainDistance))
+ {
+ //float4 ret = float4(1.0f,0.0f,0.0f,1.0f); // for debugging fragment discard area use red color (also used to debug world terrain positioning with floating origin script)
+ //o.Albedo = ret.rgb;
+ //o.Alpha = ret.a;
+ //return;
+ discard;
+ }
+
+ // fragments more distant than _BlendEnd will be discarded as well
+ const float fadeRange = _BlendEnd - _BlendStart + 1.0f;
+ float dist = distance(IN.worldPos.xz, _WorldSpaceCameraPos.xz); //max(abs(IN.worldPos.x - _WorldSpaceCameraPos.x), abs(IN.worldPos.z - _WorldSpaceCameraPos.z));
+ if (dist>_BlendEnd)
+ {
+ discard;
+ }
+
+ int index = terrainTileInfo.r * _MaxIndex;
+
+ // there are several possibilities to get the tile surface normal...
+ // float3 surfaceNormal = normalize(cross(ddx(IN.worldPos.xyz), ddy(IN.worldPos.xyz))); // approximate it from worldPosition with derivations
+ // float3 surfaceNormal = normalize(o.Normal); // interpolated vertex normal
+ // float3 surfaceNormal = IN.worldNormal; // interpolated vertex normal (by input parameter)
+ // float3 surfaceNormal = WorldNormalVector(IN, o.Normal); // don't know what the difference is (googled it - was mentioned that it does not get interpolated but i can't confirm this)
+ // float3 surfaceNormal = 0.95f*(IN.worldNormal)+0.05f*normalize(cross(ddx(IN.worldPos.xyz), ddy(IN.worldPos.xyz))); // linear interpolation of interpolated vertex normal and approximated normal
+
+
+ const float3 upVec = float3(0.0f, 1.0f, 0.0f);
+ float dotResult = dot(normalize(IN.worldNormal), upVec);
+
+
+ if (acos(dotResult) < limitAngleDirtTexture) // between angles 0 to limitAngleDirtTexture interpolate between grass and dirt texture
+ {
+ weightGrass = 1.0f - acos(dotResult) / limitAngleDirtTexture;
+ weightDirt = acos(dotResult) / limitAngleDirtTexture;
+ weightStone = 0.0f;
+ }
+ else // between angles limitAngleDirtTexture to limitAngleStoneTexture interpolate between dirt and stone texture (limitAngleStoneTexture to 90 degrees -> also stone texture)
+ {
+ weightGrass = 0.0f;
+ weightDirt = 1.0f - min(1.0f, (acos(dotResult) - limitAngleDirtTexture) / limitAngleStoneTexture);
+ weightStone = min(1.0f, (acos(dotResult) - limitAngleDirtTexture) / limitAngleStoneTexture);
+ }
+
+ if ((index==223)||(IN.worldPos.y < _WaterHeightTransformed)) // water (either by tile index or by tile world position)
+ {
+ c = getColorByTextureAtlasIndex(IN, _TileAtlasTexWoodland, 0);
+ //discard;
+ }
+ else if ((index==224)||(index==225)||(index==229)) // desert
+ {
+ c_g = getColorByTextureAtlasIndex(IN, _TileAtlasTexDesert, 8);
+ c_d = getColorByTextureAtlasIndex(IN, _TileAtlasTexDesert, 4);
+ c_s = getColorByTextureAtlasIndex(IN, _TileAtlasTexDesert, 12);
+ c = c_g * weightGrass + c_d * weightDirt + c_s * weightStone;
+ }
+ else if ((index==227)||(index==228)) // swamp
+ {
+ c_g = getColorByTextureAtlasIndex(IN, _TileAtlasTexSwamp, 8);
+ c_d = getColorByTextureAtlasIndex(IN, _TileAtlasTexSwamp, 4);
+ c_s = getColorByTextureAtlasIndex(IN, _TileAtlasTexSwamp, 12);
+ c = c_g * weightGrass + c_d * weightDirt + c_s * weightStone;
+ }
+ else if ((index==226)||(index==230)) // mountain
+ {
+ c_g = getColorByTextureAtlasIndex(IN, _TileAtlasTexMountain, 8);
+ c_d = getColorByTextureAtlasIndex(IN, _TileAtlasTexMountain, 4);
+ c_s = getColorByTextureAtlasIndex(IN, _TileAtlasTexMountain, 12);
+ c = c_g * weightGrass + c_d * weightDirt + c_s * weightStone;
+ }
+ else if ((index==231)||(index==232)||(index==233)) // moderate
+ {
+ c_g = getColorByTextureAtlasIndex(IN, _TileAtlasTexWoodland, 8);
+ c_d = getColorByTextureAtlasIndex(IN, _TileAtlasTexWoodland, 4);
+ c_s = getColorByTextureAtlasIndex(IN, _TileAtlasTexWoodland, 12);
+ c = c_g * weightGrass + c_d * weightDirt + c_s * weightStone;
+ }
+ else
+ {
+ c=half4(0.0f, 0.0f, 0.0f, 1.0f);
+ }
+
+ float treeCoverage = terrainTileInfo.g;
+
+ uint locationRangeX = terrainTileInfo.b * _MaxIndex;
+ uint locationRangeY = terrainTileInfo.a * _MaxIndex;
+
+ half3 treeColor;
+
+ // next line is the location placement I started with and which I understand...
+ // if ((locationXfract < (float)locationRangeX/8.0f) && (locationYfract < (float)locationRangeY / 8.0f)) // 8.0f is maximum location range (8 rmb blocks) - blocks are placed in the corner, so I played around with placement and came up with the following lines (no idea why it works but it does)
+
+ // do not ask me what I am doing here, I simple don't know - just played around till it kind of fitted (locations are placed in the middle of its corresponding tile now...)
+ float extraX = (2.0f - (float)locationRangeX) * (1.0f/64.0f); // 64? yeah don't ask
+ float xDividor = (16.0f - (float)locationRangeX); // don't ask about the 16 as well :D
+
+ float extraY = (2.0f - (float)locationRangeY) * (1.0f/64.0f);
+ float yDividor = (16.0f - (float)locationRangeY);
+
+ float locationXfract = (IN.uv_MainTex.x*(float)_TilemapDim)-0.5f - (float)mapPixelX;
+ float locationYfract = (499 - IN.uv_MainTex.y*(float)_TilemapDim)-0.5f - (float)mapPixelY;
+
+ // for debugging - red location markers
+ //if ((abs(locationXfract - extraX) < (float)locationRangeX/xDividor) && (abs(locationYfract - extraY) < (float)locationRangeY/yDividor))
+ //{
+ // c.rgb = 0.5f * c.rgb + 0.5f * half3(1.0f, 0.0f, 0.0f);
+ //}
+
+ if (_TextureSetSeasonCode == 0)
+ treeColor = half3(0.125f, 0.165f, 0.061f);
+ else if (_TextureSetSeasonCode == 1)
+ treeColor = half3(0.96f, 0.98f, 0.94f);
+ else if (_TextureSetSeasonCode == 2)
+ treeColor = half3(0.10f, 0.14f, 0.04f);
+
+ if ((abs(locationXfract - extraX) < (float)locationRangeX/xDividor) && (abs(locationYfract - extraY) < (float)locationRangeY/yDividor))
+ {
+ c.rgb = min(1.0f, 0.4f * c.rgb + 0.6f * ((1.0f - treeCoverage) * c.rgb + treeCoverage * treeColor));
+ c.rgb = 1.0f * c.rgb;
+ }
+ else
+ {
+ c.rgb = min(1.0f, 0.3f * c.rgb + 0.7f * ((1.0f - treeCoverage) * c.rgb + treeCoverage * treeColor));
+ c.rgb = 0.9f * c.rgb;
+ }
+
+ o.Albedo = c.rgb;
+ }
+ ENDCG
+ }
+
+
+ FallBack "Diffuse"
+}
diff --git a/Game/Addons/IncreasedTerrainDistanceMod/Shaders/DaggerfallIncreasedTerrainTilemap.shader.meta b/Game/Addons/IncreasedTerrainDistanceMod/Shaders/DaggerfallIncreasedTerrainTilemap.shader.meta
new file mode 100644
index 0000000000..99344c6ab5
--- /dev/null
+++ b/Game/Addons/IncreasedTerrainDistanceMod/Shaders/DaggerfallIncreasedTerrainTilemap.shader.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 6fcdb68e5c0026a4687f88caa539e703
+timeCreated: 1445869511
+licenseType: Free
+ShaderImporter:
+ defaultTextures: []
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod.meta b/Game/Addons/ReflectionsMod.meta
new file mode 100644
index 0000000000..364fbcec20
--- /dev/null
+++ b/Game/Addons/ReflectionsMod.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: ffd17ace2e9bf5849ad73aa2eced00a2
+folderAsset: yes
+timeCreated: 1445868463
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Prefabs.meta b/Game/Addons/ReflectionsMod/Prefabs.meta
new file mode 100644
index 0000000000..d11e0336e1
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Prefabs.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 6989a7b20a96b1947a9fef7fdc59d64d
+folderAsset: yes
+timeCreated: 1445868463
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Prefabs/ReflectionsMod.prefab b/Game/Addons/ReflectionsMod/Prefabs/ReflectionsMod.prefab
new file mode 100644
index 0000000000..a84df9effc
Binary files /dev/null and b/Game/Addons/ReflectionsMod/Prefabs/ReflectionsMod.prefab differ
diff --git a/Game/Addons/ReflectionsMod/Prefabs/ReflectionsMod.prefab.meta b/Game/Addons/ReflectionsMod/Prefabs/ReflectionsMod.prefab.meta
new file mode 100644
index 0000000000..90b716ea61
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Prefabs/ReflectionsMod.prefab.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 12e13161da1e1bf4db32ff2f21dc35dc
+timeCreated: 1445869025
+licenseType: Free
+NativeFormatImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Resources.meta b/Game/Addons/ReflectionsMod/Resources.meta
new file mode 100644
index 0000000000..e02c74b2cd
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: d650caed9a98ed84f9629bc4e74a630c
+folderAsset: yes
+timeCreated: 1445868463
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Resources/configInjectionTextures.ini b/Game/Addons/ReflectionsMod/Resources/configInjectionTextures.ini
new file mode 100644
index 0000000000..614ed03e7a
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources/configInjectionTextures.ini
@@ -0,0 +1,446 @@
+; ReflectionsMod for Daggerfall Tools For Unity by Nystul
+; http://www.reddit.com/r/dftfu
+; http://www.dfworkshop.net/
+; Author: Michael Rauter (a.k.a. Nystul)
+; License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+; Version: 0.32
+
+
+;
+; -----------------------
+; formatting description:
+; -----------------------
+;
+; example for texture-global reflectivity and smoothness:
+;
+; [337_3 {Mages Guild Ground Texture (Woodland)}]
+; textureArchive = 337
+; textureRecord = 3
+; textureFrame = 0
+; useMetallicGlossMap = false
+; reflectivity = 0.6
+; smoothness = 0.9
+;
+;
+; example for reflectivity and smoothness from metallic gloss map:
+; (metallic (reflectivity) is sampled from r-channel of texture, gloss (smoothness) is sampled from alpha channel of texture)
+; filename of metallic gloss map has to be without file extension (e.g. don't put .png in there) and the file needs to be in the Resources folder
+;
+; [444_3 {House Ground Texture (Swamp)}]
+; textureArchive = 444
+; textureRecord = 3
+; textureFrame = 0
+; useMetallicGlossMap = true
+; filenameMetallicGlossMap = metallicGlossMap_Texture_444_3
+;
+; you can inject albedo and normal maps also - ONLY WORKING FOR FLOOR TEXTURES CORRECTLY!!! - (e.g. for texture files in Resources folder and filenames custom_texture.png and custom_texture_normals.png):
+; filenameAlbedoMap = custom_texture
+; filenameNormalMap = custom_texture_normals
+;
+
+
+[337_3 {Mages Guild Ground Texture (Woodland)}]
+textureArchive = 337
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.6
+smoothness = 0.9
+
+[037_3 {Mages Guild Ground Texture (Desert)}]
+textureArchive = 037
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.4
+smoothness = 0.8
+
+[137_3 {Mages Guild Ground Texture (Mountain)}]
+textureArchive = 137
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.5
+smoothness = 0.9
+
+[437_3 {Mages Guild Ground Texture (Swamp)}]
+textureArchive = 437
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.6
+smoothness = 0.9
+
+[363_3 {Temple Ground Texture (Woodland)}]
+textureArchive = 363
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.4
+smoothness = 0.96
+
+[063_3 {Temple Ground Texture (Desert)}]
+textureArchive = 063
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.4
+smoothness = 0.96
+
+[463_3 {Temple Ground Texture (Swamp)}]
+textureArchive = 463
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.4
+smoothness = 0.96
+
+[360_3 {Kights Order Ground Texture (Woodland)}]
+textureArchive = 360
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.1
+smoothness = 0.75
+
+[060_3 {Kights Order Ground Texture (Desert)}]
+textureArchive = 060
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.1
+smoothness = 0.75
+
+[460_3 {Kights Order Ground Texture (Swamp)}]
+textureArchive = 460
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.1
+smoothness = 0.55
+
+[350_1 {Castle Daggerfall Ground Texture}]
+textureArchive = 350
+textureRecord = 1
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.6
+smoothness = 0.875
+
+[352_1 {Castle Wayrest Ground Texture}]
+textureArchive = 352
+textureRecord = 1
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.5
+smoothness = 0.0925
+
+[354_1 {Castle Sentinel Ground Texture}]
+textureArchive = 354
+textureRecord = 1
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.3
+smoothness = 0.875
+
+[150_1 {Shedugant Ground Texture}]
+textureArchive = 150
+textureRecord = 1
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.6
+smoothness = 0.875
+
+[152_1 {Woodborne Ground Texture}]
+textureArchive = 152
+textureRecord = 1
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.6
+smoothness = 0.875
+
+[153_1 {Lysandus Tomb Ground Texture}]
+textureArchive = 153
+textureRecord = 1
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.07
+smoothness = 0.875
+
+[016_3 {House Ground Texture (Desert)}]
+textureArchive = 016
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.2
+smoothness = 0.95
+
+[040_3 {House Ground Texture (Desert)}]
+textureArchive = 040
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.18
+smoothness = 0.85
+
+[044_3 {House and Palace Ground Texture (Desert)}]
+textureArchive = 044
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.3
+smoothness = 0.95
+
+[116_3 {House Ground Texture (Mountain)}]
+textureArchive = 116
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.04
+smoothness = 0.67
+
+[140_3 {House Ground Texture (Mountain)}]
+textureArchive = 140
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.075
+smoothness = 0.71
+
+[328_3 {House Ground Texture (Mountain)}]
+textureArchive = 328
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.1
+smoothness = 0.7
+
+[366_3 {House Ground Texture (Mountain)}]
+textureArchive = 366
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.1
+smoothness = 0.7
+
+[019_2 {Crypt Ground Texture (Desert)}]
+textureArchive = 019
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.14
+smoothness = 0.5
+
+[020_2 {Crypt Ground Texture (Desert)}]
+textureArchive = 020
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.16
+smoothness = 0.5625
+
+[022_2 {Dungeon Ground Texture (Desert)}]
+textureArchive = 022
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.09
+smoothness = 0.35
+
+[023_2 {Dungeon Ground Texture (Desert)}]
+textureArchive = 023
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.12
+smoothness = 0.75
+
+[024_2 {Dungeon Ground Texture (Desert)}]
+textureArchive = 024
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.14
+smoothness = 0.9725
+
+[119_2 {Crypt Ground Texture (Mountain)}]
+textureArchive = 119
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.18
+smoothness = 0.71875
+
+[120_2 {Crypt Ground Texture (Mountain)}]
+textureArchive = 120
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.08
+smoothness = 0.3
+
+[122_2 {Dungeon Ground Texture (Mountain)}]
+textureArchive = 122
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.17
+smoothness = 0.8
+
+[123_2 {Dungeon Ground Texture (Mountain)}]
+textureArchive = 123
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.16
+smoothness = 0.7
+
+[124_2 {Dungeon Ground Texture (Mountain)}]
+textureArchive = 124
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.2
+smoothness = 0.8
+
+[168_2 {Sewer Ground Texture (Mountain)}]
+textureArchive = 168
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.14
+smoothness = 0.69375
+
+[319_2 {Crypt Ground Texture (Woodland)}]
+textureArchive = 319
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.18
+smoothness = 0.72
+
+[320_2 {Crypt Ground Texture (Woodland)}]
+textureArchive = 320
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.3
+smoothness = 0.62
+
+[322_2 {Dungeon Ground Texture (Woodland)}]
+textureArchive = 322
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.14
+smoothness = 0.8
+
+[323_2 {Dungeon Ground Texture (Woodland)}]
+textureArchive = 323
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.12
+smoothness = 0.75
+
+[324_2 {Dungeon Ground Texture (Woodland)}]
+textureArchive = 324
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.18
+smoothness = 0.85
+
+[419_2 {Crypt Ground Texture (Swamp)}]
+textureArchive = 419
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.18
+smoothness = 0.72
+
+[420_2 {Crypt Ground Texture (Swamp)}]
+textureArchive = 420
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.15
+smoothness = 0.69
+
+[422_2 {Dungeon Ground Texture (Swamp)}]
+textureArchive = 422
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.16
+smoothness = 0.725
+
+[423_2 {Dungeon Ground Texture (Swamp)}]
+textureArchive = 423
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.1
+smoothness = 0.5
+
+[424_2 {Dungeon Ground Texture (Swamp)}]
+textureArchive = 424
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.15
+smoothness = 0.6
+
+[468_2 {Sewer Ground Texture (Swamp)}]
+textureArchive = 468
+textureRecord = 2
+textureFrame = 0
+useMetallicGlossMap = false
+reflectivity = 0.14
+smoothness = 0.65
+
+
+
+[311_3 {House Ground Texture (Moderate)}]
+textureArchive = 311
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = true
+; similar to texture 444_3 - so use its metallglossmap texture
+filenameMetallicGlossMap = metallicGlossMap_Texture_444_3
+
+[316_3 {House Ground Texture (Moderate)}]
+textureArchive = 316
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = true
+filenameMetallicGlossMap = metallicGlossMap_Texture_316_3
+
+[344_3 {House Ground Texture (Moderate)}]
+textureArchive = 344
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = true
+filenameMetallicGlossMap = metallicGlossMap_Texture_344_3
+
+[411_3 {House Ground Texture (Swamp)}]
+textureArchive = 411
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = true
+; similar to texture 444_3 - so use its metallglossmap texture
+filenameMetallicGlossMap = metallicGlossMap_Texture_444_3
+
+[440_3 {House Ground Texture (Swamp)}]
+textureArchive = 440
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = true
+filenameMetallicGlossMap = metallicGlossMap_Texture_440_3
+
+[444_3 {House Ground Texture (Swamp)}]
+textureArchive = 444
+textureRecord = 3
+textureFrame = 0
+useMetallicGlossMap = true
+filenameMetallicGlossMap = metallicGlossMap_Texture_444_3
diff --git a/Game/Addons/ReflectionsMod/Resources/configInjectionTextures.ini.meta b/Game/Addons/ReflectionsMod/Resources/configInjectionTextures.ini.meta
new file mode 100644
index 0000000000..b20d2a2572
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources/configInjectionTextures.ini.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 961a48610a2aab34792b21065f46f7ef
+timeCreated: 1445868463
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_316_3.png b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_316_3.png
new file mode 100644
index 0000000000..99572f02b5
Binary files /dev/null and b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_316_3.png differ
diff --git a/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_316_3.png.meta b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_316_3.png.meta
new file mode 100644
index 0000000000..e14144be6b
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_316_3.png.meta
@@ -0,0 +1,56 @@
+fileFormatVersion: 2
+guid: f7c0735cec551f44eab6574768112d77
+timeCreated: 1445868466
+licenseType: Free
+TextureImporter:
+ fileIDToRecycleName: {}
+ serializedVersion: 2
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ linearTexture: 0
+ correctGamma: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: .25
+ normalMapFilter: 0
+ isReadable: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 0
+ cubemapConvolution: 0
+ cubemapConvolutionSteps: 8
+ cubemapConvolutionExponent: 1.5
+ seamlessCubemap: 0
+ textureFormat: -1
+ maxTextureSize: 2048
+ textureSettings:
+ filterMode: -1
+ aniso: -1
+ mipBias: -1
+ wrapMode: -1
+ nPOTScale: 1
+ lightmap: 0
+ rGBM: 0
+ compressionQuality: 50
+ allowsAlphaSplitting: 0
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: .5, y: .5}
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spritePixelsToUnits: 100
+ alphaIsTransparency: 0
+ textureType: -1
+ buildTargetSettings: []
+ spriteSheet:
+ sprites: []
+ spritePackingTag:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_344_3.png b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_344_3.png
new file mode 100644
index 0000000000..ffabb74300
Binary files /dev/null and b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_344_3.png differ
diff --git a/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_344_3.png.meta b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_344_3.png.meta
new file mode 100644
index 0000000000..364850a6e0
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_344_3.png.meta
@@ -0,0 +1,56 @@
+fileFormatVersion: 2
+guid: 96502d5816119cf41bede1a57426aa26
+timeCreated: 1445868465
+licenseType: Free
+TextureImporter:
+ fileIDToRecycleName: {}
+ serializedVersion: 2
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ linearTexture: 0
+ correctGamma: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: .25
+ normalMapFilter: 0
+ isReadable: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 0
+ cubemapConvolution: 0
+ cubemapConvolutionSteps: 8
+ cubemapConvolutionExponent: 1.5
+ seamlessCubemap: 0
+ textureFormat: -1
+ maxTextureSize: 2048
+ textureSettings:
+ filterMode: -1
+ aniso: -1
+ mipBias: -1
+ wrapMode: -1
+ nPOTScale: 1
+ lightmap: 0
+ rGBM: 0
+ compressionQuality: 50
+ allowsAlphaSplitting: 0
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: .5, y: .5}
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spritePixelsToUnits: 100
+ alphaIsTransparency: 0
+ textureType: -1
+ buildTargetSettings: []
+ spriteSheet:
+ sprites: []
+ spritePackingTag:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_440_3.png b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_440_3.png
new file mode 100644
index 0000000000..944067e764
Binary files /dev/null and b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_440_3.png differ
diff --git a/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_440_3.png.meta b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_440_3.png.meta
new file mode 100644
index 0000000000..ce72747ed5
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_440_3.png.meta
@@ -0,0 +1,56 @@
+fileFormatVersion: 2
+guid: ef4098849a13418489076189a8df6b6f
+timeCreated: 1445868466
+licenseType: Free
+TextureImporter:
+ fileIDToRecycleName: {}
+ serializedVersion: 2
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ linearTexture: 0
+ correctGamma: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: .25
+ normalMapFilter: 0
+ isReadable: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 0
+ cubemapConvolution: 0
+ cubemapConvolutionSteps: 8
+ cubemapConvolutionExponent: 1.5
+ seamlessCubemap: 0
+ textureFormat: -1
+ maxTextureSize: 2048
+ textureSettings:
+ filterMode: -1
+ aniso: -1
+ mipBias: -1
+ wrapMode: -1
+ nPOTScale: 1
+ lightmap: 0
+ rGBM: 0
+ compressionQuality: 50
+ allowsAlphaSplitting: 0
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: .5, y: .5}
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spritePixelsToUnits: 100
+ alphaIsTransparency: 0
+ textureType: -1
+ buildTargetSettings: []
+ spriteSheet:
+ sprites: []
+ spritePackingTag:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_444_3.png b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_444_3.png
new file mode 100644
index 0000000000..b7e0256914
Binary files /dev/null and b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_444_3.png differ
diff --git a/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_444_3.png.meta b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_444_3.png.meta
new file mode 100644
index 0000000000..4c470f61f9
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources/metallicGlossMap_Texture_444_3.png.meta
@@ -0,0 +1,56 @@
+fileFormatVersion: 2
+guid: b4078bcfe826cc143a6d0658ec6d16dd
+timeCreated: 1445868465
+licenseType: Free
+TextureImporter:
+ fileIDToRecycleName: {}
+ serializedVersion: 2
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ linearTexture: 0
+ correctGamma: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: .25
+ normalMapFilter: 0
+ isReadable: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 0
+ cubemapConvolution: 0
+ cubemapConvolutionSteps: 8
+ cubemapConvolutionExponent: 1.5
+ seamlessCubemap: 0
+ textureFormat: -1
+ maxTextureSize: 2048
+ textureSettings:
+ filterMode: -1
+ aniso: -1
+ mipBias: -1
+ wrapMode: -1
+ nPOTScale: 1
+ lightmap: 0
+ rGBM: 0
+ compressionQuality: 50
+ allowsAlphaSplitting: 0
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: .5, y: .5}
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spritePixelsToUnits: 100
+ alphaIsTransparency: 0
+ textureType: -1
+ buildTargetSettings: []
+ spriteSheet:
+ sprites: []
+ spritePackingTag:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective.png b/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective.png
new file mode 100644
index 0000000000..4abe46378f
Binary files /dev/null and b/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective.png differ
diff --git a/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective.png.meta b/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective.png.meta
new file mode 100644
index 0000000000..5bd854a5e1
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective.png.meta
@@ -0,0 +1,56 @@
+fileFormatVersion: 2
+guid: b6d44da64cbabea4fb0396aacfb802cf
+timeCreated: 1445868466
+licenseType: Free
+TextureImporter:
+ fileIDToRecycleName: {}
+ serializedVersion: 2
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ linearTexture: 0
+ correctGamma: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: .25
+ normalMapFilter: 0
+ isReadable: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 0
+ cubemapConvolution: 0
+ cubemapConvolutionSteps: 8
+ cubemapConvolutionExponent: 1.5
+ seamlessCubemap: 0
+ textureFormat: -1
+ maxTextureSize: 2048
+ textureSettings:
+ filterMode: -1
+ aniso: -1
+ mipBias: -1
+ wrapMode: -1
+ nPOTScale: 1
+ lightmap: 0
+ rGBM: 0
+ compressionQuality: 50
+ allowsAlphaSplitting: 0
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: .5, y: .5}
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spritePixelsToUnits: 100
+ alphaIsTransparency: 0
+ textureType: -1
+ buildTargetSettings: []
+ spriteSheet:
+ sprites: []
+ spritePackingTag:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective_raining.png b/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective_raining.png
new file mode 100644
index 0000000000..e573eb6748
Binary files /dev/null and b/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective_raining.png differ
diff --git a/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective_raining.png.meta b/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective_raining.png.meta
new file mode 100644
index 0000000000..eef411aa89
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Resources/tileatlas_reflective_raining.png.meta
@@ -0,0 +1,56 @@
+fileFormatVersion: 2
+guid: 03495c4617576b64f90179ac8bf60e89
+timeCreated: 1445868465
+licenseType: Free
+TextureImporter:
+ fileIDToRecycleName: {}
+ serializedVersion: 2
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ linearTexture: 0
+ correctGamma: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: .25
+ normalMapFilter: 0
+ isReadable: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 0
+ cubemapConvolution: 0
+ cubemapConvolutionSteps: 8
+ cubemapConvolutionExponent: 1.5
+ seamlessCubemap: 0
+ textureFormat: -1
+ maxTextureSize: 2048
+ textureSettings:
+ filterMode: -1
+ aniso: -1
+ mipBias: -1
+ wrapMode: -1
+ nPOTScale: 1
+ lightmap: 0
+ rGBM: 0
+ compressionQuality: 50
+ allowsAlphaSplitting: 0
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: .5, y: .5}
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spritePixelsToUnits: 100
+ alphaIsTransparency: 0
+ textureType: -1
+ buildTargetSettings: []
+ spriteSheet:
+ sprites: []
+ spritePackingTag:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Scripts.meta b/Game/Addons/ReflectionsMod/Scripts.meta
new file mode 100644
index 0000000000..766c92e2f2
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Scripts.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 913919706e8fa1f48a5f0bfa28eb55ff
+folderAsset: yes
+timeCreated: 1445868463
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Scripts/InjectReflectiveMaterialProperty.cs b/Game/Addons/ReflectionsMod/Scripts/InjectReflectiveMaterialProperty.cs
new file mode 100644
index 0000000000..0c9e56a245
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Scripts/InjectReflectiveMaterialProperty.cs
@@ -0,0 +1,365 @@
+//ReflectionsMod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 0.32
+
+using UnityEngine;
+using UnityEngine.Rendering;
+using System.Collections.Generic;
+using DaggerfallConnect;
+using DaggerfallConnect.Arena2;
+using DaggerfallConnect.Utility;
+using DaggerfallWorkshop;
+using DaggerfallWorkshop.Game;
+using DaggerfallWorkshop.Utility;
+using IniParser;
+
+namespace ReflectionsMod
+{
+ public class InjectReflectiveMaterialProperty : MonoBehaviour
+ {
+ const string filepathConfigInjectionTextures = "Assets/daggerfall-unity/Game/Addons/ReflectionsMod/Resources/configInjectionTextures.ini";
+ // Streaming World Component
+ public StreamingWorld streamingWorld;
+
+ DaggerfallUnity dfUnity;
+
+ UpdateReflectionTextures reflectionTexturesScript = null;
+
+ IniParser.FileIniDataParser iniParser;
+ IniParser.Model.IniData parsedIniData;
+
+ void Start()
+ {
+ dfUnity = DaggerfallUnity.Instance;
+
+ reflectionTexturesScript = GameObject.Find("ReflectionsMod").GetComponent();
+
+ if (!streamingWorld)
+ streamingWorld = GameObject.Find("StreamingWorld").GetComponent();
+ if (!streamingWorld)
+ {
+ DaggerfallUnity.LogMessage("InjectReflectiveMaterialProperty: Missing StreamingWorld reference.", true);
+ if (Application.isEditor)
+ Debug.Break();
+ else
+ Application.Quit();
+ }
+
+ iniParser = new FileIniDataParser();
+ parsedIniData = iniParser.ReadFile(filepathConfigInjectionTextures);
+ }
+
+ void Awake()
+ {
+ StreamingWorld.OnInitWorld += InjectMaterialProperties;
+
+ StreamingWorld.OnTeleportToCoordinates += InjectMaterialProperties;
+
+ FloatingOrigin.OnPositionUpdate += InjectMaterialProperties;
+
+ PlayerEnterExit.OnTransitionInterior += InjectMaterialProperties;
+ PlayerEnterExit.OnTransitionExterior += InjectMaterialProperties;
+ PlayerEnterExit.OnTransitionDungeonInterior += InjectMaterialProperties;
+ PlayerEnterExit.OnTransitionDungeonExterior += InjectMaterialProperties;
+
+ DaggerfallTerrain.OnInstantiateTerrain += InjectMaterialProperties;
+ }
+
+ void OnDestroy()
+ {
+ StreamingWorld.OnInitWorld -= InjectMaterialProperties;
+
+ StreamingWorld.OnTeleportToCoordinates -= InjectMaterialProperties;
+
+ FloatingOrigin.OnPositionUpdate -= InjectMaterialProperties;
+
+ PlayerEnterExit.OnTransitionInterior -= InjectMaterialProperties;
+ PlayerEnterExit.OnTransitionExterior -= InjectMaterialProperties;
+ PlayerEnterExit.OnTransitionDungeonInterior -= InjectMaterialProperties;
+ PlayerEnterExit.OnTransitionDungeonExterior -= InjectMaterialProperties;
+
+ DaggerfallTerrain.OnInstantiateTerrain -= InjectMaterialProperties;
+ }
+
+ public void OnWillRenderObject()
+ {
+ if (reflectionTexturesScript.isOutdoorEnvironment())
+ {
+ GameObject goReflectionPlaneGroundLevel = GameObject.Find("ReflectionPlaneBottom");
+ GameObject goReflectionPlaneSeaLevel = GameObject.Find("ReflectionPlaneSeaLevel");
+
+ GameObject go = GameObject.Find("StreamingTarget");
+ foreach (Transform child in go.transform)
+ {
+ DaggerfallTerrain dfTerrain = child.GetComponent();
+ if (!dfTerrain)
+ continue;
+
+ Terrain terrain = child.GetComponent();
+ if (terrain)
+ {
+ if (terrain.materialTemplate)
+ {
+ if (terrain.materialTemplate.shader.name == "Daggerfall/TilemapWithReflections")
+ {
+ terrain.materialTemplate.SetFloat("_GroundLevelHeight", goReflectionPlaneGroundLevel.transform.position.y);
+ terrain.materialTemplate.SetFloat("_SeaLevelHeight", goReflectionPlaneSeaLevel.transform.position.y);
+ }
+ }
+ }
+ }
+ }
+
+ if (reflectionTexturesScript.isIndoorEnvironment())
+ {
+ GameObject goReflectionPlaneGroundLevel = GameObject.Find("ReflectionPlaneBottom");
+
+ GameObject goReflectionPlaneLowerLevel = GameObject.Find("ReflectionPlaneSeaLevel");
+
+ Renderer[] renderers = null;
+ if (GameObject.Find("Interior"))
+ {
+ renderers = GameObject.Find("Interior").GetComponentsInChildren();
+ }
+ else if (GameObject.Find("Dungeon"))
+ {
+ renderers = GameObject.Find("Dungeon").GetComponentsInChildren();
+ }
+
+ if (renderers != null)
+ {
+ foreach (Renderer r in renderers)
+ {
+ foreach (Material m in r.sharedMaterials)
+ {
+ if (m.shader.name == "Daggerfall/FloorMaterialWithReflections")
+ {
+ m.SetFloat("_GroundLevelHeight", goReflectionPlaneGroundLevel.transform.position.y);
+ m.SetFloat("_LowerLevelHeight", goReflectionPlaneLowerLevel.transform.position.y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //overloaded variant
+ void InjectMaterialProperties(DaggerfallTerrain sender)
+ {
+ InjectMaterialProperties(-1, -1);
+ }
+
+ //overloaded variant
+ void InjectMaterialProperties(DaggerfallWorkshop.Game.PlayerEnterExit.TransitionEventArgs args)
+ {
+ InjectMaterialProperties(-1, -1);
+ }
+
+ //overloaded variant
+ void InjectMaterialProperties(DFPosition worldPos)
+ {
+ InjectMaterialProperties(worldPos.X, worldPos.Y);
+ }
+
+ //overloaded variant
+ void InjectMaterialProperties(Vector3 offset)
+ {
+ InjectMaterialProperties();
+ }
+
+ //overloaded variant
+ void InjectMaterialProperties()
+ {
+ InjectMaterialProperties(-1, -1);
+ }
+
+ void updateMaterial(int archive, int record, int frame, Texture2D albedoMap, Texture2D normalMap, float reflectivity, float smoothness)
+ {
+ CachedMaterial cmat;
+ if (dfUnity.MaterialReader.GetCachedMaterial(archive, record, frame, out cmat))
+ {
+ Material newMat = new Material(Shader.Find("Daggerfall/FloorMaterialWithReflections"));
+ newMat.CopyPropertiesFromMaterial(cmat.material);
+ newMat.name = cmat.material.name;
+ Texture tex = GameObject.Find("ReflectionPlaneBottom").GetComponent().m_ReflectionTexture;
+ if (tex)
+ {
+ newMat.SetTexture("_ReflectionGroundTex", tex);
+ }
+ tex = GameObject.Find("ReflectionPlaneSeaLevel").GetComponent().m_ReflectionTexture;
+ if (tex)
+ {
+ newMat.SetTexture("_ReflectionLowerLevelTex", tex);
+ }
+ newMat.SetFloat("_Metallic", reflectivity);
+ newMat.SetFloat("_Smoothness", smoothness);
+
+ if (albedoMap != null)
+ {
+ newMat.SetTexture("_MainTex", albedoMap);
+ }
+
+ if (normalMap != null)
+ {
+ newMat.SetTexture("_BumpMap", normalMap);
+ }
+
+ cmat.material = newMat;
+ dfUnity.MaterialReader.SetCachedMaterial(archive, record, frame, cmat);
+ }
+ }
+ void updateMaterial(int archive, int record, int frame, Texture2D albedoMap, Texture2D normalMap, Texture2D metallicGlossMap)
+ {
+ CachedMaterial cmat;
+ if (dfUnity.MaterialReader.GetCachedMaterial(archive, record, frame, out cmat))
+ {
+ Material newMat = new Material(Shader.Find("Daggerfall/FloorMaterialWithReflections"));
+ newMat.CopyPropertiesFromMaterial(cmat.material);
+ newMat.name = cmat.material.name;
+ Texture tex = GameObject.Find("ReflectionPlaneBottom").GetComponent().m_ReflectionTexture;
+ if (tex)
+ {
+ newMat.SetTexture("_ReflectionGroundTex", tex);
+ }
+ tex = GameObject.Find("ReflectionPlaneSeaLevel").GetComponent().m_ReflectionTexture;
+ if (tex)
+ {
+ newMat.SetTexture("_ReflectionLowerLevelTex", tex);
+ }
+ newMat.EnableKeyword("USE_METALLICGLOSSMAP");
+ newMat.SetTexture("_MetallicGlossMap", metallicGlossMap);
+
+ if (albedoMap != null)
+ {
+ newMat.SetTexture("_MainTex", albedoMap);
+ }
+
+ if (normalMap != null)
+ {
+ newMat.SetTexture("_BumpMap", normalMap);
+ }
+
+ cmat.material = newMat;
+ dfUnity.MaterialReader.SetCachedMaterial(archive, record, frame, cmat);
+ }
+ }
+
+ void InjectMaterialProperties(int worldPosX, int worldPosY)
+ {
+ // mages guild 4 floors debuging worldpos: 704,337
+ //if (reflectionTexturesScript.isIndoorEnvironment()) // fails for some reason when 2indoor transition happens - TODO: investigate
+ //{
+
+ IniParser.Model.IniData textureInjectionData = parsedIniData;
+ if (parsedIniData != null)
+ {
+ foreach (IniParser.Model.SectionData section in parsedIniData.Sections)
+ {
+ int textureArchive = int.Parse(textureInjectionData[section.SectionName]["textureArchive"]);
+ int textureRecord = int.Parse(textureInjectionData[section.SectionName]["textureRecord"]);
+ int textureFrame = int.Parse(textureInjectionData[section.SectionName]["textureFrame"]);
+
+ Texture2D albedoTexture = null;
+ if (textureInjectionData[section.SectionName].ContainsKey("filenameAlbedoMap"))
+ {
+ string fileAlbedoMap = textureInjectionData[section.SectionName]["filenameAlbedoMap"];
+ albedoTexture = Resources.Load(fileAlbedoMap) as Texture2D;
+ }
+
+ Texture2D normalTexture = null;
+ if (textureInjectionData[section.SectionName].ContainsKey("filenameNormalMap"))
+ {
+ string fileNormalMap = textureInjectionData[section.SectionName]["filenameNormalMap"];
+ normalTexture = Resources.Load(fileNormalMap) as Texture2D;
+ }
+
+ bool useMetallicGlossMap = bool.Parse(textureInjectionData[section.SectionName]["useMetallicGlossMap"]);
+
+ if (useMetallicGlossMap)
+ {
+ string fileNameMetallicGlossMap = textureInjectionData[section.SectionName]["filenameMetallicGlossMap"];
+ Texture2D metallicGlossMapTexture = Resources.Load(fileNameMetallicGlossMap) as Texture2D;
+ updateMaterial(textureArchive, textureRecord, textureFrame, albedoTexture, normalTexture, metallicGlossMapTexture);
+ }
+ else
+ {
+ float reflectivity = float.Parse(textureInjectionData[section.SectionName]["reflectivity"]);
+ float smoothness = float.Parse(textureInjectionData[section.SectionName]["smoothness"]);
+ updateMaterial(textureArchive, textureRecord, textureFrame, albedoTexture, normalTexture, reflectivity, smoothness);
+ }
+ }
+ }
+
+ //}
+
+ if (reflectionTexturesScript.isOutdoorEnvironment()) // check position 474, 163,
+ {
+ GameObject go = GameObject.Find("StreamingTarget");
+ foreach (Transform child in go.transform)
+ {
+ DaggerfallTerrain dfTerrain = child.GetComponent();
+ if (!dfTerrain)
+ continue;
+
+ PlayerGPS playerGPS = GameObject.Find("PlayerAdvanced").GetComponent();
+ if (!playerGPS)
+ continue;
+
+ //if ((dfTerrain.MapPixelX != playerGPS.CurrentMapPixel.X) || (dfTerrain.MapPixelY != playerGPS.CurrentMapPixel.Y))
+ // continue;
+
+
+ Terrain terrain = child.GetComponent();
+ if (terrain)
+ {
+ if ((terrain.materialTemplate)) //&&(terrain.materialTemplate.shader.name != "Daggerfall/TilemapWithReflections")) // uncommenting this makes initial location (after startup, not fast travelling) not receive correct shader - don't know why - so workaround is to force injecting materialshader even for unset material (not sure why it works, but it does)
+ {
+ Texture tileSetTexture = terrain.materialTemplate.GetTexture("_TileAtlasTex");
+
+ //Texture2D tileAtlas = dfUnity.MaterialReader.TextureReader.GetTerrainTilesetTexture(402).albedoMap;
+ //System.IO.File.WriteAllBytes("./Assets/ReflectionsMod/Resources/tileatlas_402.png", tileAtlas.EncodeToPNG());
+
+ Texture tileMapTexture = terrain.materialTemplate.GetTexture("_TilemapTex");
+ int tileMapDim = terrain.materialTemplate.GetInt("_TilemapDim");
+
+ Material newMat = new Material(Shader.Find("Daggerfall/TilemapWithReflections"));
+
+ newMat.SetTexture("_TileAtlasTex", tileSetTexture);
+ newMat.SetTexture("_TilemapTex", tileMapTexture);
+ newMat.SetInt("_TilemapDim", tileMapDim);
+
+ GameObject goReflectionPlaneBottom = GameObject.Find("ReflectionPlaneBottom");
+ Texture tex = goReflectionPlaneBottom.GetComponent().m_ReflectionTexture;
+ newMat.SetTexture("_ReflectionGroundTex", tex);
+
+ //newMat.SetFloat("_GroundLevelHeight", goReflectionPlaneBottom.transform.position.y);
+
+ GameObject goReflectionPlaneSeaLevel = GameObject.Find("ReflectionPlaneSeaLevel");
+ Texture texSea = goReflectionPlaneSeaLevel.GetComponent().m_ReflectionTexture;
+ newMat.SetTexture("_ReflectionSeaTex", texSea);
+
+ //newMat.SetFloat("_SeaLevelHeight", goReflectionPlaneSeaLevel.transform.position.y);
+
+ WeatherManager weatherManager = GameObject.Find("WeatherManager").GetComponent();
+ if (!weatherManager.IsRaining)
+ {
+ Texture2D tileAtlasReflectiveTexture = Resources.Load("tileatlas_reflective") as Texture2D;
+ newMat.SetTexture("_TileAtlasReflectiveTex", tileAtlasReflectiveTexture);
+ }
+ else
+ {
+ Texture2D tileAtlasReflectiveTexture = Resources.Load("tileatlas_reflective_raining") as Texture2D;
+ newMat.SetTexture("_TileAtlasReflectiveTex", tileAtlasReflectiveTexture);
+ }
+
+ terrain.materialTemplate = newMat;
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/ReflectionsMod/Scripts/InjectReflectiveMaterialProperty.cs.meta b/Game/Addons/ReflectionsMod/Scripts/InjectReflectiveMaterialProperty.cs.meta
new file mode 100644
index 0000000000..034e820de7
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Scripts/InjectReflectiveMaterialProperty.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: e92a92d365318ee47914ce61a27fb2c7
+timeCreated: 1445868463
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Scripts/MirrorReflection.cs b/Game/Addons/ReflectionsMod/Scripts/MirrorReflection.cs
new file mode 100644
index 0000000000..79202e6927
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Scripts/MirrorReflection.cs
@@ -0,0 +1,239 @@
+//ReflectionsMod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 0.32
+
+// This script is derived from MirrorReflection4 script
+
+using UnityEngine;
+using System.Collections;
+using ProjectIncreasedTerrainDistance;
+
+namespace ReflectionsMod
+{
+ [ExecuteInEditMode] // Make mirror live-update even when not in play mode
+ public class MirrorReflection : MonoBehaviour
+ {
+ public bool m_DisablePixelLights = false;
+ public int m_TextureSize = 256;
+ public float m_ClipPlaneOffset = 0.05f; //0.0f; //0.07f;
+
+ public LayerMask m_ReflectLayers = -1;
+
+ private Hashtable m_ReflectionCameras = new Hashtable(); // Camera -> Camera table
+
+ public RenderTexture m_ReflectionTexture = null;
+ private int m_OldReflectionTextureSize = 0;
+
+ private static bool s_InsideRendering = false;
+
+ // This is called when it's known that the object will be rendered by some
+ // camera. We render reflections and do other updates here.
+ // Because the script executes in edit mode, reflections for the scene view
+ // camera will just work!
+ public void OnWillRenderObject()
+ //void Update()
+ {
+ var rend = GetComponent();
+ if (!enabled || !rend || !rend.sharedMaterial) // || !rend.enabled)
+ return;
+
+ Camera cam = Camera.current;
+ if( !cam )
+ return;
+
+ if (cam != Camera.main) // skip everything that is not the main camera
+ return;
+
+ // Safeguard from recursive reflections.
+ if( s_InsideRendering )
+ return;
+ s_InsideRendering = true;
+
+ Camera reflectionCamera;
+ CreateMirrorObjects( cam, out reflectionCamera );
+
+ // find out the reflection plane: position and normal in world space
+ Vector3 pos = transform.position;
+ Vector3 normal = transform.up;
+
+ // Optionally disable pixel lights for reflection
+ int oldPixelLightCount = QualitySettings.pixelLightCount;
+ if( m_DisablePixelLights )
+ QualitySettings.pixelLightCount = 0;
+
+ UpdateCameraModes( cam, reflectionCamera );
+
+ // Render reflection
+ // Reflect camera around reflection plane
+ float d = -Vector3.Dot (normal, pos) - m_ClipPlaneOffset;
+ Vector4 reflectionPlane = new Vector4 (normal.x, normal.y, normal.z, d);
+
+ Matrix4x4 reflection = Matrix4x4.zero;
+ CalculateReflectionMatrix (ref reflection, reflectionPlane);
+ Vector3 oldpos = cam.transform.position;
+ Vector3 newpos = reflection.MultiplyPoint( oldpos );
+ reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
+
+ // Setup oblique projection matrix so that near plane is our reflection
+ // plane. This way we clip everything below/above it for free.
+ Vector4 clipPlane = CameraSpacePlane( reflectionCamera, pos, normal, 1.0f );
+ //Matrix4x4 projection = cam.projectionMatrix;
+ Matrix4x4 projection = cam.CalculateObliqueMatrix(clipPlane);
+ reflectionCamera.projectionMatrix = projection;
+
+ reflectionCamera.cullingMask = ~(1<<4) & m_ReflectLayers.value; // never render water layer
+ reflectionCamera.targetTexture = m_ReflectionTexture;
+
+ // next 2 lines are important for making shadows work correctly - otherwise shadows will be broken
+ rend.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
+ rend.receiveShadows = false;
+
+ GL.invertCulling = true;
+ reflectionCamera.transform.position = newpos;
+ Vector3 euler = cam.transform.eulerAngles;
+ reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
+ reflectionCamera.Render();
+ reflectionCamera.transform.position = oldpos;
+ GL.invertCulling = false;
+
+ // Restore pixel light count
+ if( m_DisablePixelLights )
+ QualitySettings.pixelLightCount = oldPixelLightCount;
+
+ s_InsideRendering = false;
+ }
+
+
+ // Cleanup all the objects we possibly have created
+ void OnDisable()
+ {
+ if( m_ReflectionTexture ) {
+ DestroyImmediate( m_ReflectionTexture );
+ m_ReflectionTexture = null;
+ }
+ //foreach( DictionaryEntry kvp in m_ReflectionCameras )
+ // DestroyImmediate( ((Camera)kvp.Value).gameObject );
+ m_ReflectionCameras.Clear();
+ }
+
+
+ private void UpdateCameraModes( Camera src, Camera dest )
+ {
+ if( dest == null )
+ return;
+ // set camera to clear the same way as current camera
+ CameraClearFlags clearFlags = CameraClearFlags.Skybox;
+ dest.clearFlags = clearFlags;
+ dest.backgroundColor = src.backgroundColor;
+ if( clearFlags == CameraClearFlags.Skybox )
+ {
+ Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
+ Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
+ if( !sky || !sky.material )
+ {
+ mysky.enabled = false;
+ }
+ else
+ {
+ mysky.enabled = true;
+ mysky.material = sky.material;
+ }
+ }
+ // update other values to match current camera.
+ // even if we are supplying custom camera&projection matrices,
+ // some of values are used elsewhere (e.g. skybox uses far plane)
+ dest.renderingPath = src.renderingPath;
+ dest.farClipPlane = src.farClipPlane;
+ dest.nearClipPlane = 0.03f; //src.nearClipPlane;
+ dest.orthographic = src.orthographic;
+ dest.fieldOfView = src.fieldOfView;
+ dest.aspect = src.aspect;
+ dest.orthographicSize = src.orthographicSize;
+ }
+
+ // On-demand create any objects we need
+ private void CreateMirrorObjects( Camera currentCamera, out Camera reflectionCamera )
+ {
+ reflectionCamera = null;
+
+ // Reflection render texture
+ if( !m_ReflectionTexture || m_OldReflectionTextureSize != m_TextureSize )
+ {
+ if( m_ReflectionTexture )
+ DestroyImmediate( m_ReflectionTexture );
+ m_ReflectionTexture = new RenderTexture(m_TextureSize, m_TextureSize, 16); //, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
+
+ m_ReflectionTexture.name = "__MirrorReflection" + GetInstanceID();
+ m_ReflectionTexture.isPowerOfTwo = true;
+
+ //m_ReflectionTexture.generateMips = true;
+ m_ReflectionTexture.useMipMap = true;
+ m_ReflectionTexture.wrapMode = TextureWrapMode.Clamp;
+
+ //m_ReflectionTexture.hideFlags = HideFlags.DontSave;
+ m_OldReflectionTextureSize = m_TextureSize;
+ }
+
+ // Camera for reflection
+ reflectionCamera = m_ReflectionCameras[currentCamera] as Camera;
+ if( !reflectionCamera ) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
+ {
+ GameObject go = new GameObject( "Mirror Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox) );
+ reflectionCamera = go.GetComponent();
+ reflectionCamera.enabled = false;
+ reflectionCamera.transform.position = transform.position;
+ reflectionCamera.transform.rotation = transform.rotation;
+ reflectionCamera.gameObject.AddComponent();
+ //go.hideFlags = HideFlags.HideAndDontSave;
+ m_ReflectionCameras[currentCamera] = reflectionCamera;
+
+ go.transform.SetParent(GameObject.Find("ReflectionsMod").transform);
+ }
+ }
+
+ // Extended sign: returns -1, 0 or 1 based on sign of a
+ private static float sgn(float a)
+ {
+ if (a > 0.0f) return 1.0f;
+ if (a < 0.0f) return -1.0f;
+ return 0.0f;
+ }
+
+ // Given position/normal of the plane, calculates plane in camera space.
+ private Vector4 CameraSpacePlane (Camera cam, Vector3 pos, Vector3 normal, float sideSign)
+ {
+ Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
+ Matrix4x4 m = cam.worldToCameraMatrix;
+ Vector3 cpos = m.MultiplyPoint( offsetPos );
+ Vector3 cnormal = m.MultiplyVector( normal ).normalized * sideSign;
+ return new Vector4( cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos,cnormal) );
+ }
+
+ // Calculates reflection matrix around the given plane
+ private static void CalculateReflectionMatrix (ref Matrix4x4 reflectionMat, Vector4 plane)
+ {
+ reflectionMat.m00 = (1F - 2F*plane[0]*plane[0]);
+ reflectionMat.m01 = ( - 2F*plane[0]*plane[1]);
+ reflectionMat.m02 = ( - 2F*plane[0]*plane[2]);
+ reflectionMat.m03 = ( - 2F*plane[3]*plane[0]);
+
+ reflectionMat.m10 = ( - 2F*plane[1]*plane[0]);
+ reflectionMat.m11 = (1F - 2F*plane[1]*plane[1]);
+ reflectionMat.m12 = ( - 2F*plane[1]*plane[2]);
+ reflectionMat.m13 = ( - 2F*plane[3]*plane[1]);
+
+ reflectionMat.m20 = ( - 2F*plane[2]*plane[0]);
+ reflectionMat.m21 = ( - 2F*plane[2]*plane[1]);
+ reflectionMat.m22 = (1F - 2F*plane[2]*plane[2]);
+ reflectionMat.m23 = ( - 2F*plane[3]*plane[2]);
+
+ reflectionMat.m30 = 0F;
+ reflectionMat.m31 = 0F;
+ reflectionMat.m32 = 0F;
+ reflectionMat.m33 = 1F;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/ReflectionsMod/Scripts/MirrorReflection.cs.meta b/Game/Addons/ReflectionsMod/Scripts/MirrorReflection.cs.meta
new file mode 100644
index 0000000000..e1414bfa32
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Scripts/MirrorReflection.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 937a21676da9fbb46ae17a9843f8418c
+timeCreated: 1445868463
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Scripts/UpdateReflectionTextures.cs b/Game/Addons/ReflectionsMod/Scripts/UpdateReflectionTextures.cs
new file mode 100644
index 0000000000..030ace613e
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Scripts/UpdateReflectionTextures.cs
@@ -0,0 +1,421 @@
+//ReflectionsMod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 0.32
+
+using UnityEngine;
+using System.Collections;
+using UnityEngine.Rendering;
+using DaggerfallConnect;
+using DaggerfallConnect.Arena2;
+using DaggerfallConnect.Utility;
+using DaggerfallWorkshop;
+using DaggerfallWorkshop.Game;
+using DaggerfallWorkshop.Utility;
+
+namespace ReflectionsMod
+{
+ public class UpdateReflectionTextures : MonoBehaviour
+ {
+ private GameObject reflectionPlaneBottom = null;
+ private GameObject reflectionPlaneSeaLevel = null;
+
+ public bool isOutdoorEnvironment()
+ {
+ if (GameObject.Find("Exterior"))
+ return(true);
+ else
+ return(false);
+ }
+
+ public bool isIndoorEnvironment()
+ {
+ if ((GameObject.Find("Interior")) || (GameObject.Find("Dungeon")))
+ return(true);
+ else
+ return(false);
+ }
+
+ bool computeStepDownRaycast(Vector3 raycastStartPoint, Vector3 directionVec, float maxDiffMagnitude, out RaycastHit hit)
+ {
+ if (Physics.Raycast(raycastStartPoint, directionVec, out hit, 1000.0F))
+ {
+ Vector3 hitPoint = hit.point;
+ hitPoint -= directionVec * 0.1f; // move away a bit from hitpoint in opposite direction of raycast - this is required since daggerfall interiors often have small gaps/holes where a walls meet the floor - so upcoming down-raycast will not miss floor...
+
+ // and now raycast down
+ if (Physics.Raycast(hitPoint, Vector3.down, out hit, 1000.0F))
+ {
+ Vector3 diffVec = raycastStartPoint - hit.point;
+ if (diffVec.sqrMagnitude <= maxDiffMagnitude)
+ {
+ return (true);
+ }
+ }
+ else
+ {
+ return (false);
+ }
+ }
+ return (false);
+ }
+
+ float distanceToLowerLevel(Vector3 startPoint, Vector3 directionVec, out RaycastHit hit)
+ {
+ const int maxIterations = 20;
+ const float offset = 0.01f;
+ float maxDiffMagnitude = 3 * offset * offset * 1.1f; // ... once for every axis, *1.1f ... make it a bit bigger than the offset "boundingbox"
+
+ // iterative raycast in forward direction
+ Vector3 raycastStartPoint = startPoint; // +Camera.main.transform.position; // +new Vector3(0.0f, 0.1f, 0.0f);
+ for (int i = 0; i < maxIterations; i++)
+ {
+ if (computeStepDownRaycast(raycastStartPoint, directionVec, maxDiffMagnitude, out hit))
+ {
+ return (startPoint.y - hit.point.y);
+ }
+
+ Vector3 offsetVec = -directionVec * offset; // move away a bit from hitpoint in opposite direction of raycast - this is required since daggerfall interiors often have small gaps/holes where a walls meet the floor - so upcoming down-raycast will not miss floor...
+ offsetVec.y = offset; // move a bit up as well - so that next raycast starts above ground a bit
+ raycastStartPoint = hit.point + offsetVec;
+ }
+ hit = new RaycastHit();
+ return(float.MinValue);
+ }
+
+ float majorityOf3FloatValues(float value1, float value2, float value3, float allowedDistance, float valueWhenTied)
+ {
+ if ((Mathf.Abs(value1 - value2) < allowedDistance) || (Mathf.Abs(value1 - value3) < allowedDistance))
+ return (value1);
+ else if (Mathf.Abs(value2 - value3) < allowedDistance)
+ {
+ return (value2);
+ }
+ else
+ return valueWhenTied;
+ }
+
+ float distanceToLowerLevelStartingFromWall(Vector3 startPoint, Vector3 directionVectorToWall, Vector3 directionVectorRaycast1, Vector3 directionVectorRaycast2)
+ {
+ const float offset = 0.1f;
+ RaycastHit hit, hit1, hit2, hit3;
+ float biasAmount = 0.5f; // vector bias of the 2 additional parallel raycasts
+ Vector3 biasVec = biasAmount * Vector3.Normalize(Vector3.Cross(directionVectorToWall, Vector3.up)); // get normalized normal vector to directionVectorToWall and up vector
+ Physics.Raycast(startPoint, directionVectorToWall, out hit1, 1000.0F);
+ // do 2 additional parallel raycast with small bias and do majority vote - workaround to reduce problems with holes in geometry...
+ Physics.Raycast(startPoint + biasVec, directionVectorToWall, out hit2, 1000.0F);
+ Physics.Raycast(startPoint - biasVec, directionVectorToWall, out hit3, 1000.0F);
+
+ Vector3 wallPoint;
+ float distance = majorityOf3FloatValues(hit1.distance, hit2.distance, hit3.distance, 0.5f, float.MinValue); // 0.5f must be near to each other but allows for slanted walls (e.g. mages guild's spiral stair)
+ if (Mathf.Abs(hit1.distance - distance) < 0.5f)
+ {
+ wallPoint = hit1.point;
+ }
+ else if (Mathf.Abs(hit2.distance - distance) < 0.5f)
+ {
+ wallPoint = hit2.point;
+ }
+ else if (Mathf.Abs(hit3.distance - distance) < 0.5f)
+ {
+ wallPoint = hit3.point;
+ }
+ else
+ {
+ return (float.MinValue);
+ }
+
+
+ //Vector3 wallPoint = hit.point;
+ //wallPoint.y += 0.05f;
+ float distance1 = distanceToLowerLevel(wallPoint - directionVectorToWall * offset, directionVectorRaycast1, out hit);
+ float distance2 = distanceToLowerLevel(wallPoint - directionVectorToWall * offset, directionVectorRaycast2, out hit);
+ return Mathf.Max(distance1, distance2);
+ }
+
+ float getDistanceToLowerLevel(GameObject goPlayerAdvanced)
+ {
+ float distanceToLowerLevelWhenGoingForward = float.MinValue;
+ float distanceToLowerLevelWhenGoingBack = float.MinValue;
+ float distanceToLowerLevelWhenGoingLeft = float.MinValue;
+ float distanceToLowerLevelWhenGoingRight = float.MinValue;
+ float distanceToLowerLevelWhenGoingForwardLeft = float.MinValue;
+ float distanceToLowerLevelWhenGoingForwardRight = float.MinValue;
+ float distanceToLowerLevelWhenGoingBackLeft = float.MinValue;
+ float distanceToLowerLevelWhenGoingBackRight = float.MinValue;
+
+ float distanceToLowerLevelStartingFromLeftWall = float.MinValue;
+ float distanceToLowerLevelStartingFromRightWall = float.MinValue;
+ float distanceToLowerLevelStartingFromForwardWall = float.MinValue;
+ float distanceToLowerLevelStartingFromBackWall = float.MinValue;
+
+ RaycastHit hit;
+
+ Vector3 startPoint = goPlayerAdvanced.transform.position;
+
+ // 2 additional raycasts parallel to the main raycast just next to it are performed - majority vote of result of these 3 raycasts is then used as distance to lower level in the direction of interest
+ float biasAmount = 0.5f; // vector bias of the 2 additional parallel raycasts
+
+ distanceToLowerLevelWhenGoingForward = majorityOf3FloatValues(distanceToLowerLevel(startPoint, Vector3.forward, out hit),
+ distanceToLowerLevel(startPoint + new Vector3(-biasAmount, 0.0f, 0.0f), Vector3.forward, out hit),
+ distanceToLowerLevel(startPoint + new Vector3(+biasAmount, 0.0f, 0.0f), Vector3.forward, out hit),
+ 0.001f,
+ float.MinValue);
+ distanceToLowerLevelWhenGoingBack = majorityOf3FloatValues(distanceToLowerLevel(startPoint, Vector3.back, out hit),
+ distanceToLowerLevel(startPoint + new Vector3(-biasAmount, 0.0f, 0.0f), Vector3.back, out hit),
+ distanceToLowerLevel(startPoint + new Vector3(+biasAmount, 0.0f, 0.0f), Vector3.back, out hit),
+ 0.001f,
+ float.MinValue);
+
+
+ distanceToLowerLevelWhenGoingLeft = majorityOf3FloatValues(distanceToLowerLevel(startPoint, Vector3.left, out hit),
+ distanceToLowerLevel(startPoint + new Vector3(0.0f, 0.0f, -biasAmount), Vector3.left, out hit),
+ distanceToLowerLevel(startPoint + new Vector3(0.0f, 0.0f, +biasAmount), Vector3.left, out hit),
+ 0.001f,
+ float.MinValue);
+
+ distanceToLowerLevelWhenGoingRight = majorityOf3FloatValues(distanceToLowerLevel(startPoint, Vector3.right, out hit),
+ distanceToLowerLevel(startPoint + new Vector3(0.0f, 0.0f, -biasAmount), Vector3.right, out hit),
+ distanceToLowerLevel(startPoint + new Vector3(0.0f, 0.0f, +biasAmount), Vector3.right, out hit),
+ 0.001f,
+ float.MinValue);
+
+
+ distanceToLowerLevelWhenGoingForwardLeft = majorityOf3FloatValues(distanceToLowerLevel(startPoint, new Vector3(-1.0f, 0.0f, 1.0f), out hit),
+ distanceToLowerLevel(startPoint + new Vector3(-biasAmount, 0.0f, -biasAmount), new Vector3(-1.0f, 0.0f, 1.0f), out hit),
+ distanceToLowerLevel(startPoint + new Vector3(+biasAmount, 0.0f, +biasAmount), new Vector3(-1.0f, 0.0f, 1.0f), out hit),
+ 0.001f,
+ float.MinValue);
+
+ distanceToLowerLevelWhenGoingForwardRight = majorityOf3FloatValues(distanceToLowerLevel(startPoint, new Vector3(1.0f, 0.0f, 1.0f), out hit),
+ distanceToLowerLevel(startPoint + new Vector3(-biasAmount, 0.0f, +biasAmount), new Vector3(1.0f, 0.0f, 1.0f), out hit),
+ distanceToLowerLevel(startPoint + new Vector3(+biasAmount, 0.0f, -biasAmount), new Vector3(1.0f, 0.0f, 1.0f), out hit),
+ 0.001f,
+ float.MinValue);
+
+ distanceToLowerLevelWhenGoingBackLeft = majorityOf3FloatValues(distanceToLowerLevel(startPoint, new Vector3(-1.0f, 0.0f, -1.0f), out hit),
+ distanceToLowerLevel(startPoint + new Vector3(-biasAmount, 0.0f, +biasAmount), new Vector3(-1.0f, 0.0f, -1.0f), out hit),
+ distanceToLowerLevel(startPoint + new Vector3(+biasAmount, 0.0f, -biasAmount), new Vector3(-1.0f, 0.0f, -1.0f), out hit),
+ 0.001f,
+ float.MinValue);
+
+ distanceToLowerLevelWhenGoingBackRight = majorityOf3FloatValues(distanceToLowerLevel(startPoint, new Vector3(1.0f, 0.0f, -1.0f), out hit),
+ distanceToLowerLevel(startPoint + new Vector3(-biasAmount, 0.0f, -biasAmount), new Vector3(1.0f, 0.0f, -1.0f), out hit),
+ distanceToLowerLevel(startPoint + new Vector3(+biasAmount, 0.0f, +biasAmount), new Vector3(1.0f, 0.0f, -1.0f), out hit),
+ 0.001f,
+ float.MinValue);
+
+ // now go to wall and start 2 perpendicular raycast (perpendicular to the wall) to determine distance to lower level (this is important when facing the edge of a wall but you can see to lower level beside the edge, otherwise you miss the lower level plane with the above raycast steps)
+ float extraY = Camera.main.transform.localPosition.y; // start from eye position to reduce unintentional "around the corner sampling" - can still happen but less likely
+ startPoint.y += extraY;
+ distanceToLowerLevelStartingFromLeftWall = -extraY + distanceToLowerLevelStartingFromWall(startPoint, Vector3.left, Vector3.forward, Vector3.back);
+ distanceToLowerLevelStartingFromRightWall = -extraY + distanceToLowerLevelStartingFromWall(startPoint, Vector3.right, Vector3.forward, Vector3.back);
+ distanceToLowerLevelStartingFromForwardWall = -extraY + distanceToLowerLevelStartingFromWall(startPoint, Vector3.forward, Vector3.left, Vector3.right);
+ distanceToLowerLevelStartingFromBackWall = -extraY + distanceToLowerLevelStartingFromWall(startPoint, Vector3.back, Vector3.left, Vector3.right);
+
+
+ return (Mathf.Max(distanceToLowerLevelWhenGoingForward, distanceToLowerLevelWhenGoingBack, distanceToLowerLevelWhenGoingLeft, distanceToLowerLevelWhenGoingRight,
+ distanceToLowerLevelWhenGoingForwardLeft, distanceToLowerLevelWhenGoingForwardRight, distanceToLowerLevelWhenGoingBackLeft, distanceToLowerLevelWhenGoingBackRight,
+ distanceToLowerLevelStartingFromLeftWall, distanceToLowerLevelStartingFromRightWall, distanceToLowerLevelStartingFromForwardWall, distanceToLowerLevelStartingFromBackWall));
+
+ }
+
+ Mesh CreateMesh(float width, float height)
+ {
+ Mesh m = new Mesh();
+ m.name = "ScriptedMesh";
+ m.vertices = new Vector3[] {
+ new Vector3(-width, 0.01f, -height),
+ new Vector3(width, 0.01f, -height),
+ new Vector3(width, 0.01f, height),
+ new Vector3(-width, 0.01f, height),
+ new Vector3(-width, -10000.0f, -height), // create 2nd plane at level -10000, so that the stacked near camera (with near clip plane of around 1000) will trigger OnWillRenderObject() callback
+ new Vector3(width, -10000.0f, -height), // create 2nd plane at level -10000, so that the stacked near camera (with near clip plane of around 1000) will trigger OnWillRenderObject() callback
+ new Vector3(width, -10000.0f, height), // create 2nd plane at level -10000, so that the stacked near camera (with near clip plane of around 1000) will trigger OnWillRenderObject() callback
+ new Vector3(-width, -10000.0f, height) // create 2nd plane at level -10000, so that the stacked near camera (with near clip plane of around 1000) will trigger OnWillRenderObject() callback
+ };
+ m.uv = new Vector2[] {
+ new Vector2 (0, 0),
+ new Vector2 (0, 1),
+ new Vector2(1, 1),
+ new Vector2 (1, 0),
+ new Vector2 (0, 0), // from 2nd plane
+ new Vector2 (0, 1), // from 2nd plane
+ new Vector2(1, 1), // from 2nd plane
+ new Vector2 (1, 0) // from 2nd plane
+ };
+ m.triangles = new int[] { 0, 1, 2, 0, 2, 3, /* here starts 2nd plane */ 4, 5, 6, 4, 6, 7 };
+ m.RecalculateNormals();
+
+ return m;
+ }
+
+ void Awake()
+ {
+ if (!DaggerfallUnity.Settings.Nystul_RealtimeReflections)
+ return;
+
+ reflectionPlaneBottom = new GameObject("ReflectionPlaneBottom");
+ reflectionPlaneBottom.layer = LayerMask.NameToLayer("Water");
+ MeshFilter meshFilter = (MeshFilter)reflectionPlaneBottom.AddComponent(typeof(MeshFilter));
+ meshFilter.mesh = CreateMesh(100000.0f, 100000.0f); // create quad with normal facing into negative y-direction (so it is not visible but it will trigger OnWillRenderObject() in MirrorReflection.cs) - should be big enough to be "visible" even when looking parallel to the x/z-plane
+ MeshRenderer renderer = reflectionPlaneBottom.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
+
+ renderer.material.shader = Shader.Find("Standard");
+ Texture2D tex = new Texture2D(1, 1);
+ tex.SetPixel(0, 0, Color.green);
+ tex.Apply();
+ renderer.material.mainTexture = tex;
+ renderer.material.color = Color.green;
+ renderer.enabled = true; // if this is set to false OnWillRenderObject() in MirrorReflection.cs will not work (workaround would be to change OnWillRenderObject() to Update()
+
+ MirrorReflection mirrorRefl = reflectionPlaneBottom.AddComponent();
+ mirrorRefl.m_TextureSize = 512;
+ mirrorRefl.m_ReflectLayers = 1 << LayerMask.NameToLayer("Default");
+
+ reflectionPlaneBottom.transform.SetParent(this.transform);
+
+ reflectionPlaneBottom.AddComponent(typeof(InjectReflectiveMaterialProperty)); // the inject script is parented to this plane so that the OnWillRenderObject() method of the inject script will work - this is important since update() function resulted in slightly delayed update which could be seen when ground level height changed
+
+
+ reflectionPlaneSeaLevel = new GameObject("ReflectionPlaneSeaLevel");
+ reflectionPlaneSeaLevel.layer = LayerMask.NameToLayer("Water");
+ MeshFilter meshFilterSeaLevel = (MeshFilter)reflectionPlaneSeaLevel.AddComponent(typeof(MeshFilter));
+ meshFilterSeaLevel.mesh = CreateMesh(1000000.0f, 1000000.0f); // create quad facing into negative y-direction (so it is not visible but it will trigger OnWillRenderObject() in MirrorReflection.cs) - should be big enough to be "visible" even when looking parallel to the x/z-plane
+ MeshRenderer rendererSeaLevel = reflectionPlaneSeaLevel.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
+
+
+ rendererSeaLevel.material.shader = Shader.Find("Standard");
+ Texture2D texSeaLevel = new Texture2D(1, 1);
+ texSeaLevel.SetPixel(0, 0, Color.green);
+ texSeaLevel.Apply();
+ rendererSeaLevel.material.mainTexture = texSeaLevel;
+ rendererSeaLevel.material.color = Color.green;
+ rendererSeaLevel.enabled = true; // if this is set to false OnWillRenderObject() in MirrorReflection.cs will not work (workaround would be to change OnWillRenderObject() to Update()
+
+ MirrorReflection mirrorReflSeaLevel = reflectionPlaneSeaLevel.AddComponent();
+ mirrorReflSeaLevel.m_TextureSize = 512;
+ mirrorReflSeaLevel.m_ReflectLayers = 1 << LayerMask.NameToLayer("Default");
+
+ reflectionPlaneSeaLevel.transform.SetParent(this.transform);
+ }
+
+ void Update()
+ {
+ if (!DaggerfallUnity.Settings.Nystul_RealtimeReflections)
+ return;
+
+ GameObject goPlayerAdvanced = GameObject.Find("PlayerAdvanced");
+
+ PlayerGPS playerGPS = GameObject.Find("PlayerAdvanced").GetComponent();
+ if (!playerGPS)
+ return;
+
+ if (isIndoorEnvironment())
+ {
+ RaycastHit hit;
+ float distanceToGround = 0;
+
+ if (Physics.Raycast(goPlayerAdvanced.transform.position, -Vector3.up, out hit, 100.0F))
+ {
+ distanceToGround = hit.distance;
+ }
+ reflectionPlaneBottom.transform.position = goPlayerAdvanced.transform.position - new Vector3(0.0f, distanceToGround, 0.0f);
+
+ float distanceLevelBelow = getDistanceToLowerLevel(goPlayerAdvanced);
+ //Debug.Log(string.Format("distance to lower level: {0}", distanceLevelBelow));
+ reflectionPlaneSeaLevel.transform.position = goPlayerAdvanced.transform.position - new Vector3(0.0f, distanceLevelBelow, 0.0f);
+ }
+
+ if (isOutdoorEnvironment())
+ {
+ Terrain terrainInstancePlayerTerrain = null;
+
+ int referenceLocationX = playerGPS.CurrentMapPixel.X;
+ int referenceLocationY = playerGPS.CurrentMapPixel.Y;
+
+ ContentReader.MapSummary mapSummary;
+ // if there is no location at current player position...
+ if (!DaggerfallUnity.Instance.ContentReader.HasLocation(referenceLocationX, referenceLocationY, out mapSummary))
+ {
+ // search for largest location in local 8-neighborhood and take this as reference location for location reflection plane
+ int maxLocationArea = -1;
+ for (int y = -1; y <= +1; y++)
+ {
+ for (int x = -1; x <= +1; x++)
+ {
+ if (DaggerfallUnity.Instance.ContentReader.HasLocation(playerGPS.CurrentMapPixel.X + x, playerGPS.CurrentMapPixel.Y + y, out mapSummary))
+ {
+ DFLocation location = DaggerfallUnity.Instance.ContentReader.MapFileReader.GetLocation(mapSummary.RegionIndex, mapSummary.MapIndex);
+ byte locationRangeX = location.Exterior.ExteriorData.Width;
+ byte locationRangeY = location.Exterior.ExteriorData.Height;
+ int locationArea = locationRangeX * locationRangeY;
+
+ if (locationArea > maxLocationArea)
+ {
+ referenceLocationX = playerGPS.CurrentMapPixel.X + x;
+ referenceLocationY = playerGPS.CurrentMapPixel.Y + y;
+ maxLocationArea = locationArea;
+ }
+ }
+ }
+ }
+ }
+
+ GameObject go = GameObject.Find("StreamingTarget");
+ foreach (Transform child in go.transform)
+ {
+ DaggerfallTerrain dfTerrain = child.GetComponent();
+ if (!dfTerrain)
+ continue;
+
+ if ((dfTerrain.MapPixelX != referenceLocationX) || (dfTerrain.MapPixelY != referenceLocationY))
+ continue;
+
+
+ Terrain terrainInstance = child.GetComponent();
+ terrainInstancePlayerTerrain = terrainInstance;
+
+ if ((terrainInstance) && (terrainInstance.terrainData))
+ {
+ float scale = terrainInstance.terrainData.heightmapScale.x;
+ float xSamplePos = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension * 0.55f;
+ float ySamplePos = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension * 0.55f;
+ Vector3 pos = new Vector3(xSamplePos * scale, 0, ySamplePos * scale);
+ float height = terrainInstance.SampleHeight(pos + terrainInstance.transform.position);
+
+ float positionY = height + terrainInstance.transform.position.y;
+ reflectionPlaneBottom.transform.position = new Vector3(goPlayerAdvanced.transform.position.x + terrainInstance.transform.position.x, positionY, goPlayerAdvanced.transform.position.z + terrainInstance.transform.position.z);
+ }
+ }
+
+ if (!terrainInstancePlayerTerrain)
+ return;
+
+ //Debug.Log(string.Format("playerGPS: {0}, plane: {1}", goPlayerAdvanced.transform.position.y, reflectionPlaneBottom.transform.position.y));
+ if (playerGPS.transform.position.y < reflectionPlaneBottom.transform.position.y)
+ {
+ RaycastHit hit;
+ float distanceToGround = 0;
+
+ if (Physics.Raycast(goPlayerAdvanced.transform.position, -Vector3.up, out hit, 100.0F))
+ {
+ distanceToGround = hit.distance;
+ }
+
+ //Debug.Log(string.Format("distance to ground: {0}", distanceToGround));
+ reflectionPlaneBottom.transform.position = goPlayerAdvanced.transform.position - new Vector3(0.0f, distanceToGround, 0.0f);
+ }
+
+ StreamingWorld streamingWorld = GameObject.Find("StreamingWorld").GetComponent();
+ Vector3 vecWaterHeight = new Vector3(0.0f, (DaggerfallUnity.Instance.TerrainSampler.OceanElevation) * streamingWorld.TerrainScale, 0.0f); // water height level on y-axis (+1.0f some coastlines are incorrect otherwise)
+ Vector3 vecWaterHeightTransformed = terrainInstancePlayerTerrain.transform.TransformPoint(vecWaterHeight); // transform to world coordinates
+ //Debug.Log(string.Format("x,y,z: {0}, {1}, {2}", vecWaterHeight.x, vecWaterHeight.y, vecWaterHeight.z));
+ //Debug.Log(string.Format("transformed x,y,z: {0}, {1}, {2}", vecWaterHeightTransformed.x, vecWaterHeightTransformed.y, vecWaterHeightTransformed.z));
+ reflectionPlaneSeaLevel.transform.position = new Vector3(goPlayerAdvanced.transform.position.x, vecWaterHeightTransformed.y, goPlayerAdvanced.transform.position.z);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Game/Addons/ReflectionsMod/Scripts/UpdateReflectionTextures.cs.meta b/Game/Addons/ReflectionsMod/Scripts/UpdateReflectionTextures.cs.meta
new file mode 100644
index 0000000000..ccb787f824
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Scripts/UpdateReflectionTextures.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: fc15d6fdd6e20fa44b11f780c026c6ec
+timeCreated: 1445868465
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Shaders.meta b/Game/Addons/ReflectionsMod/Shaders.meta
new file mode 100644
index 0000000000..26be9b6b55
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Shaders.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: ba843aa5933b964429e6c9122bdbae24
+folderAsset: yes
+timeCreated: 1445868463
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Shaders/DaggerfallTilemapWithReflections.shader b/Game/Addons/ReflectionsMod/Shaders/DaggerfallTilemapWithReflections.shader
new file mode 100644
index 0000000000..efda255a3c
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Shaders/DaggerfallTilemapWithReflections.shader
@@ -0,0 +1,120 @@
+//ReflectionsMod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 0.32
+
+Shader "Daggerfall/TilemapWithReflections" {
+ Properties {
+ // These params are required to stop terrain system throwing errors
+ // However we won't be using them as Unity likes to init these textures
+ // and will overwrite any assignments we already made
+ // TODO: Combine splat painting with tilemapping
+ [HideInInspector] _MainTex("BaseMap (RGB)", 2D) = "white" {}
+ [HideInInspector] _Control ("Control (RGBA)", 2D) = "red" {}
+ [HideInInspector] _SplatTex3("Layer 3 (A)", 2D) = "white" {}
+ [HideInInspector] _SplatTex2("Layer 2 (B)", 2D) = "white" {}
+ [HideInInspector] _SplatTex1("Layer 1 (G)", 2D) = "white" {}
+ [HideInInspector] _SplatTex0("Layer 0 (R)", 2D) = "white" {}
+
+ // These params are used for our shader
+ _TileAtlasTex ("Tileset Atlas (RGB)", 2D) = "white" {}
+ _TilemapTex("Tilemap (R)", 2D) = "red" {}
+ _ReflectionGroundTex("Reflection Texture Ground Reflection", 2D) = "black" {}
+ _ReflectionSeaTex("Reflection Texture Sea Reflection", 2D) = "black" {}
+ _TileAtlasReflectiveTex ("Tileset Reflection Atlas (RGB)", 2D) = "black" {}
+ _BumpMap("Normal Map", 2D) = "bump" {}
+ _TilesetDim("Tileset Dimension (in tiles)", Int) = 16
+ _TilemapDim("Tilemap Dimension (in tiles)", Int) = 128
+ _MaxIndex("Max Tileset Index", Int) = 255
+ _AtlasSize("Atlas Size (in pixels)", Float) = 2048.0
+ _GutterSize("Gutter Size (in pixels)", Float) = 32.0
+ _SeaLevelHeight("sea level height", Float) = 0.0
+ }
+ SubShader {
+ Tags { "RenderType"="Opaque" }
+ LOD 200
+
+ CGPROGRAM
+ #pragma target 3.0
+ #pragma surface surf Standard
+ #pragma glsl
+
+ sampler2D _TileAtlasTex;
+ sampler2D _TilemapTex;
+ sampler2D _BumpMap;
+ sampler2D _ReflectionGroundTex;
+ sampler2D _ReflectionSeaTex;
+ sampler2D _TileAtlasReflectiveTex;
+ int _TilesetDim;
+ int _TilemapDim;
+ int _MaxIndex;
+ float _AtlasSize;
+ float _GutterSize;
+ float _SeaLevelHeight;
+
+ struct Input
+ {
+ float2 uv_MainTex;
+ float2 uv_BumpMap;
+ float3 worldPos;
+ float3 worldNormal;
+ float4 screenPos;
+ INTERNAL_DATA
+ };
+
+ void surf (Input IN, inout SurfaceOutputStandard o)
+ {
+ // Get offset to tile in atlas
+ uint index = tex2D(_TilemapTex, IN.uv_MainTex).x * _MaxIndex;
+ uint xpos = index % _TilesetDim;
+ uint ypos = index / _TilesetDim;
+ float2 uv = float2(xpos, ypos) / _TilesetDim;
+
+ // Offset to fragment position inside tile
+ float xoffset = frac(IN.uv_MainTex.x * _TilemapDim) / _GutterSize;
+ float yoffset = frac(IN.uv_MainTex.y * _TilemapDim) / _GutterSize;
+ uv += float2(xoffset, yoffset) + _GutterSize / _AtlasSize;
+
+ // Sample based on gradient and set output
+ float2 uvr = IN.uv_MainTex * ((float)_TilemapDim / _GutterSize);
+ half4 c = tex2Dgrad(_TileAtlasTex, uv, ddx(uvr), ddy(uvr));
+
+ float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
+
+ half3 refl;
+ if (IN.worldPos.y > _SeaLevelHeight + 0.01f)
+ {
+ refl = tex2Dlod(_ReflectionGroundTex, float4(screenUV, 0.0f, 1.0f)).rgb; // 4th component is blurring of reflection
+ }
+ else
+ {
+ refl = tex2Dlod(_ReflectionSeaTex, float4(screenUV, 0.0f, 1.5f)).rgb;
+ }
+
+ o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
+ float3 worldNormal = normalize(WorldNormalVector (IN, o.Normal));
+
+ float reflAmount;
+ const float3 upVec = float3(0.0f, 1.0f, 0.0f);
+ if (dot(worldNormal, upVec) > 0.99f)
+ {
+ //reflAmount = tex2D(_TileAtlasReflectiveTex, uv).r; //0.5f;
+ reflAmount = tex2Dgrad(_TileAtlasReflectiveTex, uv, ddx(uvr), ddy(uvr));
+ }
+ else
+ {
+ reflAmount = 0.0f;
+ }
+
+ c.rgb = c.rgb * (1.0f - reflAmount) + reflAmount * refl.rgb;
+ o.Albedo = c.rgb;
+ o.Alpha = c.a;
+ o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
+ o.Metallic = 0.0f;
+ }
+ ENDCG
+ }
+ FallBack "Standard"
+}
diff --git a/Game/Addons/ReflectionsMod/Shaders/DaggerfallTilemapWithReflections.shader.meta b/Game/Addons/ReflectionsMod/Shaders/DaggerfallTilemapWithReflections.shader.meta
new file mode 100644
index 0000000000..d43e3e62e6
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Shaders/DaggerfallTilemapWithReflections.shader.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 8d06399a000651e47ad80dcd43cfcf53
+timeCreated: 1445868467
+licenseType: Free
+ShaderImporter:
+ defaultTextures: []
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Addons/ReflectionsMod/Shaders/FloorMaterialWithReflections.shader b/Game/Addons/ReflectionsMod/Shaders/FloorMaterialWithReflections.shader
new file mode 100644
index 0000000000..14e8bc3e07
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Shaders/FloorMaterialWithReflections.shader
@@ -0,0 +1,151 @@
+//ReflectionsMod for Daggerfall Tools For Unity
+//http://www.reddit.com/r/dftfu
+//http://www.dfworkshop.net/
+//Author: Michael Rauter (a.k.a. Nystul)
+//License: MIT License (http://www.opensource.org/licenses/mit-license.php)
+//Version: 0.32
+
+Shader "Daggerfall/FloorMaterialWithReflections" {
+ Properties {
+ _Color("Color", Color) = (1,1,1,1)
+ _MainTex("Albedo Map", 2D) = "white" {}
+ _BumpMap("Normal Map", 2D) = "bump" {}
+ _EmissionMap("Emission Map", 2D) = "white" {}
+ [HideInInspector] _ReflectionGroundTex("Reflection Texture Ground Reflection", 2D) = "black" {}
+ [HideInInspector] _ReflectionLowerLevelTex("Reflection Texture for lower level Ground Reflection", 2D) = "black" {}
+ _EmissionColor("Emission Color", Color) = (0,0,0)
+ _MetallicGlossMap("Metallic Gloss Map", 2D) = "black" {}
+ _Metallic("metallic amount", Range(0.0, 1.0)) = 0
+ _Smoothness("smoothness amount", Range(0.0, 1.0)) = 0
+ [HideInInspector] _GroundLevelHeight("ground level height", Float) = 0.0
+ [HideInInspector] _LowerLevelHeight("lower level height", Float) = 0.0
+ }
+ SubShader {
+ Tags { "RenderType"="Opaque" }
+ LOD 200
+
+ CGPROGRAM
+ #pragma target 3.0
+ #pragma surface surf Standard vertex:customvert
+ #pragma glsl
+
+ #pragma multi_compile __ USE_METALLICGLOSSMAP
+
+ half4 _Color;
+ sampler2D _MainTex;
+ sampler2D _BumpMap;
+ sampler2D _EmissionMap;
+ sampler2D _ReflectionGroundTex;
+ sampler2D _ReflectionLowerLevelTex;
+ half4 _EmissionColor;
+
+ float _GroundLevelHeight;
+ float _LowerLevelHeight;
+
+ #if defined (USE_METALLICGLOSSMAP)
+ sampler2D _MetallicGlossMap;
+ #else
+ half _Metallic;
+ half _Smoothness;
+ #endif
+
+ struct Input
+ {
+ float2 uv_MainTex;
+ float2 uv_BumpMap;
+ float2 uv_EmissionMap;
+ float3 worldPos;
+ float4 screenPos;
+ float4 parallaxCorrectedScreenPos : TEXCOORD1;
+ };
+
+ void customvert(inout appdata_full v, out Input o)
+ {
+ UNITY_INITIALIZE_OUTPUT(Input, o);
+
+ float4 posWorldSpace = mul(_Object2World, v.vertex);
+ if ((abs(posWorldSpace.y - _GroundLevelHeight) < 0.01f) || (abs(posWorldSpace.y - _LowerLevelHeight) < 0.01f))
+ {
+ o.parallaxCorrectedScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, posWorldSpace));
+ }
+ else
+ {
+ // parallax-correct reflection position
+ if (posWorldSpace.y > _GroundLevelHeight+0.01f)
+ o.parallaxCorrectedScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, posWorldSpace-float4(0.0f, posWorldSpace.y - _GroundLevelHeight + 0.25f, 0.0f, 0.0f)));
+ else if (posWorldSpace.y < _GroundLevelHeight-0.01f)
+ o.parallaxCorrectedScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, posWorldSpace-float4(0.0f, posWorldSpace.y - _GroundLevelHeight - 0.14f, 0.0f, 0.0f)));
+ else
+ o.parallaxCorrectedScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, posWorldSpace-float4(0.0f, posWorldSpace.y - _GroundLevelHeight, 0.0f, 0.0f)));
+ }
+ }
+
+ float3 getReflectionColor(sampler2D tex, float2 screenUV, float smoothness)
+ {
+ half mipmapLevel1 = floor(smoothness);
+ half mipmapLevel2 = ceil(smoothness);
+ float w = smoothness - mipmapLevel1;
+ return((1.0f-w) * tex2Dlod(tex, float4(screenUV, 0.0f, mipmapLevel1)).rgb + w * tex2Dlod(tex, float4(screenUV, 0.0f, mipmapLevel2)).rgb);
+ }
+
+ void surf (Input IN, inout SurfaceOutputStandard o)
+ {
+ half4 albedo = tex2D(_MainTex, IN.uv_MainTex) * _Color;
+ half3 emission = tex2D(_EmissionMap, IN.uv_EmissionMap).rgb * _EmissionColor.rgb;
+
+ //float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
+ float2 screenUV = IN.parallaxCorrectedScreenPos.xy / IN.parallaxCorrectedScreenPos.w;
+
+ half3 refl;
+
+ const half fadeWidth = 0.3f;
+ half fadeOutFactX = min(1.0f, max(0.0f, abs(0.5f - screenUV.x) - (0.5f-fadeWidth)) / fadeWidth);
+ half fadeOutFactY = min(1.0f, max(0.0f, abs(0.5f - screenUV.y) - (0.5f-fadeWidth)) / fadeWidth);
+
+ half fadeOutFact = 0.0f;
+ if ((abs(IN.worldPos.y - _GroundLevelHeight) > 0.01f) && (abs(IN.worldPos.y - _LowerLevelHeight) > 0.01f))
+ fadeOutFact = max(fadeOutFactX, fadeOutFactY);
+
+ #if defined (USE_METALLICGLOSSMAP)
+ half4 metallicGloss = tex2D(_MetallicGlossMap, IN.uv_MainTex);
+ half metallic = metallicGloss.r * (1.0f-fadeOutFact);
+ half smoothness = (1.0f - metallicGloss.a) * 8.0f;
+ #else
+ half metallic = _Metallic * (1.0f-fadeOutFact);
+ half smoothness = (1.0f-_Smoothness) * 8.0f;
+ #endif
+
+ //if ((screenUV.x>0.0f)&&(screenUV.x<1.0f)&&(screenUV.y>0.0f)&&(screenUV.y<1.0f))
+ {
+ if (abs(IN.worldPos.y - _LowerLevelHeight) < 0.01f)
+ {
+ refl = getReflectionColor(_ReflectionLowerLevelTex, screenUV, smoothness); //refl = tex2Dlod(_ReflectionLowerLevelTex, float4(screenUV, 0.0f, _Smoothness)).rgb;
+ albedo.rgb += metallic * refl; // + (1.0f - metallic) * albedo.rgb;
+ albedo.rgb *= (1.0f/(1.0f + metallic));
+ }
+ else if ( //(abs(IN.worldPos.y - _GroundLevelHeight) < 0.01f)|| // fragment belong to object on current ground level plane
+ (IN.worldPos.y < _GroundLevelHeight)|| // fragment is below (use parallax-corrected reflection)
+ (IN.worldPos.y - _GroundLevelHeight < 0.32f) // fragment is slightly above (use parallax-corrected reflection) - also valid for current ground level plane
+ )
+ {
+ refl = getReflectionColor(_ReflectionGroundTex, screenUV, smoothness); //refl = tex2Dlod(_ReflectionGroundTex, float4(screenUV, 0.0f, _Smoothness)).rgb;
+ albedo.rgb += metallic * refl; // + (1.0f - metallic) * albedo.rgb;
+ albedo.rgb *= (1.0f/(1.0f + metallic));
+ }
+ else
+ {
+ albedo.rgb += metallic * 0.2f; // correction of x/z planes without reflection texture applied - 0.2 seems to work ok for most situations
+ albedo.rgb *= (1.0f/(1.0f + metallic));
+ }
+ }
+
+ o.Albedo = albedo.rgb; // - emission; // Emission cancels out other lights
+ o.Alpha = albedo.a;
+ o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
+ o.Emission = emission;
+ o.Metallic = 0.0f; //_Metallic;
+ }
+ ENDCG
+ }
+ FallBack "Standard"
+}
diff --git a/Game/Addons/ReflectionsMod/Shaders/FloorMaterialWithReflections.shader.meta b/Game/Addons/ReflectionsMod/Shaders/FloorMaterialWithReflections.shader.meta
new file mode 100644
index 0000000000..9825c13a96
--- /dev/null
+++ b/Game/Addons/ReflectionsMod/Shaders/FloorMaterialWithReflections.shader.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 8cc87b30bef729249b31f1844ee57f6d
+timeCreated: 1445868467
+licenseType: Free
+ShaderImporter:
+ defaultTextures: []
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Game/Scenes/DaggerfallUnityGame_withMods.unity b/Game/Scenes/DaggerfallUnityGame_withMods.unity
new file mode 100644
index 0000000000..6c66ca562d
Binary files /dev/null and b/Game/Scenes/DaggerfallUnityGame_withMods.unity differ
diff --git a/Game/Scenes/DaggerfallUnityGame_withMods.unity.meta b/Game/Scenes/DaggerfallUnityGame_withMods.unity.meta
new file mode 100644
index 0000000000..4b4fc8a522
--- /dev/null
+++ b/Game/Scenes/DaggerfallUnityGame_withMods.unity.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d8abae5a79531aa46a1e2a6f0f140603
+timeCreated: 1445867633
+licenseType: Free
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Scripts/MaterialReader.cs b/Scripts/MaterialReader.cs
index efdbcfe097..ff77680fd6 100644
--- a/Scripts/MaterialReader.cs
+++ b/Scripts/MaterialReader.cs
@@ -551,6 +551,29 @@ public bool GetCachedMaterial(int archive, int record, int frame, out CachedMate
return true;
}
+ ///
+ /// Sets CachedMaterial properties.
+ /// existing Material will be updated in cache with cachedMaterialIn.
+ ///
+ /// Archive index.
+ /// Record index.
+ /// Frame index.
+ /// the CachedMaterial used to update the cache.
+ /// True if CachedMaterial was found and updated successfully.
+ public bool SetCachedMaterial(int archive, int record, int frame, CachedMaterial cachedMaterialIn)
+ {
+ int key = MakeTextureKey((short)archive, (byte)record, (byte)frame);
+ if (materialDict.ContainsKey(key))
+ {
+ materialDict[key] = cachedMaterialIn;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
///
/// Gets CachedMaterial properties for an atlased material.
/// Atlas material will not be loaded automatically if not found in cache.