// ------------------------------------------------------------------------------------------ // // Private Internals // ------------------------------------------------------------------------------------------ // override protected void OnReadResults() { // Mark results as ready. m_ResultCount = 0; if (m_ResultList != null) { m_ResultList.Clear(); } uint[] resultColors = GetTextureColors(); // // Process the color buffer into result objects, if requested. // UnityEngine.Profiling.Profiler.BeginSample("Intersection: Process Results"); HashSet <object> seen = AllocateHashSet(); uint c; for (int i = 0; i < resultColors.Length; i++) { if (m_ResultCount == m_MaxResults) { break; } c = resultColors[i]; if (c > 0) { // Don't bother looking up the batch if the exact result set wasn't requested. if (m_ResultList == null) { m_ResultCount++; continue; } // TODO: the Color32 -> object lookup is deterministic and O(n), so 'seen' // should store the Color32, not the object. int triIndex = (int)(c & 0xffff) * 3; ushort batchId = (ushort)((c & 0xffff0000) >> 16); GrabWidget widget = null; Batch batch = null; BatchSubset subset = null; // See if this batch refers to a brush stroke. batch = App.ActiveCanvas.BatchManager.GetBatch(batchId); if (batch != null) { // TODO: move this into Batch, so can do binary search if necessary for (int j = 0; j < batch.m_Groups.Count; j++) { BatchSubset bs = batch.m_Groups[j]; if (triIndex >= bs.m_iTriIndex && triIndex < bs.m_iTriIndex + bs.m_nTriIndex) { subset = bs; break; } } // A stroke may be deleted by the time this executes. This is due to the delay between // sending an intersection request and processing the results. if (subset == null) { // This actually happens in practice! // TODO: investigate if it's okay // Debug.LogWarningFormat( // "Unexpected: Nonexistent subset for c = {0:x} {1:x}", // (ushort)(c >> 16), // (ushort)(c & 0xffff)); continue; } if (!seen.Add(subset)) { continue; } } else { // Not a brush stroke? See if this is a widget. widget = WidgetManager.m_Instance.GetBatch(batchId); // A widget may be deleted by the time this executes. This is due to the delay between // sending an intersection request and processing the results. if (widget == null) { continue; } if (!seen.Add(widget)) { continue; } } // A batch should never be null, but in the future that may change. This is possible due // to the delay between sending an intersection request and processing the results. if (batch == null && widget == null) { Debug.LogWarningFormat( "Unexpected: Null batch {0} and widget {1}", ReferenceEquals(batch, null), ReferenceEquals(widget, null)); continue; } // These cannot both be valid. Debug.Assert(subset == null || widget == null); m_ResultList.Add( new BatchResult { widget = widget, subset = subset }); m_ResultCount++; } } DeallocateHashSet(seen); UnityEngine.Profiling.Profiler.EndSample(); }
/// 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(); }