-
Notifications
You must be signed in to change notification settings - Fork 95
Expand file tree
/
Copy pathHEU_InputInterfaceSpline.cs
More file actions
394 lines (346 loc) · 16.1 KB
/
HEU_InputInterfaceSpline.cs
File metadata and controls
394 lines (346 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/*
* Copyright (c) <2023> Side Effects Software Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. The name of Side Effects Software may not be used to endorse or
* promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if UNITY_SPLINES_INSTALLED
using UnityEngine.Splines;
using Unity.Mathematics;
#endif
namespace HoudiniEngineUnity
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Typedefs (copy these from HEU_Common.cs)
using HAPI_NodeId = System.Int32;
[System.Serializable]
public class HEU_InputInterfaceSplineSettings
{
public float SamplingResolution
{
get => _samplingResolution;
set => _samplingResolution = value;
}
[SerializeField] private float _samplingResolution = 25.0f;
};
#if UNITY_SPLINES_INSTALLED
/// <summary>
/// This class provides functionality for uploading Unity spline data from gameobjects
/// into Houdini through an input node.
/// It derives from the HEU_InputInterface and registers with HEU_InputUtility so that it
/// can be used automatically when uploading mesh data.
/// </summary>
public class HEU_InputInterfaceSpline : HEU_InputInterface
{
#if UNITY_EDITOR
/// <summary>
/// Registers this input inteface for Unity splines on
/// the callback after scripts are reloaded in Unity.
/// </summary>
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnScriptsReloaded()
{
HEU_InputInterfaceSpline inputInterface = new HEU_InputInterfaceSpline();
HEU_InputUtility.RegisterInputInterface(inputInterface);
}
#endif
private HEU_InputInterfaceSplineSettings settings;
private HEU_InputInterfaceSpline() : base(priority: DEFAULT_PRIORITY)
{
}
public void Initialize(HEU_InputInterfaceSplineSettings settings)
{
if (settings == null)
{
settings = new HEU_InputInterfaceSplineSettings();
}
this.settings = settings;
}
/// <summary>
/// Return true if this interface supports uploading the given inputObject's data.
/// Should check the components on the inputObject and children.
/// </summary>
/// <param name="inputObject">The gameobject whose components will be checked</param>
/// <returns>True if this interface supports uploading this input object's data</returns>
public override bool IsThisInputObjectSupported(GameObject inputObject)
{
if (inputObject != null)
{
if (inputObject.GetComponent<SplineContainer>() != null)
return true;
}
return false;
}
/// <summary>
/// Create the input node and upload data based on the given inputObject.
/// </summary>
/// <param name="session">Session to create the node in</param>
/// <param name="connectNodeID">The node to connect the input node to. Usually the SOP/merge node.</param>
/// <param name="inputObject">The gameobject containing the components with data for upload</param>
/// <param name="inputNodeID">The newly created input node's ID</param>
/// <returns>Returns true if sucessfully created the input node and uploaded data.</returns>
public override bool CreateInputNodeWithDataUpload(HEU_SessionBase session, HAPI_NodeId connectNodeID, GameObject inputObject, out HAPI_NodeId inputNodeID)
{
inputNodeID = HEU_Defines.HEU_INVALID_NODE_ID;
if (!HEU_HAPIUtility.IsNodeValidInHoudini(session, connectNodeID))
{
HEU_Logger.LogError("Connection node is invalid.");
return false;
}
// Get spline data from the input object
HEU_InputDataSplineContainer inputSplines = GenerateSplineDataFromGameObject(inputObject);
if (inputSplines == null || inputSplines._inputSplines == null || inputSplines._inputSplines.Count() == 0)
{
HEU_Logger.LogError("No valid splines found on input objects.");
return false;
}
string inputName = inputObject.name + "_0";
HAPI_NodeId newNodeID = HEU_Defines.HEU_INVALID_NODE_ID;
session.CreateInputCurveNode(out newNodeID, inputName);
if (newNodeID == HEU_Defines.HEU_INVALID_NODE_ID || !HEU_HAPIUtility.IsNodeValidInHoudini(session, newNodeID))
{
HEU_Logger.LogError("Failed to create new input cruve node in Houdini session!");
return false;
}
inputNodeID = newNodeID;
HEU_InputDataSpline inputSpline = inputSplines._inputSplines[0];
if (!UploadData(session, inputNodeID, inputSpline, Matrix4x4.identity))
{
if (!session.CookNode(inputNodeID, false))
{
HEU_Logger.LogError("New input curve node failed to cook!");
return false;
}
return false;
}
// The spline is made up of branching sub-splines.
// Create an input node for each branching spline and object-merge it to the root spline.
bool createMergeNode = inputSplines._inputSplines.Count() > 1;
if (!createMergeNode)
return true;
HAPI_NodeId mergeNodeId = HEU_Defines.HEU_INVALID_NODE_ID;
HAPI_NodeId parentId = HEU_HAPIUtility.GetParentNodeID(session, inputNodeID);
if (!session.CreateNode(parentId, "merge", null, false, out mergeNodeId))
{
HEU_Logger.LogErrorFormat("Unable to create merge SOP node for connecting input assets.");
return false;
}
if (!session.ConnectNodeInput(mergeNodeId, 0, newNodeID))
{
HEU_Logger.LogErrorFormat("Unable to connect to input node!");
return false;
}
if (!session.SetNodeDisplay(mergeNodeId, 1))
{
HEU_Logger.LogWarningFormat("Unable to set display flag!");
}
inputNodeID = mergeNodeId;
Matrix4x4 localToWorld = inputSplines._transform.localToWorldMatrix;
HAPI_NodeId branchNodeID;
HEU_InputDataSpline branchSpline;
for (int i = 1; i < inputSplines._inputSplines.Count(); i++)
{
session.CreateInputCurveNode(out branchNodeID, inputObject.name + "_" + i);
if (branchNodeID == HEU_Defines.HEU_INVALID_NODE_ID || !HEU_HAPIUtility.IsNodeValidInHoudini(session, branchNodeID))
{
HEU_Logger.LogError("Failed to create new input cruve node in Houdini session!");
return false;
}
branchSpline = inputSplines._inputSplines[i];
if (!UploadData(session, branchNodeID, branchSpline, localToWorld))
{
if (!session.CookNode(branchNodeID, false))
{
HEU_Logger.LogError("New input curve node failed to cook!");
return false;
}
return false;
}
if (!session.ConnectNodeInput(mergeNodeId, i, branchNodeID))
{
HEU_Logger.LogErrorFormat("Unable to connect to input node!");
return false;
}
}
if (!session.CookNode(inputNodeID, false))
{
HEU_Logger.LogError("New input node failed to cook!");
return false;
}
return true;
}
/// <summary>
/// Contains input geometry for a single spline.
/// </summary>
public class HEU_InputDataSpline
{
public Spline _spline;
public bool _closed;
public int _count;
public float _length;
public BezierKnot[] _knots;
public bool _allKnotsLinear;
}
/// <summary>
/// Contains input geometry for multiple splines.
/// </summary>
public class HEU_InputDataSplineContainer : HEU_InputData
{
public List<HEU_InputDataSpline> _inputSplines = new List<HEU_InputDataSpline>();
public Transform _transform;
}
/// <summary>
/// Return an input data structure containing spline data that needs to be
/// uploaded from the given inputObject.
/// </summary>
/// <param name="inputObject">GameObject containing a Spline component</param>
/// <returns>A valid input data strcuture containing spline data</returns>
public HEU_InputDataSplineContainer GenerateSplineDataFromGameObject(GameObject inputObject)
{
SplineContainer splineContainer = inputObject.GetComponent<SplineContainer>();
IReadOnlyList<Spline> splines = splineContainer.Splines;
HEU_InputDataSplineContainer splineContainerData = new HEU_InputDataSplineContainer();
foreach (Spline spline in splines)
{
HEU_InputDataSpline splineData = new HEU_InputDataSpline();
splineData._spline = spline;
splineData._closed = spline.Closed;
splineData._count = spline.Count;
splineData._length = spline.GetLength();
splineData._knots = spline.Knots.ToArray<BezierKnot>();
// Check if all knots use linear tangent mode
bool allLinear = spline.Count > 0;
for (int i = 0; i < spline.Count; i++)
{
if (spline.GetTangentMode(i) != TangentMode.Linear)
{
allLinear = false;
break;
}
}
splineData._allKnotsLinear = allLinear;
splineContainerData._inputSplines.Add(splineData);
}
splineContainerData._transform = inputObject.transform;
return splineContainerData;
}
/// <summary>
/// Upload the inputData into the input curve node with inputNodeID.
/// </summary>
/// <param name="session">Session that the input node exists in</param>
/// <param name="inputNodeID">ID of the input node</param>
/// <param name="inputData">Container of the mesh geometry</param>
/// <returns>True if successfully uploaded data</returns>
public bool UploadData(HEU_SessionBase session, HAPI_NodeId inputNodeID, HEU_InputDataSpline inputSpline, Matrix4x4 localToWorld)
{
// Set the input curve info of the newly created input curve
HAPI_InputCurveInfo inputCurveInfo = new HAPI_InputCurveInfo();
if (inputSpline._allKnotsLinear)
{
inputCurveInfo.curveType = HAPI_CurveType.HAPI_CURVETYPE_LINEAR;
inputCurveInfo.order = 2;
}
else
{
inputCurveInfo.curveType = HAPI_CurveType.HAPI_CURVETYPE_BEZIER;
inputCurveInfo.order = 4;
}
inputCurveInfo.closed = inputSpline._closed;
inputCurveInfo.reverse = false;
inputCurveInfo.inputMethod = HAPI_InputCurveMethod.HAPI_CURVEMETHOD_BREAKPOINTS;
inputCurveInfo.breakpointParameterization =
HAPI_InputCurveParameterization.HAPI_CURVEPARAMETERIZATION_UNIFORM;
if (!session.SetInputCurveInfo(inputNodeID, 0, ref inputCurveInfo))
{
HEU_Logger.LogError("Failed to initialize input curve info.");
return false;
}
// Calculate the number of refined points we want
int numControlPoints = inputSpline._knots.Count();
float splineLength = inputSpline._length;
float splineResolution = settings != null ? settings.SamplingResolution : 0.0f;
int numRefinedSplinePoints =
splineResolution > 0.0f ? Mathf.CeilToInt(splineLength / splineResolution) + 1 : numControlPoints;
float[] posArr;
float[] rotArr;
float[] scaleArr;
if (numRefinedSplinePoints <= numControlPoints)
{
// There's not enough refined points, so we'll use the control points instead
posArr = new float[numControlPoints * 3];
rotArr = new float[numControlPoints * 4];
scaleArr = new float[numControlPoints * 3];
for (int i = 0; i < numControlPoints; i++)
{
BezierKnot knot = inputSpline._knots[i];
// For branching sub-splines, apply local transform on vertices to get the merged spline
float3 pos = localToWorld.MultiplyPoint(knot.Position);
HEU_HAPIUtility.ConvertPositionUnityToHoudini(pos, out posArr[i * 3 + 0], out posArr[i * 3 + 1], out posArr[i * 3 + 2]);
HEU_HAPIUtility.ConvertRotationUnityToHoudini(knot.Rotation, out rotArr[i * 4 + 0], out rotArr[i * 4 + 1], out rotArr[i * 4 + 2], out rotArr[i * 4 + 3]);
}
}
else
{
// Calculate the refined spline component
posArr = new float[numRefinedSplinePoints * 3];
rotArr = new float[numRefinedSplinePoints * 4];
scaleArr = new float[numRefinedSplinePoints * 3];
float currentDistance = 0.0f;
for (int i = 0; i < numRefinedSplinePoints; i++)
{
float3 pos =
SplineUtility.EvaluatePosition<Spline>(inputSpline._spline, currentDistance / splineLength);
// For branching sub-splines, apply local transform on vertices to get the merged spline
pos = localToWorld.MultiplyPoint(pos);
HEU_HAPIUtility.ConvertPositionUnityToHoudini(pos, out posArr[i * 3 + 0], out posArr[i * 3 + 1], out posArr[i * 3 + 2]);
currentDistance += splineResolution;
}
}
bool hasRotations = rotArr.Length == posArr.Length;
bool hasScales = scaleArr.Length == posArr.Length;
bool hapi_result = false;
if (!hasRotations && !hasScales)
{
hapi_result = session.SetInputCurvePositions(inputNodeID, 0, posArr, 0, posArr.Length);
}
else
{
hapi_result = session.SetInputCurvePositionsRotationsScales(
inputNodeID, 0,
posArr, 0, posArr.Length,
rotArr, 0, rotArr.Length,
scaleArr, 0, 0
);
}
if (!hapi_result)
{
HEU_Logger.LogError("Failed to set input curve positions.");
return false;
}
return session.CommitGeo(inputNodeID);
}
}
#endif
} // HoudiniEngineUnity