// Copy a 4-channel texcoord into 2 2-channel texcoords. // If the source isn't 4-channel, one or both lists may be left empty. static void DemuxTexcoord(GeometryPool pool, int uvSize, GeometryPool.TexcoordData texcoordData, out List <Vector2> destXy, out List <Vector2> destZw) { destXy = null; destZw = null; switch (uvSize) { case 0: break; case 2: destXy = texcoordData.v2; break; case 3: destXy = new List <Vector2>(texcoordData.v3.Select(v3 => new Vector2(v3.x, v3.y))); destZw = new List <Vector2>(texcoordData.v3.Select(v3 => new Vector2(v3.z, 0))); break; case 4: destXy = new List <Vector2>(texcoordData.v4.Select(v4 => new Vector2(v4.x, v4.y))); destZw = new List <Vector2>(texcoordData.v4.Select(v4 => new Vector2(v4.z, v4.w))); break; default: Debug.Assert(false); break; } }
private static void AppendTriangle(ref StringBuilder buffer, GeometryPool pool, int j, int chunkStartingVertex) { buffer.Append("\t\t\t" + (pool.m_Tris[j + 0] - chunkStartingVertex) + " " + (pool.m_Tris[j + 1] - chunkStartingVertex) + " " + (pool.m_Tris[j + 2] - chunkStartingVertex) + " " + "-1,\n"); }
// If geometry does not contain normals, colors, and/or uvs, dummy values // will be added. public ExportMesh(GeometryPool pool) { m_pool = pool; FbxUtils.ApplyFbxTexcoordHack(m_pool); m_linearColor = ExportUtils.ConvertToLinearColorspace(m_pool.m_Colors); var layout = m_pool.Layout; var numVerts = m_pool.m_Vertices.Count; // TODO: all this padding code seems super bogus; try to remove. if (!layout.bUseNormals) { var lst = m_pool.m_Normals; lst.SetCount(numVerts); for (int i = 0; i < numVerts; ++i) { lst[i] = Vector3.up; } } if (!layout.bUseColors) { var lst = m_linearColor; lst.SetCount(numVerts); for (int i = 0; i < numVerts; ++i) { lst[i] = Color.white; } } }
/// Destroys the batch and all resources+objects owned by it. /// The batch is no longer usable after this. public void Destroy() { m_ParentPool = null; // Writing a BatchSubset.Destroy() wouldn't be worth it; there's nothing really to destroy foreach (var subset in m_Groups) { subset.m_ParentBatch = null; } m_Groups = null; // Don't bother with mesh.Clear() since we're about to destroy it. // m_MeshFilter.mesh.Clear(); // Don't bother with m_Geometry.Reset(). Internally it uses List<>.Clear() // which wastes time zeroing out the list entries. That's wasted work since // we're going to garbage the whole thing. // m_Geometry.Reset(); // I don't think we want to do this until GeometryPool.Free() is smart enough // to limit the number of instances on the freelist. // GeometryPool.Free(m_Geometry); m_Geometry.Destroy(); m_Geometry = null; Destroy(m_InstantiatedMaterial); Destroy(m_MeshFilter.mesh); m_MeshFilter = null; Destroy(gameObject); }
public void TestLayoutAssignmentAndReset() { var pool = new GeometryPool { Layout = new VertexLayout { texcoord0 = new TexcoordInfo { size = 2 }, texcoord1 = new TexcoordInfo { size = 3 }, texcoord2 = new TexcoordInfo { size = 4 }, } }; Assert.IsNotNull(pool.m_Texcoord0.v2); Assert.IsNotNull(pool.m_Texcoord1.v3); Assert.IsNotNull(pool.m_Texcoord2.v4); int N = 30; pool.m_Texcoord0.v2.AddRange(MathTestUtils.RandomVector2List(N)); pool.m_Texcoord1.v3.AddRange(MathTestUtils.RandomVector3List(N)); pool.m_Texcoord2.v4.AddRange(MathTestUtils.RandomVector4List(N)); pool.Reset(); Assert.AreEqual(0, pool.m_Texcoord0.v2.Count); Assert.AreEqual(0, pool.m_Texcoord1.v3.Count); Assert.AreEqual(0, pool.m_Texcoord2.v4.Count); }
/// Creates and returns a new subset containing the passed geometry. /// Pass: /// brush - Selects the material/batch /// nVerts - Amount to copy from the MasterBrush public BatchSubset CreateSubset(BrushDescriptor brush, GeometryPool geometry) { var pool = GetPool(brush); var batch = GetBatch(pool, geometry.NumVerts); return(batch.AddSubset(geometry)); }
private static void ConvertScaleAndChangeBasis( GeometryPool pool, float unitChange, Matrix4x4 basisChange) { // If there's translation, it's ambiguous whether scale is applied before or after // (probably the user means after, but still) Debug.Assert((Vector3)basisChange.GetColumn(3) == Vector3.zero); Matrix4x4 basisAndUnitChange = Matrix4x4.Scale(unitChange * Vector3.one) * basisChange; #if false // this code is pendantic but is useful to describe what's _really_ going on here // xfBivector is the transform to use for normals, tangents, and other cross-products. // Extracting rotation from a mat4 is hard, so take advantage of the fact that basisChange // is a (maybe improper) rotation, an improper rotation being a rotation with scale -1. // Matrix4x4 xfBivector = ExtractRotation(basisAndUnitChange); Matrix4x4 xfBivector = basisChange; if (basisChange.determinant < 0) { // remove the -1 uniform scale by giving it yet more -1 uniform scale. xfBivector = Matrix4x4.Scale(-Vector3.one) * xfBivector; } // The exporter flips triangle winding when handedness flips, but it leaves the job unfinished; // it needs to also flip the normals and tangents, since they're calculated from cross products. // Detect that and kludge in an extra -1 scale to finish the job. if (basisChange.determinant < 0) { xfBivector = Matrix4x4.Scale(-Vector3.one) * xfBivector; } #else Matrix4x4 xfBivector = basisChange; // The mirroring and the winding-flip cancel each other out #endif pool.ApplyTransform(basisAndUnitChange, xfBivector, unitChange, 0, pool.NumVerts); }
protected override void InitBrush(BrushDescriptor desc, TrTransform localPointerXf) { base.InitBrush(desc, localPointerXf); m_bM11Compatibility = desc.m_M11Compatibility; m_geometry = GeometryPool.Allocate(); m_knots.Clear(); Vector3 pos = localPointerXf.translation; Quaternion ori = localPointerXf.rotation; Knot knot = new Knot { point = new PointerManager.ControlPoint { m_Pos = pos, m_Orient = ori, m_Pressure = 1 }, length = 0, smoothedPos = pos }; m_knots.Add(knot); m_knots.Add(knot); MeshFilter mf = GetComponent <MeshFilter>(); mf.mesh = null; // Force a new, empty, mf-owned mesh to be generated mf.mesh.MarkDynamic(); }
// Move normals into texcoord1, clearing normals and overwriting texcoord1 // (which may have data, but it's assumed not to be useful for the export) public static void ApplyFbxTexcoordHack(GeometryPool pool) { var layout = pool.Layout; if (!layout.bFbxExportNormalAsTexcoord1) { return; } // Fix up the layout layout.bFbxExportNormalAsTexcoord1 = false; // Should uv1Semantic be "Vector"? Or "Unspecified"? This case currently // does not come up, but guard for it when/if it does. Debug.Assert(layout.normalSemantic != GeometryPool.Semantic.Unspecified, "Ambiguous normalSemantic"); layout.texcoord1.semantic = layout.normalSemantic; layout.texcoord1.size = 3; layout.bUseNormals = false; layout.normalSemantic = GeometryPool.Semantic.Unspecified; pool.Layout = layout; // Swap m_Normals <-> m_UvSet1.v3 var tmp = pool.m_Normals; pool.m_Normals = pool.m_Texcoord1.v3; pool.m_Texcoord1.v3 = tmp; pool.m_Normals.Clear(); }
private static void AssertAreEqual(GeometryPool expected, GeometryPool actual) { string why = ""; bool equal = AreEqual(expected, actual, ref why); Assert.AreEqual("", why); Assert.IsTrue(equal); }
// Danger! This destroys resources shared by the entire SceneStatePayload. // It is only public so it can be called during SceneStatePayload shutdown. public void Destroy() { if (geometry != null) { geometry.Destroy(); geometry = null; } }
static ExportUtils.GroupPayload BuildGroupPayload(SceneStatePayload payload, ExportUtils.ExportGroup exportGroup) { var group = new ExportUtils.GroupPayload(); group.id = exportGroup.m_group == SketchGroupTag.None ? 0 : payload.groupIdMapping.GetId(exportGroup.m_group); // Flattens the two-level (brush, batch) iteration to a single list of meshes // with heterogeneous materials. foreach (ExportUtils.ExportBrush brush in exportGroup.SplitByBrush()) { var desc = brush.m_desc; foreach (var(batch, batchIndex) in brush.ToGeometryBatches().WithIndex()) { GeometryPool geometry = batch.pool; List <Stroke> strokes = batch.strokes; string legacyUniqueName = $"{desc.m_DurableName}_{desc.m_Guid}_{group.id}_i{batchIndex}"; string friendlyGeometryName = $"brush_{desc.m_DurableName}_g{group.id}_b{batchIndex}"; UnityEngine.Profiling.Profiler.BeginSample("ConvertToMetersAndChangeBasis"); ExportUtils.ConvertUnitsAndChangeBasis(geometry, payload); UnityEngine.Profiling.Profiler.EndSample(); if (payload.reverseWinding) { // Note: this triangle flip intentionally un-does the Unity FBX import flip. ExportUtils.ReverseTriangleWinding(geometry, 1, 2); } if (App.PlatformConfig.EnableExportMemoryOptimization && payload.temporaryDirectory != null) { string filename = Path.Combine( payload.temporaryDirectory, legacyUniqueName + ".Gpoo"); geometry.MakeGeometryNotResident(filename); } group.brushMeshes.Add(new ExportUtils.BrushMeshPayload( payload.groupIdMapping.GetId(exportGroup.m_group)) { legacyUniqueName = legacyUniqueName, // This is the only instance of the mesh, so the node doesn't need an extra instance id nodeName = friendlyGeometryName, xform = Matrix4x4.identity, geometry = geometry, geometryName = friendlyGeometryName, exportableMaterial = brush.m_desc, strokes = strokes, }); } } return(group); }
// widget.ReferenceImage must be != null // Never returns null // id is an instance id static ImageQuadPayload BuildImageQuadPayload( SceneStatePayload payload, ImageWidget widget, DynamicExportableMaterial material, int id) { string nodeName = $"image_{widget.GetExportName()}_{id}"; // The child has no T or R but has some non-uniform S that we need to preserve. // I'm going to do this by baking it into the GeometryPool GameObject widgetChild = widget.m_ImageQuad.gameObject; Matrix4x4 xform = ExportUtils.ChangeBasis(widget.transform, payload); Vector3 localScale = widgetChild.transform.localScale; // localScale is a muddle of aspect ratio and overall size. We don't know which of x or y // was modified to set the aspect ratio, so get the size from z. if (localScale.z > 0) { float overallSize = localScale.z; localScale /= overallSize; xform = xform * Matrix4x4.Scale(Vector3.one * overallSize); } // Create pool and bake in the leftover non-uniform scale GeometryPool pool = new GeometryPool(); pool.Layout = material.VertexLayout; pool.Append(widgetChild.GetComponent <MeshFilter>().sharedMesh, fallbackColor: Color.white); pool.ApplyTransform( Matrix4x4.Scale(localScale), Matrix4x4.identity, 1f, 0, pool.NumVerts); // Important: This transform should only be applied once, since the pools are shared, and // cannot include any mesh-local transformations. ExportUtils.ConvertUnitsAndChangeBasis(pool, payload); if (payload.reverseWinding) { // Note: this triangle flip intentionally un-does the Unity FBX import flip. ExportUtils.ReverseTriangleWinding(pool, 1, 2); } return(new ExportUtils.ImageQuadPayload(payload.groupIdMapping.GetId(widget.Group)) { // because Count always increments, this is guaranteed unique even if two different images // have the same export name legacyUniqueName = $"refimage_i{payload.imageQuads.Count}", // We could (but don't) share the same aspect-ratio'd-quad for all instances of a refimage. // Since the mesh is unique to the node, it can have the same name as the node. geometryName = nodeName, nodeName = nodeName, xform = xform, geometry = pool, exportableMaterial = material, }); }
// Doesn't do material export; for that see ExportMeshPayload private GlTF_Node ExportMeshPayload_NoMaterial( BaseMeshPayload mesh, [CanBeNull] GlTF_Node parent, Matrix4x4?localXf = null) { ObjectName meshNameAndId = new ObjectName(mesh.legacyUniqueName); GeometryPool pool = mesh.geometry; Matrix4x4 xf = localXf ?? mesh.xform; // Create a Node and (usually) a Mesh, both named after meshNameAndId. // This is safe because the namespaces for Node and Mesh are distinct. // If we have already seen the GeometryPool, the Mesh will be reused. // In this (less common) case, the Node and Mesh will have different names. // We don't actually ever use the "VERTEXID" attribute, even in gltf1. // It's time to cut it away. // Also, in gltf2, it needs to be called _VERTEXID anyway since it's a custom attribute GlTF_VertexLayout gltfLayout = new GlTF_VertexLayout(G, pool.Layout); int numTris = pool.NumTriIndices / 3; if (numTris < 1) { return(null); } NumTris += numTris; GlTF_Mesh gltfMesh; // Share meshes for any repeated geometry pool. if (!m_meshCache.TryGetValue(pool, out gltfMesh)) { gltfMesh = new GlTF_Mesh(G); gltfMesh.name = GlTF_Mesh.GetNameFromObject(meshNameAndId); gltfMesh.PresentationNameOverride = mesh.geometryName; m_meshCache.Add(pool, gltfMesh); // Populate mesh data only once. AddMeshDependencies(meshNameAndId, mesh.exportableMaterial, gltfMesh, gltfLayout); gltfMesh.Populate(pool); G.meshes.Add(gltfMesh); } // The mesh may or may not be shared, but every mesh will have a distinct node to allow them // to have unique transforms. GlTF_Node node = GlTF_Node.GetOrCreate(G, meshNameAndId, xf, parent, out _); node.m_mesh = gltfMesh; node.PresentationNameOverride = mesh.nodeName; return(node); }
override public void FinalizeSolitaryBrush() { var mesh = GetComponent <MeshFilter>().mesh; m_geometry.CopyToMesh(mesh); m_CachedNumVerts = NumVerts; m_CachedNumTris = NumTris; GeometryPool.Free(m_geometry); m_geometry = null; mesh.RecalculateBounds(); }
static void WriteStroke(JsonWriter json, Stroke stroke, Dictionary <Guid, int> brushMap) { json.WriteStartObject(); var brushGuid = stroke.m_BrushGuid; BrushDescriptor desc = BrushCatalog.m_Instance.GetBrush(brushGuid); int brushIndex; if (!brushMap.TryGetValue(brushGuid, out brushIndex)) { brushIndex = brushMap.Count; brushMap[brushGuid] = brushIndex; } json.WritePropertyName("brush"); json.WriteValue(brushIndex); if (stroke.m_Type == Stroke.Type.BrushStroke) { // Some strokes (eg particles) don't have meshes. For now, assume that // if the stroke has a mesh, it should be written. var meshFilter = stroke.m_Object.GetComponent <MeshFilter>(); if (meshFilter != null) { var mesh = meshFilter.sharedMesh; if (mesh != null) { WriteMesh(json, mesh, desc.VertexLayout); } } } else if (stroke.m_Type == Stroke.Type.BatchedBrushStroke) { BatchSubset subset = stroke.m_BatchSubset; GeometryPool geom = subset.m_ParentBatch.Geometry; Mesh tempMesh = new Mesh(); geom.CopyToMesh(tempMesh, subset.m_StartVertIndex, subset.m_VertLength, subset.m_iTriIndex, subset.m_nTriIndex); WriteMesh(json, tempMesh, geom.Layout); tempMesh.Clear(); UnityEngine.Object.Destroy(tempMesh); } json.WriteEndObject(); }
/// Converts a GeometryPool into a MeshSample. static void GetMeshSample(GeometryPool geomPool, Matrix4x4 mat44, MeshSample sample) { var vertexLayout = geomPool.Layout; // Used for shader binding. sample.doubleSided = true; sample.orientation = USD.NET.Orientation.LeftHanded; sample.points = geomPool.m_Vertices.ToArray(); sample.faceVertexIndices = geomPool.m_Tris.ToArray(); sample.transform = mat44; sample.extent = new Bounds(sample.points[0], Vector3.zero); for (int i = 0; i < sample.points.Length; i++) { sample.extent.Encapsulate(sample.points[i]); } // Yuck. Perhaps push this down to a lower layer. sample.faceVertexCounts = new int[sample.faceVertexIndices.Length / 3]; for (int i = 0; i < sample.faceVertexCounts.Length; i++) { sample.faceVertexCounts[i] = 3; } if (vertexLayout.bUseNormals) { sample.normals = geomPool.m_Normals.ToArray(); } if (vertexLayout.bUseTangents) { sample.tangents = geomPool.m_Tangents.ToArray(); } if (vertexLayout.bUseColors) { sample.colors = geomPool.m_Colors.Select( c => (new Color(c.r, c.g, c.b, c.a) / 255.0f).linear).ToArray(); } sample.uv = GetUv(0, vertexLayout.texcoord0.size, geomPool); sample.uv2 = GetUv(1, vertexLayout.texcoord1.size, geomPool); }
public static bool AreEqual(GeometryPool lhs, GeometryPool rhs, ref string outWhy) { string why = ""; if (!(lhs.Layout == rhs.Layout)) { why += "Layout,"; } if (!(ElementEqual(lhs.m_Vertices, rhs.m_Vertices, ref why))) { why += "verts,"; } if (!(ElementEqual(lhs.m_Tris, rhs.m_Tris, ref why))) { why += "tris,"; } if (!(ElementEqual(lhs.m_Normals, rhs.m_Normals, ref why))) { why += "normals,"; } if (!(ElementEqual(lhs.m_Colors, rhs.m_Colors, ref why))) { why += "colors,"; } if (!(ElementEqual(lhs.m_Tangents, rhs.m_Tangents, ref why))) { why += "tangents,"; } if (!(AreEqual(lhs.m_Texcoord0, rhs.m_Texcoord0, rhs.Layout.texcoord0.size, ref why))) { why += "texcoord0,"; } if (!(AreEqual(lhs.m_Texcoord1, rhs.m_Texcoord1, rhs.Layout.texcoord1.size, ref why))) { why += "texcoord1,"; } if (!(AreEqual(lhs.m_Texcoord2, rhs.m_Texcoord2, rhs.Layout.texcoord2.size, ref why))) { why += "texcoord2,"; } outWhy += why; return(why == ""); }
public void TestAppendMeshFallback() { var mesh = new Mesh(); mesh.vertices = new[] { Vector3.one }; var pool = new GeometryPool { Layout = new VertexLayout { bUseColors = true } }; Color32 white = Color.white; pool.Append(mesh, fallbackColor: white); Assert.AreEqual(white, pool.m_Colors[0]); Object.DestroyImmediate(mesh); }
/// Converts a GeometryPool into a BrushSample. static BrushSample GetBrushSample(GeometryPool geomPool, List <Stroke> strokes, Matrix4x4 mat44) { var sample = new BrushSample(); GetMeshSample(geomPool, mat44, sample); var vertexLayout = geomPool.Layout; // Used for shader binding. sample.brush = strokes[0].m_BrushGuid; // Optional StrokeInfo, this can be disabled to improve performance. sample.stroke = GetStrokeBatchInfo(strokes); return(sample); }
public void TestStructByReference() { var pool = new GeometryPool(); pool.Layout = new VertexLayout { texcoord0 = new TexcoordInfo { size = 2, semantic = GeometryPool.Semantic.XyIsUv } }; Assert.IsNotNull(pool.m_Texcoord0.v2 != null); // One or the other of m_UvSetN, m_TexcoordN is a property. // Normally, properties can only return structs by value. // Check that these properties are references. pool.m_Texcoord1.v2 = pool.m_Texcoord0.v2; Assert.IsNotNull(pool.m_Texcoord1.v2); pool.m_Texcoord0.v2 = null; Assert.IsNull(pool.m_Texcoord0.v2); }
// Returns meshes in meters, in the proper basis, with proper winding static IEnumerable <ModelMesh> GetModelMeshesFromGameObject(GameObject obj, SceneStatePayload payload, bool reverseWinding) { int i = -1; string exportName = obj.name; foreach (MeshData data in VisitMeshes(obj)) { i++; var mesh = data.mesh; for (int sm = 0; sm < mesh.subMeshCount; sm++) { var exportableMaterial = GetMaterialForMeshData(data); // TODO: The geometry pools could be aggregated, for faster downstream rendering. var geo = new GeometryPool(); geo.Layout = exportableMaterial.VertexLayout; geo.Append(mesh, geo.Layout); // Important: This transform should only be applied once, since the pools are shared, and // cannot include any mesh-local transformations. ExportUtils.ConvertToMetersAndChangeBasis(geo, ExportUtils.GetBasisMatrix(payload)); if (reverseWinding) { // Note: this triangle flip intentionally un-does the Unity FBX import flip. ExportUtils.ReverseTriangleWinding(geo, 1, 2); } var objectMesh = new ModelMesh { model = null, // The model is only used for uniquefying the texture names. name = exportName + "_" + i + "_" + sm, id = data.renderer.gameObject.GetInstanceID(), pool = geo, xform = ExportUtils.ChangeBasisNonUniformScale(payload, data.renderer.localToWorldMatrix), exportableMaterial = exportableMaterial, meshIndex = i }; yield return(objectMesh); } } }
public void TestAppendMeshFailure() { var mesh = new Mesh(); mesh.vertices = new[] { Vector3.one }; { var pool = new GeometryPool { Layout = new VertexLayout { bUseNormals = true } }; Assert.Throws <InvalidOperationException>(() => pool.Append(mesh)); } { var pool = new GeometryPool { Layout = new VertexLayout { bUseColors = true } }; Assert.Throws <InvalidOperationException>(() => pool.Append(mesh)); } { var pool = new GeometryPool { Layout = new VertexLayout { bUseTangents = true } }; Assert.Throws <InvalidOperationException>(() => pool.Append(mesh)); } { var pool = new GeometryPool { Layout = new VertexLayout { texcoord0 = new TexcoordInfo { size = 2 } } }; Assert.Throws <InvalidOperationException>(() => pool.Append(mesh)); } Object.DestroyImmediate(mesh); }
void Init(BatchPool parentPool, Bounds bounds, ushort batchId) { BatchId = batchId; m_ParentPool = parentPool; parentPool.m_Batches.Add(this); m_Groups = new List <BatchSubset>(); m_MeshFilter = GetComponent <MeshFilter>(); Debug.Assert(m_MeshFilter.sharedMesh == null); m_Geometry = new GeometryPool(); var rNewMesh = new Mesh(); rNewMesh.MarkDynamic(); gameObject.layer = ParentPool.Owner.Canvas.gameObject.layer; // This is a fix for b/27266757. I don't know precisely why it works. // // I think the mesh needs to spend "some amount of time" with a non-zero-length // vtx buffer. If this line is followed by .vertices = new Vector3[0], the bug // appears again. The mysterious thing is that immediately after creation, we // start filling up .vertices. Why does the first assignment need to happen here, // instead of waiting just a few ms for the mesh to be updated with real data? // // This seems related to how and when Unity decides to upload mesh data to the GPU. rNewMesh.vertices = new Vector3[1]; // TODO: why set bounds? rNewMesh.bounds = bounds; m_MeshFilter.mesh = rNewMesh; // Instantiate mesh so we can destroy rNewMesh; destroy rNewMesh to protect // against it leaking if/when someone reads m_MeshFilter.mesh. bool instantiationSucceeded = (m_MeshFilter.mesh != rNewMesh); Debug.Assert(instantiationSucceeded); DestroyImmediate(rNewMesh); m_bVertexDataDirty = false; m_bTopologyDirty = false; }
PreserveBatchResidency(IEnumerable <Stroke> input) { // We could do the "make it resident, make it not resident" dance on a stroke-by-stroke // basis, but that would be ridiculously wasteful. Optimize by grouping adjacent strokes // together if they share the same batch. // // When a sketch first loads, this optimization works extremely well because batches are // contiguous in the stroke list. The more the user mutates (select, unselect, recolor) // the less this will be true. There's not a lot we can do about it unless we want to // sort the strokes by batch rather than by draw time (or whatever criteria the incoming // iterator uses), which I think would be too invasive. foreach (IGrouping <GeometryPool, Stroke> group in input.GroupBy(stroke => GetPool(stroke))) { GeometryPool pool = group.Key; // If we have to bring the pool into memory, save off enough data so we can // push it back out. Mesh previousBackingMesh = null; if (pool != null && !pool.IsGeometryResident) { previousBackingMesh = pool.GetBackingMesh(); // We currently only eject batch-owned GeometryPool to Mesh, so this is unlikely to trip; // but we eventually may want to eject them to disk to save even more memory. if (previousBackingMesh == null) { Debug.LogWarning("Not yet able to eject pool back to file; leaving it in memory"); } pool.EnsureGeometryResident(); } foreach (Stroke stroke in group) { yield return(stroke); } if (previousBackingMesh != null) { pool.MakeGeometryNotResident(previousBackingMesh); } } }
// Fbx only supports 2-channel texcoord data; turn (up to) 2 float4s // into (up to) 4 float2s. // // The resulting List will have no nulls at the end, but may have some // gaps with missing data. static List <List <Vector2> > DemuxTexcoords(GeometryPool pool) { var allSets = new List <List <Vector2> >(); { List <Vector2> tmpXy, tmpZw; DemuxTexcoord(pool, pool.Layout.texcoord0.size, pool.m_Texcoord0, out tmpXy, out tmpZw); allSets.Add(tmpXy); allSets.Add(tmpZw); DemuxTexcoord(pool, pool.Layout.texcoord1.size, pool.m_Texcoord1, out tmpXy, out tmpZw); allSets.Add(tmpXy); allSets.Add(tmpZw); } // Remove unused sets from the end while (allSets.Count > 0 && allSets[allSets.Count - 1] == null) { allSets.RemoveAt(allSets.Count - 1); } return(allSets); }
// Puts the passed timestamp data into pool.texcoord2. // It's assumed that the pool does not already have anything in texcoord2. private static void AugmentWithTimestamps(GeometryPool pool, ref List <Vector3> timestamps) { if (App.UserConfig.Export.ExportStrokeTimestamp) { GeometryPool.VertexLayout withTxc2 = pool.Layout; if (withTxc2.texcoord2.size == 0 && timestamps.Count == pool.NumVerts) { withTxc2.texcoord2 = new TexcoordInfo { size = 3, semantic = Semantic.Timestamp }; pool.Layout = withTxc2; pool.m_Texcoord2.v3 = timestamps; timestamps = null; // don't let caller reuse the list } else { Debug.LogError("Internal error; cannot add timestamps"); } } }
/// Flips winding order by swapping indices indexA and indexB. /// indexA and indexB must be in the range [0, 2] and not equal to each other. public static void ReverseTriangleWinding(GeometryPool pool, int indexA, int indexB) { if (indexA == indexB || indexA < 0 || indexA > 2) { throw new ArgumentException("indexA"); } if (indexB < 0 || indexB > 2) { throw new ArgumentException("indexB"); } var tris = pool.m_Tris; int count = tris.Count; for (int i = 0; i < count; i += 3) { var tmp = tris[i + indexA]; tris[i + indexA] = tris[i + indexB]; tris[i + indexB] = tmp; } }
private static GeometryPool RandomGeometryPool() { int vertexCount = 20; int indexCount = 60; var pool = new GeometryPool(); pool.Layout = new VertexLayout { texcoord0 = new TexcoordInfo { size = 2, semantic = Semantic.XyIsUv }, bUseNormals = true, bUseColors = true, bUseTangents = true }; pool.m_Vertices = MathTestUtils.RandomVector3List(vertexCount); pool.m_Tris = MathTestUtils.RandomIntList(indexCount, 0, vertexCount); pool.m_Normals = MathTestUtils.RandomVector3List(vertexCount); pool.m_Colors = MathTestUtils.RandomColor32List(vertexCount); pool.m_Tangents = MathTestUtils.RandomVector4List(vertexCount); pool.m_Texcoord0.v2 = MathTestUtils.RandomVector2List(vertexCount); return(pool); }
public void TestAppendMeshExtraData() { // Mesh has all the data but pool doesn't want any of it var mesh = new Mesh(); mesh.vertices = new[] { Vector3.one }; mesh.normals = new[] { Vector3.up }; mesh.SetUVs(0, new List <Vector2> { Vector2.zero }); mesh.colors = new[] { Color.white }; mesh.tangents = new[] { Vector4.one }; var pool = new GeometryPool { Layout = new VertexLayout() }; // This should not throw pool.Append(mesh); Object.DestroyImmediate(mesh); }