//! @file substancearchive.cs //! @brief Substance archive interface //! @author Galen Helfter - Adobe //! @date 20210609 //! @copyright Adobe. All rights reserved. using System; using System.Runtime.InteropServices; using UnityEngine; using Adobe.Substance.Input; using Adobe.Substance.Input.Description; namespace Adobe.Substance { /// /// Object that represents a native C++ substance graph. /// public sealed class SubstanceNativeGraph : IDisposable { private IntPtr _handler; private bool _disposedValue; private bool _inRenderWork; public bool IsInitialized { get; set; } public bool InRenderWork { get { lock (this) { return _inRenderWork; } } set { lock (this) { _inRenderWork = value; } } } private readonly int _fileGraphID; /// /// Create a object that acts as a brige between Unity and Substance C++ SDK. /// /// Content of the sbsar file. /// Target graph ID inside the file. internal SubstanceNativeGraph(byte[] fileContent, int targetGraphID) { _fileGraphID = targetGraphID; int size = Marshal.SizeOf(fileContent[0]) * fileContent.Length; var nativeMemory = Marshal.AllocHGlobal(size); Marshal.Copy(fileContent, 0, nativeMemory, size); try { _handler = NativeMethods.sbsario_sbsar_load_from_memory(nativeMemory, (IntPtr)size); if (_handler == default) throw new ArgumentException(); } finally { if (nativeMemory != default) Marshal.FreeHGlobal(nativeMemory); } } /// /// Renders graph. /// /// Native render result. public IntPtr Render() { ErrorCode errorCode = (ErrorCode)NativeMethods.sbsario_sbsar_render(_handler, (IntPtr)_fileGraphID, out IntPtr result); if (errorCode != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(errorCode); return result; } /// /// Get raw thumbnail data from graph. /// /// Raw graph thumbnail RGBA data. public byte[] GetThumbnail() { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_graph_thumbnail(_handler, (IntPtr)_fileGraphID, out NativeThumbnail thumbnail); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); if (thumbnail.Size == IntPtr.Zero) return null; var thumbnailData = new byte[(int)thumbnail.Size]; Marshal.Copy(thumbnail.Data, thumbnailData, 0, thumbnailData.Length); return thumbnailData; } #region Presets public string CreatePresetFromCurrentState() { //Alocate 1Mb for the preset file text. NativePreset preset = new NativePreset { XMLString = Marshal.AllocHGlobal(1024 * 1024) }; try { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_make_preset_from_current_state(_handler, (IntPtr)_fileGraphID, ref preset); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); var stringResult = Marshal.PtrToStringAnsi(preset.XMLString); return stringResult; } finally { //Free native allocated memory. Marshal.FreeHGlobal(preset.XMLString); } } /// /// Apply preset string to graph. /// /// public void ApplyPreset(string presetXML) { NativePreset preset = new NativePreset(); preset.XMLString = Marshal.StringToHGlobalAnsi(presetXML); try { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_apply_preset(_handler, (IntPtr)_fileGraphID, ref preset); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); } finally { // Always free the unmanaged string. Marshal.FreeHGlobal(preset.XMLString); } } #endregion Presets #region Output /// /// Get the total output count for a given graph in the substance object. /// /// Total graph output count. public int GetOutputCount() { return (int)NativeMethods.sbsario_sbsar_get_output_count(_handler, (IntPtr)_fileGraphID); } public SubstanceOutputDescription GetOutputDescription(int outputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_output_desc(_handler, (IntPtr)_fileGraphID, (IntPtr)outputID, out NativeOutputDesc inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); var identifier = Marshal.PtrToStringAnsi(inputDesc.mIdentifier); var label = Marshal.PtrToStringAnsi(inputDesc.mIdentifier); var channel = Marshal.PtrToStringAnsi(inputDesc.mChannelUsage); if (string.IsNullOrEmpty(channel)) { Debug.LogWarning($"Output {outputID} does not have a channel type. This output will not be rendered. "); channel = "unkown"; } if (string.IsNullOrEmpty(identifier)) { Debug.LogWarning($"Output {outputID} does not have a identifier."); identifier = "unkown"; } if (string.IsNullOrEmpty(label)) { Debug.LogWarning($"Output {outputID} does not have a label."); label = "unkown"; } return new SubstanceOutputDescription() { Identifier = identifier, Label = label, Index = (int)inputDesc.mIndex, Type = inputDesc.mValueType.ToUnity(), Channel = channel }; } public SubstanceOutputDescription CreateVirtualOutput(SubstanceVirtualOutputCreateInfo info) { var description = info.CreateOutputDesc(); var format = info.CreateOutputFormat(); ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_create_virtual_output(_handler, (IntPtr)_fileGraphID, ref description, ref format); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); var identifier = Marshal.PtrToStringAnsi(description.mIdentifier); var label = Marshal.PtrToStringAnsi(description.mIdentifier); var channel = Marshal.PtrToStringAnsi(description.mChannelUsage); return new SubstanceOutputDescription() { Identifier = identifier, Label = label, Index = (int)description.mIndex, Type = description.mValueType.ToUnity(), Channel = channel, }; } public int CreateOutputCopy(int outputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_create_output_copy(_handler, (IntPtr)_fileGraphID, (IntPtr)outputID, out NativeOutputDesc inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); return (int)inputDesc.mIndex; } public uint GetOutputUID(int outputIndex) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_output_uid(_handler, (IntPtr)_fileGraphID, (IntPtr)outputIndex, out IntPtr outputUID); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); return (uint)outputUID; } public void AssignOutputToAlphaChannel(int targetOutputID, int alphaChannelID, bool invert = false) { float minValue = invert ? 1f : 0f; float maxValue = invert ? 0f : 1f; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_assign_as_alpha_channel(_handler, (IntPtr)_fileGraphID, (IntPtr)targetOutputID, (IntPtr)alphaChannelID, minValue, maxValue); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); } public void ResetAlphaChannelAssignment(int targetOutputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_output_desc(_handler, (IntPtr)_fileGraphID, (IntPtr)targetOutputID, out NativeOutputDesc outputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); result = (ErrorCode)NativeMethods.sbsario_sbsar_get_output_format_override(_handler, (IntPtr)_fileGraphID, (IntPtr)targetOutputID, out IntPtr isFormatOverridenPtr, out NativeOutputFormat oldFormat); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); bool isFormatOverriden = (int)isFormatOverridenPtr != 0; if (!isFormatOverriden) return; if (oldFormat.format == NativeConsts.UseDefault) return; NativeOutputFormat newFormat = oldFormat; newFormat.format = NativeConsts.UseDefault; result = (ErrorCode)NativeMethods.sbsario_sbsar_set_output_format_override(_handler, (IntPtr)_fileGraphID, (IntPtr)targetOutputID, ref newFormat); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); } public void ChangeOutputRBChannels(int targetOutputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_output_format_override(_handler, (IntPtr)_fileGraphID, (IntPtr)targetOutputID, out IntPtr isFormatOverridenPtr, out NativeOutputFormat oldFormat); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); bool isFormatOverriden = (int)isFormatOverridenPtr != 0; NativeOutputFormat newFormat = NativeOutputFormat.CreateDefault(); if (isFormatOverriden) newFormat = oldFormat; var redChannel = newFormat.ChannelComponent0; var blueChannel = newFormat.ChannelComponent2; if (blueChannel.outputIndex == NativeConsts.UseDefault || redChannel.outputIndex == NativeConsts.UseDefault) return; if (newFormat.ChannelComponent0.ShuffleIndex == ShuffleIndex.Blue && newFormat.ChannelComponent2.ShuffleIndex == ShuffleIndex.Red) { return; } newFormat.ChannelComponent0.ShuffleIndex = ShuffleIndex.Blue; newFormat.ChannelComponent2.ShuffleIndex = ShuffleIndex.Red; result = (ErrorCode)NativeMethods.sbsario_sbsar_set_output_format_override(_handler, (IntPtr)_fileGraphID, (IntPtr)targetOutputID, ref newFormat); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); } #endregion Output #region Input /// /// Get the total input count for a given graph in the substance object. /// /// Total graph input count. public int GetInputCount() { return (int)NativeMethods.sbsario_sbsar_get_input_count(_handler, (IntPtr)_fileGraphID); } /// /// Get the input object for a given graph in the substance object. /// /// Input index. /// Substance input object. public SubstanceInputBase GetInputObject(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { if (result == ErrorCode.SBSARIO_ERROR_FAILURE) { Debug.LogWarning($"Unable to load input from graphID: {_fileGraphID} and inputID:{inputID}. The input type is not supported."); return SubstanceInputFactory.CreateInvalidInput(inputID); } throw new SubstanceException(result); } var input = SubstanceInputFactory.CreateInput(inputDesc); AssignInputDescription(inputID, input); return input; } /// /// Returns true if the target input should be visible in the editor. /// /// Target input ID. /// True if the target input should be visible in the editor. public bool IsInputVisible(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input_visibility(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeInputVisibility visibility); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); return (int)visibility.IsVisible == 1; } /// /// Sets a float input. /// /// Input index. /// Input value. public void SetInputFloat(int inputID, float value) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_FLOAT; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mFloatData0 = value; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } /// /// Sets a float2 input. /// /// Input index. /// Input value. public void SetInputFloat2(int inputID, Vector2 value) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_FLOAT2; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mFloatData0 = value.x; inputData.Data.mFloatData1 = value.y; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } /// /// Sets a float3 input. /// /// Input index. /// Input value. public void SetInputFloat3(int inputID, Vector3 value) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_FLOAT3; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mFloatData0 = value.x; inputData.Data.mFloatData1 = value.y; inputData.Data.mFloatData2 = value.z; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } /// /// Sets a float4 input. /// /// Input index. /// Input value. public void SetInputFloat4(int inputID, Vector4 value) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_FLOAT4; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mFloatData0 = value.x; inputData.Data.mFloatData1 = value.y; inputData.Data.mFloatData2 = value.z; inputData.Data.mFloatData3 = value.w; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } /// /// Sets a int input. /// /// Input index. /// Input value. public void SetInputInt(int inputID, int value) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_INT; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mIntData0 = value; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } /// /// Sets a int2 input. /// /// Input index. /// Input value. public void SetInputInt2(int inputID, Vector2Int value) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_INT2; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mIntData0 = value.x; inputData.Data.mIntData1 = value.y; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } /// /// Sets a int3 input. /// /// Input index. /// Input value. public void SetInputInt3(int inputID, Vector3Int value) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_INT3; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mIntData0 = value.x; inputData.Data.mIntData1 = value.y; inputData.Data.mIntData2 = value.z; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } /// /// Sets a int4 input. /// /// Input index. /// Input x value. /// Input y value. /// Input z value. /// Input w value. public void SetInputInt4(int inputID, int x, int y, int z, int w) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_INT4; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mIntData0 = x; inputData.Data.mIntData1 = y; inputData.Data.mIntData2 = z; inputData.Data.mIntData3 = w; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } public void SetInputString(int inputID, string value) { NativeData inputData = new NativeData(); inputData.DataType = DataType.SBSARIO_DATA_INPUT; inputData.ValueType = ValueType.SBSARIO_VALUE_STRING; inputData.Index = (IntPtr)inputID; inputData.Data = new DataInternalNumeric(); inputData.Data.mPtr = Marshal.StringToHGlobalAnsi(value); ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } public void SetInputTexture2DNull(int inputID) { var imageData = new NativeDataImage { channel_order = ChannelOrder.SBSARIO_CHANNEL_ORDER_RGBA, height = IntPtr.Zero, width = IntPtr.Zero }; var numericData = new DataInternalNumeric { ImageData = imageData }; NativeData inputData = new NativeData { DataType = DataType.SBSARIO_DATA_INPUT, ValueType = ValueType.SBSARIO_VALUE_IMAGE, Index = (IntPtr)inputID, Data = numericData }; ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } public void SetInputTexture2D(int inputID, Color32[] pixelData, int width, int height) { if (pixelData == null) return; Color32[] flippedData = TextureExtensions.FlipY(pixelData, width, height); byte[] textureBytes = TextureExtensions.Color32ArrayToByteArray(flippedData); int textureSize = Marshal.SizeOf(textureBytes[0]) * textureBytes.Length; IntPtr tempNativeMemory = Marshal.AllocHGlobal(textureSize); Marshal.Copy(textureBytes, 0, tempNativeMemory, textureBytes.Length); var imageData = new NativeDataImage { channel_order = ChannelOrder.SBSARIO_CHANNEL_ORDER_RGBA, height = (IntPtr)width, width = (IntPtr)height, mipmaps = (IntPtr)0, image_format = TextureFormat.RGBA32.ToSubstance(), data = tempNativeMemory }; var numericData = new DataInternalNumeric { ImageData = imageData }; NativeData inputData = new NativeData { DataType = DataType.SBSARIO_DATA_INPUT, ValueType = ValueType.SBSARIO_VALUE_IMAGE, Index = (IntPtr)inputID, Data = numericData }; try { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_set_input(_handler, (IntPtr)_fileGraphID, ref inputData); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to update Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } } catch (Exception) { throw; } finally { Marshal.FreeHGlobal(tempNativeMemory); } } public float GetInputFloat(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return inputDesc.Data.mFloatData0; } public Vector2 GetInputFloat2(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return new Vector2(inputDesc.Data.mFloatData0, inputDesc.Data.mFloatData1); } public Vector3 GetInputFloat3(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return new Vector3(inputDesc.Data.mFloatData0, inputDesc.Data.mFloatData1, inputDesc.Data.mFloatData2); } public Vector4 GetInputFloat4(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return new Vector4(inputDesc.Data.mFloatData0, inputDesc.Data.mFloatData1, inputDesc.Data.mFloatData2, inputDesc.Data.mFloatData2); } public int GetInputInt(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return inputDesc.Data.mIntData0; } public Vector2Int GetInputInt2(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return new Vector2Int(inputDesc.Data.mIntData0, inputDesc.Data.mIntData1); } public Vector3Int GetInputInt3(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return new Vector3Int(inputDesc.Data.mIntData0, inputDesc.Data.mIntData1, inputDesc.Data.mIntData2); } public int[] GetInputInt4(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return new int[] { inputDesc.Data.mIntData0, inputDesc.Data.mIntData1, inputDesc.Data.mIntData2, inputDesc.Data.mIntData2 }; } public string GetInputString(int inputID) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeData inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input {inputID} for graph {_fileGraphID}"); throw new SubstanceException(result); } return Marshal.PtrToStringAnsi(inputDesc.Data.mPtr); } #endregion Input public IntPtr GetNativeHandle() { return _handler; } public int GetFileGraphID() { return _fileGraphID; } #region IDisposable public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!_disposedValue) { if (_handler != default) { NativeMethods.sbsario_sbsar_close(_handler); _handler = default; } _disposedValue = true; } } ~SubstanceNativeGraph() { Dispose(disposing: false); } #endregion IDisposable private void AssignInputDescription(int inputID, SubstanceInputBase input) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_input_desc(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeInputDesc inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); var identifier = inputDesc.mIdentifier == default ? null : Marshal.PtrToStringAnsi(inputDesc.mIdentifier); var label = inputDesc.mLabel == default ? null : Marshal.PtrToStringAnsi(inputDesc.mLabel); var guiGroup = inputDesc.GuiGroup == default ? null : Marshal.PtrToStringAnsi(inputDesc.GuiGroup); var guiDescription = inputDesc.GuiDescription == default ? null : Marshal.PtrToStringAnsi(inputDesc.GuiDescription); input.Description = new SubstanceInputDescription() { Identifier = identifier, Label = label, GuiGroup = guiGroup, GuiDescription = guiDescription, Type = ((ValueType)inputDesc.mValueType).ToUnity(), WidgetType = ((WidgetType)inputDesc.inputWidgetType).ToUnity() }; if (input.IsNumeric) AssignNumericInputDescription(inputID, input); } private void AssignNumericInputDescription(int inputID, SubstanceInputBase substanceInput) { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_numeric_input_desc(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, out NativeNumericInputDesc inputDesc); if (result != ErrorCode.SBSARIO_ERROR_OK) throw new SubstanceException(result); substanceInput.SetNumericDescription(inputDesc); if ((int)(inputDesc.enumValueCount) != 0) { var count = (int)inputDesc.enumValueCount; NativeEnumInputDesc[] buffer = new NativeEnumInputDesc[count]; GCHandle gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); IntPtr pointer = gcHandle.AddrOfPinnedObject(); try { result = (ErrorCode)NativeMethods.sbsario_sbsar_get_enum_input_desc(_handler, (IntPtr)_fileGraphID, (IntPtr)inputID, pointer, inputDesc.enumValueCount); substanceInput.SetEnumOptions(buffer); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError("Unable to get data for enum."); return; } } finally { gcHandle.Free(); } } } public Vector3 GetPhysicalSize() { ErrorCode result = (ErrorCode)NativeMethods.sbsario_sbsar_get_physical_size(_handler, (IntPtr)_fileGraphID, out NativePhysicalSize nativePhysicalSize); if (result != ErrorCode.SBSARIO_ERROR_OK) { Debug.LogError($"Fail to get Substance input physical size for graph {_fileGraphID}"); throw new SubstanceException(result); } return new Vector3(nativePhysicalSize.X, nativePhysicalSize.Y, nativePhysicalSize.Z); } } } // namespace Adobe.Substance