Updated ToNative conversion to accept multiple displayValue meshes

This commit is contained in:
JR-Morgan 2022-02-03 00:39:36 +00:00
Родитель 8b44834934
Коммит 6437c3b20b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 746B82374C87C2EF
7 изменённых файлов: 264 добавлений и 153 удалений

Просмотреть файл

@ -41,7 +41,7 @@ namespace Speckle.ConnectorUnity
SelectStreamText.text = $"Select a stream on {defaultAccount.serverInfo.name}:";
StreamList = await Streams.List();
StreamList = await Streams.List(30);
if (!StreamList.Any())
{
Debug.Log("There are no streams in your account, please create one online.");

Просмотреть файл

@ -4,11 +4,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Objects.Other;
using Objects.Utils;
using Speckle.ConnectorUnity;
using Speckle.Core.Models;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using Mesh = Objects.Geometry.Mesh;
using SColor = System.Drawing.Color;
@ -248,149 +249,27 @@ namespace Objects.Converter.Unity
}
public GameObject MeshToNative(Base speckleMeshObject)
{
if (!(speckleMeshObject["displayMesh"] is Mesh))
return null;
return MeshToNative(speckleMeshObject["displayMesh"] as Mesh,
speckleMeshObject["renderMaterial"] as RenderMaterial, speckleMeshObject.GetMembers());
}
/// <summary>
/// Converts <paramref name="speckleMesh"/> to a <see cref="GameObject"/> with a <see cref="MeshRenderer"/>
/// Converts multiple <paramref name="meshes"/> (e.g. with different materials) into one native mesh
/// </summary>
/// <param name="speckleMesh">Mesh to convert</param>
/// <param name="renderMaterial">If provided, will override the renderMaterial on the mesh itself</param>
/// <param name="element">The <see cref="Base"/> element from which properties should be grabbed from</param>
/// <param name="meshes">Collection of <see cref="Objects.Geometry.Mesh"/>es that shall be converted</param>
/// <param name="properties">If provided, will override the properties on the mesh itself</param>
/// <returns></returns>
public GameObject MeshToNative(
Mesh speckleMesh, RenderMaterial renderMaterial = null,
Dictionary<string, object> properties = null
)
/// <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<Mesh> meshes, Dictionary<string, object> properties = null)
{
if (speckleMesh.vertices.Count == 0 || speckleMesh.faces.Count == 0)
MeshDataToNative(meshes, out var nativeMesh, out var nativeMaterials);
var go = new GameObject
{
return null;
}
name = element.speckle_type
};
var recenterMeshTransforms = true; //TODO: figure out how best to change this?
speckleMesh.AlignVerticesWithTexCoordsByIndex();
var verts = ArrayToPoints(speckleMesh.vertices, speckleMesh.units);
//convert speckleMesh.faces into triangle array
List<int> tris = new List<int>();
int i = 0;
// TODO: Check if this is causing issues with normals for mesh
while (i < speckleMesh.faces.Count)
{
int n = speckleMesh.faces[i];
if (n < 3) n += 3; // 0 -> 3, 1 - > 4
if (n == 3)
{
//Triangles
tris.Add(speckleMesh.faces[i + 1]);
tris.Add(speckleMesh.faces[i + 3]);
tris.Add(speckleMesh.faces[i + 2]);
}
else if (n == 4)
{
//Quads to triangles
tris.Add(speckleMesh.faces[i + 1]);
tris.Add(speckleMesh.faces[i + 3]);
tris.Add(speckleMesh.faces[i + 2]);
tris.Add(speckleMesh.faces[i + 1]);
tris.Add(speckleMesh.faces[i + 4]);
tris.Add(speckleMesh.faces[i + 3]);
}
else
{
//TODO n-gon triangulation, for now n-gon faces will be ignored
}
i += n + 1;
}
var go = new GameObject { name = speckleMesh.speckle_type };
var mesh = new UnityEngine.Mesh { name = speckleMesh.speckle_type };
if (verts.Length >= UInt16.MaxValue)
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
// center transform pivot according to the bounds of the model
if (recenterMeshTransforms)
{
Bounds meshBounds = new Bounds
{
center = verts[0]
};
foreach (var vert in verts)
{
meshBounds.Encapsulate(vert);
}
go.transform.position = meshBounds.center;
// offset mesh vertices
for (int l = 0; l < verts.Length; l++)
{
verts[l] -= meshBounds.center;
}
}
go.SafeMeshSet(nativeMesh, true);
mesh.SetVertices(verts);
mesh.SetTriangles(tris, 0);
//Set 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}", mesh);
if (hasValidUVs)
{
var uv = new List<Vector2>(speckleMesh.TextureCoordinatesCount);
for (int j = 0; j < speckleMesh.TextureCoordinatesCount; j++)
{
var (u, v) = speckleMesh.GetTextureCoordinate(j);
uv.Add(new Vector2((float)u,(float)v));
}
mesh.SetUVs(0, uv);
}
else if (speckleMesh.bbox != null)
{
//Attempt to generate some crude UV coordinates using bbox
var uv = GenerateUV(verts, (float)speckleMesh.bbox.xSize.Length, (float)speckleMesh.bbox.ySize.Length).ToList();
mesh.SetUVs(0, uv);
}
//Set vertex colors
if (speckleMesh.colors.Count == speckleMesh.VerticesCount)
{
var colors = speckleMesh.colors.Select(c => c.ToUnityColor()).ToList();
mesh.SetColors(colors);
}
else if (speckleMesh.colors.Count != 0)
{
Debug.LogWarning($"{typeof(Mesh)} {speckleMesh.id} has invalid number of vertex {nameof(Mesh.colors)}. Expected 0 or {speckleMesh.VerticesCount}, got {speckleMesh.colors.Count}");
}
// BUG: causing some funky issues with meshes
mesh.RecalculateNormals();
mesh.RecalculateTangents();
mesh.Optimize();
// Setting mesh to filter once all mesh modifying is done
go.SafeMeshSet(mesh, true);
var meshRenderer = go.AddComponent<MeshRenderer>();
var speckleMaterial = renderMaterial ?? (RenderMaterial)speckleMesh["renderMaterial"];
meshRenderer.sharedMaterial = GetMaterial(speckleMaterial);
meshRenderer.sharedMaterials = nativeMaterials;
//Add mesh collider
// MeshCollider mc = go.AddComponent<MeshCollider>( );
// mc.sharedMesh = mesh;
@ -401,16 +280,164 @@ namespace Objects.Converter.Unity
//means the mesh originated in Rhino or similar
if (properties == null)
{
var meshprops = typeof(Mesh).GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(x => x.Name)
.ToList();
properties = speckleMesh.GetMembers()
.Where(x => !meshprops.Contains(x.Key))
.ToDictionary(x => x.Key, x => x.Value);
var meshprops = typeof(Base).GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(x => x.Name)
.ToList();
properties = element.GetMembers()
.Where(x => !meshprops.Contains(x.Key))
.ToDictionary(x => x.Key, x => x.Value);
}
AttachSpeckleProperties(go, properties);
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>
/// <param name="properties">If provided, will override the properties on the mesh itself</param>
/// <returns></returns>
public GameObject MeshToNative(Mesh speckleMesh, Dictionary<string, object> properties = null)
{
if (speckleMesh.vertices.Count == 0 || speckleMesh.faces.Count == 0)
{
return null;
}
return MeshesToNative(speckleMesh, new[] {speckleMesh}, properties);
}
/// <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>
public void MeshDataToNative(IReadOnlyCollection<Mesh> meshes, out UnityEngine.Mesh nativeMesh, out Material[] nativeMaterials)
{
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 (Mesh 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();
nativeMesh = new UnityEngine.Mesh();
//TODO not sure if this is necessary, could just set the GameObject transform like we are in unreal?
// center transform pivot according to the bounds of the model
// if (recenterMeshTransforms)
// {
// Bounds meshBounds = new Bounds
// {
// center = verts[0]
// };
//
// foreach (var vert in verts)
// {
// meshBounds.Encapsulate(vert);
// }
//
// // offset mesh vertices
// for (int l = 0; l < verts.Count; l++)
// {
// verts[l] -= meshBounds.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();
}
private void SubmeshToNative(Mesh 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(Mesh)} {speckleMesh.id} has invalid number of vertex {nameof(Mesh.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(GetMaterial(speckleMesh["renderMaterial"] as RenderMaterial));
}
private static IEnumerable<Vector2> GenerateUV(IReadOnlyList<Vector3> verts, float xSize, float ySize)
{

Просмотреть файл

@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Objects.BuiltElements;
using Unity.Plastic.Antlr3.Runtime.Debug;
using UnityEngine;
using Mesh = Objects.Geometry.Mesh;
@ -16,6 +17,8 @@ namespace Objects.Converter.Unity
{
#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";
@ -66,20 +69,23 @@ namespace Objects.Converter.Unity
// return View3DToNative(o);
case Mesh o:
return MeshToNative(o);
//Built elements with a mesh representation implement this interface
case IDisplayMesh o:
return MeshToNative((Base) o);
default:
//capture any other object that might have a mesh representation
if (@object["displayMesh"] is Mesh)
return MeshToNative(@object["displayMesh"] as Mesh);
throw new NotSupportedException();
if (@object["displayValue"] is Base b)
return ConvertToNative(b);
if (@object["displayValue"] is IEnumerable<Base> bs)
return MeshesToNative(@object, bs.OfType<Mesh>().ToList());
if (@object["displayMesh"] is Base m)
return ConvertToNative(m);
Debug.LogWarning($"Skipping {@object.GetType()} {@object.id} - Not supported type");
return null;
}
}
public List<Base> ConvertToSpeckle(List<object> objects)
{
return objects.Select(x => ConvertToSpeckle(x)).ToList();
return objects.Select(ConvertToSpeckle).ToList();
}
public List<object> ConvertToNative(List<Base> objects)
@ -115,12 +121,16 @@ namespace Objects.Converter.Unity
// return true;
// case View2D _:
// return false;
case IDisplayMesh _:
return true;
case Mesh _:
return true;
default:
return @object["displayMesh"] is Mesh;
if (@object["displayValue"] is Base)
return true;
if (@object["displayValue"] is IEnumerable<Base>)
return true;
if (@object["displayMesh"] is Base)
return true;
return false;
}
}

Просмотреть файл

@ -7,7 +7,7 @@ PluginImporter:
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:

Двоичный файл не отображается.

Просмотреть файл

@ -706,6 +706,12 @@
</summary>
<param name="objects"></param>
</member>
<member name="M:Speckle.Core.Kits.ISpeckleConverter.SetConverterSettings(System.Object)">
<summary>
Some converters need to be able to receive some settings to modify their internal behaviour (i.e. Rhino's Brep Meshing options). Use this method to set them.
</summary>
<param name="settings">The object representing the settings for your converter.</param>
</member>
<member name="P:Speckle.Core.Kits.ISpeckleKit.Types">
<summary>
Returns all the object types (the object model) provided by this kit.
@ -774,6 +780,74 @@
<param name="referenceName">The reference assembly name.</param>
<returns>A boolean value indicating if there is a reference.</returns>
</member>
<member name="T:Speckle.Core.Logging.Analytics">
<summary>
Anonymous telemetry to help us understand how to make a better Speckle.
This really helps us to deliver a better open source project and product!
</summary>
</member>
<member name="T:Speckle.Core.Logging.Analytics.Events">
<summary>
Default Mixpanel events
</summary>
</member>
<member name="F:Speckle.Core.Logging.Analytics.Events.Send">
<summary>
Event triggered when data is sent to a Speckle Server
</summary>
</member>
<member name="F:Speckle.Core.Logging.Analytics.Events.Receive">
<summary>
Event triggered when data is received from a Speckle Server
</summary>
</member>
<member name="F:Speckle.Core.Logging.Analytics.Events.NodeRun">
<summary>
Event triggered when a node is executed in a visual programming environment, it should contain the name of the action and the host application
</summary>
</member>
<member name="F:Speckle.Core.Logging.Analytics.Events.DUIAction">
<summary>
Event triggered when an action is executed in Desktop UI, it should contain the name of the action and the host application
</summary>
</member>
<member name="P:Speckle.Core.Logging.Analytics.LastEmail">
<summary>
Cached email
</summary>
</member>
<member name="P:Speckle.Core.Logging.Analytics.LastServer">
<summary>
Cached server URL
</summary>
</member>
<member name="M:Speckle.Core.Logging.Analytics.TrackEvent(Speckle.Core.Logging.Analytics.Events,System.Collections.Generic.Dictionary{System.String,System.Object})">
<summary>
Tracks an event without specifying the email and server.
It's not always possible to know which account the user has selected, especially in visual programming.
Therefore we are caching the email and server values so that they can be used also when nodes such as "Serialize" are used.
If no account info is cached, we use the default account data.
</summary>
<param name="eventName">Name of the even</param>
<param name="customProperties">Additional parameters to pass in to event</param>
</member>
<member name="M:Speckle.Core.Logging.Analytics.TrackEvent(Speckle.Core.Credentials.Account,Speckle.Core.Logging.Analytics.Events,System.Collections.Generic.Dictionary{System.String,System.Object})">
<summary>
Tracks an event from a specified account, anonymizes personal information
</summary>
<param name="account">Account to use, it will be anonymized</param>
<param name="eventName">Name of the event</param>
<param name="customProperties">Additional parameters to pass to the event</param>
</member>
<member name="M:Speckle.Core.Logging.Analytics.TrackEvent(System.String,System.String,Speckle.Core.Logging.Analytics.Events,System.Collections.Generic.Dictionary{System.String,System.Object})">
<summary>
Tracks an event from a specified email and server, anonymizes personal information
</summary>
<param name="email">Email of the user, it will be anonymized</param>
<param name="server">Server URL, it will be anonymized</param>
<param name="eventName">Name of the event</param>
<param name="customProperties">Additional parameters to pass to the event</param>
</member>
<member name="T:Speckle.Core.Logging.Log">
<summary>
Anonymous telemetry to help us understand how to make a better Speckle.
@ -958,7 +1032,7 @@
</member>
<member name="T:Speckle.Core.Models.Abstract">
<summary>
Wrapper around other, thrid party, classes that are not coming from a speckle kit.
Wrapper around other, third party, classes that are not coming from a speckle kit.
<para>Serialization and deserialization of the base object happens through default Newtonsoft converters. If your object does not de/serialize correctly, this class will not prevent that from happening.</para>
<para><b>Limitations:</b></para>
<para>- Base object needs to be serializable.</para>

Двоичный файл не отображается.