#if UNITY_OPENXR_1_9_0_OR_NEWER using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using UnityEngine.XR.MagicLeap.Native; using UnityEngine.XR.OpenXR.NativeTypes; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.XR.OpenXR.Features.MagicLeapSupport { public partial class MagicLeapLightEstimationFeature { private const int EstimateQueryThreshold = 2000; // 2 seconds private ulong lightEstimation; private long lastEstimateQueryTimeNanoSeconds; private bool lightEstimationReady; private DateTime lastUpdateTime; public bool LightEstimationCreated { get; private set; } /// /// Creates a light estimation to enable light estimates to be created for a given cubemap resolution. /// /// The width/height resolution of the face of the cubemap to be used. public void CreateLightEstimation(HDRCubemapFaceResolution cubemapFaceResolution) { if (LightEstimationCreated) { DestroyLightEstimation(); } var createInfo = new XrLightEstimationCreateInfo { Type = XrLightEstimationStructTypes.XrTypeLightEstimationCreateInfoML, Next = default, BaseSpace = AppSpace, Time = NextPredictedDisplayTime, CubemapFaceResolution = (XrLightEstimationHDRCubemapFaceResolution)cubemapFaceResolution }; unsafe { var resultCode = nativeFunctions.XrCreateLightEstimation(AppSession, ref createInfo, out lightEstimation); bool didSucceed = Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrCreateLightEstimation)); if (didSucceed) { LightEstimationCreated = true; DelayReadiness(); } } } /// /// Destroys the active light estimation so light estimates can no longer be created. /// public void DestroyLightEstimation() { unsafe { var resultCode = nativeFunctions.XrDestroyLightEstimation(lightEstimation); bool didSucceed = Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrDestroyLightEstimation)); if (didSucceed) { lightEstimation = 0; LightEstimationCreated = false; lightEstimationReady = false; lastUpdateTime = DateTime.MinValue; } } } /// /// Gets the last time that the light estimation state was updated. /// /// The time of last update in nanoseconds. public long GetLastUpdateTime() { var state = new XrLightEstimationState { Type = XrLightEstimationStructTypes.XrTypeLightEstimationStateML, Next = default, LastUpdate = default }; unsafe { var resultCode = nativeFunctions.XrGetLightEstimationState(lightEstimation, ref state); Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrGetLightEstimationState)); } return state.LastUpdate; } /// /// Determines if enough time has passed for a light estimation estimate to obtain data. /// /// The readiness of the light estimation estimate. public bool CheckEstimationEstimateReadiness() { if (!lightEstimationReady) { return false; } // allow first time creating estimate to occur if it did not occur for the estimation already if (lastUpdateTime == DateTime.MinValue) { return true; } TimeSpan elapsed = DateTime.Now - lastUpdateTime; return elapsed.TotalMilliseconds >= EstimateQueryThreshold; } /// /// Creates a light estimation estimate, obtains the light estimate data, then destroys the light estimate. /// /// The data obtained from a light estimation estimate. public EstimateData GetLightEstimationEstimateData() { EstimateData estimateData; ulong lightEstimationEstimate = CreateLightEstimationEstimate(); lastUpdateTime = DateTime.Now; estimateData.CubeMap = GetHDRCubemapData(lightEstimationEstimate); estimateData.DirectionalLight = GetMainDirectionalLight(lightEstimationEstimate); estimateData.HarmonicsCoefficients = GetSphericalHarmonics(lightEstimationEstimate); estimateData.TimeStampNanoSeconds = GetTimestamp(lightEstimationEstimate); DestroyLightEstimationEstimate(lightEstimationEstimate); return estimateData; } /// /// Gets a Unity Cubemap object from a light estimate's cubemap data. /// /// The float array of pixels obtained from a light estimation estimate. /// The desired width/height dimension of each face of the cubemap. /// A cubemap object from the light estimate data in a format usable by Unity. public Cubemap GetEstimateCubemap(float[] rawPixelsArray, int faceDim) { Cubemap cubemap = new Cubemap(faceDim, TextureFormat.RGBA32, false); int numBytes = faceDim * faceDim * 4; for (int i = 0; i < 6; i++) { byte[] pixelBytes = new byte[numBytes]; // convert the values in the float array for the given face into a byte array for (int j = 0; j < numBytes; j++) { pixelBytes[j] = (byte)(rawPixelsArray[i * numBytes + j] * 255); } pixelBytes = MLTextureUtils.FlipPixelsVertically(pixelBytes, faceDim, faceDim); CubemapFace cubemapFace; switch (i) { case 0: cubemapFace = CubemapFace.PositiveX; break; case 1: cubemapFace = CubemapFace.NegativeX; break; case 2: cubemapFace = CubemapFace.PositiveY; break; case 3: cubemapFace = CubemapFace.NegativeY; break; case 4: cubemapFace = CubemapFace.PositiveZ; break; case 5: cubemapFace = CubemapFace.NegativeZ; break; default: cubemapFace = CubemapFace.Unknown; break; } cubemap.SetPixelData(pixelBytes, 0, cubemapFace); } cubemap.Apply(); return cubemap; } private unsafe ulong CreateLightEstimationEstimate() { var resultCode = nativeFunctions.XrCreateLightEstimationEstimate(lightEstimation, out ulong lightEstimationEstimate); Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrCreateLightEstimationEstimate)); return lightEstimationEstimate; } private unsafe HDRCubemapData GetHDRCubemapData(ulong lightEstimationEstimate) { var hdrCubemap = new XrLightEstimationHDRCubemap { Type = XrLightEstimationStructTypes.XrTypeLightEstimationHDRCubemapML, Next = default, PixelCountInput = default, PixelCountOutput = default, Pixels = null, FaceDim = default }; // call first time to get pixelCountOutput var resultCode = nativeFunctions.XrGetLightEstimationHDRCubemap(lightEstimationEstimate, ref hdrCubemap); bool didSucceed = Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrGetLightEstimationHDRCubemap)); if (!didSucceed) { return default; } int pixelsArraySize = (int)hdrCubemap.PixelCountOutput; hdrCubemap.PixelCountInput = hdrCubemap.PixelCountOutput; hdrCubemap.Pixels = (float*)new NativeArray(pixelsArraySize, Allocator.Temp).GetUnsafePtr(); // call second time to get pixels data resultCode = nativeFunctions.XrGetLightEstimationHDRCubemap(lightEstimationEstimate, ref hdrCubemap); didSucceed = Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrGetLightEstimationHDRCubemap)); if (!didSucceed) { return default; } HDRCubemapData outputData; outputData.Pixels = new float[pixelsArraySize]; outputData.FaceDimension = hdrCubemap.FaceDim; for (int i = 0; i < pixelsArraySize; i++) { outputData.Pixels[i] = hdrCubemap.Pixels[i]; } return outputData; } private MainDirectionalLight GetMainDirectionalLight(ulong lightEstimationEstimate) { var mainDirectionalLight = new XrLightEstimationMainDirectionalLight { Type = XrLightEstimationStructTypes.XrTypeLightEstimationMainDirectionalLightML, Next = default, Direction = default, Color = default }; unsafe { var resultCode = nativeFunctions.XrGetLightEstimationMainDirectionalLight(lightEstimationEstimate, ref mainDirectionalLight); Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrGetLightEstimationMainDirectionalLight)); } MainDirectionalLight lightOutput; lightOutput.Direction = mainDirectionalLight.Direction; lightOutput.Color = mainDirectionalLight.Color; return lightOutput; } private unsafe float[] GetSphericalHarmonics(ulong lightEstimationEstimate) { float[] outputArray = new float[TotalHarmonics]; var sphericalHarmonics = new XrLightEstimationSphericalHarmonics(); sphericalHarmonics.Type = XrLightEstimationStructTypes.XrTypeLightEstimationSphericalHarmonicsML; sphericalHarmonics.Next = default; var resultCode = nativeFunctions.XrGetLightEstimationSphericalHarmonics(lightEstimationEstimate, ref sphericalHarmonics); Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrGetLightEstimationSphericalHarmonics)); for (int i = 0; i < TotalHarmonics; i++) { outputArray[i] = sphericalHarmonics.HarmonicsCoefficients[i]; } return outputArray; } private unsafe long GetTimestamp(ulong lightEstimationEstimate) { var resultCode = nativeFunctions.XrGetLightEstimationTimestamp(lightEstimationEstimate, out long lastTimestampNanoSeconds); Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrGetLightEstimationTimestamp)); return lastTimestampNanoSeconds; } private unsafe void DestroyLightEstimationEstimate(ulong lightEstimationEstimate) { var resultCode = nativeFunctions.XrDestroyLightEstimationEstimate(lightEstimationEstimate); bool didSucceed = Utils.DidXrCallSucceed(resultCode, nameof(nativeFunctions.XrDestroyLightEstimationEstimate)); if (didSucceed) { lightEstimationEstimate = 0; } } private async void DelayReadiness() { await Task.Delay(EstimateQueryThreshold); lightEstimationReady = true; } } } #endif