0

I'm trying to translate code that generates a Voronoi Diagram from Javascript into Python. This is a struggle because I don't know Javascript. I think I can sort-of make it out, but I'm still running into issues with things I don't understand. Please help me figure out what is wrong with my code.

The code I've written simply give a blank window. Please help me generate a Voronoi Diagram.

In order, I have my code, then the original code.

Here's a link to the website I found it at: Procedural Generation Wiki

My Code:

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

Original Example Code:

//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
        }
    }
LuminousNutria
  • 1,883
  • 2
  • 18
  • 44
  • One difference is that the author uses dictionaries/associative arrays for the points and `lowestDelta`, but it should work with lists (as in your version) as well. Also, you need to call `pygame.display.flip()` to update the display after you've drawn the pixels. However, after changing these things, I still see only some white points on the screen. – skrx Apr 02 '18 at 04:46
  • I just had to add the `dx` and `dy` to the positions of the points to make it work. I'll post a working example. – skrx Apr 02 '18 at 05:03

2 Answers2

1

Okay, I'm still not sure exactly what the original javascript code is doing. However, I managed to make my own Voronoi Diagram generator based off what I learned by going through it. Here it is. Thank you all for trying to help.

"""
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()
LuminousNutria
  • 1,883
  • 2
  • 18
  • 44
1

Here's the fixed version. The author uses dictionaries/associative arrays for the points and lowestDelta, but it works with lists and tuples as well. I only changed the code to "push the cell to the nearest point" and add the offset of the citizen points in the drawing loop. BTW, since you only need to draw the points once, you can do it before the while loop to make the code more efficient.

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()
skrx
  • 19,980
  • 5
  • 34
  • 48