Files
speckle-unity/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs
T
2023-06-07 13:54:10 +01:00

358 lines
12 KiB
C#

using System;
using Objects.Geometry;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Objects.Other;
using Speckle.ConnectorUnity.NativeCache;
using Speckle.ConnectorUnity.Utils;
using Speckle.ConnectorUnity.Wrappers;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Models.GraphTraversal;
using UnityEditor;
using UnityEngine;
using Mesh = UnityEngine.Mesh;
using Object = UnityEngine.Object;
using SMesh = Objects.Geometry.Mesh;
using Transform = UnityEngine.Transform;
using STransform = Objects.Other.Transform;
#nullable enable
namespace Objects.Converter.Unity
{
public partial class ConverterUnity
{
#region helper methods
/// <summary>
/// Converts a 3D vector from Speckle's RH Z-up to Unity's LH Y-up coordinate system
/// </summary>
/// <returns>Scaled Vector in Unity coordinate space</returns>
public Vector3 VectorByCoordinates(double x, double y, double z, double scaleFactor = 1d)
{
// switch y and z //TODO is this correct? LH -> RH
return new Vector3((float) (x * scaleFactor), (float) (z * scaleFactor), (float) (y * scaleFactor));
}
public Vector3 VectorByCoordinates(double x, double y, double z, string units)
{
var f = Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
return VectorByCoordinates(x, y, z, f);
}
public Vector3 VectorFromPoint(Point p) => VectorByCoordinates(p.x, p.y, p.z, p.units);
/// <summary>
///
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
public Vector3[] ArrayToPoints(IList<double> arr, string units)
{
if (arr.Count % 3 != 0) throw new Exception("Array malformed: length%3 != 0.");
Vector3[] points = new Vector3[arr.Count / 3];
var f = Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
for (int i = 2, k = 0; i < arr.Count; i += 3)
points[k++] = VectorByCoordinates(arr[i - 2], arr[i - 1], arr[i], f);
return points;
}
#endregion
#region ToSpeckle
//TODO: more of these
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public virtual Point PointToSpeckle(Vector3 p)
{
//switch y and z
return new Point(p.x, p.z, p.y);
}
#endregion
#region ToNative
protected GameObject? NewPointBasedGameObject(Vector3[] points, string name)
{
if (points.Length == 0) return null;
float pointDiameter = 1; //TODO: figure out how best to change this?
var go = new GameObject();
go.name = name;
var lineRenderer = go.AddComponent<LineRenderer>();
lineRenderer.positionCount = points.Length;
lineRenderer.SetPositions(points);
lineRenderer.numCornerVertices = lineRenderer.numCapVertices = 8;
lineRenderer.startWidth = lineRenderer.endWidth = pointDiameter;
return go;
}
/// <summary>
/// Converts a Speckle <paramref name="point"/> to a <see cref="GameObject"/> with a <see cref="LineRenderer"/>
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public GameObject? PointToNative(Point point)
{
Vector3 newPt = VectorByCoordinates(point.x, point.y, point.z, point.units);
var go = NewPointBasedGameObject(new Vector3[] {newPt, newPt}, point.speckle_type);
return go;
}
/// <summary>
/// Converts a Speckle <paramref name="line"/> to a <see cref="GameObject"/> with a <see cref="LineRenderer"/>
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
public GameObject? LineToNative(Line line)
{
var points = new List<Vector3> {VectorFromPoint(line.start), VectorFromPoint(line.end)};
var go = NewPointBasedGameObject(points.ToArray(), line.speckle_type);
return go;
}
/// <summary>
/// Converts a Speckle <paramref name="polyline"/> to a <see cref="GameObject"/> with a <see cref="LineRenderer"/>
/// </summary>
/// <param name="polyline"></param>
/// <returns></returns>
public GameObject? PolylineToNative(Polyline polyline)
{
var points = polyline.GetPoints().Select(VectorFromPoint);
var go = NewPointBasedGameObject(points.ToArray(), polyline.speckle_type);
return go;
}
/// <summary>
/// Converts a Speckle <paramref name="curve"/> to a <see cref="GameObject"/> with a <see cref="LineRenderer"/>
/// </summary>
/// <param name="curve"></param>
/// <returns></returns>
public GameObject? CurveToNative(Curve curve)
{
var points = ArrayToPoints(curve.points, curve.units);
var go = NewPointBasedGameObject(points, curve.speckle_type);
return go;
}
public Dictionary<string, object?> GetProperties(Base o) => GetProperties(o, typeof(Base));
public Dictionary<string, object?> GetProperties(Base o, Type excludeType)
{
var excludeProps = new HashSet<string>(excludeType
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(x => x.Name));
foreach (string alias in DisplayValuePropertyAliases)
{
excludeProps.Add(alias);
}
excludeProps.Add("renderMaterial");
excludeProps.Add("elements");
excludeProps.Add("name");
//excludeProps.Add("tag");
excludeProps.Add("physicsLayer");
return o.GetMembers()
.Where(x => !(excludeProps.Contains(x.Key) || excludeProps.Contains(x.Key.TrimStart('@'))))
.ToDictionary(x => x.Key, x => (object?) x.Value);
}
#endregion
private Base CreateSpeckleObjectFromProperties(GameObject go)
{
var sd = go.GetComponent<SpeckleProperties>();
if (sd == null || sd.Data == null)
return new Base();
Base sobject = (Base) Activator.CreateInstance(sd.SpeckleType);
foreach (var key in sd.Data.Keys)
{
try
{
sobject[key] = sd.Data[key];
}
catch (SpeckleException)
{
// Ignore SpeckleExceptions that may be caused by get only properties
}
}
return sobject;
}
public GameObject? InstanceToNative(Instance instance)
{
if (instance.definition == null)
{
Debug.Log($"Skipping {typeof(BlockInstance)} {instance.id}, block definition was null");
return null;
}
var defName = instance.definition["name"] as string ?? "";
// Check for existing converted object
if(LoadedAssets.TryGetObject(instance.definition, out GameObject? existingGo))
{
var go = InstantiateCopy(existingGo);
go.name = defName;
TransformToNativeTransform(go.transform, instance.transform);
return go;
}
// Convert the block definition
GameObject native = new GameObject(defName);
List<SMesh> meshes = new();
List<Base> others = new();
var geometry = instance.definition is BlockDefinition b
? b.geometry
: GraphTraversal.TraverseMember(instance.definition["elements"]);
foreach (Base geo in geometry)
{
if (geo is SMesh m) meshes.Add(m);
else if (geo is IDisplayValue<List<SMesh>> s) meshes.AddRange(s.displayValue);
else others.Add(geo);
}
if (meshes.Any())
{
if(!TryGetMeshFromCache(instance.definition, meshes, out Mesh? nativeMesh, out _))
{
MeshToNativeMesh(meshes, out nativeMesh);
string name = AssetHelpers.GenerateObjectName(instance.definition);
nativeMesh.name = name;
LoadedAssets.TrySaveObject(instance.definition, nativeMesh);
}
var nativeMaterials = RenderMaterialsToNative(meshes);
native.SafeMeshSet(nativeMesh, nativeMaterials);
}
foreach (Base child in others)
{
GameObject? c = ConvertToNativeGameObject(child);
if (c == null) continue;
c.transform.SetParent(native.transform, false);
}
LoadedAssets.TrySaveObject(instance.definition, native);
TransformToNativeTransform(native.transform, instance.transform);
if (instance["name"] is string instanceName) native.name = instanceName;
return native;
}
private static GameObject InstantiateCopy(GameObject existingGo)
{
#if UNITY_EDITOR
GameObject? prefabInstance = null;
bool isPrefab = PrefabUtility.GetPrefabAssetType(existingGo) != PrefabAssetType.NotAPrefab;
if (isPrefab)
{
GameObject? prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(existingGo);
if (prefabAsset == null) prefabAsset = existingGo;
prefabInstance = (GameObject) PrefabUtility.InstantiatePrefab(prefabAsset);
}
if (prefabInstance != null)
return prefabInstance;
#endif
return Object.Instantiate(existingGo);
}
/// <summary>
/// Converts a 4x4 transformation matrix from Speckle's <see cref="Other.Transform"/> format,
/// to a Unity <see cref="Matrix4x4"/>. Applying Z -> Y up conversion, and applying units to the translation
/// </summary>
/// <param name="speckleTransform"></param>
/// <returns>Transformation matrix in Unity's coordinate system</returns>
public Matrix4x4 TransformToNativeMatrix(STransform speckleTransform)
{
var sf = Speckle.Core.Kits.Units.GetConversionFactor(speckleTransform.units, ModelUnits);
var smatrix = speckleTransform.matrix;
return new Matrix4x4
{
// Left (X -> X)
[0, 0] = smatrix.M11,
[2, 0] = smatrix.M21,
[1, 0] = smatrix.M31,
[3, 0] = smatrix.M41,
//Up (Z -> Y)
[0, 2] = smatrix.M12,
[2, 2] = smatrix.M22,
[1, 2] = smatrix.M32,
[3, 2] = smatrix.M42,
//Forwards (Y -> Z)
[0, 1] = smatrix.M13,
[2, 1] = smatrix.M23,
[1, 1] = smatrix.M33,
[3, 1] = smatrix.M43,
//Translation
[0, 3] = (float) (smatrix.M14 * sf),
[2, 3] = (float) (smatrix.M24 * sf),
[1, 3] = (float) (smatrix.M34 * sf),
[3, 3] = smatrix.M44,
};
}
public void TransformToNativeTransform(Transform transform, STransform speckleTransform)
{
Matrix4x4 matrix = TransformToNativeMatrix(speckleTransform);
ApplyMatrixToTransform(transform, matrix);
}
protected static void ApplyMatrixToTransform(Transform transform, Matrix4x4 m)
{
transform.localScale =
m.lossyScale; //doesn't work for non TRS, maybe we could fallback to squareSum approach (see TransformVectorized::SetFromMatrix in UE src)
//We can't use m.rotation, as it gives us incorrect results (perhaps because of RH -> LH? or maybe our MatrixToNative is broken?)
transform.localRotation = Quaternion.LookRotation(
m.GetColumn(2),
m.GetColumn(1)
);
transform.localPosition = m.GetPosition();
}
}
}