Home > Mobile >  Move points along their own path
Move points along their own path

Time:10-23

I have a list of connected points.
I want to move the points/white squares along their own paths like so: move points along edge

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()

output

  • Related