A game about forced loneliness, made by TACStudios
1using System; 2using System.IO; 3using Unity.Collections; 4using Unity.Collections.LowLevel.Unsafe; 5using UnityEditor; 6#if UNITY_2020_2_OR_NEWER 7using UnityEditor.AssetImporters; 8#else 9using UnityEditor.Experimental.AssetImporters; 10#endif 11using UnityEngine; 12 13namespace UnityEditor.Rendering 14{ 15 // Photometric type coordinate system references: 16 // https://www.ies.org/product/approved-method-guide-to-goniometer-measurements-and-types-and-photometric-coordinate-systems/ 17 // https://support.agi32.com/support/solutions/articles/22000209748-type-a-type-b-and-type-c-photometry 18 /// <summary> 19 /// IES class which is common for the Importers 20 /// </summary> 21 22 [System.Serializable] 23 public class IESEngine 24 { 25 const float k_HalfPi = 0.5f * Mathf.PI; 26 const float k_TwoPi = 2.0f * Mathf.PI; 27 28 internal IESReader m_iesReader = new IESReader(); 29 30 internal string FileFormatVersion { get => m_iesReader.FileFormatVersion; } 31 32 internal TextureImporterType m_TextureGenerationType = TextureImporterType.Cookie; 33 34 /// <summary> 35 /// setter for the Texture generation Type 36 /// </summary> 37 public TextureImporterType TextureGenerationType 38 { 39 set { m_TextureGenerationType = value; } 40 } 41 42 /// <summary> 43 /// Method to read the IES File 44 /// </summary> 45 /// <param name="iesFilePath">Path to the IES file in the Disk.</param> 46 /// <returns>An error message or warning otherwise null if no error</returns> 47 public string ReadFile(string iesFilePath) 48 { 49 if (!File.Exists(iesFilePath)) 50 { 51 return "IES file does not exist."; 52 } 53 54 string errorMessage; 55 56 try 57 { 58 errorMessage = m_iesReader.ReadFile(iesFilePath); 59 } 60 catch (IOException ioEx) 61 { 62 return ioEx.Message; 63 } 64 65 return errorMessage; 66 } 67 68 /// <summary> 69 /// Check a keyword 70 /// </summary> 71 /// <param name="keyword">A keyword to check if exist.</param> 72 /// <returns>A Keyword if exist inside the internal Dictionary</returns> 73 public string GetKeywordValue(string keyword) 74 { 75 return m_iesReader.GetKeywordValue(keyword); 76 } 77 78 /// <summary> 79 /// Getter (as a string) for the Photometric Type 80 /// </summary> 81 /// <returns>The current Photometric Type</returns> 82 public string GetPhotometricType() 83 { 84 switch (m_iesReader.PhotometricType) 85 { 86 case 3: // type A 87 return "Type A"; 88 case 2: // type B 89 return "Type B"; 90 default: // type C 91 return "Type C"; 92 } 93 } 94 95 /// <summary> 96 /// Get the CUrrent Max intensity 97 /// </summary> 98 /// <returns>A pair of the intensity follow by the used unit (candelas or lumens)</returns> 99 public (float, string) GetMaximumIntensity() 100 { 101 if (m_iesReader.TotalLumens == -1f) // absolute photometry 102 { 103 return (m_iesReader.MaxCandelas, "Candelas"); 104 } 105 else 106 { 107 return (m_iesReader.TotalLumens, "Lumens"); 108 } 109 } 110 111 /// <summary> 112 /// Generated a Cube texture based on the internal PhotometricType 113 /// </summary> 114 /// <param name="compression">Compression parameter requestted.</param> 115 /// <param name="textureSize">The resquested size.</param> 116 /// <returns>A Cubemap representing this IES</returns> 117 public (string, Texture) GenerateCubeCookie(TextureImporterCompression compression, int textureSize) 118 { 119 int width = 2 * textureSize; 120 int height = 2 * textureSize; 121 122 NativeArray<Color> colorBuffer; 123 124 switch (m_iesReader.PhotometricType) 125 { 126 case 3: // type A 127 colorBuffer = BuildTypeACylindricalTexture(width, height); 128 break; 129 case 2: // type B 130 colorBuffer = BuildTypeBCylindricalTexture(width, height); 131 break; 132 default: // type C 133 colorBuffer = BuildTypeCCylindricalTexture(width, height); 134 break; 135 } 136 137 return GenerateTexture(m_TextureGenerationType, TextureImporterShape.TextureCube, compression, width, height, colorBuffer); 138 } 139 140 // Gnomonic projection reference: 141 // http://speleotrove.com/pangazer/gnomonic_projection.html 142 /// <summary> 143 /// Generating a 2D Texture of this cookie, using a Gnomonic projection of the bottom of the IES 144 /// </summary> 145 /// <param name="compression">Compression parameter requestted.</param> 146 /// <param name="coneAngle">Cone angle used to performe the Gnomonic projection.</param> 147 /// <param name="textureSize">The resquested size.</param> 148 /// <param name="applyLightAttenuation">Bool to enable or not the Light Attenuation based on the squared distance.</param> 149 /// <returns>A Generated 2D texture doing the projection of the IES using the Gnomonic projection of the bottom half hemisphere with the given 'cone angle'</returns> 150 public (string, Texture) Generate2DCookie(TextureImporterCompression compression, float coneAngle, int textureSize, bool applyLightAttenuation) 151 { 152 NativeArray<Color> colorBuffer; 153 154 switch (m_iesReader.PhotometricType) 155 { 156 case 3: // type A 157 colorBuffer = BuildTypeAGnomonicTexture(coneAngle, textureSize, applyLightAttenuation); 158 break; 159 case 2: // type B 160 colorBuffer = BuildTypeBGnomonicTexture(coneAngle, textureSize, applyLightAttenuation); 161 break; 162 default: // type C 163 colorBuffer = BuildTypeCGnomonicTexture(coneAngle, textureSize, applyLightAttenuation); 164 break; 165 } 166 167 return GenerateTexture(m_TextureGenerationType, TextureImporterShape.Texture2D, compression, textureSize, textureSize, colorBuffer); 168 } 169 170 private (string, Texture) GenerateCylindricalTexture(TextureImporterCompression compression, int textureSize) 171 { 172 int width = 2 * textureSize; 173 int height = textureSize; 174 175 NativeArray<Color> colorBuffer; 176 177 switch (m_iesReader.PhotometricType) 178 { 179 case 3: // type A 180 colorBuffer = BuildTypeACylindricalTexture(width, height); 181 break; 182 case 2: // type B 183 colorBuffer = BuildTypeBCylindricalTexture(width, height); 184 break; 185 default: // type C 186 colorBuffer = BuildTypeCCylindricalTexture(width, height); 187 break; 188 } 189 190 return GenerateTexture(TextureImporterType.Default, TextureImporterShape.Texture2D, compression, width, height, colorBuffer); 191 } 192 193 (string, Texture) GenerateTexture(TextureImporterType type, TextureImporterShape shape, TextureImporterCompression compression, int width, int height, NativeArray<Color> colorBuffer) 194 { 195 // Default values set by the TextureGenerationSettings constructor can be found in this file on GitHub: 196 // https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs 197 198 var settings = new TextureGenerationSettings(type); 199 200 SourceTextureInformation textureInfo = settings.sourceTextureInformation; 201 textureInfo.containsAlpha = true; 202 textureInfo.height = height; 203 textureInfo.width = width; 204 205 TextureImporterSettings textureImporterSettings = settings.textureImporterSettings; 206 textureImporterSettings.alphaSource = TextureImporterAlphaSource.FromInput; 207 textureImporterSettings.aniso = 0; 208 textureImporterSettings.borderMipmap = (textureImporterSettings.textureType == TextureImporterType.Cookie); 209 textureImporterSettings.filterMode = FilterMode.Bilinear; 210 textureImporterSettings.generateCubemap = TextureImporterGenerateCubemap.Cylindrical; 211 textureImporterSettings.mipmapEnabled = false; 212 textureImporterSettings.npotScale = TextureImporterNPOTScale.None; 213 textureImporterSettings.readable = true; 214 textureImporterSettings.sRGBTexture = false; 215 textureImporterSettings.textureShape = shape; 216 textureImporterSettings.wrapMode = textureImporterSettings.wrapModeU = textureImporterSettings.wrapModeV = textureImporterSettings.wrapModeW = TextureWrapMode.Clamp; 217 218 TextureImporterPlatformSettings platformSettings = settings.platformSettings; 219 platformSettings.maxTextureSize = 2048; 220 platformSettings.resizeAlgorithm = TextureResizeAlgorithm.Bilinear; 221 platformSettings.textureCompression = compression; 222 platformSettings.format = TextureImporterFormat.RGB9E5; 223 224 TextureGenerationOutput output = TextureGenerator.GenerateTexture(settings, colorBuffer); 225 226 if (output.importWarnings.Length > 0) 227 { 228 Debug.LogWarning("Cannot properly generate IES texture:\n" + string.Join("\n", output.importWarnings)); 229 } 230 231 return (output.importInspectorWarnings, output.output); 232 } 233 234 Color ComputePixelColor(float horizontalAnglePosition, float verticalAnglePosition, float attenuation = 1.0f) 235 { 236 float value = m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * attenuation); 237 return new Color(value, value, value, value); 238 } 239 240 NativeArray<Color> BuildTypeACylindricalTexture(int width, int height) 241 { 242 float stepU = 360f / (width - 1); 243 float stepV = 180f / (height - 1); 244 245 var textureBuffer = new NativeArray<Color>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory); 246 247 for (int y = 0; y < height; y++) 248 { 249 var slice = new NativeSlice<Color>(textureBuffer, y * width, width); 250 251 float latitude = y * stepV - 90f; // in range [-90..+90] degrees 252 253 float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); 254 255 for (int x = 0; x < width; x++) 256 { 257 float longitude = x * stepU - 180f; // in range [-180..+180] degrees 258 259 float horizontalAnglePosition = m_iesReader.ComputeTypeAorBHorizontalAnglePosition(longitude); 260 261 slice[x] = ComputePixelColor(horizontalAnglePosition, verticalAnglePosition); 262 } 263 } 264 265 return textureBuffer; 266 } 267 268 NativeArray<Color> BuildTypeBCylindricalTexture(int width, int height) 269 { 270 float stepU = k_TwoPi / (width - 1); 271 float stepV = Mathf.PI / (height - 1); 272 273 var textureBuffer = new NativeArray<Color>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory); 274 275 for (int y = 0; y < height; y++) 276 { 277 var slice = new NativeSlice<Color>(textureBuffer, y * width, width); 278 279 float v = y * stepV - k_HalfPi; // in range [-90..+90] degrees 280 281 float sinV = Mathf.Sin(v); 282 float cosV = Mathf.Cos(v); 283 284 for (int x = 0; x < width; x++) 285 { 286 float u = Mathf.PI - x * stepU; // in range [+180..-180] degrees 287 288 float sinU = Mathf.Sin(u); 289 float cosU = Mathf.Cos(u); 290 291 // Since a type B luminaire is turned on its side, rotate it to make its polar axis horizontal. 292 float longitude = Mathf.Atan2(sinV, cosU * cosV) * Mathf.Rad2Deg; // in range [-180..+180] degrees 293 float latitude = Mathf.Asin(-sinU * cosV) * Mathf.Rad2Deg; // in range [-90..+90] degrees 294 295 float horizontalAnglePosition = m_iesReader.ComputeTypeAorBHorizontalAnglePosition(longitude); 296 float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); 297 298 slice[x] = ComputePixelColor(horizontalAnglePosition, verticalAnglePosition); 299 } 300 } 301 302 return textureBuffer; 303 } 304 305 NativeArray<Color> BuildTypeCCylindricalTexture(int width, int height) 306 { 307 float stepU = k_TwoPi / (width - 1); 308 float stepV = Mathf.PI / (height - 1); 309 310 var textureBuffer = new NativeArray<Color>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory); 311 312 for (int y = 0; y < height; y++) 313 { 314 var slice = new NativeSlice<Color>(textureBuffer, y * width, width); 315 316 float v = y * stepV - k_HalfPi; // in range [-90..+90] degrees 317 318 float sinV = Mathf.Sin(v); 319 float cosV = Mathf.Cos(v); 320 321 for (int x = 0; x < width; x++) 322 { 323 float u = Mathf.PI - x * stepU; // in range [+180..-180] degrees 324 325 float sinU = Mathf.Sin(u); 326 float cosU = Mathf.Cos(u); 327 328 // Since a type C luminaire is generally aimed at nadir, orient it toward +Z at the center of the cylindrical texture. 329 float longitude = ((Mathf.Atan2(sinU * cosV, sinV) + k_TwoPi) % k_TwoPi) * Mathf.Rad2Deg; // in range [0..360] degrees 330 float latitude = (Mathf.Asin(-cosU * cosV) + k_HalfPi) * Mathf.Rad2Deg; // in range [0..180] degrees 331 332 float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude); 333 float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); 334 335 slice[x] = ComputePixelColor(horizontalAnglePosition, verticalAnglePosition); 336 } 337 } 338 339 return textureBuffer; 340 } 341 342 NativeArray<Color> BuildTypeAGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation) 343 { 344 float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad); 345 float stepUV = (2 * limitUV) / (size - 3); 346 347 var textureBuffer = new NativeArray<Color>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory); 348 349 // Leave a one-pixel black border around the texture to avoid cookie spilling. 350 for (int y = 1; y < size - 1; y++) 351 { 352 var slice = new NativeSlice<Color>(textureBuffer, y * size, size); 353 354 float v = (y - 1) * stepUV - limitUV; 355 356 for (int x = 1; x < size - 1; x++) 357 { 358 float u = (x - 1) * stepUV - limitUV; 359 360 float rayLengthSquared = u * u + v * v + 1; 361 362 float longitude = Mathf.Atan(u) * Mathf.Rad2Deg; // in range [-90..+90] degrees 363 float latitude = Mathf.Asin(v / Mathf.Sqrt(rayLengthSquared)) * Mathf.Rad2Deg; // in range [-90..+90] degrees 364 365 float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude); 366 float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); 367 368 // Factor in the light attenuation further from the texture center. 369 float lightAttenuation = applyLightAttenuation ? rayLengthSquared : 1f; 370 371 slice[x] = ComputePixelColor(horizontalAnglePosition, verticalAnglePosition, lightAttenuation); 372 } 373 } 374 375 return textureBuffer; 376 } 377 378 NativeArray<Color> BuildTypeBGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation) 379 { 380 float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad); 381 float stepUV = (2 * limitUV) / (size - 3); 382 383 var textureBuffer = new NativeArray<Color>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory); 384 385 // Leave a one-pixel black border around the texture to avoid cookie spilling. 386 for (int y = 1; y < size - 1; y++) 387 { 388 var slice = new NativeSlice<Color>(textureBuffer, y * size, size); 389 390 float v = (y - 1) * stepUV - limitUV; 391 392 for (int x = 1; x < size - 1; x++) 393 { 394 float u = (x - 1) * stepUV - limitUV; 395 396 float rayLengthSquared = u * u + v * v + 1; 397 398 // Since a type B luminaire is turned on its side, U and V are flipped. 399 float longitude = Mathf.Atan(v) * Mathf.Rad2Deg; // in range [-90..+90] degrees 400 float latitude = Mathf.Asin(u / Mathf.Sqrt(rayLengthSquared)) * Mathf.Rad2Deg; // in range [-90..+90] degrees 401 402 float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude); 403 float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); 404 405 // Factor in the light attenuation further from the texture center. 406 float lightAttenuation = applyLightAttenuation ? rayLengthSquared : 1f; 407 408 slice[x] = ComputePixelColor(horizontalAnglePosition, verticalAnglePosition, lightAttenuation); 409 } 410 } 411 412 return textureBuffer; 413 } 414 415 NativeArray<Color> BuildTypeCGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation) 416 { 417 float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad); 418 float stepUV = (2 * limitUV) / (size - 3); 419 420 var textureBuffer = new NativeArray<Color>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory); 421 422 // Leave a one-pixel black border around the texture to avoid cookie spilling. 423 for (int y = 1; y < size - 1; y++) 424 { 425 var slice = new NativeSlice<Color>(textureBuffer, y * size, size); 426 427 float v = (y - 1) * stepUV - limitUV; 428 429 for (int x = 1; x < size - 1; x++) 430 { 431 float u = (x - 1) * stepUV - limitUV; 432 433 float uvLength = Mathf.Sqrt(u * u + v * v); 434 435 float longitude = ((Mathf.Atan2(v, u) - k_HalfPi + k_TwoPi) % k_TwoPi) * Mathf.Rad2Deg; // in range [0..360] degrees 436 float latitude = Mathf.Atan(uvLength) * Mathf.Rad2Deg; // in range [0..90] degrees 437 438 float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude); 439 float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude); 440 441 // Factor in the light attenuation further from the texture center. 442 float lightAttenuation = applyLightAttenuation ? (uvLength * uvLength + 1) : 1f; 443 444 slice[x] = ComputePixelColor(horizontalAnglePosition, verticalAnglePosition, lightAttenuation); 445 } 446 } 447 448 return textureBuffer; 449 } 450 } 451}