static public Batch Create(BatchPool parentPool, Transform rParent, Bounds rBounds) { var brush = BrushCatalog.m_Instance.GetBrush(parentPool.m_BrushGuid); string name = string.Format("Batch_{0}_{1}", parentPool.m_Batches.Count, brush.m_Guid); GameObject newObj = new GameObject(name); Transform t = newObj.transform; t.parent = rParent; t.localPosition = Vector3.zero; t.localRotation = Quaternion.identity; t.localScale = Vector3.one; newObj.AddComponent <MeshFilter>(); Renderer renderer = newObj.AddComponent <MeshRenderer>(); renderer.material = brush.Material; var propertyBlock = new MaterialPropertyBlock(); renderer.GetPropertyBlock(propertyBlock); ushort batchId = GpuIntersector.GetNextBatchId(); propertyBlock.SetFloat("_BatchID", batchId); renderer.SetPropertyBlock(propertyBlock); Batch batch = newObj.AddComponent <Batch>(); batch.Init(parentPool, rBounds, batchId); // This forces instantiation, but we can detect and clean it up in Destroy() batch.m_InstantiatedMaterial = renderer.material; return(batch); }
/// 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); }
/// Creates and returns a new subset containing the passed geometry. /// Pass: /// otherSubset - /// Specifies both the material/batch to copy into, /// as well as the geometry to copy. /// May be owned by a different BatchManager. /// leftTransform - optional transform to transform the subset. public BatchSubset CreateSubset(BatchSubset otherSubset, TrTransform?leftTransform = null) { Batch otherBatch = otherSubset.m_ParentBatch; BatchPool otherPool = otherBatch.ParentPool; var pool = GetPool(otherPool.m_BrushGuid); var batch = GetBatch(pool, otherSubset.m_VertLength); return(batch.AddSubset( otherBatch.Geometry, otherSubset.m_StartVertIndex, otherSubset.m_VertLength, otherSubset.m_iTriIndex, otherSubset.m_nTriIndex, leftTransform)); }
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; }
BatchPool GetPool(Guid brushGuid) { try { return(m_BrushToPool[brushGuid]); } catch (KeyNotFoundException) { BatchPool rNewPool = new BatchPool(this); rNewPool.m_BrushGuid = brushGuid; rNewPool.m_Batches = new List <Batch>(); Batch b = Batch.Create(rNewPool, m_ParentTransform, m_MeshBounds); sm_BatchMap.Add(b.BatchId, b); m_Pools.Add(rNewPool); m_BrushToPool[rNewPool.m_BrushGuid] = rNewPool; foreach (string keyword in m_MaterialKeywords) { b.InstantiatedMaterial.EnableKeyword(keyword); } return(rNewPool); } }
protected void DebugDrawBounds() { CanvasScript canvas = App.ActiveCanvas; float fSelectionRadius = GetSize(); if (App.Config.m_UseBatchedBrushes) { int iNumBatchPools = canvas.BatchManager.GetNumBatchPools(); for (int i = 0; i < iNumBatchPools; ++i) { BatchPool rPool = canvas.BatchManager.GetBatchPool(i); for (int j = 0; j < rPool.m_Batches.Count; ++j) { for (int k = 0; k < rPool.m_Batches[j].m_Groups.Count; ++k) { Bounds rMeshBounds = rPool.m_Batches[j].m_Groups[k].m_Bounds; rMeshBounds.Expand(fSelectionRadius); Color rDrawColor = rPool.m_Batches[j].m_Groups[k].m_Active ? Color.white : Color.red; DebugDrawBox(rMeshBounds, Vector3.zero, rDrawColor); } } } } else { Transform rCanvas = canvas.transform; for (int i = 0; i < rCanvas.childCount; ++i) { Transform rChild = rCanvas.GetChild(i); if (rChild.gameObject.activeSelf) { MeshFilter rMeshFilter = rChild.GetComponent <MeshFilter>(); if (rMeshFilter) { Bounds rMeshBounds = rMeshFilter.mesh.bounds; rMeshBounds.Expand(fSelectionRadius); DebugDrawBox(rMeshBounds, rChild.position, Color.white); } } } } }
// Returns a batch such that it has space for at least nVerts Batch GetBatch(BatchPool pool, int nVerts) { if (pool.m_Batches.Count > 0) { var batch = pool.m_Batches[pool.m_Batches.Count - 1]; if (batch.HasSpaceFor(nVerts)) { return(batch); } } { Batch b = Batch.Create(pool, m_ParentTransform, m_MeshBounds); sm_BatchMap.Add(b.BatchId, b); foreach (string keyword in m_MaterialKeywords) { b.InstantiatedMaterial.EnableKeyword(keyword); } Debug.Assert(pool.m_Batches[pool.m_Batches.Count - 1] == b); return(b); } }
/// Detection Center should be in Global Space. protected void UpdateBatchedBrushDetection(Vector3 vDetectionCenter_GS) { // The CPU intersection code still needs to be updated to iterate over multiple canvases. // // TODO: Update CPU intersection checking to work on multiple canvases, // then get rid of automatic defaulting to ActiveCanvas. // Possibly let m_CurrentCanvas be null to represent a desire to intersect // with all canvases. if (m_CurrentCanvas == null) { m_CurrentCanvas = App.ActiveCanvas; } // If we changed canvases, abandon any progress we made on checking for // intersections in the previous canvas. if (m_CurrentCanvas != m_PreviousCanvas) { ResetDetection(); m_PreviousCanvas = m_CurrentCanvas; } TrTransform canvasPose = m_CurrentCanvas.Pose; Vector3 vDetectionCenter_CS = canvasPose.inverse * vDetectionCenter_GS; m_TimesUp = false; // Reset detection if we've moved or adjusted our size float fDetectionRadius_CS = GetSize() / canvasPose.scale; float fDetectionRadiusSq_CS = fDetectionRadius_CS * fDetectionRadius_CS; // Start the timer! m_DetectionStopwatch.Reset(); m_DetectionStopwatch.Start(); int iSanityCheck = 10000; bool bNothingChecked = true; if (App.Config.m_GpuIntersectionEnabled) { // Run GPU intersection if enabled; will update m_TimesUp. if (UpdateGpuIntersection(vDetectionCenter_GS, GetSize())) { IntersectionHappenedThisFrame(); m_DetectionStopwatch.Stop(); DoIntersectionResets(); return; } } m_TimesUp = m_DetectionStopwatch.ElapsedTicks > m_TimeSliceInTicks; //check batch pools first int iNumBatchPools = m_CurrentCanvas.BatchManager.GetNumBatchPools(); if (!App.Config.m_GpuIntersectionEnabled && iNumBatchPools > 0 && m_BatchPoolIndex < iNumBatchPools) { bNothingChecked = false; m_ResetDetection = false; Plane rTestPlane = new Plane(); BatchPool rPool = m_CurrentCanvas.BatchManager.GetBatchPool(m_BatchPoolIndex); //spin until we've taken up too much time while (!m_TimesUp) { --iSanityCheck; if (iSanityCheck == 0) { Batch tmpBatch = rPool.m_Batches[m_BatchObjectIndex]; Debug.LogErrorFormat("Stroke while loop error. NumPools({0}) BatchPoolIndex({1}) NumBatchStrokes({2}) BatchStrokeIndex({3}) NumStrokeGroups({4})", iNumBatchPools, m_BatchPoolIndex, rPool.m_Batches.Count, m_BatchObjectIndex, tmpBatch.m_Groups.Count); } Batch batch = rPool.m_Batches[m_BatchObjectIndex]; if (m_BatchVertGroupIndex < batch.m_Groups.Count) { var subset = batch.m_Groups[m_BatchVertGroupIndex]; Bounds rMeshBounds = subset.m_Bounds; rMeshBounds.Expand(2.0f * fDetectionRadius_CS); if (subset.m_Active && rMeshBounds.Contains(vDetectionCenter_CS)) { //bounds valid, check triangle intersections with sphere int nTriIndices = subset.m_nTriIndex; Vector3[] aVerts; int nVerts; int[] aTris; int nTris; batch.GetTriangles(out aVerts, out nVerts, out aTris, out nTris); while (m_BatchTriIndexIndex < nTriIndices - 2) { //check to see if we're within the brush size (plus some) radius to this triangle int iTriIndex = subset.m_iTriIndex + m_BatchTriIndexIndex; Vector3 v0 = aVerts[aTris[iTriIndex]]; Vector3 v1 = aVerts[aTris[iTriIndex + 1]]; Vector3 v2 = aVerts[aTris[iTriIndex + 2]]; Vector3 vTriCenter = (v0 + v1 + v2) * 0.33333f; Vector3 vToTestCenter = vDetectionCenter_CS - vTriCenter; float fTestSphereRadius_CS = Vector3.Distance(v1, v2) + fDetectionRadius_CS; if (vToTestCenter.sqrMagnitude < fTestSphereRadius_CS * fTestSphereRadius_CS) { //check to see if we're within the sphere radius to the plane of this triangle Vector3 vNorm = Vector3.Cross(v1 - v0, v2 - v0).normalized; rTestPlane.SetNormalAndPosition(vNorm, v0); float fDistToPlane = rTestPlane.GetDistanceToPoint(vDetectionCenter_CS); if (Mathf.Abs(fDistToPlane) < fDetectionRadius_CS) { //we're within the radius to this triangle's plane, find the point projected on to the plane fDistToPlane *= -1.0f; Vector3 vPlaneOffsetVector = vNorm * fDistToPlane; Vector3 vPlaneIntersection = vDetectionCenter_CS - vPlaneOffsetVector; //walk the projected point toward the triangle center to find the triangle test position bool bIntersecting = false; Vector3 vPointToTriCenter = vTriCenter - vDetectionCenter_CS; if (vPointToTriCenter.sqrMagnitude < fDetectionRadiusSq_CS) { //if the triangle center is within the detection distance, we're definitely intersecting bIntersecting = true; } //check against triangle segments else if (SegmentSphereIntersection(v0, v1, vDetectionCenter_CS, fDetectionRadiusSq_CS)) { bIntersecting = true; } else if (SegmentSphereIntersection(v1, v2, vDetectionCenter_CS, fDetectionRadiusSq_CS)) { bIntersecting = true; } else if (SegmentSphereIntersection(v2, v0, vDetectionCenter_CS, fDetectionRadiusSq_CS)) { bIntersecting = true; } else { //figure out how far we have left to move toward the tri-center float fNormAngle = Mathf.Acos(Mathf.Abs(fDistToPlane) / fDetectionRadius_CS); float fDistLeft = Mathf.Sin(fNormAngle) * fDetectionRadius_CS; Vector3 vToTriCenter = vTriCenter - vPlaneIntersection; vToTriCenter.Normalize(); vToTriCenter *= fDistLeft; vPlaneIntersection += vToTriCenter; //see if this projected point is in the triangle if (PointInTriangle(ref vPlaneIntersection, ref v0, ref v1, ref v2)) { bIntersecting = true; } } if (bIntersecting) { if (HandleIntersectionWithBatchedStroke(subset)) { DoIntersectionResets(); break; } } } } //after each triangle, check our time m_BatchTriIndexIndex += 3; m_TimesUp = m_DetectionStopwatch.ElapsedTicks > m_TimeSliceInTicks; if (m_TimesUp) { break; } } } } //if we're not flagged as done, we just finished this group, so move on to the next group if (!m_TimesUp) { m_BatchTriIndexIndex = 0; ++m_BatchVertGroupIndex; //if we're done with groups, go to the next object if (m_BatchVertGroupIndex >= batch.m_Groups.Count) { m_BatchVertGroupIndex = 0; ++m_BatchObjectIndex; //aaaand if we're done with objects, go on to the next pool if (m_BatchObjectIndex >= rPool.m_Batches.Count) { m_BatchObjectIndex = 0; ++m_BatchPoolIndex; if (m_BatchPoolIndex >= iNumBatchPools) { //get out if we've traversed the last pool break; } rPool = m_CurrentCanvas.BatchManager.GetBatchPool(m_BatchPoolIndex); } } //we check again here in case the early checks fail m_TimesUp = m_DetectionStopwatch.ElapsedTicks > m_TimeSliceInTicks; } } } if (App.Config.m_GpuIntersectionEnabled && m_GpuFutureResult != null) { // We have an intersection test in flight, make sure we don't reset detection. // This ensures consistency between collision tests and avoids strobing of the result (e.g. // without this, one test will return "no result" and the next may return some result and this // oscillation will continue). bNothingChecked = false; m_ResetDetection = false; return; } // If our scene doesn't have anything in it, reset our detection. if (bNothingChecked) { m_ResetDetection = true; } m_DetectionStopwatch.Stop(); }