将 Brute-Force Voronoi 图从 Javascript 示例转换为 Python

Translating Brute-Force Voronoi Diagram into Python from Javascript Example

我正在尝试将生成 Voronoi 图的代码从 Javascript 转换为 Python。这是一场斗争,因为我不知道 Javascript。我想我可以解决这个问题,但我仍然 运行 遇到一些我不理解的问题。请帮我找出我的代码有什么问题。

我写的代码只是给了一个空白window。请帮我生成一个Voronoi图。

依次是我的代码,然后是原代码。

这是我在以下网址找到的 link 网站: Procedural Generation Wiki

我的代码:

"""
Translated from javascript example at
http://pcg.wikidot.com/pcg-algorithm:voronoi-diagram

I have included the original comments.

My own comments are preceded by two hash-tags/pound-signs.
IE
# Original Javascript comments here
## My comments here
"""

import random
import pygame
from pygame import gfxdraw

# specify an empty points array
## I will use a list.
points = []
lines = []

# get a random number in range min, max -1
## No need for our own random number function.
## Just, import random, instead.


def definePoints(numPoints, mapSize):
    # we want to take a group of points that will fit on our map at random
    for typ in range(numPoints):
        # here's the random points
        x = random.randrange(0, mapSize)
        y = random.randrange(0, mapSize)
        # "type:" decides what point it is
        # x, y: location
        # citizens: the cells in our grid that belong to this point
        ## Can't use "type:" (without quotes) in python comments
        ## Since I don't know Javascript, I dunno what he's doing here.
        ## I'm just going to append lists inside points.
        ## order is: type, x, y, citizens
        points.append([typ, x, y, []])

    # brute force-y but it works
    # for each cell in the grid
    for x in range(mapSize):
        for y in range(mapSize):
            # find the nearest point
            lowestDelta = (0, mapSize * mapSize)
            for p in range(len(points)):
                # for each point get the difference in distance
                # between our point and the current cell
                ## I split the above comment into two so
                ## it would be less than 80 characters.
                delta = abs(points[p][1] - x) + abs(points[p][2] - y)
                # store the point nearest if it's closer than the last one
                if delta < lowestDelta[1]:
                    lowestDelta = (p, delta)

            # push the cell to the nearest point
            ## Okay, here's where I start getting confused.
            ## I dunno how to do whatever he's doing in Python.
            for point in points:
                if lowestDelta[0] == point[0]:
                        activePoint = point
            dx = x - activePoint[1]
            dy = y - activePoint[2]
            # log delta in cell for drawing
            ## Again, not sure what he's doing here.
            for point in points:
                if activePoint == point:
                    point[3].append(dx)
                    point[3].append(dy)

    return points

## all comments and code from here on are mine.
def main():
    # Get points
    points = definePoints(20, 400)
    print("lines: ", lines)

    # Setup pygame screens.
    pygame.init()
    size = (400, 400)
    screen = pygame.display.set_mode(size)
    white = (255, 255, 255)

    done = False

    # Control fps of window.
    clock = pygame.time.Clock()
    fps = 40

    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

        for point in points:
            for p in point[3]:
                # draw white wherever there's a point.
                # pygame windows are black by default
                gfxdraw.pixel(screen, point[1], point[2], white)

        # controls FPS
        clock.tick(fps)

if __name__ == "__main__":
    main()

原始示例代码:

//specify an empty points array
var points = [];

//get a random number in range min, max - 1
function randRange(min, max) {
    return Math.floor(Math.random() * ((max) - min) + min);
}

function definePoints(numPoints, mapSize) {
    //we want to take a group of points that will fit on our map at random
    for(var i = 0; i < numPoints; i++) {
        //here's the random points
        var x = randRange(0, mapSize);
        var y = randRange(0, mapSize);
        //type: decides which point it is
        //x, y: location
        //citizens: the cells in our grid that belong to this point
        points.push({type: i, x: x, y: y, citizens: []});
    }
    //brute force-y but it works
    //for each cell in the grid
    for(var x = 0; x < mapSize; x++) {
        for(var y = 0; y < mapSize; y++) {
            //find the nearest point
            var lowestDelta = {pointId: 0, delta: mapSize * mapSize};
            for(var p = 0; p < points.length; p++) {
                //for each point get the difference in distance between our point and the current cell
                var delta = Math.abs(points[p].x - x) + Math.abs(points[p].y - y);
                //store the point as nearest if it's closer than the last one
                if(delta < lowestDelta.delta) {
                    lowestDelta = {pointId: p, delta: delta};
                }
            }
            //push the cell to the nearest point
            var activePoint = points[lowestDelta.pointId];
            var dx = x - activePoint.x;
            var dy = y - activePoint.y;
            //log delta in cell for drawing
            activePoint.citizens.push({
                dx: dx, 
                dy: dy
            });
        }
    }
}

definePoints(20, 40);

for(var point of points) {
        for(var citizen of point.citizens) {
            //set color of cell based on point
            //draw cell at (point.x + citizen.dx) * cellSize, (point.y + citizen.dy) * cellSize
        }
    }

好吧,我仍然不确定原始 javascript 代码到底在做什么。然而,我设法根据我通过它学到的知识制作了我自己的 Voronoi 图生成器。这里是。谢谢大家的帮助。

"""
Working Voronoi Diagram Generator

"""

import random as r
import math as m
import pygame as pg
from pygame import gfxdraw


def makeMap(numPoints, mapSize):

    # Generate random colors so I can see what's happened.
    colors = []
    for color in range(numPoints):
        red = r.randint(0, 255)
        grn = r.randint(0, 255)
        blu = r.randint(0, 255)
        colors.append((red, grn, blu))

    # Generate the base points.
    ct = 0
    basePoints = []
    for point in range(numPoints):
        x = r.randint(0, mapSize)
        y = r.randint(0, mapSize)
        basePoints.append((x, y, colors[ct]))
        ct += 1

    # Generate all the other points on the map.
    points = []
    for x in range(mapSize):
        for y in range(mapSize):
            distance = mapSize * 2
            for bp in basePoints:
                newDistance = m.sqrt(((x - bp[0]) ** 2) + ((y - bp[1]) ** 2))
                if newDistance < distance:
                    distance = newDistance
                    color = bp[2]
            points.append([x, y, color])

    return points


def colorer(surf, points):
    for p in points:
        gfxdraw.pixel(surf, p[0], p[1], p[2])


def main():
    # Get points.
    points = makeMap(20, 400)
    print("points: ", points)

    # Setup pygame screens.
    pg.init()
    size = (400, 400)
    surf = pg.display.set_mode(size)

    done = False

    # Control fps of window.
    clock = pg.time.Clock()
    fps = 40

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True

        colorer(surf, points)

        pg.display.flip()

        # Control FPS.
        clock.tick(fps)


if __name__ == "__main__":
    main()

这是固定版本。作者使用 dictionaries/associative 数组作为点和 lowestDelta,但它也适用于列表和元组。我只是把代码改成了"push the cell to the nearest point",在绘图循环中加入了市民点数的偏移量。顺便说一句,因为你只需要绘制一次点,所以你可以在 while 循环之前做,使代码更高效。

import random
import pygame
from pygame import gfxdraw


def definePoints(numPoints, mapSize):
    points = []
    # we want to take a group of points that will fit on our map at random
    for typ in range(numPoints):
        # here's the random points
        x = random.randrange(0, mapSize)
        y = random.randrange(0, mapSize)
        # "type:" decides what point it is
        # x, y: location
        # citizens: the cells in our grid that belong to this point
        points.append([typ, x, y, []])

    # brute force-y but it works
    # for each cell in the grid
    for x in range(mapSize):
        for y in range(mapSize):
            # find the nearest point
            lowestDelta = (0, mapSize * mapSize)
            for p in range(len(points)):
                # for each point get the difference in distance
                # between our point and the current cell
                delta = abs(points[p][1] - x) + abs(points[p][2] - y)
                # store the point nearest if it's closer than the last one
                if delta < lowestDelta[1]:
                    lowestDelta = (p, delta)

            # push the cell to the nearest point
            activePoint = points[lowestDelta[0]]
            dx = x - activePoint[1]
            dy = y - activePoint[2]
            activePoint[3].append((dx, dy))

    return points


def main():
    pygame.init()
    size = (400, 400)
    screen = pygame.display.set_mode(size)
    white = pygame.Color('white')

    done = False
    clock = pygame.time.Clock()
    fps = 40

    points = definePoints(50, 400)

    # Draw the points before the while loop starts. No need to draw them again.
    for point in points:
        # Use different random colors for the points.
        color = (random.randrange(256), random.randrange(256),
                 random.randrange(256))
        for p in point[3]:
            # Draw the citizen points.
            # Add the offset of the citizen to the positions of the points.
            gfxdraw.pixel(screen, point[1]+p[0], point[2]+p[1], color)
        # draw white wherever there's a point.
        gfxdraw.pixel(screen, point[1], point[2], white)

    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

        clock.tick(fps)
        pygame.display.flip()


if __name__ == '__main__':
    main()