You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Here's a trial version that uses a numpy structured array with integers instead of pointers.
Some notes:
ObjectPool is currently a misnomer, since it's not a pool but just a big block of memory.
Numba does not seem to like functions that return a single structured array element (np.void). The functions here return the integer index instead.
There's likely still some porting issues since the answers aren't exactly the same. For the big water example, the last 296 triangles or something?
From earlier experience, numba does not optimize recursion that well. This uses a stack to push and pop.
Performance seems a lot worse: 70 ms vs 1 ms (!). This is somewhat surprising to me, almost makes me wonder whether it's allocating somewhere unexpected...
fromtypingimportNamedTupleimportnumpyasnpimportnumbaasnbIntDType=np.int64FloatDType=np.float64NodeDType=np.dtype(
[
("i", IntDType),
("x", FloatDType),
("y", FloatDType),
("prev", IntDType),
("next", IntDType),
("z", IntDType),
("prev_z", IntDType),
("next_z", IntDType),
("steiner", bool),
("address", IntDType),
]
)
Node=np.voidITEMSIZE=NodeDType.itemsizeNULL=np.void((-1, np.nan, np.nan, -1, -1, -1, -1, -1, True, -1), dtype=NodeDType)
ListPointer=intclassObjectPool(NamedTuple):
allocated: np.ndarraysize: intblocksize: int@nb.njit(inline="always")defconstruct(nodes: ObjectPool, index: int, i: int, x: float, y: float):
node=nodes.allocated[index]
node["i"] =inode["x"] =xnode["y"] =ynode["address"] =indexnode["steiner"] =Falsereturnindex+1@nb.njit(inline="always")defisnull(p) ->bool:
returnp["i"] <0@nb.njit(inline="always")defisvalid(p) ->bool:
returnp["i"] >-1@nb.njitdefearcut(vertices, hole_indices):
n_vertex=len(vertices)
has_holes=len(hole_indices) >0ifhas_holes:
outer_length=hole_indices[0]
else:
outer_length=len(vertices)
# create_linked_list for the exterior and eliminate_holes will never exceed# the initial memory requirements, as every node is featured only once.# Hence, the object pool will not be reallocated.size=int(1.5*n_vertex)
pool=ObjectPool(
np.full(size*ITEMSIZE, -1, dtype=np.uint8).view(NodeDType),
size,
size,
)
index=0outer_node_address, index=create_linked_list(
pool, index, vertices, 0, outer_length, True
)
ifouter_node_address==-1:
returnnp.empty((0, 3), dtype=IntDType)
outer_node=pool.allocated[outer_node_address]
ifhas_holes:
outer_node_address, index=eliminate_holes(
pool, index, vertices, hole_indices, outer_node
)
xmin=0.0ymin=0.0size=0.0# if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox# if n_vertex > 80:xmin=vertices[:, 0].min()
xmax=vertices[:, 0].max()
ymin=vertices[:, 1].min()
ymax=vertices[:, 1].max()
size=max(xmax-xmin, ymax-ymin)
# else:# xmin = 0.0# ymin = 0.0# inv_size = 0.0triangles=earcut_linked(
pool, index, outer_node_address, n_vertex, xmin, ymin, size
)
returntriangles.copy()
@nb.njitdefcreate_linked_list(pool, index, vertices, start, end, clockwise):
ifclockwise== (signed_area(vertices, start, end) >0):
step=1else:
start, end=end-1, start-1step=-1last_address=-1foriinrange(start, end, step):
x, y=vertices[i]
last_address, index=insert_node(pool, index, i, x, y, last_address)
iflast_address!=-1:
last=pool.allocated[last_address]
_next=pool.allocated[last["next"]]
ifequals(last, _next):
remove_node(pool, last)
last=_nextreturnlast["address"], index# eliminate colinear or duplicate points@nb.njit(inline="always")deffilter_points(pool, start_address, end_address) ->ListPointer:
ifend_address==-1:
end_address=start_addressstart=pool.allocated[start_address]
end=pool.allocated[end_address]
p=startagain=Truewhileagainor (p["address"] !=end["address"]):
again=Falsep_next=pool.allocated[p["next"]]
p_prev=pool.allocated[p["prev"]]
ifnotp["steiner"] and (equals(p, p_next) orarea(p_prev, p, p_next) ==0):
remove_node(pool, p)
p=end=p_previfp["address"] ==p_next["address"]:
return-1again=Trueelse:
p=p_nextreturnend["address"]
@nb.njit(inline="always")defpush(array, value, size):
array[size] =valuereturnsize+1@nb.njit(inline="always")defpop(array, size):
returnarray[size-1], size-1@nb.njit(inline="always")defset_triangle(triangles, triangle_count, a, b, c):
triangles[triangle_count, 0] =a["i"]
triangles[triangle_count, 1] =b["i"]
triangles[triangle_count, 2] =c["i"]
returntriangle_count+1@nb.njitdefearcut_linked(pool, index, ear_address, n_vertex, xmin, ymin, size):
# main ear slicing loop which triangulates a polygon (given as a linked _list)triangles=np.full((n_vertex*2, 3), -1, dtype=IntDType)
triangle_count=0stack=np.full(n_vertex, -1, dtype=IntDType)
stack[0] =ear_addressstack_size=1pass_stack=np.full(n_vertex, 0, dtype=np.uint8)
pass_stack[0] =0pass_size=1# stack = [ear_address]# pass_stack = [0]whilestack_size>0:
# while len(stack) > 0:address, stack_size=pop(stack, stack_size)
_pass, pass_size=pop(pass_stack, pass_size)
# address = stack.pop()# _pass = pass_stack.pop()ifaddress==-1:
continueear=pool.allocated[address]
# interlink polygon nodes in z-orderif (_pass==0) and (size!=0.0):
index_curve(pool, ear, xmin, ymin, size)
# iterate through ears, slicing them one by onestop=earwhileear["prev"] !=ear["next"]:
_prev=pool.allocated[ear["prev"]]
_next=pool.allocated[ear["next"]]
if (
is_ear_hashed(pool, ear, xmin, ymin, size)
if (size!=0)
elseis_ear(pool, ear)
):
# cut off the triangletriangle_count=set_triangle(
triangles, triangle_count, _prev, ear, _next
)
remove_node(pool, ear)
# skipping the next vertice leads to less sliver trianglesear=stop=pool.allocated[_next["next"]]
continueear=_next# if we looped through the whole remaining polygon and can't find any more earsifear["address"] ==stop["address"]:
# try filtering points and slicing againif_pass==0:
end_address=filter_points(pool, ear["address"], -1)
stack_size=push(stack, end_address, stack_size)
pass_size=push(pass_stack, 1, pass_size)
# stack.append(end_address)# pass_stack.append(1)# if this didn't work, try curing all small self-intersections locallyelif_pass==1:
ear_address, triangle_count=cure_local_intersections(
pool, ear, triangles, triangle_count
)
stack_size=push(stack, ear_address, stack_size)
pass_size=push(pass_stack, 2, pass_size)
# stack.append(ear_address)# pass_stack.append(2)# as a last resort, try splitting the remaining polygon into twoelif_pass==2:
# This function may add entries to the stackpool, index, stack_size, pass_size=split_earcut(
pool, index, stack, stack_size, pass_stack, pass_size, ear
)
breakreturntriangles[:triangle_count]
@nb.njit(inline="always")defsplit_earcut(pool, index, stack, stack_size, pass_stack, pass_size, start):
do=Truea=startwhiledoor (a["address"] !=start["address"]):
do=False_next=pool.allocated[a["next"]]
b=pool.allocated[_next["next"]]
whileb["address"] !=a["prev"]:
ifa["i"] !=b["i"] andis_valid_diagonal(pool, a, b):
# split the polygon in two by the diagonalc_address, pool, index=split_polygon(pool, index, a, b)
c=pool.allocated[c_address]
# filter colinear points around the cutsa_address=filter_points(pool, a["address"], a["next"])
c_address=filter_points(pool, c["address"], c["next"])
# stack.append(c_address)# pass_stack.append(0)# stack.append(a_address)# pass_stack.append(0)# run earcut on each halfstack_size=push(stack, c_address, stack_size)
pass_size=push(pass_stack, 0, pass_size)
stack_size=push(stack, a_address, stack_size)
pass_size=push(pass_stack, 0, pass_size)
returnpool, index, stack_size, pass_sizeelse:
b=pool.allocated[b["next"]]
a=pool.allocated[a["next"]]
returnpool, index, stack_size, pass_size# check whether a polygon node forms a valid ear with adjacent nodes@nb.njit(inline="always")defis_ear(pool, ear) ->bool:
a=pool.allocated[ear["prev"]]
b=earc=pool.allocated[ear["next"]]
ifarea(a, b, c) >=0:
returnFalse# reflex, can't be an ear# now make sure we don't have other points inside the potential ear_next=pool.allocated[ear["next"]]
p=pool.allocated[_next["next"]]
p_prev=pool.allocated[p["prev"]]
p_next=pool.allocated[p["next"]]
whilep["address"] !=ear["prev"]:
if (
point_in_triangle(
a["x"], a["y"], b["x"], b["y"], c["x"], c["y"], p["x"], p["y"]
)
andarea(p_prev, p, p_next) >=0
):
returnFalsep=p_nextreturnTrue@nb.njit(inline="always")defis_ear_hashed(pool, ear, xmin, ymin, size):
a=pool.allocated[ear["prev"]]
b=earc=pool.allocated[ear["next"]]
ifarea(a, b, c) >=0:
returnFalse# reflex, can't be an ear# triangle bbox; min & max are calculated like this for speedmin_tx= (
(a["x"] ifa["x"] <c["x"] elsec["x"])
ifa["x"] <b["x"]
else (b["x"] ifb["x"] <c["x"] elsec["x"])
)
min_ty= (
(a["y"] ifa["y"] <c["y"] elsec["y"])
ifa["y"] <b["y"]
else (b["y"] ifb["y"] <c["y"] elsec["y"])
)
max_tx= (
(a["x"] ifa["x"] >c["x"] elsec["x"])
ifa["x"] >b["x"]
else (b["x"] ifb["x"] >c["x"] elsec["x"])
)
max_ty= (
(a["y"] ifa["y"] >c["y"] elsec["y"])
ifa["y"] >b["y"]
else (b["y"] ifb["y"] >c["y"] elsec["y"])
)
# z-order range for the current triangle bbox;zmin=z_order(min_tx, min_ty, xmin, ymin, size)
zmax=z_order(max_tx, max_ty, xmin, ymin, size)
# first look for points inside the triangle in increasing z-orderp_address=ear["next_z"]
whilep_address!=-1:
p=pool.allocated[p_address]
ifp["z"] >zmax:
breakp_prev=pool.allocated[p["prev"]]
p_next=pool.allocated[p["next"]]
if (
p["address"] !=ear["prev"]
andp["address"] !=ear["next"]
andpoint_in_triangle(
a["x"], a["y"], b["x"], b["y"], c["x"], c["y"], p["x"], p["y"]
)
andarea(p_prev, p, p_next) >=0
):
returnFalsep_address=p["next_z"]
# then look for points in decreasing z-orderp_address=ear["prev_z"]
whilep_address!=-1:
p=pool.allocated[p_address]
ifp["z"] <zmin:
breakear_prev=pool.allocated[ear["prev"]]
ear_next=pool.allocated[ear["next"]]
p_prev=pool.allocated[p["prev"]]
p_next=pool.allocated[p["next"]]
if (
p["address"] !=ear_prev["address"]
andp["address"] !=ear_next["address"]
andpoint_in_triangle(
a["x"], a["y"], b["x"], b["y"], c["x"], c["y"], p["x"], p["y"]
)
andarea(p_prev, p, p_next) >=0
):
returnFalsep_address=p["prev_z"]
returnTrue# go through all polygon nodes and cure small local self-intersections@nb.jit(inline="always")defcure_local_intersections(pool, start, triangles, triangle_count):
do=Truep=startwhiledoorp["address"] !=start["address"]:
do=Falsep_next=pool.allocated[p["next"]]
a=pool.allocated[p["prev"]]
b=pool.allocated[p_next["next"]]
if (
notequals(a, b)
andintersects(a, p, p_next, b)
andlocally_inside(pool, a, b)
andlocally_inside(pool, b, a)
):
triangle_count=set_triangle(triangles, triangle_count, a, p, b)
# remove two nodes involvedremove_node(pool, p)
remove_node(pool, p_next)
p=start=bp=p_nextaddress=filter_points(pool, p["address"], -1)
returnaddress, triangle_count# link every hole into the outer loop, producing a single-ring polygon without holes@nb.njitdefeliminate_holes(pool, index, vertices, hole_indices, outer_node):
n_hole=len(hole_indices)
hole_addresses=np.zeros(n_hole, IntDType)
hole_x=np.zeros(n_hole, FloatDType)
fori, startinenumerate(hole_indices):
start=hole_indices[i]
ifi< (n_hole-1):
end=hole_indices[i+1]
else:
end=len(vertices)
_list_address, index=create_linked_list(
pool, index, vertices, start, end, False
)
_list=pool.allocated[_list_address]
_next=pool.allocated[_list["next"]]
if_list["address"] ==_next["address"]:
_list["steiner"] =Trueaddress, x=get_leftmost(pool, _list)
hole_addresses[i] =addresshole_x[i] =x# process holes from left to rightorder=np.argsort(hole_x)
foriinorder:
address=hole_addresses[i]
hole=pool.allocated[address]
pool, index=eliminate_hole(pool, index, hole, outer_node)
_next=pool.allocated[outer_node["next"]]
outer_node_address=filter_points(
pool, outer_node["address"], outer_node["next"]
)
outer_node=pool.allocated[outer_node_address]
returnouter_node["address"], index# find a bridge between vertices that connects hole with an outer ring and and link it@nb.njitdefeliminate_hole(pool, index, hole, outer_node):
outer_node_address=find_hole_bridge(pool, hole, outer_node)
ifouter_node_address!=-1:
outer_node=pool.allocated[outer_node_address]
b_address, pool, index=split_polygon(pool, index, outer_node, hole)
b=pool.allocated[b_address]
b_next=pool.allocated[b["next"]]
filter_points(pool, b["address"], b_next["address"])
returnpool, index# David Eberly's algorithm for finding a bridge between hole and outer polygon@nb.njitdeffind_hole_bridge(pool, hole, outer_node) ->ListPointer:
do=Truep=outer_nodehx=hole["x"]
hy=hole["y"]
qx=-np.infm_address=-1# find a segment intersected by a ray from the hole's leftmost point to the left;# segment's endpoint with lesser x will be potential connection pointwhiledoorp["address"] !=outer_node["address"]:
do=Falsep_next=pool.allocated[p["next"]]
ifhy<=p["y"] andhy>=p_next["y"] andp_next["y"] -p["y"] !=0:
x=p["x"] + (hy-p["y"]) * (p_next["x"] -p["x"]) / (p_next["y"] -p["y"])
ifx<=hxandx>qx:
qx=xifx==hx:
ifhy==p["y"]:
returnp["address"]
ifhy==p_next["y"]:
returnp_next["address"]
m=pifp["x"] <p_next["x"] elsep_nextm_address=m["address"]
p=p_nextifm_address==-1:
return-1ifhx==qx:
returnm["prev"]
# look for points inside the triangle of hole point, segment intersection and endpoint;# if there are no points found, we have a valid connection;# otherwise choose the point of the minimum angle with the ray as connection pointstop=mmx=m["x"]
my=m["y"]
tan_min=np.inftan=Nonep=pool.allocated[p["next"]]
whilep["address"] !=stop["address"]:
hx_or_qx=hxifhy<myelseqxqx_or_hx=qxifhy<myelsehxif (
hx>=p["x"]
andp["x"] >=mxandpoint_in_triangle(hx_or_qx, hy, mx, my, qx_or_hx, hy, p["x"], p["y"])
):
tan=abs(hy-p["y"]) / (hx-p["x"]) # tangentialif (
tan<tan_minor (tan==tan_minandp["x"] >m["x"])
) andlocally_inside(pool, p, hole):
m=ptan_min=tanp=pool.allocated[p["next"]]
returnm["address"]
# interlink polygon nodes in z-order@nb.njit(inline="always")defindex_curve(pool, start, xmin, ymin, size) ->None:
do=Truep=startwhiledoorp["address"] !=start["address"]:
do=Falseifp["z"] ==-1:
p["z"] =z_order(p["x"], p["y"], xmin, ymin, size)
p["prev_z"] =p["prev"]
p["next_z"] =p["next"]
p=pool.allocated[p["next"]]
p_prev_z=pool.allocated[p["prev_z"]]
p_prev_z["next_z"] =-1p["prev_z"] =-1sort_linked(pool, p["address"])
return@nb.njitdefsort_linked(pool, _list):
in_size=1whileTrue:
p_address=_list_list=-1tail_address=-1n_merges=0whilep_address!=-1:
n_merges+=1q_address=p_addressp_size=0for_inrange(in_size):
p_size+=1q=pool.allocated[q_address]
q_address=q["next_z"]
ifq_address==-1:
breakq_size=in_sizewhile (p_size>0) or ((q_size>0) and (q_address!=-1)):
ifp_size==0:
e_address=q_addressq=pool.allocated[q_address]
q_address=q["next_z"]
q_size-=1elifq_size==0orq_address==-1:
e_address=p_addressp=pool.allocated[p_address]
p_address=p["next_z"]
p_size-=1else:
p=pool.allocated[p_address]
q=pool.allocated[q_address]
ifp["z"] <=q["z"]:
e_address=p_addressp_address=p["next_z"]
p_size-=1else:
e_address=q_addressq_address=q["next_z"]
q_size-=1iftail_address!=-1:
tail=pool.allocated[tail_address]
tail["next_z"] =e_addresselse:
_list=e_address# Update list head when tail is nulle=pool.allocated[e_address]
e["prev_z"] =tail_addresstail_address=e_addressp_address=q_addressiftail_address!=-1:
tail=pool.allocated[tail_address]
tail["next_z"] =-1ifn_merges<=1:
return_listin_size*=2# z-order of a point given coords and size of the vertices bounding box@nb.njit(inline="always")defz_order(x, y, xmin, ymin, size):
# coords are transformed into non-negative 15-bit integer rangex=32767*int((x-xmin) /size)
y=32767*int((y-ymin) /size)
x= (x| (x<<8)) &0x00FF00FFx= (x| (x<<4)) &0x0F0F0F0Fx= (x| (x<<2)) &0x33333333x= (x| (x<<1)) &0x55555555y= (y| (y<<8)) &0x00FF00FFy= (y| (y<<4)) &0x0F0F0F0Fy= (y| (y<<2)) &0x33333333y= (y| (y<<1)) &0x55555555returnx| (y<<1)
# find the leftmost node of a polygon ring@nb.njit(inline="always")defget_leftmost(pool, start):
do=Truep=startleftmost=startwhiledoorp["address"] !=start["address"]:
do=Falseifp["x"] <leftmost["x"]:
leftmost=pp=pool.allocated[p["next"]]
returnleftmost["address"], leftmost["x"]
# check if a point lies within a convex triangle@nb.njit(inline="always")defpoint_in_triangle(ax, ay, bx, by, cx, cy, px, py):
return (
(cx-px) * (ay-py) - (ax-px) * (cy-py) >=0and (ax-px) * (by-py) - (bx-px) * (ay-py) >=0and (bx-px) * (cy-py) - (cx-px) * (by-py) >=0
)
# check if a diagonal between two polygon nodes is valid (lies in polygon interior)@nb.njit(inline="always")defis_valid_diagonal(pool, a, b):
anext=pool.allocated[a["next"]]
aprev=pool.allocated[a["prev"]]
bprev=pool.allocated[b["prev"]]
bnext=pool.allocated[b["next"]]
return (
# Doesn't intersect other edgesanext["i"] !=b["i"]
andaprev["i"] !=b["i"]
andnotintersects_polygon(pool, a, b)
and (
# locally visible
(
locally_inside(pool, a, b)
andlocally_inside(pool, b, a)
andmiddle_inside(pool, a, b)
# does not create opposite-facing sectorsand (area(aprev, a, bprev) !=0.0orarea(a, bprev, b) !=0.0)
)
# special zero-length caseor (
equals(a, b) andarea(aprev, a, anext) >0andarea(bprev, b, bnext) >0
)
)
)
# signed area of a triangle@nb.njit(inline="always")defarea(p, q, r):
return (q["y"] -p["y"]) * (r["x"] -q["x"]) - (q["x"] -p["x"]) * (r["y"] -q["y"])
# check if two points are equal@nb.njit(inline="always")defequals(p1, p2):
returnp1["x"] ==p2["x"] andp1["y"] ==p2["y"]
# check if two segments intersect@nb.njit(inline="always")defintersects(p1, q1, p2, q2) ->bool:
o1=sign(area(p1, q1, p2))
o2=sign(area(p1, q1, q2))
o3=sign(area(p2, q2, p1))
o4=sign(area(p2, q2, q1))
if (o1!=o2) and (o3!=o4):
returnTrueif (o1==0andon_segment(p1, p2, q1)):
returnTrueif (o2==0andon_segment(p1, q2, q1)):
returnTrueif (o3==0andon_segment(p2, p1, q2)):
returnTrueif (o4==0andon_segment(p2, q1, q2)):
returnTruereturnFalse@nb.njit(inline="always")defon_segment(p, q, r) ->bool:
return (
q["x"] <=max(p["x"], r["x"])
andq["x"] >=min(p["x"], r["x"])
andq["y"] <=max(p["y"], r["y"])
andq["y"] >=min(p["y"], r["y"])
)
@nb.njit(inline="always")defsign(val) ->int:
return (0.0<val) - (val<0.0)
# check if a polygon diagonal intersects any polygon segments@nb.njit(inline="always")defintersects_polygon(pool, a, b):
do=Truep=awhiledoorp["address"] !=a["address"]:
do=Falsep_next=pool.allocated[p["next"]]
if (
p["i"] !=a["i"]
andp_next["i"] !=a["i"]
andp["i"] !=b["i"]
andp_next["i"] !=b["i"]
andintersects(p, p_next, a, b)
):
returnTruep=p_nextreturnFalse# check if a polygon diagonal is locally inside the polygon@nb.njit(inline="always")deflocally_inside(pool, a, b):
aprev=pool.allocated[a["prev"]]
anext=pool.allocated[a["next"]]
ifarea(aprev, a, anext) <0:
returnarea(a, b, anext) >=0andarea(a, aprev, b) >=0else:
returnarea(a, b, aprev) <0orarea(a, anext, b) <0# check if the middle point of a polygon diagonal is inside the polygon@nb.njit(inline="always")defmiddle_inside(pool, a, b):
do=Truep=ainside=Falsepx=0.5* (a["x"] +b["x"])
py=0.5* (a["y"] +b["y"])
whiledoorp["address"] !=a["address"]:
do=Falsep_next=pool.allocated[p["next"]]
if ((p["y"] >py) != (p_next["y"] >py)) and (
px< (p_next["x"] -p["x"]) * (py-p["y"]) / (p_next["y"] -p["y"]) +p["x"]
):
inside=notinsidep=p_nextreturninside# link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;# if one belongs to the outer ring and another to a hole, it merges it into a single ring@nb.njit(inline="always")defsplit_polygon(pool, index, a, b):
# This is the only place where we don't know how many nodes will be created.# Make sure there is room by reserving for the two additional nodes.ifindex+2>=pool.size:
array=pool.allocatednew_size=pool.size+pool.blocksize# numba refuses to allocate NodeDType directly...allocated=np.full(new_size*ITEMSIZE, -1, dtype=np.uint8).view(NodeDType)
allocated[: array.size] =array[:]
pool=ObjectPool(allocated, new_size, pool.blocksize)
a2=pool.allocated[index]
index=construct(pool, index, a["i"], a["x"], a["y"])
b2=pool.allocated[index]
index=construct(pool, index, b["i"], b["x"], b["y"])
an=pool.allocated[a["next"]]
bp=pool.allocated[b["prev"]]
a["next"] =b["address"]
b["prev"] =a["address"]
a2["next"] =an["address"]
an["prev"] =a2["address"]
b2["next"] =a2["address"]
a2["prev"] =b2["address"]
bp["next"] =b2["address"]
b2["prev"] =bp["address"]
returnb2["address"], pool, index# create a node and optionally link it with previous one (in a circular doubly linked _list)@nb.njit(inline="always")definsert_node(pool, index, i, x, y, last_address):
p=pool.allocated[index]
index=construct(pool, index, i, x, y)
iflast_address==-1:
p["prev"] =p["address"]
p["next"] =p["address"]
else:
last=pool.allocated[last_address]
p["next"] =last["next"]
p["prev"] =last["address"]
_next=pool.allocated[last["next"]]
_next["prev"] =p["address"]
last["next"] =p["address"]
returnp["address"], index@nb.njit(inline="always")defremove_node(pool, p) ->None:
_next=pool.allocated[p["next"]]
_next["prev"] =p["prev"]
_prev=pool.allocated[p["prev"]]
_prev["next"] =p["next"]
ifp["prev_z"] !=-1:
p_prev_z=pool.allocated[p["prev_z"]]
p_prev_z["next_z"] =p["next_z"]
ifp["next_z"] !=-1:
p_next_z=pool.allocated[p["next_z"]]
p_next_z["prev_z"] =p["prev_z"]
return@nb.njit(inline="always")defsigned_area(vertices, start, end):
area=0.0x0, y0=vertices[end-1]
foriinrange(start, end):
x1, y1=vertices[i]
area+= (x0-x1) * (y1+y0)
x0, y0=x1, y1returnarea
The text was updated successfully, but these errors were encountered:
Here's a trial version that uses a numpy structured array with integers instead of pointers.
Some notes:
The text was updated successfully, but these errors were encountered: