예제 #1
0
        public static void poke_test()
        {
            DMesh3 mesh = TestUtil.LoadTestInputMesh("plane_250v.obj");

            //DMesh3 mesh = TestUtil.LoadTestInputMesh("sphere_bowtie_groups.obj");
            mesh.CheckValidity();


            int NT = mesh.TriangleCount;

            for (int i = 0; i < NT; i += 5)
            {
                Vector3d n = mesh.GetTriNormal(i);
                DMesh3.PokeTriangleInfo pokeinfo;
                MeshResult result = mesh.PokeTriangle(i, out pokeinfo);

                Vector3d v = mesh.GetVertex(pokeinfo.new_vid);
                v += 0.25f * n;
                mesh.SetVertex(pokeinfo.new_vid, v);

                mesh.CheckValidity();
            }

            //TestUtil.WriteTestOutputMesh(mesh, "poke_test_result.obj");
        }
예제 #2
0
        /// <summary>
        /// Find regions of the input meshes that are horizontal, returns binned by Z-value.
        /// Currently only finds upward-facing regions.
        /// </summary>
        protected Dictionary <double, List <PlanarRegion> > FindPlanarZRegions(double minDimension, double dotNormalTol = 0.999)
        {
            Dictionary <double, List <PlanarRegion> > Regions = new Dictionary <double, List <PlanarRegion> >();
            SpinLock region_lock = new SpinLock();

            gParallel.ForEach(Meshes, (sliceMesh) => {
                if (sliceMesh.options.IsCavity == false)
                {
                    return;
                }
                DMesh3 mesh = sliceMesh.mesh;

                HashSet <int> planar_tris = new HashSet <int>();
                foreach (int tid in mesh.TriangleIndices())
                {
                    Vector3d n = mesh.GetTriNormal(tid);
                    double dot = n.Dot(Vector3d.AxisZ);
                    if (dot > dotNormalTol)
                    {
                        planar_tris.Add(tid);
                    }
                }

                MeshConnectedComponents regions = new MeshConnectedComponents(mesh);
                regions.FilterF = planar_tris.Contains;
                regions.FindConnectedT();
                foreach (var c in regions)
                {
                    AxisAlignedBox3d bounds = MeshMeasurements.BoundsT(mesh, c.Indices);
                    if (bounds.Width > minDimension && bounds.Height > minDimension)
                    {
                        double z = Math.Round(bounds.Center.z, PrecisionDigits);

                        PlanarRegion planar = new PlanarRegion()
                        {
                            Mesh = mesh, Z = z, Triangles = c.Indices
                        };

                        bool taken = false;
                        region_lock.Enter(ref taken);
                        List <PlanarRegion> zregions;
                        if (Regions.TryGetValue(z, out zregions))
                        {
                            zregions.Add(planar);
                        }
                        else
                        {
                            zregions = new List <PlanarRegion>()
                            {
                                planar
                            };
                            Regions[z] = zregions;
                        }
                        region_lock.Exit();
                    }
                }
            });

            return(Regions);
        }
예제 #3
0
        /// <summary>
        /// Find intersection of *WORLD* ray with Mesh
        /// </summary>
        override public bool FindRayIntersection(Ray3f rayW, out SORayHit hit)
        {
            hit = null;
            if (enable_spatial == false)
            {
                return(false);
            }

            if (spatial == null)
            {
                spatial = new DMeshAABBTree3(mesh);
                spatial.Build();
            }

            // convert ray to local
            Frame3f f = new Frame3f(rayW.Origin, rayW.Direction);

            f = SceneTransforms.TransformTo(f, this, CoordSpace.WorldCoords, CoordSpace.ObjectCoords);
            Ray3d local_ray = new Ray3d(f.Origin, f.Z);

            int hit_tid = spatial.FindNearestHitTriangle(local_ray);

            if (hit_tid != DMesh3.InvalidID)
            {
                IntrRay3Triangle3 intr = MeshQueries.TriangleIntersection(mesh, hit_tid, local_ray);

                Frame3f hitF = new Frame3f(local_ray.PointAt(intr.RayParameter), mesh.GetTriNormal(hit_tid));
                hitF = SceneTransforms.TransformTo(hitF, this, CoordSpace.ObjectCoords, CoordSpace.WorldCoords);

                hit           = new SORayHit();
                hit.hitPos    = hitF.Origin;
                hit.hitNormal = hitF.Z;
                hit.hitIndex  = hit_tid;
                hit.fHitDist  = hit.hitPos.Distance(rayW.Origin);   // simpler than transforming!
                hit.hitGO     = RootGameObject;
                hit.hitSO     = this;
                return(true);
            }
            return(false);
        }
예제 #4
0
        /// <summary>
        /// Find intersection of *WORLD* ray with Mesh
        /// </summary>
        override public bool FindRayIntersection(Ray3f rayW, out SORayHit hit)
        {
            hit = null;
            if (enable_spatial == false)
            {
                return(false);
            }

            validate_spatial();

            // convert ray to local
            FScene scene     = this.GetScene();
            Ray3f  rayS      = scene.ToSceneRay(rayW);
            Ray3d  local_ray = SceneTransforms.SceneToObject(this, rayS);

            int hit_tid = spatial.FindNearestHitTriangle(local_ray);

            if (hit_tid != DMesh3.InvalidID)
            {
                IntrRay3Triangle3 intr = MeshQueries.TriangleIntersection(mesh, hit_tid, local_ray);

                Vector3f hitPos = (Vector3f)local_ray.PointAt(intr.RayParameter);
                hitPos = SceneTransforms.ObjectToSceneP(this, hitPos);
                hitPos = scene.ToWorldP(hitPos);

                Vector3f hitNormal = (Vector3f)mesh.GetTriNormal(hit_tid);
                hitNormal = SceneTransforms.ObjectToSceneN(this, hitNormal);
                hitNormal = scene.ToWorldN(hitNormal);

                hit           = new SORayHit();
                hit.hitPos    = hitPos;
                hit.hitNormal = hitNormal;
                hit.hitIndex  = hit_tid;
                hit.fHitDist  = hit.hitPos.Distance(rayW.Origin);   // simpler than transforming!
                hit.hitGO     = RootGameObject;
                hit.hitSO     = this;
                return(true);
            }
            return(false);
        }
예제 #5
0
        protected override void OnPointUpdated(ControlPoint pt, Frame3f prevFrameS, bool isFirst)
        {
            DMesh3         mesh    = InputMeshSO.Mesh;
            DMeshAABBTree3 spatial = InputMeshSO.Spatial;

            Vector3f ptO    = SceneTransforms.SceneToObjectP(InputMeshSO, pt.currentFrameS.Origin);
            Frame3f  frameO = MeshQueries.NearestPointFrame(mesh, spatial, ptO, true);

            Vector3d dir = -frameO.Z;

            if (hole_direction != HoleDirections.Normal)
            {
                Vector3f axis = Vector3f.AxisX;
                if (hole_direction == HoleDirections.AxisY)
                {
                    axis = Vector3f.AxisY;
                }
                else if (hole_direction == HoleDirections.AxisZ)
                {
                    axis = Vector3f.AxisZ;
                }
                axis = SceneTransforms.SceneToObjectN(InputMeshSO, axis);
                dir  = (dir.Dot(axis) < 0) ? -axis : axis;
            }
            //dir.Normalize();

            LastUpdateRay = new Ray3d(frameO.Origin, dir);

            List <int> hitTris  = new List <int>();
            int        hit_tris = spatial.FindAllHitTriangles(LastUpdateRay, hitTris);
            double     max_t    = 0;

            foreach (int tid in hitTris)
            {
                Vector3d n = mesh.GetTriNormal(tid);
                if (n.Dot(LastUpdateRay.Direction) < 0)
                {
                    continue;
                }
                IntrRay3Triangle3 rayhit = MeshQueries.TriangleIntersection(InputMeshSO.Mesh, tid, LastUpdateRay);
                max_t = rayhit.RayParameter;
                break;
            }
            if (max_t <= 0)
            {
                return;
            }

            LastThroughDepth = max_t;
            update_current_hole_type();
        }
예제 #6
0
        UseFillType classify_hole()
        {
            return(UseFillType.MinimalFill);

#if false
            int NV = FillLoop.VertexCount;
            int NE = FillLoop.EdgeCount;

            Vector3d size = FillLoop.ToCurve().GetBoundingBox().Diagonal;

            NormalHistogram hist = new NormalHistogram(4096, true);

            for (int k = 0; k < NE; ++k)
            {
                int      eid = FillLoop.Edges[k];
                Index2i  et  = Mesh.GetEdgeT(eid);
                Vector3d n   = Mesh.GetTriNormal(et.a);
                hist.Count(n, 1.0, true);
            }

            if (hist.UsedBins.Count == 1)
            {
                return(UseFillType.PlanarFill);
            }

            //int nontrivial_bins = 0;
            //foreach ( int bin in hist.UsedBins ) {
            //    if (hist.Counts[bin] > 8)
            //        nontrivial_bins++;
            //}
            //if (nontrivial_bins > 0)
            //    return UseFillType.PlanarSpansFill;

            return(UseFillType.SmoothFill);
#endif
        }
예제 #7
0
        public static void test_normals()
        {
            // check that frames are ok
            DMesh3 mesh = TestUtil.LoadTestInputMesh("bunny_solid.obj");

            foreach (int tid in mesh.TriangleIndices())
            {
                Vector3f n = (Vector3f)mesh.GetTriNormal(tid);
                for (int j = 0; j < 3; ++j)
                {
                    Frame3f f = mesh.GetTriFrame(tid, j);
                    if (Math.Abs(f.X.Dot(f.Y)) > MathUtil.ZeroTolerancef ||
                        Math.Abs(f.X.Dot(f.Z)) > MathUtil.ZeroTolerancef ||
                        Math.Abs(f.Y.Dot(f.Z)) > MathUtil.ZeroTolerancef)
                    {
                        throw new Exception("argh");
                    }
                    Vector3f fn = f.Z;
                    if (fn.Dot(n) < 0.99)
                    {
                        throw new Exception("shit");
                    }
                }
            }

            MeshNormals.QuickCompute(mesh);

            foreach (int vid in mesh.VertexIndices())
            {
                Vector3f n = mesh.GetVertexNormal(vid);
                for (int j = 1; j <= 2; ++j)
                {
                    Frame3f  f  = mesh.GetVertexFrame(vid, (j == 1) ? true : false);
                    Vector3f fn = f.GetAxis(j);
                    if (Math.Abs(f.X.Dot(f.Y)) > MathUtil.ZeroTolerancef ||
                        Math.Abs(f.X.Dot(f.Z)) > MathUtil.ZeroTolerancef ||
                        Math.Abs(f.Y.Dot(f.Z)) > MathUtil.ZeroTolerancef)
                    {
                        throw new Exception("argh");
                    }
                    if (fn.Dot(n) < 0.99)
                    {
                        throw new Exception("shit2");
                    }
                }
            }
        }
예제 #8
0
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            DMesh3_goo goo = null;

            DA.GetData(0, ref goo);

            DMesh3 mesh = new DMesh3(goo.Value);

            List <Rhino.Geometry.Vector3d> vecs = new List <Rhino.Geometry.Vector3d>();

            foreach (var ind in mesh.TriangleIndices())
            {
                vecs.Add(mesh.GetTriNormal(ind).ToRhinoVec());
            }

            DA.SetDataList(0, vecs);
        }
예제 #9
0
    private static Vector3d GetAveragedNormal(DMesh3 mesh, int vertex)
    {
        MeshFaceSelection selection = new MeshFaceSelection(mesh);
        var surroundingTris         = new List <int>();

        mesh.GetVtxTriangles(vertex, surroundingTris, false);
        selection.FloodFill(surroundingTris.ToArray(), i => IsInRange(mesh, vertex, i, 3));
        Vector3d cumulative = Vector3d.Zero;

        foreach (var i in selection)
        {
            cumulative += mesh.GetTriNormal(i);
        }

        var normal = cumulative / selection.Count;

        return(normal);
    }
예제 #10
0
        // [RMS] this is not working right now...
        override public bool FindRayIntersection(Ray3f ray, out SORayHit hit)
        {
            hit = null;
            if (enable_spatial == false)
            {
                return(false);
            }

            if (spatial == null)
            {
                spatial = new DMeshAABBTree3(mesh);
                spatial.Build();
            }

            Transform xform = ((GameObject)RootGameObject).transform;

            // convert ray to local
            Ray3d local_ray = new Ray3d();

            local_ray.Origin    = xform.InverseTransformPoint(ray.Origin);
            local_ray.Direction = xform.InverseTransformDirection(ray.Direction);
            local_ray.Direction.Normalize();

            int hit_tid = spatial.FindNearestHitTriangle(local_ray);

            if (hit_tid != DMesh3.InvalidID)
            {
                IntrRay3Triangle3 intr = MeshQueries.TriangleIntersection(mesh, hit_tid, local_ray);

                hit           = new SORayHit();
                hit.fHitDist  = (float)intr.RayParameter;
                hit.hitPos    = xform.TransformPoint((Vector3f)local_ray.PointAt(intr.RayParameter));
                hit.hitNormal = xform.TransformDirection((Vector3f)mesh.GetTriNormal(hit_tid));
                hit.hitGO     = RootGameObject;
                hit.hitSO     = this;
                return(true);
            }
            return(false);
        }
예제 #11
0
        public static UnityMesh ToUnityMesh(this DMesh3 dMesh,
                                            bool bNorm = true,
                                            bool bUV   = false,
                                            bool bCol  = false)
        {
            bNorm &= dMesh.HasVertexNormals;
            bUV   &= dMesh.HasVertexUVs;
            bCol  &= dMesh.HasVertexColors;

            int[] vertexMap   = new int[dMesh.VerticesBuffer.Length];
            int[] triangleMap = new int[dMesh.TrianglesBuffer.Length];
            int[] triangles   = new int[dMesh.TriangleCount * 3];

            List <Vector3d> vertices       = new List <Vector3d>();
            List <Vector3f> normals        = new List <Vector3f>();
            List <Vector2f> uv             = new List <Vector2f>();
            List <Colorf>   colors         = new List <Colorf>();
            List <int>      vertexUseCount = new List <int>();

            NewVertexInfo vInfo = new NewVertexInfo(new Vector3d(),
                                                    new Vector3f(),
                                                    new Vector3f(),
                                                    new Vector2f());

            IEnumerator e  = dMesh.TrianglesRefCounts.GetEnumerator();
            int         ti = 0;

            while (e.MoveNext())
            {
                int     iRef     = (int)e.Current;
                Index3i triangle = dMesh.GetTriangle(iRef);
                triangleMap[iRef] = ti;

                for (int i = 0; i < 3; i++)
                {
                    int vertIndex = triangle[i];
                    if (vertexMap[vertIndex] == 0)
                    {
                        vertexUseCount.Add(1);
                        dMesh.GetVertex(vertIndex, ref vInfo, bNorm, bCol, bUV);
                        vertices.Add(new Vector3f((float)vInfo.v.x,
                                                  (float)vInfo.v.y,
                                                  (float)vInfo.v.z));
                        vertexMap[vertIndex] = vertices.Count - 1;

                        if (bNorm)
                        {
                            normals.Add(vInfo.n);
                        }
                        if (bUV)
                        {
                            uv.Add(vInfo.uv);
                        }
                        if (bCol)
                        {
                            colors.Add(new Colorf(vInfo.c.x, vInfo.c.y, vInfo.c.z));
                        }
                    }
                    else
                    {
                        vertexUseCount[vertexMap[vertIndex]]++;
                    }

                    triangles[ti * 3 + i] = vertexMap[vertIndex];
                }
                ti++;
            }

            UnityMesh uMesh = new UnityMesh(vertexUseCount.ToArray(),
                                            triangles,
                                            vertices,
                                            normals,
                                            uv,
                                            colors);

            // Triangle normals and neighbors.
            e = dMesh.TrianglesRefCounts.GetEnumerator();
            while (e.MoveNext())
            {
                int   iRef      = (int)e.Current;
                int[] nb        = dMesh.GetTriNeighbourTris(iRef).array;
                int[] neighbors = new int[3];

                for (int i = 0; i < 3; i++)
                {
                    neighbors[i] = (nb[i] != -1) ? triangleMap[nb[i]] : -1;
                }
                uMesh.AddTriangleInfo(triangleMap[iRef],
                                      (Vector3f)dMesh.GetTriNormal(iRef),
                                      neighbors);
            }

            return(uMesh);
        }
예제 #12
0
        // NO DOES NOT WORK. DOES NOT FIND EDGE SPANS THAT ARE IN PLANE BUT HAVE DIFFERENT NORMAL!
        // NEED TO COLLECT UP SPANS USING NORMAL HISTOGRAM NORMALS!
        // ALSO NEED TO ACTUALLY CHECK FOR COPLANARITY, NOT JUST SAME NORMAL!!

        Dictionary <Vector3d, List <EdgeSpan> > find_coplanar_span_sets(DMesh3 mesh, EdgeLoop loop)
        {
            double dot_thresh = 0.999;

            var span_sets = new Dictionary <Vector3d, List <EdgeSpan> >();

            int NV = loop.Vertices.Length;
            int NE = loop.Edges.Length;

            var edge_normals = new Vector3d[NE];

            for (int k = 0; k < NE; ++k)
            {
                edge_normals[k] = mesh.GetTriNormal(mesh.GetEdgeT(loop.Edges[k]).a);
            }

            // find coplanar verts
            // [RMS] this is wrong, if normals vary smoothly enough we will mark non-coplanar spans as coplanar
            bool[] vert_coplanar = new bool[NV];
            int    nc            = 0;

            for (int k = 0; k < NV; ++k)
            {
                int prev = (k == 0) ? NV - 1 : k - 1;
                if (edge_normals[k].Dot(ref edge_normals[prev]) > dot_thresh)
                {
                    vert_coplanar[k] = true;
                    nc++;
                }
            }
            if (nc < 2)
            {
                return(null);
            }

            int iStart = 0;

            while (vert_coplanar[iStart])
            {
                iStart++;
            }

            int iPrev = iStart;
            int iCur  = iStart + 1;

            while (iCur != iStart)
            {
                if (vert_coplanar[iCur] == false)
                {
                    iPrev = iCur;
                    iCur  = (iCur + 1) % NV;
                    continue;
                }

                var edges = new List <int>()
                {
                    loop.Edges[iPrev]
                };
                int span_start_idx = iCur;
                while (vert_coplanar[iCur])
                {
                    edges.Add(loop.Edges[iCur]);
                    iCur = (iCur + 1) % NV;
                }

                if (edges.Count > 1)
                {
                    Vector3d span_n = edge_normals[span_start_idx];
                    var      span   = EdgeSpan.FromEdges(mesh, edges);
                    span.CheckValidity();
                    foreach (var pair in span_sets)
                    {
                        if (pair.Key.Dot(ref span_n) > dot_thresh)
                        {
                            span_n = pair.Key;
                            break;
                        }
                    }
                    List <EdgeSpan> found;
                    if (span_sets.TryGetValue(span_n, out found) == false)
                    {
                        span_sets[span_n] = new List <EdgeSpan>()
                        {
                            span
                        };
                    }
                    else
                    {
                        found.Add(span);
                    }
                }
            }



            return(span_sets);
        }
예제 #13
0
        public virtual bool Apply()
        {
            DMesh3 testAgainstMesh = Mesh;

            if (InsideMode == CalculationMode.RayParity)
            {
                MeshBoundaryLoops loops = new MeshBoundaryLoops(testAgainstMesh);
                if (loops.Count > 0)
                {
                    testAgainstMesh = new DMesh3(Mesh);
                    foreach (var loop in loops)
                    {
                        if (Cancelled())
                        {
                            return(false);
                        }
                        SimpleHoleFiller filler = new SimpleHoleFiller(testAgainstMesh, loop);
                        filler.Fill();
                    }
                }
            }

            DMeshAABBTree3 spatial = (Spatial != null && testAgainstMesh == Mesh) ?
                                     Spatial : new DMeshAABBTree3(testAgainstMesh, true);

            if (InsideMode == CalculationMode.AnalyticWindingNumber)
            {
                spatial.WindingNumber(Vector3d.Zero);
            }
            else if (InsideMode == CalculationMode.FastWindingNumber)
            {
                spatial.FastWindingNumber(Vector3d.Zero);
            }

            if (Cancelled())
            {
                return(false);
            }

            // ray directions
            List <Vector3d> ray_dirs = null; int NR = 0;

            if (InsideMode == CalculationMode.SimpleOcclusionTest)
            {
                ray_dirs = new List <Vector3d>();
                ray_dirs.Add(Vector3d.AxisX); ray_dirs.Add(-Vector3d.AxisX);
                ray_dirs.Add(Vector3d.AxisY); ray_dirs.Add(-Vector3d.AxisY);
                ray_dirs.Add(Vector3d.AxisZ); ray_dirs.Add(-Vector3d.AxisZ);
                NR = ray_dirs.Count;
            }

            Func <Vector3d, bool> isOccludedF = (pt) => {
                if (InsideMode == CalculationMode.RayParity)
                {
                    return(spatial.IsInside(pt));
                }
                else if (InsideMode == CalculationMode.AnalyticWindingNumber)
                {
                    return(spatial.WindingNumber(pt) > WindingIsoValue);
                }
                else if (InsideMode == CalculationMode.FastWindingNumber)
                {
                    return(spatial.FastWindingNumber(pt) > WindingIsoValue);
                }
                else
                {
                    for (int k = 0; k < NR; ++k)
                    {
                        int hit_tid = spatial.FindNearestHitTriangle(new Ray3d(pt, ray_dirs[k]));
                        if (hit_tid == DMesh3.InvalidID)
                        {
                            return(false);
                        }
                    }
                    return(true);
                }
            };

            bool cancel = false;

            BitArray vertices = null;

            if (PerVertex)
            {
                vertices = new BitArray(Mesh.MaxVertexID);

                MeshNormals normals = null;
                if (Mesh.HasVertexNormals == false)
                {
                    normals = new MeshNormals(Mesh);
                    normals.Compute();
                }

                gParallel.ForEach(Mesh.VertexIndices(), (vid) => {
                    if (cancel)
                    {
                        return;
                    }
                    if (vid % 10 == 0)
                    {
                        cancel = Cancelled();
                    }

                    Vector3d c    = Mesh.GetVertex(vid);
                    Vector3d n    = (normals == null) ? Mesh.GetVertexNormal(vid) : normals[vid];
                    c            += n * NormalOffset;
                    vertices[vid] = isOccludedF(c);
                });
            }
            if (Cancelled())
            {
                return(false);
            }

            RemovedT = new List <int>();
            SpinLock removeLock = new SpinLock();

            gParallel.ForEach(Mesh.TriangleIndices(), (tid) => {
                if (cancel)
                {
                    return;
                }
                if (tid % 10 == 0)
                {
                    cancel = Cancelled();
                }

                bool inside = false;
                if (PerVertex)
                {
                    Index3i tri = Mesh.GetTriangle(tid);
                    inside      = vertices[tri.a] || vertices[tri.b] || vertices[tri.c];
                }
                else
                {
                    Vector3d c = Mesh.GetTriCentroid(tid);
                    Vector3d n = Mesh.GetTriNormal(tid);
                    c         += n * NormalOffset;
                    inside     = isOccludedF(c);
                }

                if (inside)
                {
                    bool taken = false;
                    removeLock.Enter(ref taken);
                    RemovedT.Add(tid);
                    removeLock.Exit();
                }
            });

            if (Cancelled())
            {
                return(false);
            }

            if (RemovedT.Count > 0)
            {
                MeshEditor editor = new MeshEditor(Mesh);
                bool       bOK    = editor.RemoveTriangles(RemovedT, true);
                RemoveFailed = (bOK == false);
            }

            return(true);
        }
예제 #14
0
        public static void test_AABBTree_RayHit(int meshCase = 8)
        {
            DMesh3         mesh = MakeSpatialTestMesh(meshCase);
            DMeshAABBTree3 tree = new DMeshAABBTree3(mesh);

            tree.Build();
            tree.TestCoverage();

            AxisAlignedBox3d bounds = mesh.CachedBounds;
            Vector3d         ext    = bounds.Extents;
            Vector3d         c      = bounds.Center;
            double           r      = bounds.DiagonalLength / 4;

            Random rand = new Random(316136327);


            tree.FindNearestHitTriangle(
                new Ray3f(100 * Vector3f.One, Vector3f.One));


            // test rays out from center of box, and rays in towards it
            // (should all hit for standard test cases)
            int hits = 0;
            int N    = (meshCase > 7) ? 1000 : 10000;

#if true
            for (int ii = 0; ii < N; ++ii)
            {
                if (ii % 100 == 0)
                {
                    System.Console.WriteLine("{0} / {1}", ii, N);
                }

                Vector3d p   = (ii < N / 2) ? c : c + 2 * r * rand.Direction();
                Vector3d d   = (ii < N / 2) ? rand.Direction() : (c - p).Normalized;
                Ray3d    ray = new Ray3d(p, d);

                int tNearBrute = MeshQueries.FindHitTriangle_LinearSearch(mesh, ray);
                int tNearTree  = tree.FindNearestHitTriangle(ray);

                //System.Console.WriteLine("{0} - {1}", tNearBrute, tree.TRI_TEST_COUNT);

                if (tNearBrute == DMesh3.InvalidID)
                {
                    Debug.Assert(tNearBrute == tNearTree);
                    continue;
                }
                ++hits;

                IntrRay3Triangle3 qBrute = MeshQueries.TriangleIntersection(mesh, tNearBrute, ray);
                IntrRay3Triangle3 qTree  = MeshQueries.TriangleIntersection(mesh, tNearTree, ray);

                double dotBrute = mesh.GetTriNormal(tNearBrute).Dot(ray.Direction);
                double dotTree  = mesh.GetTriNormal(tNearTree).Dot(ray.Direction);

                Debug.Assert(Math.Abs(qBrute.RayParameter - qTree.RayParameter) < MathUtil.ZeroTolerance);
            }
            Debug.Assert(hits == N);
            System.Console.WriteLine("in/out rays: {0} hits out of {1} rays", hits, N);
#endif



            // random rays
            hits = 0;
            for (int ii = 0; ii < N; ++ii)
            {
                if (ii % 100 == 0)
                {
                    System.Console.WriteLine("{0} / {1}", ii, N);
                }

                Vector3d target = c + rand.PointInRange(r);
                Vector3d o      = c + rand.PointInRange(10 * r);
                Ray3d    ray    = new Ray3d(o, (target - o).Normalized);

                int tNearBrute = MeshQueries.FindHitTriangle_LinearSearch(mesh, ray);
                int tNearTree  = tree.FindNearestHitTriangle(ray);

                //System.Console.WriteLine("{0} - {1}", tNearBrute, tree.TRI_TEST_COUNT);

                if (tNearBrute == DMesh3.InvalidID)
                {
                    Debug.Assert(tNearBrute == tNearTree);
                    continue;
                }
                ++hits;

                IntrRay3Triangle3 qBrute = MeshQueries.TriangleIntersection(mesh, tNearBrute, ray);
                IntrRay3Triangle3 qTree  = MeshQueries.TriangleIntersection(mesh, tNearTree, ray);

                double dotBrute = mesh.GetTriNormal(tNearBrute).Dot(ray.Direction);
                double dotTree  = mesh.GetTriNormal(tNearTree).Dot(ray.Direction);

                Debug.Assert(Math.Abs(qBrute.RayParameter - qTree.RayParameter) < MathUtil.ZeroTolerance);
            }

            System.Console.WriteLine("random rays: hit {0} of {1} rays", hits, N);
        }