I have a list of connected points.
I want to move the points/white squares along their own paths like so:
I like to have a numpy version, but I'm not sure if that is possible.
So a python approach would be fine, too.
End result should be a list of new points.
CodePudding user response:
Here's something to get you thinking. I define a set of points. I compute the length and angle of the line segments (note that this will fail for vertical segments). I then start a matplotlib animation plotting the original line segments and the current points. This isn't quite enough for you, because I'm not rounding the corners. The points just continue off in the same direction. An exercise left for the reader?
import math
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Define a set of points that make up a rough circle.
curve = np.array((
( 0, 0 ),
( 8, 2 ),
( 12, 7 ),
( 5, 11 ),
( -2, 13 ),
( -4, 8 ),
( -2, 4 ),
( 0, 0 )
))
# Cmpute angle and length of segment.
segments = []
for c in range(len(curve)-1):
delta = curve[c 1]-curve[c]
length = math.sqrt(delta[0]*delta[0] delta[1]*delta[1])
angle = math.atan2(delta[1], delta[0])
segments.append( (delta, length, angle) )
# Step 0.5 units per frame.
STEP = 0.5
def movePoint( pt, segment ):
# Move pt down the line between prv and nxt.
ptx = math.cos(segment[2]) * STEP
pty = math.sin(segment[2]) * STEP
return pt (ptx,pty)
def advance(points, curve):
newpts = []
for i in range(len(curve)-1):
newpts.append( movePoint( points[i], segments[i]) )
return np.array(newpts)
fig, ax = plt.subplots()
plt.plot( curve[:,0], curve[:,1] )
def init():
global points
global ln
points = curve.copy()
ln = plt.scatter( points[:,0], points[:,1] )
return ln,
def update(frame):
global points
points = advance(points, curve)
ln.set_offsets( points )
return ln,
ani = FuncAnimation( fig, update, frames=12, init_func=init, blit=True )
plt.show()
CodePudding user response:
from __future__ import annotations
from typing import List
import numpy as np
class _LineSegment:
def __init__(self, v1: np.ndarray, v2: np.ndarray) -> None:
"""Helper representation of a line segment. Makes use of PolygonialPath's
data integrity (i.e. no further range checks) and implements handling
of v1 == v2 case.
"""
self.v1 = v1
self.v2 = v2
v_delta = v2 - v1
self.length = np.linalg.norm(v_delta)
self.direction = v_delta / self.length if self.length else v_delta
class PolygonalChain:
def __init__(self, vertices: List[np.ndarray]) -> None:
if len(vertices) < 2:
raise ValueError("Trivial polygonial paths not supported!")
v_shape = vertices[0].shape
if len(v_shape) != 1:
raise ValueError("Vertices have to be represented as vectors!")
if any(v.shape != v_shape for v in vertices):
raise ValueError("Vertices have to be of consistent length!")
self._vertices = vertices
self._vertices_arclen = [0]
self._strict_vertices_arclen = [0]
self._strict_segments = []
v_prev = self._vertices[0]
for v_next in vertices[1:]:
segment = _LineSegment(v_prev, v_next)
self._vertices_arclen.append(self._vertices_arclen[-1] segment.length)
if segment.length:
self._strict_vertices_arclen.append(self._vertices_arclen[-1])
self._strict_segments.append(segment)
v_prev = v_next
if not self._strict_segments:
raise ValueError("Trivial polygonial paths not supported!")
# for convenience in `point_at`, set last value to inf (to handle
# extrapolation beyond last point)
self._strict_vertices_arclen[-1] = np.inf
def vertices_arclen(self) -> List[float]:
"""For each vertice, its arc-length along the path."""
return self._vertices_arclen
def point_at(self, arclen: float) -> np.ndarray:
"""Provide point corresponding to the arc-length. Extrapolation done by
first and last segment.
"""
# map to line segment (could be optimized by bi-section)
segment_idx = 0
while arclen > self._strict_vertices_arclen[segment_idx 1]:
segment_idx = 1
# compute point
segment = self._strict_segments[segment_idx]
arclen_on_segment = arclen - self._strict_vertices_arclen[segment_idx]
return segment.v1 arclen_on_segment * segment.direction
def example() -> None:
from matplotlib import pyplot as plt
# some data
pts = [
np.array((1, 2)),
np.array((1, 2)),
np.array((3, 5)),
np.array((4, 7)),
np.array((4, 8)),
np.array((3, 6)),
]
# polygonial chain representation
chain = PolygonalChain(pts)
pts_arclen = chain.vertices_arclen()
# apply some step
delta_arclen = -0.5
pts_shifted = [chain.point_at(p_arclen delta_arclen) for p_arclen in pts_arclen]
# plot
plt.plot([p[0] for p in pts], [p[1] for p in pts], "- k")
plt.plot([p[0] for p in pts_shifted], [p[1] for p in pts_shifted], "r*")
plt.show()
if __name__ == "__main__":
example()