Skip to content

Commit 490312a

Browse files
committed
Polygon overlap
1 parent 5cff19e commit 490312a

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

src/phyjax2d/utils.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
Shape,
2121
ShapeDict,
2222
Space,
23+
State,
2324
StateDict,
2425
_vmap_dot,
2526
empty,
27+
normalize,
2628
)
2729
from phyjax2d.vec2d import Vec2d
2830

@@ -517,6 +519,32 @@ def make_square_segments(
517519
return lines
518520

519521

522+
def _circle_polygon_overlap(
523+
polygon: Polygon,
524+
pstate: State,
525+
xy: jax.Array,
526+
radius: jax.Array | float,
527+
) -> jax.Array:
528+
n_batch, n_vertices = polygon.points.shape[:2]
529+
# Suppose that pstate.p.xy.shape == (N, 2) and xy.shape == (2,)
530+
cxy = pstate.p.inv_transform(jnp.expand_dims(xy, axis=0))
531+
p2cxy = jnp.expand_dims(cxy, axis=1) - polygon.points
532+
separation = _vmap_dot(polygon.normals, (p2cxy - polygon.points)) # (N, NP)
533+
max_sep = jnp.max(separation, axis=1)
534+
i1 = jnp.argmax(separation, axis=1)
535+
i2 = (i1 + 1) % n_vertices
536+
select_all = jnp.arange(n_batch)
537+
v1 = polygon.points[select_all, i1]
538+
v2 = polygon.points[select_all, i2]
539+
u1 = _vmap_dot(cxy - v1, v2 - v1)
540+
u2 = _vmap_dot(cxy - v2, v1 - v2)
541+
v = jnp.where(u1 < 0.0, v1, v2)
542+
_, dist = normalize(cxy - v, axis=1)
543+
c_out = dist < polygon.radius + radius
544+
c_in = max_sep < polygon.radius + radius
545+
return jax.lax.select(jnp.logical_or(u1 < 0.0, u2 < 0.0), c_out, c_in)
546+
547+
520548
def circle_overlap(
521549
shaped: ShapeDict,
522550
stated: StateDict,
@@ -542,6 +570,23 @@ def circle_overlap(
542570
has_overlap = jnp.logical_and(stated.static_circle.is_active, penetration >= 0)
543571
overlap = jnp.logical_or(jnp.any(has_overlap), overlap)
544572

573+
# Circle-segment overlap
574+
if stated.segment is not None and shaped.segment is not None:
575+
spos = stated.segment.p
576+
# Suppose that spos.shape == (N, 2) and xy.shape == (2,)
577+
pb = spos.inv_transform(jnp.expand_dims(xy, axis=0))
578+
p1, p2 = shaped.segment.point1, shaped.segment.point2
579+
edge = p2 - p1
580+
s1 = jnp.expand_dims(_vmap_dot(pb - p1, edge), axis=1)
581+
s2 = jnp.expand_dims(_vmap_dot(p2 - pb, edge), axis=1)
582+
in_segment = jnp.logical_and(s1 >= 0.0, s2 >= 0.0)
583+
ee = jnp.sum(jnp.square(edge), axis=-1, keepdims=True)
584+
pa = jnp.where(in_segment, p1 + edge * s1 / ee, jnp.where(s1 < 0.0, p1, p2))
585+
dist = jnp.linalg.norm(pb - pa, axis=-1)
586+
penetration = radius - dist
587+
has_overlap = jnp.logical_and(stated.segment.is_active, penetration >= 0)
588+
overlap = jnp.logical_or(jnp.any(has_overlap), overlap)
589+
545590
# Circle-segment overlap
546591
if stated.segment is not None and shaped.segment is not None:
547592
spos = stated.segment.p
@@ -559,4 +604,32 @@ def circle_overlap(
559604
has_overlap = jnp.logical_and(stated.segment.is_active, penetration >= 0)
560605
overlap = jnp.logical_or(jnp.any(has_overlap), overlap)
561606

607+
# Circle-polygon overlap
608+
if stated.triangle is not None and shaped.trianlge is not None:
609+
has_overlap = _circle_polygon_overlap(
610+
shaped.triangle,
611+
stated.triangle,
612+
xy,
613+
radius,
614+
)
615+
overlap = jnp.logical_or(jnp.any(has_overlap), overlap)
616+
617+
if stated.quadrangle is not None and shaped.quadrangle is not None:
618+
has_overlap = _circle_polygon_overlap(
619+
shaped.quadrangle,
620+
stated.quadrangle,
621+
xy,
622+
radius,
623+
)
624+
overlap = jnp.logical_or(jnp.any(has_overlap), overlap)
625+
626+
if stated.pentagon is not None and shaped.pentagon is not None:
627+
has_overlap = _circle_polygon_overlap(
628+
shaped.pentagon,
629+
stated.pentagon,
630+
xy,
631+
radius,
632+
)
633+
overlap = jnp.logical_or(jnp.any(has_overlap), overlap)
634+
562635
return overlap

0 commit comments

Comments
 (0)