Notifications
Clear all

[Closed] Mini-challenge. Get ordered poly edge loops

I’ve been looking into solving the problem from post #10, and similar. Below is the description of some possible algorithms, absolutely untested, just theory.

As you have not specified what the task should exactly be, as I mentioned earlier, there are two solutions for this specific problem I see. And both of them are valid, depending on what your goal is.

For instance, if you wanted to build a race track out of the selected edges, with one solution you could have a track with bridges (or tunnels) and with the other solution you could have a flat track with no intersections.

TASKS

FIND THE EDGES GROUPS (EDGES CONNECTED BY VERTS)
#. Split the selected edges into groups of “connected” edges

From now on we can work with individual groups.

BUILD THE BRIDGED (NATURAL FLOW) PATH

Find the Head/Tail of the group

If it is an open path find either the “Head” or the “Tail” of it.

If it is a closed path pick any two connected edges that do not intersect with any others and make one the “Head” and the other the “Tail”

Find the “Natural Flow” of this group.

Starting with the “Head” edge, call it ‘Current’ and get all the connected edges from the group.

If there are multiple edges connected, pick the edge that does not share any face with the ‘Current’ edge.

Continue until you hit the “Tail”

You now have one path of edges from Head to Tail.

BUILD THE “UNBRIDGED” PATH

First build the “Natural Flow” path

Now, starting with the “Head” edge from the path, keep adding the edges to a new array and at every intersection do:

# If the intersection is an odd number then from all the connected edges to the 'Current' edge, discard the one from the path and from the remaining ones pick the one with lower index.
# Continue with this edge and find the next connected edge, adding them to the new array, until you hit a second intersection.
# If the intersection is an even number pick the edge with higher index instead of lower.
# continue until you reach the "Tail".

Neither of these algorithms requires to know where the next edge goes, you just start the array and keep appending them.
There are many ways to optimize them, although in C++ a brute force approach should be very fast.

Unlike the build-in “create Shape from Selection”, these algorithms should give you more consistent results, in theory

Here is a very rudimentary code as proof of concept.

It only works with the provided samples, and similar ones.
Does not work with closed paths or paths with more than 2 Head/Tails or multiple paths.

try destroydialog ::RO_CONNECTED_EDGES catch()

rollout RO_CONNECTED_EDGES "Connected Edges" width:272 height:112
(
	button btn1 "Build Scene 1" pos:[8,8] width:104 height:32
	button btn2 "Build Scene 1" pos:[8,40] width:104 height:32
	button btn3 "Build Scene 2" pos:[8,72] width:104 height:32
	button btn4 "Find Bridged Path" pos:[128,8] width:136 height:32
	button btn5 "Find Unbridged Path" pos:[128,40] width:136 height:32
	
	local edges = ()
	local node = ()
	
	fn FindHeadTail mEdges =
	(
		verts = #{}
		for j in mEdges do
		(
			v = polyop.getedgeverts node j
			verts[v[1]] = not verts[v[1]]
			verts[v[2]] = not verts[v[2]]
		)
		return ((polyop.getedgesusingvert node verts) * mEdges) as array
	)
	
	fn BuildBridgedPath mEdges mStart =
	(
		current = mStart
		
		result = #()
		append result current
		
		prevVerts = #{}
		
		for j = 1 to mEdges.numberset-1 do
		(
			currentFaces = polyop.getfacesusingedge node current
			currentVerts = (polyop.getedgeverts node current) as bitarray
			neigthbors = ((polyop.getedgesusingvert node currentVerts) * mEdges)
			neigthbors -= result as bitarray
			neigthbors -= polyop.getedgesusingvert node prevVerts
			neigthbors[current] = false

			if neigthbors.numberset > 1 then
			(
				for k in neigthbors do
				(
					nfaces = polyop.getfacesusingedge node k
					shared = nfaces * currentFaces
					if shared.isempty do
					(
						append result k
						current = k
					)
				)
			)else(
				append result (neigthbors as array)[1]
				current = (neigthbors as array)[1]
			)
			prevVerts = currentVerts
		)
		return result
	)
	
	fn BuildUnbridgedPath mEdges mBridgedPath =
	(
		current = mBridgedPath[1]
		
		result = #()
		append result current
		
		prevVerts = #{}
		intersections = 0
		
		for j = 1 to mEdges.numberset-1 do
		(
			currentFaces = polyop.getfacesusingedge node current
			currentVerts = (polyop.getedgeverts node current) as bitarray
			neigthbors = ((polyop.getedgesusingvert node currentVerts) * mEdges)
			neigthbors -= result as bitarray
			neigthbors -= polyop.getedgesusingvert node prevVerts
			neigthbors[current] = false

			if neigthbors.numberset > 2 then
			(
				intersections += 1
				
				nedges = (polyop.getedgesusingface node currentFaces) * neigthbors
				neigthbors = nedges as array
				
				i1 = finditem mBridgedPath neigthbors[1]
				i2 = finditem mBridgedPath neigthbors[2]
				
				if mod intersections 2 == 1 then
				(
					if i1 < i2 then next = neigthbors[1] else next = neigthbors[2]
				)else(
					if i1 > i2 then next = neigthbors[1] else next = neigthbors[2]
				)

				append result next
				current = next
				
			)else if neigthbors.numberset <= 2 then (
				append result (neigthbors as array)[1]
				current = (neigthbors as array)[1]
			)
			
			prevVerts = currentVerts
			
		)
		
		return result
	)
	
	/* ============================================================================ */
	/* ============================================================================ */
	
	fn BuildTestScene n =
	(
		delete objects
		node = converttopoly (plane pos:[0,0,1] length:100 width:100 lengthsegs:9 widthsegs:9 isselected:on wirecolor:yellow)
		max modify mode
		subobjectlevel = 2
		
		edges = case n of
		(
			1: #{20, 40..43, 55, 57..60, 73, 77..78, 91..93, 95, 97..98, 109, 111..112, 127..129, 131..132, 147}
			2: #{5, 20, 31..34, 38, 40..41, 43..44, 51..52, 54, 56..57, 59, 61, 64, 69..70, 72..74, 76, 78, 81, 83, 87, 89, 92, 98, 100, 102, 108..112, 114, 116..121, 126, 128, 130, 135..136, 138, 145, 147..149, 153..154, 156..157, 164}
			3: #{14, 20, 23, 32, 34, 39..40, 42, 44..46, 50..51, 53..55, 59, 61, 63..66, 73..77, 80, 82..83, 89, 93..95, 97..99, 107, 109..113, 117..119, 126, 130..131, 133, 145..146, 148, 150, 152..153}
		)
		
		polyop.setedgeselection node edges
	)
	
	fn TracePath path =
	(
		done = #{}
		for j in path while not keyboard.escPressed do
		(
			done[j] = true
			polyop.setedgeselection node done
			completeredraw()
			sleep 0.2
		)
	)
	
	/* ============================================================================ */
	/* ============================================================================ */
	
	on btn1 pressed do BuildTestScene 1
	on btn2 pressed do BuildTestScene 2
	on btn3 pressed do BuildTestScene 3
		
	on btn4 pressed do
	(
		if node != undefined do
		(
			edges = polyop.getedgeselection node
			headTail = FindHeadTail edges
			bridgedPath = BuildBridgedPath edges headTail[1]
			TracePath bridgedPath
		)
	)
	
	on btn5 pressed do
	(
		if node != undefined do
		(
			edges = polyop.getedgeselection node
			headTail = FindHeadTail edges
			bridgedPath = BuildBridgedPath edges headTail[1]
			unbridgedPath = BuildUnbridgedPath edges bridgedPath
			TracePath unbridgedPath
		)
	)
)
createdialog RO_CONNECTED_EDGES

Yes, you are right. I can’t take advantage of triangularization the way I’ve done.
I’ll think a little more…

Here’s a new version that works for the three examples of closed-unconnected loops (and hope for all cases).
It takes 99ms instead of 90ms for the first example (I suppose 40ms in Jorge’s machine).
In fact, the same algorithm should work for meshes, taking care of duplicated edges.

(
	fn findLoops obj &loop_edge &loop_vert =
	(
		edges_b = polyop.getEdgeSelection obj
		edgeVerts_b  = polyop.getVertsUsingEdge obj edges_b
		edges = edges_b as array
		edgeVerts  = edgeVerts_b as array
		edgeVerts_check = #{1..edgeVerts.count}
		edgeVerts_index = edgeVerts_check as array
		
		fn compareFn val index valArray: =
		(
			arrayVal = valArray[index]
			case of (
				(val < arrayVal): -1
				(val > arrayVal): 1
				default: 0
			)
		)
		
		fn firstBit arr = 
		(
			local b
			for n in arr while (b = n; off) do ()
			b
		)
		
		data = #()
		for i = 1 to edges.count do
		(
			vv = polyop.getedgeverts obj edges[i]
			v1_index = bsearch vv[1] edgeVerts_index compareFn valArray:edgeVerts
			v2_index = bsearch vv[2] edgeVerts_index compareFn valArray:edgeVerts
			
			datatemp = #(edges[i], v2_index)
			if data[v1_index] == undefined then data[v1_index] = datatemp else (if data[v1_index][2] < v2_index then data[v1_index] += datatemp else data[v1_index] = datatemp + data[v1_index])
			datatemp = #(edges[i], v1_index)
			if data[v2_index] == undefined then data[v2_index] = datatemp else (if data[v2_index][2] < v1_index then data[v2_index] += datatemp else data[v2_index] = datatemp + data[v2_index])
		)
		
		loop_vert = #()
		loop_edge = #()
		first = firstbit edgeVerts_check
		k = 0	
		while not keyboard.escPressed and first != undefined do
		(
			k += 1
			edgeVerts_check[first] = off
			loop_vert[k] = #(edgeVerts[first])
			loop_edge[k] = #(data[first][3])
			next = data[first][4]
			pos = 3
			
			while not keyboard.escPressed and next != first do
			(
				edgeVerts_check[next] = off
				append loop_vert[k] edgeVerts[next]
				newnext = if edgeVerts_check[data[next][4]] then (pos = 3; data[next][4]) else (pos = 1; data[next][2])
				append loop_edge[k] data[next][pos]
				next = newnext
			)
			first = firstbit edgeVerts_check
		)
	)

	--- PolyTools3D's TEST ----
	delete objects
	obj = converttopoly (Torus segs:100 sides:25 radius1:50 radius2:10)
	obj.EditablePoly.SetSelection #Edge #{2}
	obj.EditablePoly.SelectEdgeRing()
	obj.EditablePoly.SelectEdgeLoop()
	
	/*
	delete objects
	obj = converttopoly (plane pos:[0,0,1] length:100 width:100 lengthsegs:9 widthsegs:9 wirecolor:yellow)
	polyop.setedgeselection obj #{34, 36, 38, 40, 42, 52, 55, 57, 59, 62, 71, 73, 76, 79, 81, 90, 92, 94..96, 98, 100, 109, 111..112, 114, 116..117, 119, 128..129, 131, 133, 135, 137..138}
	*/
		
	t1 = timestamp()
		loop_edge = #()
		loop_vert = #()
		findLoops obj &loop_edge &loop_vert
	t2 = timestamp(); format "Time= %
" (t2-t1)
	
	-- loop_edge[k] ==> holds the sorted edges for the number 'k' loop: polyop.setEdgeSelection obj loop_edge[k] to select the edge loop
	-- loop_vert[k] ==> holds the sorted verts for the number 'k' loop: polyop.setVertSelection obj loop_vert[k] to select the vert loop
)

P.S.: thanks to DenisT for the ‘firstbit’ function.

1 Reply
(@polytools3d)
Joined: 11 months ago

Posts: 0

For the cases I could test it works great and the speed is as you said on my end (39ms for the torus test).
I wonder if it could be modified to work with opened loops as well. For example, from the torus test remove one edge in each loop.

BTW…

something about a year ago someone told that it’s safe now to use exit, continue, break, return in loops.
i’ve tried to check… see the difference:

fn firstbit0 bits = 
(
	for b in bits do return b
)
fn firstbit1 bits = 
(
	for b in bits do exit with b
)
fn firstbit2 bits = 
(
	local n
	for b in bits while (n = b) < 0 do ()
	n
)

(
	t = timestamp()
	h = heapfree
	bb = #{23..67}
	for k=1 to 100000 do
	(
		firstbit0 bb		
		--firstbit1 bb		
		--firstbit2 bb		
	)

	format "time:% heap:%
" (timestamp() - t) (h - heapfree)
)


I’m just with it. Not easy. And performance will go down.

1 Reply
(@aaandres)
Joined: 11 months ago

Posts: 0

This new version seems to work in all cases, for paths of unconnected edges (closed or not).
Time for first Torus example (25 loops of 100 edges): 105ms (must be 43ms in your machine).
Again valid for meshes and polys (with few command changes).


(
	fn findLoops obj &loop_edge &loop_vert =
	(
		edges_b = polyop.getEdgeSelection obj
		edgeVerts_b  = polyop.getVertsUsingEdge obj edges_b
		edges = edges_b as array
		edgeVerts  = edgeVerts_b as array
		edgeVerts_check = #{1..edgeVerts.count}
		edgeVerts_index = edgeVerts_check as array
		
		fn compareFn val index valArray: =
		(
			arrayVal = valArray[index]
			case of (
				(val < arrayVal): -1
				(val > arrayVal): 1
				default: 0
			)
		)
		fn firstBit arr = 
		(
			local b
			for n in arr while (b = n; off) do ()
			b
		)
		fn reverseArray &arr =
		(
			arr = for i = arr.count to 1 by -1 collect arr[i]
		)
		
		data = #()
		for i = 1 to edges.count do
		(
			vv = polyop.getedgeverts obj edges[i]
			v1_index = bsearch vv[1] edgeVerts_index compareFn valArray:edgeVerts
			v2_index = bsearch vv[2] edgeVerts_index compareFn valArray:edgeVerts
			datatemp = #(edges[i], v2_index)
			if data[v1_index] == undefined then data[v1_index] = datatemp else (if data[v1_index][2] < v2_index then data[v1_index] += datatemp else data[v1_index] = datatemp + data[v1_index])
			datatemp = #(edges[i], v1_index)
			if data[v2_index] == undefined then data[v2_index] = datatemp else (if data[v2_index][2] < v1_index then data[v2_index] += datatemp else data[v2_index] = datatemp + data[v2_index])
		)
		
		loop_vert = #()
		loop_edge = #()
		closed = #()
		first = firstbit edgeVerts_check
		k = 0	
		while not keyboard.escPressed and first != undefined do
		(
			k += 1
			closed[k] = false
			edgeVerts_check[first] = off
			loop_vert[k] = #(edgeVerts[first])
			loop_edge[k] = #()
			next = data[first][data[first].count]
			prev = first
			pos = data[first].count - 1
			end = false
			
			while not keyboard.escPressed and not end do
			(
				edgeVerts_check[next] = off
				append loop_vert[k] edgeVerts[next]
				append loop_edge[k] data[prev][pos]
				prev = next
				dim = data[next].count
				used = true
				while used and dim > 1 do
				(
					used = not edgeVerts_check[data[next][dim]]
					if used do (dim -= 2)
				)
				if dim < 2 do (dim = 2; end = true)
				pos = dim - 1
				next = data[next][dim]
			)
			if next == first do (append loop_edge[k] data[prev][pos]; closed[k] = true)
			if loop_vert[k].count == 2 do (closed[k] = false; deleteItem loop_edge[k] 2)	--	Isolated Edges
			
			if not closed[k] and data[first].count > 2 do	--	Backwards in open loops
			(
				loop_vert_temp = #()
				loop_edge_temp = #()
				next = data[first][data[first].count-2]
				prev = first
				pos = data[first].count - 3
				end = false
				
				while not keyboard.escPressed and not end do
				(
					edgeVerts_check[next] = off
					append loop_vert_temp edgeVerts[next]
					append loop_edge_temp data[prev][pos]
					prev = next
					dim = data[next].count
					used = true
					while used and dim > 1 do
					(
						used = not edgeVerts_check[data[next][dim]]
						if used do (dim -= 2)
					)
					if dim < 2 do (dim = 2; end = true)
					pos = dim - 1
					next = data[next][dim]
				)
				reverseArray &loop_vert_temp; reverseArray &loop_edge_temp
				loop_vert[k] = loop_vert_temp + loop_vert[k] 
				loop_edge[k] = loop_edge_temp + loop_edge[k]
			)
			
			first = firstbit edgeVerts_check
		)
	)

	--- PolyTools3D's TEST ----
	--/*
	delete objects
	obj = converttopoly (Torus segs:100 sides:25 radius1:50 radius2:10)
	obj.EditablePoly.SetSelection #Edge #{2}
	obj.EditablePoly.SelectEdgeRing()
	obj.EditablePoly.SelectEdgeLoop()
	--*/
	
	/*
	delete objects
	obj = converttopoly (plane pos:[0,0,1] length:100 width:100 lengthsegs:9 widthsegs:9 wirecolor:yellow)
	polyop.setedgeselection obj #{34, 36, 38, 40, 42, 52, 55, 57, 59, 62, 71, 73, 76, 79, 81, 90, 92, 94..96, 98, 100, 109, 111..112, 114, 116..117, 119, 128..129, 131, 133, 135, 137..138}
	--*/
		
	t1 = timestamp()
		loop_edge = #()
		loop_vert = #()
		findLoops obj &loop_edge &loop_vert
	t2 = timestamp(); format "Time= %
" (t2-t1)
	
	-- loop_edge[k] ==> holds the sorted edges for the number 'k' loop: polyop.setEdgeSelection obj loop_edge[k] to select the edge loop
	-- loop_vert[k] ==> holds the sorted verts for the number 'k' loop: polyop.setVertSelection obj loop_vert[k] to select the vert loop
)

Conversion to C# shouldn’t be too difficult and just have to pass ‘object’ handle.

@Denis,

Did you find the algorithm I proposed useful? Do you have any other example it should handle?

I got it to solve many complex situations, and it does way better than the algorithm in Max used to create splines. But as you have no make any additional comment, I don’t know if this is what you where after. It does certainly solve the situation you posted.

As far as the performance, even as it is, I find it very good. Not to mention if it is optimized.

I’ve stuck with the algorithm used for poly splines. Only changed using of BitArrays to CUSTOM FLAG, added passing of hidden edges, and added reversing loops which are going reversed to an ‘average’ loop direction…

Also I store a Point4 values instead of vertex index, where Point4 is vert’s position and it’s index.

I works for me well…

There are several my definitions in code but the code itself is:


def_visible_primitive(getPolyEdgeVertLoops, "getPolyEdgeVertLoops");
Value* getPolyEdgeVertLoops_cf(Value** arg_list, int count)
{
	check_arg_count_with_keys(getPolyEdgeVertLoops, 1, count);

	BOOL exact = default_exact_key(TRUE);
	BOOL convert = default_delete_key(FALSE);
	BOOL local = default_local_key(FALSE);
	BOOL hidden = default_BOOL_key(hidden, FALSE);

	BOOL canDelete = FALSE;
	TimeValue t = default_time_key(time);
	PolyObject* poly = getObjectPolyObject(arg_list[0], t, canDelete, exact, convert);

	if (poly)
	{
		MNMesh* mesh = &(poly->GetMesh());

		Matrix3* tm = AsMatrix3(arg_list[0], FALSE, FALSE, &t);
		if (Matrix3* m = AsMatrix3(key_arg(transform)))
		{
			if (tm) *tm = *tm * *m;
			else tm = m;
		}

		local_array(loops);
		vl.loops = new_array(0);

		BitArray oldsel;
		mesh->getEdgeSel(oldsel);
		BitArray* edges = GetSelectionBitArray(key_arg(edge), mesh->nume, oldsel, n_selection);
		if (edges && edges->AnyBitSet()) 
		{
			mesh->ClearVFlags(MN_VERT_DONE);
			for (int i=0; i < mesh->nume; i++) 
			{
				if ((*edges)[i] && (hidden || !mesh->e[i].GetFlag(MN_HIDDEN))) mesh->e[i].SetFlag(MN_LOCAL_SEL); 
				else mesh->e[i].ClearFlag(MN_LOCAL_SEL);
			}

			BitArray done;
			done.SetSize (mesh->nume);

			for (int i=0; i<mesh->nume; i++) 
			{
				if (done[i]) continue;
				if (mesh->e[i].GetFlag(MN_DEAD)) continue;
				if (!mesh->e[i].GetFlag(MN_LOCAL_SEL)) continue;

				// The array of points for the spline
				Point4Tab pts;

				// Add the first two points.
				int start = mesh->e[i].v1;
				pts.Append(1, &Point4(mesh->v[start].p, start + 1.0f), 10);
				int nextv = mesh->e[i].v2;
				pts.Append(1, &Point4(mesh->v[nextv].p, nextv + 1.0f), 10);
				
				// Mark this edge as done
				done.Set(i);

				// Trace along selected edges
				// Use loopcount/maxLoop just to avoid a while(1) loop.
				int loopCount, maxLoop=mesh->nume;
				for (loopCount = 0; loopCount < maxLoop; loopCount++) 
				{
					IntTab& ve = mesh->vedg[nextv];
					int j;
					
					for (j=0; j < ve.Count(); j++) 
					{
						if (done[ve[j]]) continue;
						if (mesh->e[ve[j]].GetFlag(MN_LOCAL_SEL)) break;
					}
					if (j == ve.Count()) break;

					if (mesh->e[ve[j]].v1 == nextv) nextv = mesh->e[ve[j]].v2;
					else nextv = mesh->e[ve[j]].v1;

					// Mark this edge as done
					done.Set(ve[j]);

					// Add this vertex to the list
					pts.Append(1, &Point4(mesh->v[nextv].p, nextv + 1.0f), 10);
				}
				int lastv = nextv;

				// Now trace backwards
				nextv = start;
				for (loopCount=0; loopCount<maxLoop; loopCount++) 
				{
					IntTab& ve = mesh->vedg[nextv];
					int j;

					for (j=0; j < ve.Count(); j++) 
					{
						if (done[ve[j]]) continue;
						if (mesh->e[ve[j]].GetFlag(MN_LOCAL_SEL)) break;
					}
					if (j == ve.Count()) break;
					if (mesh->e[ve[j]].v1 == nextv) nextv = mesh->e[ve[j]].v2;
					else nextv = mesh->e[ve[j]].v1;

					// Mark this edge as done
					done.Set(ve[j]);

					// Add this vertex to the list
					pts.Insert(0, 1, &Point4(mesh->v[nextv].p, nextv + 1.0f));
				}
				int firstv = nextv;

				if (!local && tm) for (int k = 0; k < pts.Count(); k++) pts[k] = Point4(tm->PointTransform(Point3(pts[k])), pts[k].w); 
				vl.loops->append(new Point4TabValue(pts));
			}
			if default_BOOL_key(align, TRUE)
			{
				Point3 normal = Point3::Origin;
				for (int k=0; k < vl.loops->size; k++)
				{
					Point4Tab tab = ((Point4TabValue*)vl.loops->data[k])->tab;
					normal += Point3(tab[1] - tab[0]);
				}
				for (int k=0; k < vl.loops->size; k++)
				{
					Point4TabValue* loop = (Point4TabValue*)vl.loops->data[k];
					if (DotProd(normal, Point3(loop->tab[1] - loop->tab[0])) < 0) loop->Reverse();
				}
			}
		}
		if (canDelete) poly->DeleteThis();
		return_value(vl.loops);
	}
	return &undefined;
}    

it takes almost 0 time for last (torus) example.

It doesn’t work well for loop crossings but as I said works good enough for me.

Same version but inserting items backwards instead of reversing and joining (for opened loops).
Really can’t find any performance difference, so this version is clearer:


(
	fn findLoops obj &loop_edge &loop_vert &closed =
	(
		edges_b = polyop.getEdgeSelection obj
		edgeVerts_b  = polyop.getVertsUsingEdge obj edges_b
		edges = edges_b as array
		edgeVerts  = edgeVerts_b as array
		edgeVerts_check = #{1..edgeVerts.count}
		edgeVerts_index = edgeVerts_check as array
		
		fn compareFn val index valArray: =
		(
			arrayVal = valArray[index]
			case of (
				(val < arrayVal): -1
				(val > arrayVal): 1
				default: 0
			)
		)
		fn firstBit arr = 
		(
			local b
			for n in arr while (b = n; off) do ()
			b
		)

		data = #()
		for i = 1 to edges.count do
		(
			vv = polyop.getedgeverts obj edges[i]
			v1_index = bsearch vv[1] edgeVerts_index compareFn valArray:edgeVerts
			v2_index = bsearch vv[2] edgeVerts_index compareFn valArray:edgeVerts
			datatemp = #(edges[i], v2_index)
			if data[v1_index] == undefined then data[v1_index] = datatemp else (if data[v1_index][2] < v2_index then data[v1_index] += datatemp else data[v1_index] = datatemp + data[v1_index])
			datatemp = #(edges[i], v1_index)
			if data[v2_index] == undefined then data[v2_index] = datatemp else (if data[v2_index][2] < v1_index then data[v2_index] += datatemp else data[v2_index] = datatemp + data[v2_index])
		)
		
		loop_vert = #()
		loop_edge = #()
		closed = #()
		first = firstbit edgeVerts_check
		k = 0	
		while not keyboard.escPressed and first != undefined do
		(
			k += 1
			closed[k] = false
			edgeVerts_check[first] = off
			loop_vert[k] = #(edgeVerts[first])
			loop_edge[k] = #()
			next = data[first][data[first].count]
			prev = first
			pos = data[first].count - 1
			end = false
			
			while not keyboard.escPressed and not end do
			(
				edgeVerts_check[next] = off
				append loop_vert[k] edgeVerts[next]
				append loop_edge[k] data[prev][pos]
				prev = next
				dim = data[next].count
				used = true
				while used and dim > 1 do
				(
					used = not edgeVerts_check[data[next][dim]]
					if used do (dim -= 2)
				)
				if dim < 2 do (dim = 2; end = true)
				pos = dim - 1
				next = data[next][dim]
			)
			if next == first do (append loop_edge[k] data[prev][pos]; closed[k] = true)
			if loop_vert[k].count == 2 do (closed[k] = false; deleteItem loop_edge[k] 2)	--	Isolated Edges
			
			if not closed[k] and data[first].count > 2 do	--	Backwards in open loops
			(
				next = data[first][data[first].count-2]
				prev = first
				pos = data[first].count - 3
				end = false
				
				while not keyboard.escPressed and not end do
				(
					edgeVerts_check[next] = off
					insertItem edgeVerts[next] loop_vert[k] 1
					insertItem data[prev][pos] loop_edge[k] 1
					prev = next
					dim = data[next].count
					used = true
					while used and dim > 1 do
					(
						used = not edgeVerts_check[data[next][dim]]
						if used do (dim -= 2)
					)
					if dim < 2 do (dim = 2; end = true)
					pos = dim - 1
					next = data[next][dim]
				)
			)
			
			first = firstbit edgeVerts_check
		)
	)

	--- PolyTools3D's TEST ----
	--/*
	delete objects
	obj = converttopoly (Torus segs:100 sides:25 radius1:50 radius2:10)
	obj.EditablePoly.SetSelection #Edge #{2}
	obj.EditablePoly.SelectEdgeRing()
	obj.EditablePoly.SelectEdgeLoop()
	--*/
	
	/*
	delete objects
	obj = converttopoly (plane pos:[0,0,1] length:100 width:100 lengthsegs:9 widthsegs:9 wirecolor:yellow)
	polyop.setedgeselection obj #{34, 36, 38, 40, 42, 52, 55, 57, 59, 62, 71, 73, 76, 79, 81, 90, 92, 94..96, 98, 100, 109, 111..112, 114, 116..117, 119, 128..129, 131, 133, 135, 137..138}
	--*/
		
	t1 = timestamp()
		loop_edge = #()
		loop_vert = #()
		closed = #()
		findLoops obj &loop_edge &loop_vert &closed
	t2 = timestamp(); format "Time= %
" (t2-t1)
	
	-- loop_edge[k] ==> holds the sorted edges for the number 'k' loop: polyop.setEdgeSelection obj loop_edge[k] to select the edge loop
	-- loop_vert[k] ==> holds the sorted verts for the number 'k' loop: polyop.setVertSelection obj loop_vert[k] to select the vert loop
	-- closed[k] 	  ==> tells if the number 'k' loop is closed or not (True/False)
)


Page 2 / 3