Check if an stl file may contain two models - c#

An stl file may contain 2 3D models. Is there any way I can detect if there are 2 or more models stored in one stl file?
In my current code, it can detect that there are 2 models in the example, but there are instances that it detects a lot of model even though it only has one.
The Triangle class structure has Vertices that contains 3 points (x, y, z)..
Sample STL File:
EDIT: Using #Gebb's answer this is how I implemented it:
private int GetNumberOfModels(List<TopoVertex> vertices)
{
Vertex[][] triangles = new Vertex[vertices.Count() / 3][];
int vertIdx = 0;
for(int i = 0; i < vertices.Count() / 3; i++)
{
Vertex v1 = new Vertex(vertices[vertIdx].pos.x, vertices[vertIdx].pos.y, vertices[vertIdx].pos.z);
Vertex v2 = new Vertex(vertices[vertIdx + 1].pos.x, vertices[vertIdx + 1].pos.y, vertices[vertIdx + 1].pos.z);
Vertex v3 = new Vertex(vertices[vertIdx + 2].pos.x, vertices[vertIdx + 2].pos.y, vertices[vertIdx + 2].pos.z);
triangles[i] = new Vertex[] { v1, v2, v3 };
vertIdx += 3;
}
var uniqueVertices = new HashSet<Vertex>(triangles.SelectMany(t => t));
int vertexCount = uniqueVertices.Count;
// The DisjointUnionSets class works with integers, so we need a map from vertex
// to integer (its id).
Dictionary<Vertex, int> indexedVertices = uniqueVertices
.Zip(
Enumerable.Range(0, vertexCount),
(v, i) => new { v, i })
.ToDictionary(vi => vi.v, vi => vi.i);
int[][] indexedTriangles =
triangles
.Select(t => t.Select(v => indexedVertices[v]).ToArray())
.ToArray();
var du = new XYZ.view.wpf.DisjointUnionSets(vertexCount);
// Iterate over the "triangles" consisting of vertex ids.
foreach (int[] triangle in indexedTriangles)
{
int vertex0 = triangle[0];
// Mark 0-th vertexes connected component as connected to those of all other vertices.
foreach (int v in triangle.Skip(1))
{
du.Union(vertex0, v);
}
}
var connectedComponents =
new HashSet<int>(Enumerable.Range(0, vertexCount).Select(x => du.Find(x)));
return connectedComponents.Count;
}
In some cases, it produces the correct output, but for the example image above, it outputs 3 instead of 2. I am now trying to optimize the snippet #Gebb gave to use float values since I believe that the floating points are necessary to the comparisons. Does anyone have a way to do that as well? Maybe I need another perspective.

You could do this by representing vertices and connections between them as a graph and finding the number of connected components of the graph with the help of the Disjoint-set data structure.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Vertex = System.ValueTuple<double,double,double>;
namespace UnionFindSample
{
internal class DisjointUnionSets
{
private readonly int _n;
private readonly int[] _rank;
private readonly int[] _parent;
public DisjointUnionSets(int n)
{
_rank = new int[n];
_parent = new int[n];
_n = n;
MakeSet();
}
// Creates n sets with single item in each
public void MakeSet()
{
for (var i = 0; i < _n; i++)
// Initially, all elements are in
// their own set.
_parent[i] = i;
}
// Finds the representative of the set
// that x is an element of.
public int Find(int x)
{
if (_parent[x] != x)
{
// if x is not the parent of itself, then x is not the representative of
// his set.
// We do the path compression by moving x’s node directly under the representative
// of this set.
_parent[x] = Find(_parent[x]);
}
return _parent[x];
}
// Unites the set that includes x and
// the set that includes x
public void Union(int x, int y)
{
// Find representatives of two sets.
int xRoot = Find(x), yRoot = Find(y);
// Elements are in the same set, no need to unite anything.
if (xRoot == yRoot)
{
return;
}
if (_rank[xRoot] < _rank[yRoot])
{
// Then move x under y so that depth of tree remains equal to _rank[yRoot].
_parent[xRoot] = yRoot;
}
else if (_rank[yRoot] < _rank[xRoot])
{
// Then move y under x so that depth of tree remains equal to _rank[xRoot].
_parent[yRoot] = xRoot;
}
else
{
// if ranks are the same
// then move y under x (doesn't matter which one goes where).
_parent[yRoot] = xRoot;
// And increment the result tree's
// rank by 1
_rank[xRoot] = _rank[xRoot] + 1;
}
}
}
internal class Program
{
private static void Main(string[] args)
{
string file = args[0];
Vertex[][] triangles = ParseStl(file);
var uniqueVertices = new HashSet<Vertex>(triangles.SelectMany(t => t));
int vertexCount = uniqueVertices.Count;
// The DisjointUnionSets class works with integers, so we need a map from vertex
// to integer (its id).
Dictionary<Vertex, int> indexedVertices = uniqueVertices
.Zip(
Enumerable.Range(0, vertexCount),
(v, i) => new {v, i})
.ToDictionary(vi => vi.v, vi => vi.i);
int[][] indexedTriangles =
triangles
.Select(t => t.Select(v => indexedVertices[v]).ToArray())
.ToArray();
var du = new DisjointUnionSets(vertexCount);
// Iterate over the "triangles" consisting of vertex ids.
foreach (int[] triangle in indexedTriangles)
{
int vertex0 = triangle[0];
// Mark 0-th vertexes connected component as connected to those of all other vertices.
foreach (int v in triangle.Skip(1))
{
du.Union(vertex0, v);
}
}
var connectedComponents =
new HashSet<int>(Enumerable.Range(0, vertexCount).Select(x => du.Find(x)));
int count = connectedComponents.Count;
Console.WriteLine($"Number of connected components: {count}.");
var groups = triangles.GroupBy(t => du.Find(indexedVertices[t[0]]));
foreach (IGrouping<int, Vertex[]> g in groups)
{
Console.WriteLine($"Group id={g.Key}:");
foreach (Vertex[] triangle in g)
{
string tr = string.Join(' ', triangle);
Console.WriteLine($"\t{tr}");
}
}
}
private static Regex _triangleStart = new Regex(#"^\s+outer loop");
private static Regex _triangleEnd = new Regex(#"^\s+endloop");
private static Regex _vertex = new Regex(#"^\s+vertex\s+(\S+)\s+(\S+)\s+(\S+)");
private static Vertex[][] ParseStl(string file)
{
double ParseCoordinate(GroupCollection gs, int i) =>
double.Parse(gs[i].Captures[0].Value, CultureInfo.InvariantCulture);
var triangles = new List<Vertex[]>();
bool isInsideTriangle = false;
List<Vertex> triangle = new List<Vertex>();
foreach (string line in File.ReadAllLines(file))
{
if (isInsideTriangle)
{
if (_triangleEnd.IsMatch(line))
{
isInsideTriangle = false;
triangles.Add(triangle.ToArray());
triangle = new List<Vertex>();
continue;
}
Match vMatch = _vertex.Match(line);
if (vMatch.Success)
{
double x1 = ParseCoordinate(vMatch.Groups, 1);
double x2 = ParseCoordinate(vMatch.Groups, 2);
double x3 = ParseCoordinate(vMatch.Groups, 3);
triangle.Add((x1, x2, x3));
}
}
else
{
if (_triangleStart.IsMatch(line))
{
isInsideTriangle = true;
}
}
}
return triangles.ToArray();
}
}
}
I'm also using the fact that System.ValueTuple implements Equals and GetHashCode in an appropriate way, so we can easily compare vertices (this is used implicitly by HashSet) and use them as keys in a dictionary.

Related

Can I find wins and draws faster

I have a time sensitive application where I get the rank of each object and work out who won or tied for first. With my current algorithm I get some lag in my program so am hoping to speed it up. In this demo version I just get a random number for the ranks as the real rank calc requires too much code to post.
Can anybody suggest how I might do this more quickly as the Update method can get called tens of thousands of times for a single run?
class Program
{
static int runOuts = 0;
static Dictionary<RankedObject, int> dictionaryWins = new Dictionary<RankedObject, int>();
static Dictionary<RankedObject, int> dictionaryDraws = new Dictionary<RankedObject, int>();
static Random random = new Random();
static List<RankedObject> rankedObjects = new List<RankedObject>();
static void Main(string[] args)
{
// create some objects for demo
rankedObjects.Add(new RankedObject());
rankedObjects.Add(new RankedObject());
rankedObjects.Add(new RankedObject());
// add each object to the win/draw dictionaries so we can count wins/draws for each
foreach (RankedObject rankedObj in rankedObjects)
{
dictionaryWins.Add(rankedObj, 0);
dictionaryDraws.Add(rankedObj, 0);
}
// calculate wins/draws many times
for (int i = 0; i < 10000; i++)
{
Update();
}
// set equity results in each ranked combo and print results
foreach (RankedObject rankedCombo in rankedObjects)
{
rankedCombo.WinEquity = dictionaryWins[rankedCombo] / (double)runOuts;
rankedCombo.DrawEquity = dictionaryDraws[rankedCombo] / (double)runOuts;
Console.WriteLine(rankedCombo);
}
}
private static void Update()
{
// keep a list of ranks for each object so we can get max/winning value easily
List<int> ranks = new List<int>();
// get a rank for each object
foreach (RankedObject rankedCombo in rankedObjects)
{
int rank = random.Next(3);
ranks.Add(rank);
rankedCombo.Rank = rank;
}
// get the maximum rank and how many times it occurs, so we know if their is a tie for the win
int max = ranks.Max();
int maxOccurences = 0;
foreach (int i in ranks)
{
if (i == max)
{
maxOccurences++;
}
}
// loop over each object to record if the object won or tied for win
foreach (RankedObject rankedObj in rankedObjects)
{
if (rankedObj.Rank == max && maxOccurences == 1) // current rankedObj was winner
{
dictionaryWins[rankedObj] += 1;
}
else if (rankedObj.Rank == max && maxOccurences > 1) // current rankedObj Tied for win
{
dictionaryDraws[rankedObj] += 1;
}
}
runOuts++;
}
}
class RankedObject
{
int rank;
double winEquity;
double drawEquity;
public int Rank { get => rank; set => rank = value; }
public double WinEquity { get => winEquity; set => winEquity = value; }
public double DrawEquity { get => drawEquity; set => drawEquity = value; }
public override string ToString()
{
return "Win Equity: " + winEquity + ", Draw Equity: " + drawEquity;
}
}

How to do radial collections querying in C#?

I have lots of points (with .X and .Y coordinates) in a 2d space For example a KDTree or some other IEnumerable<AnyXYPointImpl> collection. I want to do query that is not square but Circle\Radius based:
What I want is simple: having a point P, direction D and half angle a we want to get all points in the collection area covered by a sector formed by them.
So how to do such radial collections querying in C# with LINQ?
Here is a solution:
Example usage:
var field = new List<Vector2>() {
new Vector2(-1,1), new Vector2(1,1),
new Vector2(-1,-1), new Vector2(1,-1)
};
Console.WriteLine(string.Join("; ", field.IsInSector(new Vector2(0,0), new Vector2(2,2), 45.0)));
Console.WriteLine(string.Join("; ", field.IsInSector(new Vector2(0,0), new Vector2(2,-2), 45.0)));
Console.WriteLine(string.Join("; ", field.IsInSector(new Vector2(0,0), new Vector2(2,0), 90.0)));
Main logic to check point is it in sector via IEnumerable extension:
public static class Extenctions {
public static IEnumerable<IXYPoint> IsInSector(this IEnumerable<IXYPoint> source, Vector2 pointToCheck, Vector2 direction, double halfAngle)
{
if(!source.Any() || pointToCheck == null || halfAngle <= 0)
return new IXYPoint[0];
var dirVector = new Vector2() {
X = direction.X - pointToCheck.X,
Y = direction.Y - pointToCheck.Y
};
var radius = Distance(direction, pointToCheck);
var result = new List<IXYPoint>();
foreach(var p in source)
{
if(Distance(p, pointToCheck) > radius){ // check is point in circle
continue;
}
if(IsPointInSector(dirVector, p, halfAngle)) // main check
result.Add(p);
}
return result;
}
private static double Distance(IXYPoint from, IXYPoint to)
{
return Math.Sqrt(Math.Pow(to.X - from.X,2) + Math.Pow(from.Y-to.Y,2));
}
private static bool IsPointInSector(IXYPoint directionVector, IXYPoint pointToCheck, double halfAngle)
{
var rq0 = Math.Pow(directionVector.X,2) + Math.Pow(directionVector.Y,2);
var rq = Math.Pow(pointToCheck.X,2) + Math.Pow(pointToCheck.Y,2);
return rq0 >= rq && (directionVector.X*pointToCheck.X + directionVector.Y*pointToCheck.Y)/Math.Sqrt(rq0*rq) >= Math.Cos(halfAngle/180.0*Math.PI);
}
}
Here's a non-LINQ solution.
var filteredPoints = new List<Point2D>();
foreach (var point in points)
if (point.IsInSector(origin, d, a))
filteredPoints.Add(point);

How to make a struct component a variable?

Not sure I am asking this right or this even possible.
I feel to explain my question it is best to ask right in the code at the relevant places so please see my comments in the snippet below.
I wonder how to achieve this without building a new list of values for each I iteration. I feel this should not be necessary.
The bigger picture of this loop is to plot individual dimensions of 3D points to three new 2D plots of these. Hope that makes sense.
for (int i = 0; i < 3; i++) // 3 iterations (X,Y;Z)
{
// what here? how to make the data component of Vector3D a variable
for (int k = 0; k <= Points.Count - 1; k++)
{
Vector2D TL = new Vector2D();
TL.x = ((1 / (float)FrameCount.Sum()) * k);
TL.y = Points[k].x; // on i = 0 want Points[k].x
// on i = 1 want Points[k].y
// on i = 2 want Points[k].z
TimelinePoints.Add(TL); // just collect to a flat list for now
}
}
One option would be to have an array of extraction functions that you could apply to points. You can then use the LINQ Select overload that accepts a Func<TInput, int, TOutput> to generate a sequence of the values you want to add, and add it to TimeLinePoints that way.
// Possibly store this in a static variable somewhere
var extractors = new Func<Point, float>[] { p => p.x, p => p.y, p => p.z };
// Just do this once; store the result as a float for simplicity when dividing later.
float frameSum = FrameCount.Sum();
foreach (var extractor in extractors)
{
TimeLinePoints.AddRange(Points.Select((point, index) =>
new Vector2D(index / frameSum, extractor(point));
}
(You could go even further using SelectMany potentially, but that's where I'd start...)
A considerably more pedestrian approach compared to Jon Skeet's answer would be to modify the Point struct to include an indexer, assuming that is an option:
public struct Point
{
float x;
float y;
float z;
public float this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
default:
throw new IndexOutOfRangeException();
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
default:
throw new IndexOutOfRangeException();
}
}
}
}
Then you could assign the proper field according to the value of your loop counter, like so:
for (int k = 0; k < Points.Count; k++)
{
Vector2D TL = new Vector2D();
TL.x = ((1 / (float)FrameCount.Sum()) * k);
TL.y = Points[k][i];
TimelinePoints.Add(TL); // just collect to a flat list for now
}
The alternate way is to use Linq to archive this:
Points
//foreach point create 3 Vector2D with X, Y and Z coordinate
.SelectMany((p, index) => new []
{
new Vector2D(index / frameSum, p.X),
new Vector2D(index / frameSum, p.Y),
new Vector2D(index / frameSum, p.Z)
})
//unfurl IEnumerable<IEnumerable<Vector2D>> to IEnumerable<Vector2D>
.Select(v => v)
.ToList();

Implementing a fast DBSCAN in C#

I tried to implement a DBSCAN in C# using kd-trees. I followed the implementation from:
http://www.yzuzun.com/2015/07/dbscan-clustering-algorithm-and-c-implementation/
public class DBSCANAlgorithm
{
private readonly Func<PointD, PointD, double> _metricFunc;
public DBSCANAlgorithm(Func<PointD, PointD, double> metricFunc)
{
_metricFunc = metricFunc;
}
public void ComputeClusterDbscan(ScanPoint[] allPoints, double epsilon, int minPts, out HashSet<ScanPoint[]> clusters)
{
clusters = null;
var allPointsDbscan = allPoints.Select(x => new DbscanPoint(x)).ToArray();
var tree = new KDTree.KDTree<DbscanPoint>(2);
for (var i = 0; i < allPointsDbscan.Length; ++i)
{
tree.AddPoint(new double[] { allPointsDbscan[i].ClusterPoint.point.X, allPointsDbscan[i].ClusterPoint.point.Y }, allPointsDbscan[i]);
}
var C = 0;
for (int i = 0; i < allPointsDbscan.Length; i++)
{
var p = allPointsDbscan[i];
if (p.IsVisited)
continue;
p.IsVisited = true;
DbscanPoint[] neighborPts = null;
RegionQuery(tree, p.ClusterPoint.point, epsilon, out neighborPts);
if (neighborPts.Length < minPts)
p.ClusterId = (int)ClusterIds.NOISE;
else
{
C++;
ExpandCluster(tree, p, neighborPts, C, epsilon, minPts);
}
}
clusters = new HashSet<ScanPoint[]>(
allPointsDbscan
.Where(x => x.ClusterId > 0)
.GroupBy(x => x.ClusterId)
.Select(x => x.Select(y => y.ClusterPoint).ToArray())
);
return;
}
private void ExpandCluster(KDTree.KDTree<DbscanPoint> tree, DbscanPoint p, DbscanPoint[] neighborPts, int c, double epsilon, int minPts)
{
p.ClusterId = c;
for (int i = 0; i < neighborPts.Length; i++)
{
var pn = neighborPts[i];
if (!pn.IsVisited)
{
pn.IsVisited = true;
DbscanPoint[] neighborPts2 = null;
RegionQuery(tree, pn.ClusterPoint.point, epsilon, out neighborPts2);
if (neighborPts2.Length >= minPts)
{
neighborPts = neighborPts.Union(neighborPts2).ToArray();
}
}
if (pn.ClusterId == (int)ClusterIds.UNCLASSIFIED)
pn.ClusterId = c;
}
}
private void RegionQuery(KDTree.KDTree<DbscanPoint> tree, PointD p, double epsilon, out DbscanPoint[] neighborPts)
{
int totalCount = 0;
var pIter = tree.NearestNeighbors(new double[] { p.X, p.Y }, 10, epsilon);
while (pIter.MoveNext())
{
totalCount++;
}
neighborPts = new DbscanPoint[totalCount];
int currCount = 0;
pIter.Reset();
while (pIter.MoveNext())
{
neighborPts[currCount] = pIter.Current;
currCount++;
}
return;
}
}
//Dbscan clustering identifiers
public enum ClusterIds
{
UNCLASSIFIED = 0,
NOISE = -1
}
//Point container for Dbscan clustering
public class DbscanPoint
{
public bool IsVisited;
public ScanPoint ClusterPoint;
public int ClusterId;
public DbscanPoint(ScanPoint point)
{
ClusterPoint = point;
IsVisited = false;
ClusterId = (int)ClusterIds.UNCLASSIFIED;
}
}
, and modifying the regionQuery(P, eps) to invoke the nearest neighbour function of a kd-tree. To do so, I used the kd-sharp library for C#, which is one of the fastest kd-tree implementations out there.
However, when given a dataset of about 20000 2d points, its performance is in the region of 40s, as compared to the scikit-learn python implementation of DBSCAN, which given the same parameters, takes about 2s.
Since this algorithm is for a C# program that I am writing, I am stuck using C#. As such, I would like to find out what am I still missing out in terms of optimization of the algorithm?

Creating a List of Arrays in C#?

So I'm working on a game that uses a coordinate system, and I want to populate the map with a certain number of trees. The way I'm doing it (and it may not be the best way) is picking a random set of coordinates, checking to see if those coordinates are in the list of locations with trees in them, and if not adding the tree to the map, and adding those coordinates to the list. My instinct was to store the coordinates as an array, however I can't seem to figure out the syntax for it. Here's what I have:
int boardWidth = 10;
int boardHeight = 10;
int numTrees = 75;
List<int[]> hasTree = new List<int[]>();
public Transform tree;
Transform[,] SetTrees(Transform[,] map) {
int treex = Random.Range(0,boardWidth-1);
int treey = Random.Range(0,boardHeight-1);
int[] treeCoords = new int[] { treex,treey };
int treeCount = 0;
while(treeCount < numTrees){
if (hasTree.Contains(treeCoords)){
treex = Random.Range(0,boardWidth-1);
treey = Random.Range(0,boardHeight-1);
}
else{
map[treex,treey] = (Transform)Instantiate(tree, new Vector3(i*tileWidth, 10, j*tileHeight), Quaternion.AngleAxis(90, Vector3.left));
hasTree.Add(treeCoords);
treex = Random.Range(0,boardWidth-1);
treey = Random.Range(0,boardHeight-1);
treeCount++;
}
}
return(map);
}
Any thoughts?
If I were you I'd try something like this:
int boardWidth = 10;
int boardHeight = 10;
int numTrees = 75;
var rnd = new Random();
var query =
from x in Enumerable.Range(0, boardWidth)
from y in Enumerable.Range(0, boardHeight)
orderby rnd.NextDouble()
select new { x, y };
var board = new bool[boardWidth, boardHeight];
foreach (var pair in query.Take(numTrees))
{
board[pair.x, pair.y] = true;
}
Keep It Simple Silly:
Transform[,] SetTrees(Transform[,] map) {
for(int treeCount = 0; treeCount < numTrees; treeCount++){
int treex = Random.Range(0,boardWidth-1);
int treey = Random.Range(0,boardHeight-1);
map[treex,treey] = new TreeTransform(treex, treey}
}
return(map);
}
Bury the details of Tree creation in its constructor TreeTransform, where it belongs.
Who cares about a creation ordering of the trees on the board? it has no use.
There is no reason for the number of trees to be exact, so just ignore duplicates.
Simplify the code then it might be easier to determine your best course of action.
I simplify by breaking down the problem until its so simple I can't really see how not to do it !!!
I am guessing how some of this code works here but I think you want something like this ...
Transform[,] SetTrees(Transform[,] map) {
for (int i = 0; i < numTrees; i++){
if(!AddTreeTo(map)){
--i;
}
}
return(map);
}
bool AddTreeToMap(Transform[,] map)
{
int[] treeCoord = GetRandomCoord(map.Width, map.Height);
if (!map[treeCoord[0],treeCoord[1]].HasTree()){
map[treex,treey] = (Transform)Instantiate(tree, new Vector3(i*tileWidth, 10, j*tileHeight), Quaternion.AngleAxis(90, Vector3.left));
map[treeCoord[0],treeCoord[1]].Add(treeCoords);
return true;
}
return false;
}
int[] GetRandomTreeCoord(int maxX, int maxY)
{
int treex = Random.Range(0,maxX-1);
int treey = Random.Range(0,maxY-1);
int[] treeCoords = new int[] { treex,treey };
return treeCoords;
}

Categories

Resources