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}