给定一组伪随机二维点,如下所示:

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图(不是这个问题的一部分)。

最佳答案

结果发现,这是一个比我最初估计的纯实现工作任务更具挑战性的问题。困难在于如何定义一个好的吸引力模型。
由于必须指定此过程的持续时间,因此模拟吸引子上的平面自由下落(就像在由多个点质量创建的真实重力场中)是有问题的。如果持续时间足够短,则位移很小,吸引子周围的聚集不明显。如果持续时间足够长,那么所有点都将落在吸引子上或离吸引子太近。
一次计算每个点的新位置(不进行基于时间的模拟)比较简单,但问题是每个点的最终位置必须受所有吸引子的影响还是只受其中最接近的吸引子的影响。后一种方法(只吸引到最近的一个)被证明能产生更吸引人的视觉效果。使用前一种方法,我无法取得好的效果(但请注意,我只尝试了相对简单的吸引函数)。
使用matplotlib可视化的Python3.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()

关于python - 将一个或多个吸引子添加到一组随机2D点,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39433259/

10-10 13:22