将一个或多个吸引子添加到一组随机2D点

时间:2016-09-11 05:50:15

标签: python game-physics voronoi

给定一组伪随机2D点,如下所示:

points = random.sample([[x, y] for x in xrange(width) for y in yrange(height)], 100)

我希望能够添加一个或多个非随机吸引点,围绕这些点将绘制其他点。我不打算为此设置动画,因此它不需要非常高效,但我希望能够指定如何绘制每个吸引点的随机点(例如基于正方形距离和给定的引力常数)然后将我的点提供给返回原始列表的修改版本的函数:

points = random.sample([[x, y] for x in xrange(width) for y in xrange(height)], 100)

attractors = [(25, 102), (456, 300), (102, 562)]

def attract(random_points_list, attractor_points_list):
    (...)
    return modified_points_list

new_points_list = attract(points, attractors)

然后,这个新的点列表将用于播种Voronoi图(不是此问题的一部分)。

2 个答案:

答案 0 :(得分:1)

这比我最初估计的纯粹的实施工作任务更具挑战性。困难在于定义一个好的吸引力模型。

  1. 模拟吸引子上的平滑自由落体(好像在由多点质量创建的真实重力场中)是有问题的,因为您必须指定此过程的持续时间。如果持续时间足够短,则位移将很小并且吸引子周围的聚集将不会明显。如果持续时间足够长,那么所有点都将落在吸引子上或者距离它们太近。

  2. 一次计算每个点的新位置(不进行基于时间的模拟)比较简单,但问题是每个点的最终位置是否必须受 all 的影响吸引者或只有最接近的。后一种方法(吸引到最接近一种)被证明产生了更具视觉吸引力的结果。我用前一种方法无法取得好成绩(但请注意,我只尝试过相对简单的吸引功能)。

  3. 使用matplotlib进行可视化的Python 3.4代码如下:

    #!/usr/bin/env python3
    
    import random
    import numpy as np
    import matplotlib.pyplot as plt
    
    def dist(p1, p2):
        return np.linalg.norm(np.asfarray(p1) - np.asfarray(p2))
    
    
    def closest_neighbor_index(p, attractors):
        min_d = float('inf')
        closest = None
        for i,a in enumerate(attractors):
            d = dist(p, a)
            if d < min_d:
                closest, min_d = i, d
        return closest
    
    
    def group_by_closest_neighbor(points, attractors):
        g = []
        for a in attractors:
            g.append([])
        for p in points:
            g[closest_neighbor_index(p, attractors)].append(p)
        return g
    
    
    def attracted_point(p, a, f):
        a = np.asfarray(a)
        p = np.asfarray(p)
        r = p - a
        d = np.linalg.norm(r)
        new_d = f(d)
        assert(new_d <= d)
        return a + r * new_d/d
    
    
    def attracted_point_list(points, attractor, f):
        result=[]
        for p in points:
            result.append(attracted_point(p, attractor, f))
        return result
    
    
    # Each point is attracted only to its closest attractor (as if the other
    # attractors don't exist).
    def attract_to_closest(points, attractors, f):
        redistributed_points = []
        grouped_points = group_by_closest_neighbor(points, attractors)
        for a,g in zip(attractors, grouped_points):
            redistributed_points.extend(attracted_point_list(g,a,f))
        return redistributed_points
    
    
    def attraction_translation(p, a, f):
        return attracted_point(p, a, f) - p
    
    
    # Each point is attracted by multiple attracters.
    # The resulting point is the average of the would-be positions
    # computed for each attractor as if the other attractors didn't exist.
    def multiattract(points, attractors, f):
        redistributed_points = []
        n = float(len(attractors))
        for p in points:
            p = np.asfarray(p)
            t = np.zeros_like(p)
            for a in attractors:
                t += attraction_translation(p,a,f)
            redistributed_points.append(p+t/n)
        return redistributed_points
    
    
    def attract(points, attractors, f):
        """ Draw points toward attractors
    
        points and attractors must be lists of points (2-tuples of the form (x, y)).
    
        f maps distance of the point from an attractor to the new distance value,
        i.e. for a single point P and attractor A, f(distance(P, A)) defines the
        distance of P from A in its new (attracted) location.
    
        0 <= f(x) <= x must hold for all non-negative values of x.
        """
    
        # multiattract() doesn't work well with simple attraction functions
        # return multiattract(points, attractors, f);
        return attract_to_closest(points, attractors, f);
    
    if __name__ == '__main__':
        width=400
        height=300
        points = random.sample([[x, y] for x in range(width) for y in range(height)], 100)
        attractors = [(25, 102), (256, 256), (302, 62)]
    
        new_points = attract(points, attractors, lambda d: d*d/(d+100))
        #plt.scatter(*zip(*points), marker='+', s=32)
        plt.scatter(*zip(*new_points))
        plt.scatter(*zip(*attractors), color='red', marker='x', s=64, linewidths=2)
    
        plt.show()
    

答案 1 :(得分:0)

你说你希望能够改变这个功能,所以我们需要在f的参数中添加一个函数attract。这个函数应该需要一个参数,即点的距离,它应该返回一个数字。

import math


def attract(random_point_list, attractor_point_list, f = lambda x: 1/x**2):
    for x0, y0 in attractor_point_list:
        x0, y0 = float(x0), float(y0)
        modified_point_list = []
        for x, y in random_point_list:
            rx, ry = x0 - x, y0 - y  # the relative position of the attractor
            distance = math.sqrt(rx**2 + ry**2)
            attraction = f(d)
            modified_point_list.append((x + attraction*rx/distance, y + attraction*ry/distance))  # (rx, ry) is a vector with length distance -> by dividing with distance we get a vector (rx / distance, ry / distance) with length 1. Multiplying it with attraction, we get the difference of the original and the new location.
        random_point_list = modified_point_list
    return modified_point_list