diff --git a/Packages/systems.speckle.speckle-unity/ConverterUnity.Geometry.cs b/Packages/systems.speckle.speckle-unity/ConverterUnity.Geometry.cs index 842e9d8..970d21f 100644 --- a/Packages/systems.speckle.speckle-unity/ConverterUnity.Geometry.cs +++ b/Packages/systems.speckle.speckle-unity/ConverterUnity.Geometry.cs @@ -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) + + /// + /// Converts a 3D vector from Speckle's RH Z-up to Unity's LH Y-up coordinate system + /// + /// Scaled Vector in Unity coordinate space + 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 /// @@ -82,173 +76,13 @@ namespace Objects.Converter.Unity //switch y and z return new Point(p.x, p.z, p.y); } - - - - - public virtual List? MeshToSpeckle(MeshFilter meshFilter) - { - Material[]? materials = meshFilter.GetComponent()?.materials; -#if UNITY_EDITOR - var nativeMesh = meshFilter.sharedMesh; -#else - var nativeMesh = meshFilter.mesh; -#endif - if (nativeMesh == null) return null; - - List convertedMeshes = new List(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 sFaces = new List(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 sVertices = new List(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 sColors = new List(nColors.Length); - sColors.AddRange(nColors.Select(c => c.ToIntColor())); - - var nTexCoords = nativeMesh.uv.Skip(indexOffset).Take(vertexTake).ToArray(); - List sTexCoords = new List(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 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 /// public GameObject? LineToNative(Line line) { - var points = new List { VectorFromPoint(line.start), VectorFromPoint(line.end) }; + var points = new List {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; } - /// - /// Converts multiple (e.g. with different materials) into one native mesh - /// - /// Collection of es that shall be converted - /// A with the converted , , and - public GameObject? MeshesToNative(IReadOnlyCollection 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.sharedMaterials = nativeMaterials; - - return go; - } - public Dictionary GetProperties(Base o) => GetProperties(o, typeof(Base)); + public Dictionary GetProperties(Base o, Type excludeType) { - var excludeProps = new HashSet(excludeType.GetProperties(BindingFlags.Instance | BindingFlags.Public) + var excludeProps = new HashSet(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); - } - - /// - /// Converts to a with a - /// - /// Mesh to convert - /// - 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); } - /// - /// - /// - /// meshes to be converted as SubMeshes - /// The converted native mesh - /// The converted materials (one per converted sub-mesh) - /// Center position for the mesh - public void MeshDataToNative(IReadOnlyCollection meshes, out Mesh nativeMesh, out Material[] nativeMaterials, out Vector3 center) - { - var verts = new List(); - - var uvs = new List(); - var vertexColors = new List(); - - var materials = new List(meshes.Count); - var subMeshes = new List>(meshes.Count); - - foreach (SMesh m in meshes) - { - if(m.vertices.Count == 0 || m.faces.Count == 0 ) continue; - List tris = new List(); - 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 verts, List tris, List texCoords, List vertexColors, List 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 GenerateUV(IReadOnlyList 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 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 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 properties) - { - var sd = go.AddComponent(); - sd.Data = properties; - sd.SpeckleType = speckleType; - return sd; - } - private Base CreateSpeckleObjectFromProperties(GameObject go) { var sd = go.GetComponent(); 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($"{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 meshes = new(); + List 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; + } + + + + /// + /// Converts a 4x4 transformation matrix from Speckle's format, + /// to a Unity . Applying Z -> Y up conversion, and applying units to the translation + /// + /// + /// Transformation matrix in Unity's coordinate system + 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(); + } } + } \ No newline at end of file diff --git a/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs b/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs new file mode 100644 index 0000000..1fb664c --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs @@ -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? MeshToSpeckle(MeshFilter meshFilter) + { + Material[]? materials = meshFilter.GetComponent()?.materials; +#if UNITY_EDITOR + var nativeMesh = meshFilter.sharedMesh; +#else + var nativeMesh = meshFilter.mesh; +#endif + if (nativeMesh == null) return null; + + List convertedMeshes = new List(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 sFaces = new List(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 sVertices = new List(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 sColors = new List(nColors.Length); + sColors.AddRange(nColors.Select(c => c.ToIntColor())); + + var nTexCoords = nativeMesh.uv.Skip(indexOffset).Take(vertexTake).ToArray(); + List sTexCoords = new List(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; + } + + /// + /// List of officially supported shaders. Will attempt to convert shaders not on this list, but will throw warning. + /// + protected static HashSet 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 + + /// + /// Converts multiple (e.g. with different materials) into one native mesh + /// + /// Root element who's name/id is used to identify the mesh + /// Collection of es that shall be converted + /// A with the converted , , and + public GameObject? MeshesToNative(Base element, IReadOnlyCollection 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 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; + } + + + /// + /// Converts to a with a + /// + /// Mesh to convert + /// + 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; + } + + /// + /// Converts Speckle s as a native object + /// + /// meshes to be converted as SubMeshes + /// The converted native mesh + public void MeshToNativeMesh(IReadOnlyCollection meshes, out Mesh nativeMesh) + => MeshToNativeMesh(meshes, out nativeMesh, out _, false); + + + /// + /// when true, will recenter vertices + /// Center position for the mesh + public void MeshToNativeMesh(IReadOnlyCollection meshes, + out Mesh nativeMesh, + out Vector3 center, + bool recenterVerts = true) + { + MeshDataToNative(meshes, + out List verts, + out List uvs, + out List vertexColors, + out List> 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 meshes, + out List verts, + out List uvs, + out List vertexColors, + out List> subMeshes) + { + verts = new List(); + uvs = new List(); + vertexColors = new List(); + subMeshes = new List>(meshes.Count); + + foreach (SMesh m in meshes) + { + if (m.vertices.Count == 0 || m.faces.Count == 0) continue; + + List tris = new List(); + SubmeshToNative(m, verts, tris, uvs, vertexColors); + subMeshes.Add(tris); + } + } + + + protected void SubmeshToNative(SMesh speckleMesh, List verts, List tris, List texCoords, + List 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 GenerateUV(int indexOffset, IReadOnlyList 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 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 points) + { + Bounds b = new Bounds {center = points[0]}; + + foreach (var p in points) + b.Encapsulate(p); + + return b; + } + + + public Material[] RenderMaterialsToNative(IEnumerable 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 + + } + +} diff --git a/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs.meta b/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs.meta new file mode 100644 index 0000000..0b734bc --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f174f59c0ccd3c74cb6b832d1a04b637 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/ConverterUnity.cs b/Packages/systems.speckle.speckle-unity/ConverterUnity.cs index 7a201c2..b78cd98 100644 --- a/Packages/systems.speckle.speckle-unity/ConverterUnity.cs +++ b/Packages/systems.speckle.speckle-unity/ConverterUnity.cs @@ -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 GetServicedApplications() => new string[] {VersionedHostApplications.Unity}; - - public HashSet ConversionErrors { get; private set; } - - public Dictionary LoadedAssets { get; private set; } - - public void SetContextDocument(object doc) + public partial class ConverterUnity : ISpeckleConverter { - if(doc is not Dictionary loadedAssets) throw new ArgumentException($"Expected {nameof(doc)} to be of type {typeof(Dictionary)}", nameof(doc)); - LoadedAssets = loadedAssets; - } + #region implemented methods - public void SetContextObjects(List 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 GetServicedApplications() => new string[] {VersionedHostApplications.Unity}; + + public Dictionary LoadedAssets { get; private set; } + + public void SetContextDocument(object doc) + { + if (doc is not Dictionary loadedAssets) + throw new ArgumentException( + $"Expected {nameof(doc)} to be of type {typeof(Dictionary)}", nameof(doc)); + LoadedAssets = loadedAssets; + } + + public void SetContextObjects(List objects) => throw new NotImplementedException(); + + public void SetPreviousContextObjects(List objects) => throw new NotImplementedException(); - public void SetPreviousContextObjects(List 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()) - { - 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()) + { + 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 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().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 ConvertToSpeckle(List objects) - { - return objects.Select(ConvertToSpeckle).ToList(); - } - public List ConvertToNative(List objects) - { - return objects.Select(x => ConvertToNative(x)).ToList(); - } + public IList DisplayValuePropertyAliases { get; set; } = + new[] {"displayValue", "@displayValue", "displayMesh", "@displayMesh"}; - public bool CanConvertToSpeckle(object @object) - { - switch (@object) - { - case GameObject o: - return o.GetComponent() != 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().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 properties) + { + var sd = go.AddComponent(); + sd.Data = properties; + sd.SpeckleType = speckleType; + return sd; + } + + + public List ConvertToSpeckle(List objects) + { + return objects.Select(ConvertToSpeckle).ToList(); + } + + public List ConvertToNative(List objects) + { + return objects.Select(x => ConvertToNative(x)).ToList(); + } + + public bool CanConvertToSpeckle(object @object) + { + switch (@object) + { + case GameObject o: + return o.GetComponent() != 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(); } } - } } \ No newline at end of file diff --git a/Packages/systems.speckle.speckle-unity/Editor/StreamManagerEditor.cs b/Packages/systems.speckle.speckle-unity/Editor/StreamManagerEditor.cs index 92e41d3..41b8824 100644 --- a/Packages/systems.speckle.speckle-unity/Editor/StreamManagerEditor.cs +++ b/Packages/systems.speckle.speckle-unity/Editor/StreamManagerEditor.cs @@ -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 diff --git a/Packages/systems.speckle.speckle-unity/RecursiveConverter.ToNative.cs b/Packages/systems.speckle.speckle-unity/RecursiveConverter.ToNative.cs index 1fa93ab..98b021a 100644 --- a/Packages/systems.speckle.speckle-unity/RecursiveConverter.ToNative.cs +++ b/Packages/systems.speckle.speckle-unity/RecursiveConverter.ToNative.cs @@ -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) { diff --git a/Packages/systems.speckle.speckle-unity/StreamManager.cs b/Packages/systems.speckle.speckle-unity/StreamManager.cs index 75af762..622f64d 100644 --- a/Packages/systems.speckle.speckle-unity/StreamManager.cs +++ b/Packages/systems.speckle.speckle-unity/StreamManager.cs @@ -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 Branches; #if UNITY_EDITOR - public static bool GenerateMaterials = false; + public static bool GenerateAssets = false; #endif #nullable enable diff --git a/Packages/systems.speckle.speckle-unity/Utils.cs b/Packages/systems.speckle.speckle-unity/Utils.cs index f6d134a..80b8ee2 100644 --- a/Packages/systems.speckle.speckle-unity/Utils.cs +++ b/Packages/systems.speckle.speckle-unity/Utils.cs @@ -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(); + MeshRenderer? mr = go.GetComponent(); - var mf = go.GetComponent(); - if (mf == null) + if (addMeshComponentsIfNotFound) { - if (!addMeshFilterIfNotFound) return; - - mf = go.AddComponent(); + if (mf == null) + mf = go.AddComponent(); + if (mr == null) + mr = go.AddComponent(); } - - if (Application.isPlaying) - mf.mesh = m; - else - mf.sharedMesh = m; - } - - - public static void SafeMeshSet(this GameObject go, Mesh m) - { - var mf = go.GetComponent(); - if (mf == null) return; - - if (Application.isPlaying) + { mf.mesh = m; + mr.materials = materials; + } else + { mf.sharedMesh = m; + mr.sharedMaterials = materials; + } }