feat(converter): Refactor to enable Block conversion
This commit is contained in:
@@ -5,18 +5,12 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Objects.Other;
|
||||
using Objects.Utils;
|
||||
using Speckle.ConnectorUnity;
|
||||
using Speckle.Core.Logging;
|
||||
using Speckle.Core.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Material = UnityEngine.Material;
|
||||
using Mesh = UnityEngine.Mesh;
|
||||
using Object = UnityEngine.Object;
|
||||
using SMesh = Objects.Geometry.Mesh;
|
||||
using SColor = System.Drawing.Color;
|
||||
using Transform = UnityEngine.Transform;
|
||||
using STransform = Objects.Other.Transform;
|
||||
|
||||
@@ -26,20 +20,19 @@ namespace Objects.Converter.Unity
|
||||
public partial class ConverterUnity
|
||||
{
|
||||
|
||||
protected static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
|
||||
protected static readonly int Metallic = Shader.PropertyToID("_Metallic");
|
||||
protected static readonly int Glossiness = Shader.PropertyToID("_Glossiness");
|
||||
|
||||
#region helper methods
|
||||
|
||||
|
||||
|
||||
public Vector3 VectorByCoordinates(double x, double y, double z, double scaleFactor)
|
||||
|
||||
/// <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));
|
||||
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);
|
||||
@@ -60,16 +53,17 @@ namespace Objects.Converter.Unity
|
||||
|
||||
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>
|
||||
@@ -82,173 +76,13 @@ namespace Objects.Converter.Unity
|
||||
//switch y and z
|
||||
return new Point(p.x, p.z, p.y);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public virtual List<SMesh>? MeshToSpeckle(MeshFilter meshFilter)
|
||||
{
|
||||
Material[]? materials = meshFilter.GetComponent<Renderer>()?.materials;
|
||||
#if UNITY_EDITOR
|
||||
var nativeMesh = meshFilter.sharedMesh;
|
||||
#else
|
||||
var nativeMesh = meshFilter.mesh;
|
||||
#endif
|
||||
if (nativeMesh == null) return null;
|
||||
|
||||
List<SMesh> convertedMeshes = new List<SMesh>(nativeMesh.subMeshCount);
|
||||
for (int i = 0; i < nativeMesh.subMeshCount; i++)
|
||||
{
|
||||
var subMesh = nativeMesh.GetSubMesh(i);
|
||||
SMesh converted;
|
||||
switch (subMesh.topology)
|
||||
{
|
||||
// case MeshTopology.Points:
|
||||
// //TODO convert as pointcloud
|
||||
// continue;
|
||||
case MeshTopology.Triangles:
|
||||
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 3);
|
||||
convertedMeshes.Add(converted);
|
||||
break;
|
||||
case MeshTopology.Quads:
|
||||
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 4);
|
||||
convertedMeshes.Add(converted);
|
||||
break;
|
||||
default:
|
||||
Debug.LogError($"Failed to convert submesh {i} of {typeof(GameObject)} {meshFilter.gameObject.name} to Speckle, Unsupported Mesh Topography {subMesh.topology}. Submesh will be ignored.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (materials == null || materials.Length <= i) continue;
|
||||
|
||||
Material mat = materials[i];
|
||||
if(mat != null) converted["renderMaterial"] = MaterialToSpeckle(mat);
|
||||
}
|
||||
|
||||
return convertedMeshes;
|
||||
}
|
||||
|
||||
|
||||
protected virtual SMesh SubMeshToSpeckle(Mesh nativeMesh, Transform instanceTransform, SubMeshDescriptor subMesh, int subMeshIndex, int faceN)
|
||||
{
|
||||
var nFaces = nativeMesh.GetIndices( subMeshIndex, true);
|
||||
int numFaces = nFaces.Length / faceN;
|
||||
List<int> sFaces = new List<int>(numFaces * (faceN + 1));
|
||||
|
||||
int indexOffset = subMesh.firstVertex;
|
||||
|
||||
// int i = 0;
|
||||
// int j = 0;
|
||||
// while (i < nFaces.Length)
|
||||
// {
|
||||
// if (j == 0)
|
||||
// {
|
||||
// sFaces.Add(faceN);
|
||||
// j = faceN;
|
||||
// }
|
||||
// sFaces.Add(nFaces[i] - indexOffset);
|
||||
// j--;
|
||||
// i++;
|
||||
// }
|
||||
|
||||
int i = nFaces.Length - 1;
|
||||
int j = 0;
|
||||
while (i >= 0) //Traverse backwards to ensure CCW face orientation
|
||||
{
|
||||
if (j == 0)
|
||||
{
|
||||
//Add face cardinality indicator ever
|
||||
sFaces.Add(faceN);
|
||||
j = faceN;
|
||||
}
|
||||
sFaces.Add(nFaces[i] - indexOffset);
|
||||
j--;
|
||||
i--;
|
||||
}
|
||||
|
||||
int vertexTake = subMesh.vertexCount;
|
||||
var nVertices = nativeMesh.vertices.Skip(indexOffset).Take(vertexTake);
|
||||
List<double> sVertices = new List<double>(subMesh.vertexCount * 3);
|
||||
foreach (var vertex in nVertices)
|
||||
{
|
||||
var p = instanceTransform.TransformPoint(vertex);
|
||||
sVertices.Add(p.x);
|
||||
sVertices.Add(p.z); //z and y swapped //TODO is this correct? LH -> RH
|
||||
sVertices.Add(p.y);
|
||||
}
|
||||
|
||||
var nColors = nativeMesh.colors.Skip(indexOffset).Take(vertexTake).ToArray();;
|
||||
List<int> sColors = new List<int>(nColors.Length);
|
||||
sColors.AddRange(nColors.Select(c => c.ToIntColor()));
|
||||
|
||||
var nTexCoords = nativeMesh.uv.Skip(indexOffset).Take(vertexTake).ToArray();
|
||||
List<double> sTexCoords = new List<double>(nTexCoords.Length * 2);
|
||||
foreach (var uv in nTexCoords)
|
||||
{
|
||||
sTexCoords.Add(uv.x);
|
||||
sTexCoords.Add(uv.y);
|
||||
}
|
||||
|
||||
var convertedMesh = new SMesh
|
||||
{
|
||||
vertices = sVertices,
|
||||
faces = sFaces,
|
||||
colors = sColors,
|
||||
textureCoordinates = sTexCoords,
|
||||
units = ModelUnits
|
||||
};
|
||||
|
||||
return convertedMesh;
|
||||
}
|
||||
|
||||
protected static HashSet<string> SupportedShadersToSpeckle = new()
|
||||
{
|
||||
"Legacy Shaders/Transparent/Diffuse", "Standard"
|
||||
};
|
||||
public virtual RenderMaterial MaterialToSpeckle(Material nativeMaterial)
|
||||
{
|
||||
//Warning message for unknown shaders
|
||||
if(!SupportedShadersToSpeckle.Contains(nativeMaterial.shader.name)) Debug.LogWarning($"Material Shader \"{nativeMaterial.shader.name}\" is not explicitly supported, the resulting material may be incorrect");
|
||||
|
||||
var color = nativeMaterial.color;
|
||||
var opacity = 1f;
|
||||
if (nativeMaterial.shader.name.ToLower().Contains("transparent"))
|
||||
{
|
||||
opacity = color.a;
|
||||
color.a = 255;
|
||||
}
|
||||
|
||||
var emissive = nativeMaterial.IsKeywordEnabled("_EMISSION")
|
||||
? nativeMaterial.GetColor(EmissionColor).ToIntColor()
|
||||
: SColor.Black.ToArgb();
|
||||
|
||||
var materialName = !string.IsNullOrWhiteSpace(nativeMaterial.name)
|
||||
? nativeMaterial.name.Replace("(Instance)", string.Empty).TrimEnd()
|
||||
: $"material-{Guid.NewGuid().ToString().Substring(0, 8)}";
|
||||
|
||||
var metalness = nativeMaterial.HasProperty(Metallic)
|
||||
? nativeMaterial.GetFloat(Metallic)
|
||||
: 0;
|
||||
|
||||
var roughness = nativeMaterial.HasProperty(Glossiness)
|
||||
? 1 - nativeMaterial.GetFloat(Glossiness)
|
||||
: 1;
|
||||
|
||||
return new RenderMaterial
|
||||
{
|
||||
name = materialName,
|
||||
diffuse = color.ToIntColor(),
|
||||
opacity = opacity,
|
||||
metalness = metalness,
|
||||
roughness = roughness,
|
||||
emissive = emissive,
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region ToNative
|
||||
|
||||
protected GameObject? NewPointBasedGameObject(Vector3[] points, string name)
|
||||
{
|
||||
if (points.Length == 0) return null;
|
||||
@@ -277,7 +111,7 @@ namespace Objects.Converter.Unity
|
||||
{
|
||||
Vector3 newPt = VectorByCoordinates(point.x, point.y, point.z, point.units);
|
||||
|
||||
var go = NewPointBasedGameObject(new Vector3[] { newPt, newPt }, point.speckle_type);
|
||||
var go = NewPointBasedGameObject(new Vector3[] {newPt, newPt}, point.speckle_type);
|
||||
return go;
|
||||
}
|
||||
|
||||
@@ -289,7 +123,7 @@ namespace Objects.Converter.Unity
|
||||
/// <returns></returns>
|
||||
public GameObject? LineToNative(Line line)
|
||||
{
|
||||
var points = new List<Vector3> { VectorFromPoint(line.start), VectorFromPoint(line.end) };
|
||||
var points = new List<Vector3> {VectorFromPoint(line.start), VectorFromPoint(line.end)};
|
||||
|
||||
var go = NewPointBasedGameObject(points.ToArray(), line.speckle_type);
|
||||
return go;
|
||||
@@ -320,301 +154,52 @@ namespace Objects.Converter.Unity
|
||||
return go;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts multiple <paramref name="meshes"/> (e.g. with different materials) into one native mesh
|
||||
/// </summary>
|
||||
/// <param name="meshes">Collection of <see cref="Objects.Geometry.Mesh"/>es that shall be converted</param>
|
||||
/// <returns>A <see cref="GameObject"/> with the converted <see cref="UnityEngine.Mesh"/>, <see cref="MeshFilter"/>, and <see cref="MeshRenderer"/></returns>
|
||||
public GameObject? MeshesToNative(IReadOnlyCollection<SMesh> meshes)
|
||||
{
|
||||
if (!meshes.Any()) return null;
|
||||
|
||||
var go = new GameObject();
|
||||
MeshDataToNative(meshes, out var nativeMesh, out var nativeMaterials, out var center);
|
||||
|
||||
go.transform.position = center;
|
||||
go.SafeMeshSet(nativeMesh, true);
|
||||
|
||||
var meshRenderer = go.AddComponent<MeshRenderer>();
|
||||
|
||||
meshRenderer.sharedMaterials = nativeMaterials;
|
||||
|
||||
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)
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts <paramref name="speckleMesh"/> to a <see cref="GameObject"/> with a <see cref="MeshRenderer"/>
|
||||
/// </summary>
|
||||
/// <param name="speckleMesh">Mesh to convert</param>
|
||||
/// <returns></returns>
|
||||
public GameObject? MeshToNative(SMesh speckleMesh)
|
||||
{
|
||||
if (speckleMesh.vertices.Count == 0 || speckleMesh.faces.Count == 0)
|
||||
{
|
||||
Debug.Log($"Skipping mesh {speckleMesh.id}, mesh data was empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject? converted = MeshesToNative(new[] {speckleMesh});
|
||||
//if(converted != null) AttachSpeckleProperties(converted,speckleMesh.GetType(), GetProperties(speckleMesh, typeof(Mesh)));
|
||||
return converted;
|
||||
.ToDictionary(x => x.Key, x => (object?) x.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="meshes">meshes to be converted as SubMeshes</param>
|
||||
/// <param name="nativeMesh">The converted native mesh</param>
|
||||
/// <param name="nativeMaterials">The converted materials (one per converted sub-mesh)</param>
|
||||
/// <param name="center">Center position for the mesh</param>
|
||||
public void MeshDataToNative(IReadOnlyCollection<SMesh> meshes, out Mesh nativeMesh, out Material[] nativeMaterials, out Vector3 center)
|
||||
{
|
||||
var verts = new List<Vector3>();
|
||||
|
||||
var uvs = new List<Vector2>();
|
||||
var vertexColors = new List<Color>();
|
||||
|
||||
var materials = new List<Material>(meshes.Count);
|
||||
var subMeshes = new List<List<int>>(meshes.Count);
|
||||
|
||||
foreach (SMesh m in meshes)
|
||||
{
|
||||
if(m.vertices.Count == 0 || m.faces.Count == 0 ) continue;
|
||||
List<int> tris = new List<int>();
|
||||
SubmeshToNative(m, verts, tris, uvs, vertexColors, materials);
|
||||
subMeshes.Add(tris);
|
||||
}
|
||||
nativeMaterials = materials.ToArray();
|
||||
|
||||
Debug.Assert(verts.Count >= 0);
|
||||
Debug.Assert(verts.Count >= 0);
|
||||
nativeMesh = new Mesh();
|
||||
|
||||
RecenterVertices(verts, out center);
|
||||
|
||||
nativeMesh.subMeshCount = subMeshes.Count;
|
||||
|
||||
nativeMesh.SetVertices(verts);
|
||||
nativeMesh.SetUVs(0, uvs);
|
||||
nativeMesh.SetColors(vertexColors);
|
||||
|
||||
|
||||
int j = 0;
|
||||
foreach(var subMeshTriangles in subMeshes)
|
||||
{
|
||||
nativeMesh.SetTriangles(subMeshTriangles, j);
|
||||
j++;
|
||||
}
|
||||
|
||||
if (nativeMesh.vertices.Length >= UInt16.MaxValue)
|
||||
nativeMesh.indexFormat = IndexFormat.UInt32;
|
||||
|
||||
nativeMesh.Optimize();
|
||||
nativeMesh.RecalculateBounds();
|
||||
nativeMesh.RecalculateNormals();
|
||||
nativeMesh.RecalculateTangents();
|
||||
}
|
||||
|
||||
|
||||
protected void SubmeshToNative(SMesh speckleMesh, List<Vector3> verts, List<int> tris, List<Vector2> texCoords, List<Color> vertexColors, List<Material> materials)
|
||||
{
|
||||
speckleMesh.AlignVerticesWithTexCoordsByIndex();
|
||||
speckleMesh.TriangulateMesh();
|
||||
|
||||
int indexOffset = verts.Count;
|
||||
|
||||
// Convert Vertices
|
||||
verts.AddRange(ArrayToPoints(speckleMesh.vertices, speckleMesh.units));
|
||||
|
||||
// Convert texture coordinates
|
||||
bool hasValidUVs = speckleMesh.TextureCoordinatesCount == speckleMesh.VerticesCount;
|
||||
if(speckleMesh.textureCoordinates.Count > 0 && !hasValidUVs) Debug.LogWarning($"Expected number of UV coordinates to equal vertices. Got {speckleMesh.TextureCoordinatesCount} expected {speckleMesh.VerticesCount}. \nID = {speckleMesh.id}");
|
||||
|
||||
if (hasValidUVs)
|
||||
{
|
||||
texCoords.Capacity += speckleMesh.TextureCoordinatesCount;
|
||||
for (int j = 0; j < speckleMesh.TextureCoordinatesCount; j++)
|
||||
{
|
||||
var (u, v) = speckleMesh.GetTextureCoordinate(j);
|
||||
texCoords.Add(new Vector2((float)u,(float)v));
|
||||
}
|
||||
}
|
||||
else if (speckleMesh.bbox != null)
|
||||
{
|
||||
//Attempt to generate some crude UV coordinates using bbox //TODO this will be broken for submeshes
|
||||
texCoords.AddRange(GenerateUV(verts, (float)speckleMesh.bbox.xSize.Length, (float)speckleMesh.bbox.ySize.Length));
|
||||
}
|
||||
|
||||
// Convert vertex colors
|
||||
if (speckleMesh.colors != null)
|
||||
{
|
||||
if (speckleMesh.colors.Count == speckleMesh.VerticesCount)
|
||||
{
|
||||
vertexColors.AddRange(speckleMesh.colors.Select(c => c.ToUnityColor()));
|
||||
}
|
||||
else if (speckleMesh.colors.Count != 0)
|
||||
{
|
||||
//TODO what if only some submeshes have colors?
|
||||
Debug.LogWarning($"{typeof(SMesh)} {speckleMesh.id} has invalid number of vertex {nameof(SMesh.colors)}. Expected 0 or {speckleMesh.VerticesCount}, got {speckleMesh.colors.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert faces
|
||||
tris.Capacity += (int) (speckleMesh.faces.Count / 4f) * 3;
|
||||
|
||||
for (int i = 0; i < speckleMesh.faces.Count; i += 4)
|
||||
{
|
||||
//We can safely assume all faces are triangles since we called TriangulateMesh
|
||||
tris.Add(speckleMesh.faces[i + 1] + indexOffset);
|
||||
tris.Add(speckleMesh.faces[i + 3] + indexOffset);
|
||||
tris.Add(speckleMesh.faces[i + 2] + indexOffset);
|
||||
}
|
||||
|
||||
// Convert RenderMaterial
|
||||
materials.Add(RenderMaterialToNative(speckleMesh["renderMaterial"] as RenderMaterial));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static IEnumerable<Vector2> GenerateUV(IReadOnlyList<Vector3> verts, float xSize, float ySize)
|
||||
{
|
||||
var uv = new Vector2[verts.Count];
|
||||
for (int i = 0; i < verts.Count; i++)
|
||||
{
|
||||
|
||||
var vert = verts[i];
|
||||
uv[i] = new Vector2(vert.x / xSize, vert.y / ySize);
|
||||
}
|
||||
return uv;
|
||||
}
|
||||
|
||||
private static Matrix4x4 UnflattenMatrix(IList<double> flatMatrix)
|
||||
{
|
||||
Matrix4x4 matrix = new Matrix4x4();
|
||||
for(int row = 0; row < 4; row++)
|
||||
for(int col = 0; col < 4; col++)
|
||||
{
|
||||
matrix[row,col] = (float)flatMatrix[row * 4 + col];
|
||||
}
|
||||
|
||||
return matrix.transpose;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static void RecenterVertices(List<Vector3> vertices, out Vector3 center)
|
||||
{
|
||||
center = Vector3.zero;
|
||||
|
||||
if (vertices == null || !vertices.Any()) return;
|
||||
|
||||
Bounds meshBounds = new Bounds { center = vertices[0] };
|
||||
|
||||
foreach (var vert in vertices)
|
||||
meshBounds.Encapsulate(vert);
|
||||
|
||||
center = meshBounds.center;
|
||||
|
||||
for (int i = 0; i < vertices.Count; i++)
|
||||
vertices[i] -= meshBounds.center;
|
||||
}
|
||||
|
||||
private Material RenderMaterialToNative(RenderMaterial? renderMaterial)
|
||||
{
|
||||
//todo support more complex materials
|
||||
var shader = Shader.Find("Standard");
|
||||
Material mat = new Material(shader);
|
||||
|
||||
//if a renderMaterial is passed use that, otherwise try get it from the mesh itself
|
||||
if (renderMaterial == null) return mat;
|
||||
|
||||
// 1. match material by name, if any
|
||||
string materialName = string.IsNullOrWhiteSpace(renderMaterial.name)
|
||||
? $"material-{renderMaterial.id}"
|
||||
: renderMaterial.name.Replace('/', '-');
|
||||
|
||||
if (LoadedAssets.TryGetValue(materialName, out Object asset)
|
||||
&& asset is Material loadedMaterial) return loadedMaterial;
|
||||
|
||||
// 2. re-create material by setting diffuse color and transparency on standard shaders
|
||||
if (renderMaterial.opacity < 1)
|
||||
{
|
||||
shader = Shader.Find("Transparent/Diffuse");
|
||||
mat = new Material(shader);
|
||||
}
|
||||
|
||||
var c = renderMaterial.diffuse.ToUnityColor();
|
||||
mat.color = new Color(c.r, c.g, c.b, (float)renderMaterial.opacity);
|
||||
mat.name = materialName;
|
||||
mat.SetFloat(Metallic, (float)renderMaterial.metalness);
|
||||
mat.SetFloat(Glossiness, 1 - (float)renderMaterial.roughness);
|
||||
|
||||
if (renderMaterial.emissive != SColor.Black.ToArgb()) mat.EnableKeyword ("_EMISSION");
|
||||
mat.SetColor(EmissionColor, renderMaterial.emissive.ToUnityColor());
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (StreamManager.GenerateMaterials)
|
||||
{
|
||||
string name = mat.name.Trim(Path.GetInvalidFileNameChars());
|
||||
if (!AssetDatabase.IsValidFolder("Assets/Resources")) AssetDatabase.CreateFolder("Assets", "Resources");
|
||||
if (!AssetDatabase.IsValidFolder("Assets/Resources/Materials")) AssetDatabase.CreateFolder("Assets/Resources", "Materials");
|
||||
if (!AssetDatabase.IsValidFolder("Assets/Resources/Materials/Speckle Generated")) AssetDatabase.CreateFolder("Assets/Resources/Materials", "Speckle Generated");
|
||||
|
||||
if (AssetDatabase.LoadAllAssetsAtPath($"Assets/Resources/Materials/Speckle Generated/" + name + ".mat").Length == 0) AssetDatabase.CreateAsset(mat, "Assets/Resources/Materials/Speckle Generated/" + name + ".mat");
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
return mat;
|
||||
// 3. if not renderMaterial was passed, the default shader will be used
|
||||
}
|
||||
|
||||
private SpeckleProperties AttachSpeckleProperties(GameObject go, Type speckleType, IDictionary<string, object?> properties)
|
||||
{
|
||||
var sd = go.AddComponent<SpeckleProperties>();
|
||||
sd.Data = properties;
|
||||
sd.SpeckleType = speckleType;
|
||||
return sd;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
|
||||
Base sobject = (Base) Activator.CreateInstance(sd.SpeckleType);
|
||||
|
||||
foreach (var key in sd.Data.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
sobject[key] = sd.Data[key];
|
||||
}
|
||||
catch(SpeckleException)
|
||||
catch (SpeckleException)
|
||||
{
|
||||
// Ignore SpeckleExceptions that may be caused by get only properties
|
||||
}
|
||||
@@ -622,5 +207,134 @@ namespace Objects.Converter.Unity
|
||||
|
||||
return sobject;
|
||||
}
|
||||
|
||||
public GameObject? BlockToNative(BlockInstance block)
|
||||
{
|
||||
if (block.blockDefinition == null)
|
||||
{
|
||||
Debug.Log($"Skipping {typeof(BlockInstance)} {block.id}, block definition was null");
|
||||
return null;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Check `Resources` for existing cached prefab asset
|
||||
// TODO: this isn't how we check for existing materials, maybe there's a reason not to call LoadAssetPath constantly
|
||||
string assetName = $"{GetAssetName(block.blockDefinition)}.prefab"
|
||||
.Trim(Path.GetInvalidFileNameChars());
|
||||
const string assetPath = "Assets/Resources/Prefabs/";
|
||||
if (StreamManager.GenerateAssets) //TODO: I don't like how the converter is aware of StreamManager
|
||||
{
|
||||
GameObject? existing = AssetDatabase.LoadAssetAtPath<GameObject>($"{assetPath}/{assetName}");
|
||||
if (existing)
|
||||
{
|
||||
var go = (GameObject) PrefabUtility.InstantiatePrefab(existing);
|
||||
go.name = block.blockDefinition.name ?? "";
|
||||
return go;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// No existing found, so we Convert the block
|
||||
|
||||
GameObject native = new GameObject(block.blockDefinition.name ?? "");
|
||||
TransformToNativeTransform(native.transform, block.transform);
|
||||
|
||||
List<SMesh> meshes = new();
|
||||
List<Base> others = new();
|
||||
foreach (Base geo in block.blockDefinition.geometry)
|
||||
{
|
||||
if (geo is SMesh m) meshes.Add(m);
|
||||
else if (geo is Brep s) meshes.AddRange(s.displayValue);
|
||||
else others.Add(geo);
|
||||
}
|
||||
|
||||
if (meshes.Any())
|
||||
{
|
||||
MeshToNativeMesh(meshes, out var 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);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (StreamManager.GenerateAssets) //TODO: I don't like how the converter is aware of StreamManager
|
||||
{
|
||||
CreateDirectoryFromAssetPath(assetPath);
|
||||
PrefabUtility.SaveAsPrefabAssetAndConnect(native, $"Assets/Resources/Prefabs/{assetName}",
|
||||
InteractionMode.AutomatedAction);
|
||||
}
|
||||
#endif
|
||||
|
||||
return native;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <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)
|
||||
{
|
||||
double VD(int i) => speckleTransform.value[i];
|
||||
float V(int i) => (float) VD(i);
|
||||
|
||||
var sf = Speckle.Core.Kits.Units.GetConversionFactor(speckleTransform.units, ModelUnits);
|
||||
|
||||
return new Matrix4x4
|
||||
{
|
||||
// Left (X -> X)
|
||||
[0, 0] = V(0),
|
||||
[2, 0] = V(4),
|
||||
[1, 0] = V(8),
|
||||
[3, 0] = V(12),
|
||||
|
||||
//Up (Z -> Y)
|
||||
[0, 2] = V(1),
|
||||
[2, 2] = V(5),
|
||||
[1, 2] = V(9),
|
||||
[3, 2] = V(13),
|
||||
|
||||
//Forwards (Y -> Z)
|
||||
[0, 1] = V(2),
|
||||
[2, 1] = V(6),
|
||||
[1, 1] = V(10),
|
||||
[3, 1] = V(14),
|
||||
|
||||
//Translation
|
||||
[0, 3] = (float) (VD(3) * sf),
|
||||
[2, 3] = (float) (VD(7) * sf),
|
||||
[1, 3] = (float) (VD(11) * sf),
|
||||
[3, 3] = V(15),
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Objects.Other;
|
||||
using Objects.Utils;
|
||||
using PlasticPipe.Certificates;
|
||||
using Speckle.ConnectorUnity;
|
||||
using Speckle.Core.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Material = UnityEngine.Material;
|
||||
using Mesh = UnityEngine.Mesh;
|
||||
using Object = UnityEngine.Object;
|
||||
using SMesh = Objects.Geometry.Mesh;
|
||||
using SColor = System.Drawing.Color;
|
||||
using Transform = UnityEngine.Transform;
|
||||
using STransform = Objects.Other.Transform;
|
||||
|
||||
#nullable enable
|
||||
namespace Objects.Converter.Unity
|
||||
{
|
||||
public partial class ConverterUnity
|
||||
{
|
||||
|
||||
protected static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
|
||||
protected static readonly int Metallic = Shader.PropertyToID("_Metallic");
|
||||
protected static readonly int Glossiness = Shader.PropertyToID("_Glossiness");
|
||||
|
||||
#region ToSpeckle
|
||||
|
||||
public virtual List<SMesh>? MeshToSpeckle(MeshFilter meshFilter)
|
||||
{
|
||||
Material[]? materials = meshFilter.GetComponent<Renderer>()?.materials;
|
||||
#if UNITY_EDITOR
|
||||
var nativeMesh = meshFilter.sharedMesh;
|
||||
#else
|
||||
var nativeMesh = meshFilter.mesh;
|
||||
#endif
|
||||
if (nativeMesh == null) return null;
|
||||
|
||||
List<SMesh> convertedMeshes = new List<SMesh>(nativeMesh.subMeshCount);
|
||||
for (int i = 0; i < nativeMesh.subMeshCount; i++)
|
||||
{
|
||||
var subMesh = nativeMesh.GetSubMesh(i);
|
||||
SMesh converted;
|
||||
switch (subMesh.topology)
|
||||
{
|
||||
// case MeshTopology.Points:
|
||||
// //TODO convert as pointcloud
|
||||
// continue;
|
||||
case MeshTopology.Triangles:
|
||||
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 3);
|
||||
convertedMeshes.Add(converted);
|
||||
break;
|
||||
case MeshTopology.Quads:
|
||||
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 4);
|
||||
convertedMeshes.Add(converted);
|
||||
break;
|
||||
default:
|
||||
Debug.LogError(
|
||||
$"Failed to convert submesh {i} of {typeof(GameObject)} {meshFilter.gameObject.name} to Speckle, Unsupported Mesh Topography {subMesh.topology}. Submesh will be ignored.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (materials == null || materials.Length <= i) continue;
|
||||
|
||||
Material mat = materials[i];
|
||||
if (mat != null) converted["renderMaterial"] = MaterialToSpeckle(mat);
|
||||
}
|
||||
|
||||
return convertedMeshes;
|
||||
}
|
||||
|
||||
|
||||
protected virtual SMesh SubMeshToSpeckle(Mesh nativeMesh, Transform instanceTransform,
|
||||
SubMeshDescriptor subMesh, int subMeshIndex, int faceN)
|
||||
{
|
||||
var nFaces = nativeMesh.GetIndices(subMeshIndex, true);
|
||||
int numFaces = nFaces.Length / faceN;
|
||||
List<int> sFaces = new List<int>(numFaces * (faceN + 1));
|
||||
|
||||
int indexOffset = subMesh.firstVertex;
|
||||
|
||||
// int i = 0;
|
||||
// int j = 0;
|
||||
// while (i < nFaces.Length)
|
||||
// {
|
||||
// if (j == 0)
|
||||
// {
|
||||
// sFaces.Add(faceN);
|
||||
// j = faceN;
|
||||
// }
|
||||
// sFaces.Add(nFaces[i] - indexOffset);
|
||||
// j--;
|
||||
// i++;
|
||||
// }
|
||||
|
||||
int i = nFaces.Length - 1;
|
||||
int j = 0;
|
||||
while (i >= 0) //Traverse backwards to ensure CCW face orientation
|
||||
{
|
||||
if (j == 0)
|
||||
{
|
||||
//Add face cardinality indicator ever
|
||||
sFaces.Add(faceN);
|
||||
j = faceN;
|
||||
}
|
||||
|
||||
sFaces.Add(nFaces[i] - indexOffset);
|
||||
j--;
|
||||
i--;
|
||||
}
|
||||
|
||||
int vertexTake = subMesh.vertexCount;
|
||||
var nVertices = nativeMesh.vertices.Skip(indexOffset).Take(vertexTake);
|
||||
List<double> sVertices = new List<double>(subMesh.vertexCount * 3);
|
||||
foreach (var vertex in nVertices)
|
||||
{
|
||||
var p = instanceTransform.TransformPoint(vertex);
|
||||
sVertices.Add(p.x);
|
||||
sVertices.Add(p.z); //z and y swapped //TODO is this correct? LH -> RH
|
||||
sVertices.Add(p.y);
|
||||
}
|
||||
|
||||
var nColors = nativeMesh.colors.Skip(indexOffset).Take(vertexTake).ToArray();
|
||||
;
|
||||
List<int> sColors = new List<int>(nColors.Length);
|
||||
sColors.AddRange(nColors.Select(c => c.ToIntColor()));
|
||||
|
||||
var nTexCoords = nativeMesh.uv.Skip(indexOffset).Take(vertexTake).ToArray();
|
||||
List<double> sTexCoords = new List<double>(nTexCoords.Length * 2);
|
||||
foreach (var uv in nTexCoords)
|
||||
{
|
||||
sTexCoords.Add(uv.x);
|
||||
sTexCoords.Add(uv.y);
|
||||
}
|
||||
|
||||
var convertedMesh = new SMesh
|
||||
{
|
||||
vertices = sVertices,
|
||||
faces = sFaces,
|
||||
colors = sColors,
|
||||
textureCoordinates = sTexCoords,
|
||||
units = ModelUnits
|
||||
};
|
||||
|
||||
return convertedMesh;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of officially supported shaders. Will attempt to convert shaders not on this list, but will throw warning.
|
||||
/// </summary>
|
||||
protected static HashSet<string> SupportedShadersToSpeckle = new()
|
||||
{
|
||||
"Legacy Shaders/Transparent/Diffuse", "Standard"
|
||||
};
|
||||
|
||||
public virtual RenderMaterial MaterialToSpeckle(Material nativeMaterial)
|
||||
{
|
||||
//Warning message for unknown shaders
|
||||
if (!SupportedShadersToSpeckle.Contains(nativeMaterial.shader.name))
|
||||
Debug.LogWarning(
|
||||
$"Material Shader \"{nativeMaterial.shader.name}\" is not explicitly supported, the resulting material may be incorrect");
|
||||
|
||||
var color = nativeMaterial.color;
|
||||
var opacity = 1f;
|
||||
if (nativeMaterial.shader.name.ToLower().Contains("transparent"))
|
||||
{
|
||||
opacity = color.a;
|
||||
color.a = 255;
|
||||
}
|
||||
|
||||
var emissive = nativeMaterial.IsKeywordEnabled("_EMISSION")
|
||||
? nativeMaterial.GetColor(EmissionColor).ToIntColor()
|
||||
: SColor.Black.ToArgb();
|
||||
|
||||
var materialName = !string.IsNullOrWhiteSpace(nativeMaterial.name)
|
||||
? nativeMaterial.name.Replace("(Instance)", string.Empty).TrimEnd()
|
||||
: $"material-{Guid.NewGuid().ToString().Substring(0, 8)}";
|
||||
|
||||
var metalness = nativeMaterial.HasProperty(Metallic)
|
||||
? nativeMaterial.GetFloat(Metallic)
|
||||
: 0;
|
||||
|
||||
var roughness = nativeMaterial.HasProperty(Glossiness)
|
||||
? 1 - nativeMaterial.GetFloat(Glossiness)
|
||||
: 1;
|
||||
|
||||
return new RenderMaterial
|
||||
{
|
||||
name = materialName,
|
||||
diffuse = color.ToIntColor(),
|
||||
opacity = opacity,
|
||||
metalness = metalness,
|
||||
roughness = roughness,
|
||||
emissive = emissive,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region ToNative
|
||||
|
||||
/// <summary>
|
||||
/// Converts multiple <paramref name="meshes"/> (e.g. with different materials) into one native mesh
|
||||
/// </summary>
|
||||
/// <param name="element">Root element who's name/id is used to identify the mesh</param>
|
||||
/// <param name="meshes">Collection of <see cref="Objects.Geometry.Mesh"/>es that shall be converted</param>
|
||||
/// <returns>A <see cref="GameObject"/> with the converted <see cref="UnityEngine.Mesh"/>, <see cref="MeshFilter"/>, and <see cref="MeshRenderer"/></returns>
|
||||
public GameObject? MeshesToNative(Base element, IReadOnlyCollection<SMesh> meshes)
|
||||
{
|
||||
if (!meshes.Any())
|
||||
{
|
||||
Debug.Log($"Skipping {element.GetType()} {element.id}, zero {typeof(SMesh)} provided");
|
||||
return null;
|
||||
}
|
||||
|
||||
Mesh nativeMesh;
|
||||
Material[] nativeMaterials = RenderMaterialsToNative(meshes);
|
||||
Vector3 center;
|
||||
|
||||
if (LoadedAssets.TryGetValue(element.id, out var existingObj)
|
||||
&& existingObj is Mesh existing)
|
||||
{
|
||||
nativeMesh = existing;
|
||||
MeshDataToNative(meshes,
|
||||
out List<Vector3> verts,
|
||||
out _,
|
||||
out _,
|
||||
out _);
|
||||
center = CalculateBounds(verts).center;
|
||||
}
|
||||
else
|
||||
{
|
||||
MeshToNativeMesh(meshes, out nativeMesh, out center);
|
||||
string name = GetAssetName(element);
|
||||
nativeMesh.name = name;
|
||||
#if UNITY_EDITOR
|
||||
if (StreamManager.GenerateAssets) //TODO: I don't like how the converter is aware of StreamManager
|
||||
{
|
||||
const string assetPath = "Assets/Resources/Meshes/Speckle Generated/";
|
||||
CreateDirectoryFromAssetPath(assetPath);
|
||||
AssetDatabase.CreateAsset(nativeMesh, $"{assetPath}/{name}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
var go = new GameObject();
|
||||
go.transform.position = center;
|
||||
go.SafeMeshSet(nativeMesh, nativeMaterials);
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts <paramref name="speckleMesh"/> to a <see cref="GameObject"/> with a <see cref="MeshRenderer"/>
|
||||
/// </summary>
|
||||
/// <param name="speckleMesh">Mesh to convert</param>
|
||||
/// <returns></returns>
|
||||
public GameObject? MeshToNative(SMesh speckleMesh)
|
||||
{
|
||||
if (speckleMesh.vertices.Count == 0 || speckleMesh.faces.Count == 0)
|
||||
{
|
||||
Debug.Log($"Skipping mesh {speckleMesh.id}, mesh data was empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject? converted = MeshesToNative(speckleMesh, new[] {speckleMesh});
|
||||
|
||||
// Raw meshes shouldn't have dynamic props to attach
|
||||
//if (converted != null) AttachSpeckleProperties(converted,speckleMesh.GetType(), GetProperties(speckleMesh, typeof(Mesh)));
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Speckle <see cref="SMesh"/>s as a native <see cref="Mesh"/> object
|
||||
/// </summary>
|
||||
/// <param name="meshes">meshes to be converted as SubMeshes</param>
|
||||
/// <param name="nativeMesh">The converted native mesh</param>
|
||||
public void MeshToNativeMesh(IReadOnlyCollection<SMesh> meshes, out Mesh nativeMesh)
|
||||
=> MeshToNativeMesh(meshes, out nativeMesh, out _, false);
|
||||
|
||||
|
||||
/// <inheritdoc cref="MeshDataToNative(System.Collections.Generic.IReadOnlyCollection{Objects.Geometry.Mesh},out UnityEngine.Mesh,out UnityEngine.Material[])"/>
|
||||
/// <param name="recenterVerts">when true, will recenter vertices</param>
|
||||
/// <param name="center">Center position for the mesh</param>
|
||||
public void MeshToNativeMesh(IReadOnlyCollection<SMesh> meshes,
|
||||
out Mesh nativeMesh,
|
||||
out Vector3 center,
|
||||
bool recenterVerts = true)
|
||||
{
|
||||
MeshDataToNative(meshes,
|
||||
out List<Vector3> verts,
|
||||
out List<Vector2> uvs,
|
||||
out List<Color> vertexColors,
|
||||
out List<List<int>> subMeshes);
|
||||
|
||||
Debug.Assert(verts.Count >= 0);
|
||||
|
||||
center = recenterVerts ? RecenterVertices(verts) : Vector3.zero;
|
||||
|
||||
nativeMesh = new Mesh();
|
||||
nativeMesh.subMeshCount = subMeshes.Count;
|
||||
nativeMesh.SetVertices(verts);
|
||||
nativeMesh.SetUVs(0, uvs);
|
||||
nativeMesh.SetColors(vertexColors);
|
||||
|
||||
|
||||
int j = 0;
|
||||
foreach (var subMeshTriangles in subMeshes)
|
||||
{
|
||||
nativeMesh.SetTriangles(subMeshTriangles, j);
|
||||
j++;
|
||||
}
|
||||
|
||||
if (nativeMesh.vertices.Length >= UInt16.MaxValue)
|
||||
nativeMesh.indexFormat = IndexFormat.UInt32;
|
||||
|
||||
nativeMesh.Optimize();
|
||||
nativeMesh.RecalculateBounds();
|
||||
nativeMesh.RecalculateNormals();
|
||||
nativeMesh.RecalculateTangents();
|
||||
}
|
||||
|
||||
public void MeshDataToNative(IReadOnlyCollection<SMesh> meshes,
|
||||
out List<Vector3> verts,
|
||||
out List<Vector2> uvs,
|
||||
out List<Color> vertexColors,
|
||||
out List<List<int>> subMeshes)
|
||||
{
|
||||
verts = new List<Vector3>();
|
||||
uvs = new List<Vector2>();
|
||||
vertexColors = new List<Color>();
|
||||
subMeshes = new List<List<int>>(meshes.Count);
|
||||
|
||||
foreach (SMesh m in meshes)
|
||||
{
|
||||
if (m.vertices.Count == 0 || m.faces.Count == 0) continue;
|
||||
|
||||
List<int> tris = new List<int>();
|
||||
SubmeshToNative(m, verts, tris, uvs, vertexColors);
|
||||
subMeshes.Add(tris);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void SubmeshToNative(SMesh speckleMesh, List<Vector3> verts, List<int> tris, List<Vector2> texCoords,
|
||||
List<Color> vertexColors)
|
||||
{
|
||||
speckleMesh.AlignVerticesWithTexCoordsByIndex();
|
||||
speckleMesh.TriangulateMesh();
|
||||
|
||||
int indexOffset = verts.Count;
|
||||
|
||||
// Convert Vertices
|
||||
verts.AddRange(ArrayToPoints(speckleMesh.vertices, speckleMesh.units));
|
||||
|
||||
// Convert texture coordinates
|
||||
bool hasValidUVs = speckleMesh.TextureCoordinatesCount == speckleMesh.VerticesCount;
|
||||
if (speckleMesh.textureCoordinates.Count > 0 && !hasValidUVs)
|
||||
Debug.LogWarning(
|
||||
$"Expected number of UV coordinates to equal vertices. Got {speckleMesh.TextureCoordinatesCount} expected {speckleMesh.VerticesCount}. \nID = {speckleMesh.id}");
|
||||
|
||||
if (hasValidUVs)
|
||||
{
|
||||
texCoords.Capacity += speckleMesh.TextureCoordinatesCount;
|
||||
for (int j = 0; j < speckleMesh.TextureCoordinatesCount; j++)
|
||||
{
|
||||
var (u, v) = speckleMesh.GetTextureCoordinate(j);
|
||||
texCoords.Add(new Vector2((float) u, (float) v));
|
||||
}
|
||||
}
|
||||
else if (speckleMesh.bbox != null)
|
||||
{
|
||||
// Attempt to generate some crude UV coordinates using bbox
|
||||
texCoords.AddRange(GenerateUV(indexOffset, verts, (float) speckleMesh.bbox.xSize.Length,
|
||||
(float) speckleMesh.bbox.ySize.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
texCoords.AddRange(Enumerable.Repeat(Vector2.zero, verts.Count - indexOffset));
|
||||
}
|
||||
|
||||
// Convert vertex colors
|
||||
if (speckleMesh.colors != null)
|
||||
{
|
||||
if (speckleMesh.colors.Count == speckleMesh.VerticesCount)
|
||||
{
|
||||
vertexColors.AddRange(speckleMesh.colors.Select(c => c.ToUnityColor()));
|
||||
}
|
||||
else if (speckleMesh.colors.Count != 0)
|
||||
{
|
||||
//TODO what if only some submeshes have colors?
|
||||
Debug.LogWarning(
|
||||
$"{typeof(SMesh)} {speckleMesh.id} has invalid number of vertex {nameof(SMesh.colors)}. Expected 0 or {speckleMesh.VerticesCount}, got {speckleMesh.colors.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert faces
|
||||
tris.Capacity += (int) (speckleMesh.faces.Count / 4f) * 3;
|
||||
|
||||
for (int i = 0; i < speckleMesh.faces.Count; i += 4)
|
||||
{
|
||||
// We can safely assume all faces are triangles since we called TriangulateMesh
|
||||
tris.Add(speckleMesh.faces[i + 1] + indexOffset);
|
||||
tris.Add(speckleMesh.faces[i + 3] + indexOffset);
|
||||
tris.Add(speckleMesh.faces[i + 2] + indexOffset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static IEnumerable<Vector2> GenerateUV(int indexOffset, IReadOnlyList<Vector3> verts, float xSize,
|
||||
float ySize)
|
||||
{
|
||||
var uv = new Vector2[verts.Count - indexOffset];
|
||||
for (int i = 0; i < verts.Count - indexOffset; i++)
|
||||
{
|
||||
|
||||
var vert = verts[i];
|
||||
uv[i] = new Vector2(vert.x / xSize, vert.y / ySize);
|
||||
}
|
||||
|
||||
return uv;
|
||||
}
|
||||
|
||||
protected static Vector3 RecenterVertices(IList<Vector3> vertices)
|
||||
{
|
||||
if (!vertices.Any()) return Vector3.zero;
|
||||
|
||||
Bounds meshBounds = CalculateBounds(vertices);
|
||||
|
||||
for (int i = 0; i < vertices.Count; i++)
|
||||
vertices[i] -= meshBounds.center;
|
||||
|
||||
return meshBounds.center;
|
||||
}
|
||||
|
||||
protected static Bounds CalculateBounds(IList<Vector3> points)
|
||||
{
|
||||
Bounds b = new Bounds {center = points[0]};
|
||||
|
||||
foreach (var p in points)
|
||||
b.Encapsulate(p);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
public Material[] RenderMaterialsToNative(IEnumerable<SMesh> meshes)
|
||||
{
|
||||
return meshes.Select(m => RenderMaterialToNative(m["renderMaterial"] as RenderMaterial)).ToArray();
|
||||
}
|
||||
|
||||
public Material RenderMaterialToNative(RenderMaterial? renderMaterial)
|
||||
{
|
||||
//todo support more complex materials
|
||||
var shader = Shader.Find("Standard");
|
||||
Material mat = new Material(shader);
|
||||
|
||||
//if a renderMaterial is passed use that, otherwise try get it from the mesh itself
|
||||
if (renderMaterial == null) return mat;
|
||||
|
||||
// 1. match material by name, if any
|
||||
string materialName = string.IsNullOrWhiteSpace(renderMaterial.name)
|
||||
? $"material-{renderMaterial.id}"
|
||||
: renderMaterial.name.Replace('/', '-');
|
||||
|
||||
if (LoadedAssets.TryGetValue(materialName, out Object asset)
|
||||
&& asset is Material loadedMaterial) return loadedMaterial;
|
||||
|
||||
// 2. re-create material by setting diffuse color and transparency on standard shaders
|
||||
if (renderMaterial.opacity < 1)
|
||||
{
|
||||
shader = Shader.Find("Transparent/Diffuse");
|
||||
mat = new Material(shader);
|
||||
}
|
||||
|
||||
var c = renderMaterial.diffuse.ToUnityColor();
|
||||
mat.color = new Color(c.r, c.g, c.b, (float) renderMaterial.opacity);
|
||||
mat.name = materialName;
|
||||
mat.SetFloat(Metallic, (float) renderMaterial.metalness);
|
||||
mat.SetFloat(Glossiness, 1 - (float) renderMaterial.roughness);
|
||||
|
||||
if (renderMaterial.emissive != SColor.Black.ToArgb()) mat.EnableKeyword("_EMISSION");
|
||||
mat.SetColor(EmissionColor, renderMaterial.emissive.ToUnityColor());
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (StreamManager.GenerateAssets) //TODO: I don't like how the converter is aware of StreamManager
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
string name = new(mat.name.Where(x => !invalidChars.Contains(x)).ToArray());
|
||||
|
||||
const string assetPath = "Assets/Resources/Materials/Speckle Generated/";
|
||||
CreateDirectoryFromAssetPath(assetPath);
|
||||
|
||||
if (AssetDatabase.LoadAllAssetsAtPath($"{assetPath}/{name}.mat")
|
||||
.Length == 0)
|
||||
AssetDatabase.CreateAsset(mat, $"{assetPath}/{name}.mat");
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
return mat;
|
||||
// 3. if not renderMaterial was passed, the default shader will be used
|
||||
}
|
||||
|
||||
protected static string GetAssetName(Base b, bool alwaysIncludeId = true)
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
string id = b.id;
|
||||
foreach (var nameAlias in new[] {"name", "Name"})
|
||||
{
|
||||
string? rawName = b[nameAlias] as string;
|
||||
if (string.IsNullOrWhiteSpace(rawName)) continue;
|
||||
|
||||
string name = new(rawName.Where(x => !invalidChars.Contains(x)).ToArray());
|
||||
|
||||
return alwaysIncludeId ? $"{name} - {id}" : name;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f174f59c0ccd3c74cb6b832d1a04b637
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -3,203 +3,228 @@ using Speckle.Core.Models;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Objects.BuiltElements;
|
||||
using Objects.Other;
|
||||
using Speckle.ConnectorUnity;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Mesh = Objects.Geometry.Mesh;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Objects.Converter.Unity
|
||||
{
|
||||
public partial class ConverterUnity : ISpeckleConverter
|
||||
{
|
||||
#region implemented methods
|
||||
|
||||
public void SetConverterSettings(object settings) => throw new NotImplementedException();
|
||||
|
||||
public string Description => "Default Speckle Kit for Unity";
|
||||
public string Name => nameof(ConverterUnity);
|
||||
public string Author => "Speckle";
|
||||
public string WebsiteOrEmail => "https://speckle.systems";
|
||||
|
||||
public ProgressReport Report { get; }
|
||||
public ReceiveMode ReceiveMode { get; set; }
|
||||
|
||||
public IEnumerable<string> GetServicedApplications() => new string[] {VersionedHostApplications.Unity};
|
||||
|
||||
public HashSet<Exception> ConversionErrors { get; private set; }
|
||||
|
||||
public Dictionary<string, Object> LoadedAssets { get; private set; }
|
||||
|
||||
public void SetContextDocument(object doc)
|
||||
public partial class ConverterUnity : ISpeckleConverter
|
||||
{
|
||||
if(doc is not Dictionary<string, Object> loadedAssets) throw new ArgumentException($"Expected {nameof(doc)} to be of type {typeof(Dictionary<string, Object>)}", nameof(doc));
|
||||
LoadedAssets = loadedAssets;
|
||||
}
|
||||
#region implemented methods
|
||||
|
||||
public void SetContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
|
||||
public void SetConverterSettings(object settings) => throw new NotImplementedException();
|
||||
|
||||
public string Description => "Default Speckle Kit for Unity";
|
||||
public string Name => nameof(ConverterUnity);
|
||||
public string Author => "Speckle";
|
||||
public string WebsiteOrEmail => "https://speckle.systems";
|
||||
|
||||
public ProgressReport Report { get; }
|
||||
public ReceiveMode ReceiveMode { get; set; }
|
||||
|
||||
public IEnumerable<string> GetServicedApplications() => new string[] {VersionedHostApplications.Unity};
|
||||
|
||||
public Dictionary<string, Object> LoadedAssets { get; private set; }
|
||||
|
||||
public void SetContextDocument(object doc)
|
||||
{
|
||||
if (doc is not Dictionary<string, Object> loadedAssets)
|
||||
throw new ArgumentException(
|
||||
$"Expected {nameof(doc)} to be of type {typeof(Dictionary<string, Object>)}", nameof(doc));
|
||||
LoadedAssets = loadedAssets;
|
||||
}
|
||||
|
||||
public void SetContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
|
||||
|
||||
public void SetPreviousContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
|
||||
|
||||
public void SetPreviousContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
|
||||
|
||||
#nullable enable
|
||||
public object? ConvertToNative(Base @object) => ConvertToNativeGameObject(@object);
|
||||
public object? ConvertToNative(Base @object) => ConvertToNativeGameObject(@object);
|
||||
|
||||
public Base ConvertToSpeckle(object @object)
|
||||
{
|
||||
if (!(@object is GameObject go)) throw new NotSupportedException($"Cannot convert object of type {@object.GetType()} to Speckle");
|
||||
return ConvertGameObjectToSpeckle(go);
|
||||
}
|
||||
|
||||
#endregion implemented methods
|
||||
|
||||
|
||||
|
||||
public Base ConvertGameObjectToSpeckle(GameObject go)
|
||||
{
|
||||
|
||||
Base speckleObject = CreateSpeckleObjectFromProperties(go);
|
||||
|
||||
speckleObject["name"] = go.name;
|
||||
//speckleObject["transform"] = TransformToSpeckle(go.Transform); //TODO
|
||||
speckleObject["tag"] = go.tag;
|
||||
speckleObject["physicsLayer"] = LayerMask.LayerToName(go.layer);
|
||||
//speckleObject["isStatic"] = go.isStatic; //todo figure out realtime-rendered static mobility interoperability (unreal)
|
||||
|
||||
foreach (Component component in go.GetComponents<Component>())
|
||||
{
|
||||
try
|
||||
public Base ConvertToSpeckle(object @object)
|
||||
{
|
||||
switch (component)
|
||||
{
|
||||
case MeshFilter meshFilter:
|
||||
var displayValues = MeshToSpeckle(meshFilter);
|
||||
|
||||
speckleObject.SetDetachedPropertyChecked("displayValue", displayValues);
|
||||
|
||||
break;
|
||||
// case Camera camera:
|
||||
// speckleObject["cameraComponent"] = CameraToSpeckle(camera);
|
||||
// break;
|
||||
}
|
||||
if (!(@object is GameObject go))
|
||||
throw new NotSupportedException($"Cannot convert object of type {@object.GetType()} to Speckle");
|
||||
return ConvertGameObjectToSpeckle(go);
|
||||
}
|
||||
catch(Exception e)
|
||||
|
||||
#endregion implemented methods
|
||||
|
||||
|
||||
public Base ConvertGameObjectToSpeckle(GameObject go)
|
||||
{
|
||||
Debug.LogError($"Failed to convert {component.GetType()} component\n{e}", component);
|
||||
Base speckleObject = CreateSpeckleObjectFromProperties(go);
|
||||
|
||||
speckleObject["name"] = go.name;
|
||||
//speckleObject["transform"] = TransformToSpeckle(go.Transform); //TODO
|
||||
speckleObject["tag"] = go.tag;
|
||||
speckleObject["physicsLayer"] = LayerMask.LayerToName(go.layer);
|
||||
//speckleObject["isStatic"] = go.isStatic; //todo figure out realtime-rendered static mobility interoperability (unreal)
|
||||
|
||||
foreach (Component component in go.GetComponents<Component>())
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (component)
|
||||
{
|
||||
case MeshFilter meshFilter:
|
||||
var displayValues = MeshToSpeckle(meshFilter);
|
||||
|
||||
speckleObject.SetDetachedPropertyChecked("displayValue", displayValues);
|
||||
|
||||
break;
|
||||
// case Camera camera:
|
||||
// speckleObject["cameraComponent"] = CameraToSpeckle(camera);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Failed to convert {component.GetType()} component\n{e}", component);
|
||||
}
|
||||
}
|
||||
|
||||
return speckleObject;
|
||||
}
|
||||
}
|
||||
|
||||
return speckleObject;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public GameObject? ConvertToNativeGameObject(Base speckleObject)
|
||||
{
|
||||
switch (speckleObject)
|
||||
{
|
||||
// case Point o:
|
||||
// return PointToNative(o);
|
||||
// case Line o:
|
||||
// return LineToNative(o);
|
||||
// case Polyline o:
|
||||
// return PolylineToNative(o);
|
||||
// case Curve o:
|
||||
// return CurveToNative(o);
|
||||
case View3D v:
|
||||
return View3DToNative(v);
|
||||
case Mesh o:
|
||||
return MeshToNative(o);
|
||||
default:
|
||||
|
||||
//Object is not a raw geometry, convert it as display value element
|
||||
GameObject? element = DisplayValueToNative(speckleObject);
|
||||
if (element != null)
|
||||
{
|
||||
if(!speckleObject.speckle_type.Contains("Objects.Geometry"))
|
||||
AttachSpeckleProperties(element, speckleObject.GetType(), GetProperties(speckleObject));
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
return new GameObject();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public IList<string> DisplayValuePropertyAliases { get; set; } = new[] {"displayValue","@displayValue","displayMesh", "@displayMesh" };
|
||||
public GameObject? DisplayValueToNative(Base @object)
|
||||
{
|
||||
foreach (string alias in DisplayValuePropertyAliases)
|
||||
{
|
||||
switch (@object[alias])
|
||||
public GameObject? ConvertToNativeGameObject(Base speckleObject)
|
||||
{
|
||||
//capture any other object that might have a mesh representation
|
||||
case IList dvCollection:
|
||||
return MeshesToNative(dvCollection.OfType<Mesh>().ToList());
|
||||
case Mesh dvMesh:
|
||||
return MeshesToNative(new[] {dvMesh});
|
||||
case Base dvBase:
|
||||
return ConvertToNativeGameObject(dvBase);
|
||||
switch (speckleObject)
|
||||
{
|
||||
// case Point o:
|
||||
// return PointToNative(o);
|
||||
// case Line o:
|
||||
// return LineToNative(o);
|
||||
// case Polyline o:
|
||||
// return PolylineToNative(o);
|
||||
// case Curve o:
|
||||
// return CurveToNative(o);
|
||||
case View3D v:
|
||||
return View3DToNative(v);
|
||||
case Mesh o:
|
||||
return MeshToNative(o);
|
||||
case BlockInstance o:
|
||||
return BlockToNative(o);
|
||||
default:
|
||||
|
||||
//Object is not a raw geometry, convert it as display value element
|
||||
GameObject? element = DisplayValueToNative(speckleObject);
|
||||
if (element != null)
|
||||
{
|
||||
if (!speckleObject.speckle_type.Contains("Objects.Geometry"))
|
||||
AttachSpeckleProperties(element, speckleObject.GetType(), GetProperties(speckleObject));
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
return new GameObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Base> ConvertToSpeckle(List<object> objects)
|
||||
{
|
||||
return objects.Select(ConvertToSpeckle).ToList();
|
||||
}
|
||||
|
||||
public List<object?> ConvertToNative(List<Base> objects)
|
||||
{
|
||||
return objects.Select(x => ConvertToNative(x)).ToList();
|
||||
}
|
||||
public IList<string> DisplayValuePropertyAliases { get; set; } =
|
||||
new[] {"displayValue", "@displayValue", "displayMesh", "@displayMesh"};
|
||||
|
||||
public bool CanConvertToSpeckle(object @object)
|
||||
{
|
||||
switch (@object)
|
||||
{
|
||||
case GameObject o:
|
||||
return o.GetComponent<MeshFilter>() != null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public GameObject? DisplayValueToNative(Base @object)
|
||||
{
|
||||
foreach (string alias in DisplayValuePropertyAliases)
|
||||
{
|
||||
switch (@object[alias])
|
||||
{
|
||||
//capture any other object that might have a mesh representation
|
||||
case IList dvCollection:
|
||||
return MeshesToNative(@object, dvCollection.OfType<Mesh>().ToList());
|
||||
case Mesh dvMesh:
|
||||
return MeshesToNative(@object, new[] {dvMesh});
|
||||
case Base dvBase:
|
||||
return ConvertToNativeGameObject(dvBase);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanConvertToNative(Base @object)
|
||||
{
|
||||
switch (@object)
|
||||
{
|
||||
// case Point _:
|
||||
// return true;
|
||||
// case Line _:
|
||||
// return true;
|
||||
// case Polyline _:
|
||||
// return true;
|
||||
// case Curve _:
|
||||
// return true;
|
||||
// case View3D _:
|
||||
// return true;
|
||||
// case View2D _:
|
||||
// return false;
|
||||
case Mesh _:
|
||||
return true;
|
||||
default:
|
||||
|
||||
foreach (string alias in DisplayValuePropertyAliases)
|
||||
{
|
||||
if (@object[alias] is Base)
|
||||
return true;
|
||||
if (@object[alias] is IList)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
private SpeckleProperties AttachSpeckleProperties(GameObject go, Type speckleType,
|
||||
IDictionary<string, object?> properties)
|
||||
{
|
||||
var sd = go.AddComponent<SpeckleProperties>();
|
||||
sd.Data = properties;
|
||||
sd.SpeckleType = speckleType;
|
||||
return sd;
|
||||
}
|
||||
|
||||
|
||||
public List<Base> ConvertToSpeckle(List<object> objects)
|
||||
{
|
||||
return objects.Select(ConvertToSpeckle).ToList();
|
||||
}
|
||||
|
||||
public List<object?> ConvertToNative(List<Base> objects)
|
||||
{
|
||||
return objects.Select(x => ConvertToNative(x)).ToList();
|
||||
}
|
||||
|
||||
public bool CanConvertToSpeckle(object @object)
|
||||
{
|
||||
switch (@object)
|
||||
{
|
||||
case GameObject o:
|
||||
return o.GetComponent<MeshFilter>() != null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanConvertToNative(Base @object)
|
||||
{
|
||||
switch (@object)
|
||||
{
|
||||
// case Point _:
|
||||
// return true;
|
||||
// case Line _:
|
||||
// return true;
|
||||
// case Polyline _:
|
||||
// return true;
|
||||
// case Curve _:
|
||||
// return true;
|
||||
// case View3D _:
|
||||
// return true;
|
||||
case View2D _:
|
||||
return false;
|
||||
case Mesh _:
|
||||
return true;
|
||||
case BlockInstance _:
|
||||
return true;
|
||||
default:
|
||||
|
||||
foreach (string alias in DisplayValuePropertyAliases)
|
||||
{
|
||||
if (@object[alias] is Base)
|
||||
return true;
|
||||
if (@object[alias] is IList)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected static void CreateDirectoryFromAssetPath(string assetPath)
|
||||
{
|
||||
string directoryPath = Path.GetDirectoryName(assetPath);
|
||||
if (Directory.Exists(directoryPath))
|
||||
return;
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,7 +313,7 @@ namespace Speckle.ConnectorUnity.Editor
|
||||
|
||||
SelectedCommitIndex = EditorGUILayout.Popup("Commits",
|
||||
SelectedCommitIndex,
|
||||
Branches[SelectedBranchIndex].commits.items.Select(x => x.message).ToArray(),
|
||||
Branches[SelectedBranchIndex].commits.items.Select(x => $"{x.message} - {x.id}").ToArray(),
|
||||
GUILayout.Height(20),
|
||||
GUILayout.ExpandWidth(true));
|
||||
|
||||
@@ -325,7 +325,7 @@ namespace Speckle.ConnectorUnity.Editor
|
||||
|
||||
GUILayout.Label("Generate material assets");
|
||||
GUILayout.FlexibleSpace();
|
||||
StreamManager.GenerateMaterials = GUILayout.Toggle(StreamManager.GenerateMaterials, "");
|
||||
StreamManager.GenerateAssets = GUILayout.Toggle(StreamManager.GenerateAssets, "");
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
#endregion
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Speckle.ConnectorUnity
|
||||
foreach (var nameAlias in namePropertyAliases)
|
||||
{
|
||||
string? s = baseObject[nameAlias] as string;
|
||||
if (!string.IsNullOrWhiteSpace(s)) return s!; //TODO any sanitization needed?
|
||||
if (!string.IsNullOrWhiteSpace(s)) return s; //TODO any sanitization needed?
|
||||
}
|
||||
|
||||
// 2. Use type + id as fallback name
|
||||
@@ -63,12 +63,14 @@ namespace Speckle.ConnectorUnity
|
||||
if (converted is GameObject go)
|
||||
{
|
||||
outCreatedObjects.Add(go);
|
||||
|
||||
nextParent = go.transform;
|
||||
|
||||
go.name = GenerateObjectName(baseObject);
|
||||
go.transform.SetParent(parent);
|
||||
//TODO add support for unity specific props
|
||||
go.transform.SetParent(parent, true);
|
||||
|
||||
//Set some common for all created GameObjects
|
||||
//TODO add support for more unity specific props
|
||||
if(go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name))
|
||||
go.name = GenerateObjectName(baseObject);
|
||||
//if (baseObject["tag"] is string t) go.tag = t;
|
||||
if (baseObject["physicsLayer"] is string layerName)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using Speckle.Core.Credentials;
|
||||
using System.Collections.Generic;
|
||||
using Speckle.Core.Models;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Speckle.ConnectorUnity
|
||||
{
|
||||
@@ -27,7 +28,7 @@ namespace Speckle.ConnectorUnity
|
||||
public List<Branch> Branches;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static bool GenerateMaterials = false;
|
||||
public static bool GenerateAssets = false;
|
||||
#endif
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
#nullable enable
|
||||
namespace Speckle.ConnectorUnity
|
||||
{
|
||||
public static class Utils
|
||||
@@ -22,35 +23,29 @@ namespace Speckle.ConnectorUnity
|
||||
|
||||
|
||||
|
||||
public static void SafeMeshSet(this GameObject go, Mesh m, bool addMeshFilterIfNotFound)
|
||||
public static void SafeMeshSet(this GameObject go, Mesh m, Material[] materials, bool addMeshComponentsIfNotFound = true)
|
||||
{
|
||||
MeshFilter? mf = go.GetComponent<MeshFilter>();
|
||||
MeshRenderer? mr = go.GetComponent<MeshRenderer>();
|
||||
|
||||
var mf = go.GetComponent<MeshFilter>();
|
||||
if (mf == null)
|
||||
if (addMeshComponentsIfNotFound)
|
||||
{
|
||||
if (!addMeshFilterIfNotFound) return;
|
||||
|
||||
mf = go.AddComponent<MeshFilter>();
|
||||
if (mf == null)
|
||||
mf = go.AddComponent<MeshFilter>();
|
||||
if (mr == null)
|
||||
mr = go.AddComponent<MeshRenderer>();
|
||||
}
|
||||
|
||||
|
||||
if (Application.isPlaying)
|
||||
mf.mesh = m;
|
||||
else
|
||||
mf.sharedMesh = m;
|
||||
}
|
||||
|
||||
|
||||
public static void SafeMeshSet(this GameObject go, Mesh m)
|
||||
{
|
||||
var mf = go.GetComponent<MeshFilter>();
|
||||
if (mf == null) return;
|
||||
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
mf.mesh = m;
|
||||
mr.materials = materials;
|
||||
}
|
||||
else
|
||||
{
|
||||
mf.sharedMesh = m;
|
||||
mr.sharedMaterials = materials;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user