j4g
j4g

Reputation: 31

Problem with finding the closest intersection

The rays keep casting on the wrong "wall", but only if the lamp is more in the bottom right. If the lamp is in the left up corner everything is working fine.

I have tried a lot of things, but last time I had a problem I wrote I checked the formulars many times and in the end it was a problem with the formular so I am not even gonne try

(https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection)

function for finding the closest wall:

    def draw(self):
        bestdist = 1000000000000000000
        for obs in run.Obs:
            x1, y1 = obs.startp
            x2, y2 = obs.endp
            x3, y3 = run.lamp
            x4, y4 = self.maxendpoint

            d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
            if d != 0:
                t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / d
                u = ((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / d
                if 0 < t < 1 and u > 0:
                    px = round(x1 + t * (x2 - x1))
                    py = round(y1 + t * (y2 - y1))
                    dist = px**2+py**2
                    if dist < bestdist:
                        bestdist = dist
                        self.endpoint= [px, py]
                    # pygame.draw.circle(run.screen, pygame.Color('green'), (px, py), 3)
        if len(self.endpoint) == 2:
            pygame.draw.line(run.screen, pygame.Color('white'), run.lamp, self.endpoint)

the whole code:

import pygame
import sys
import math
import random as rd
import numpy as np

class Obs(object):
    def __init__(self, startp, endp):
        self.startp = startp
        self.endp = endp

    def drawww(self):
        pygame.draw.line(run.screen, pygame.Color('red'), (self.startp), (self.endp))


class rays(object):
    def __init__(self, maxendpoint):
        self.maxendpoint = maxendpoint
        self.endpoint = []

    def draw(self):
        bestdist = 1000000000000000000
        for obs in run.Obs:
            x1, y1 = obs.startp
            x2, y2 = obs.endp
            x3, y3 = run.lamp
            x4, y4 = self.maxendpoint

            d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
            if d != 0:
                t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / d
                u = ((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / d
                if 0 < t < 1 and u > 0:
                    px = round(x1 + t * (x2 - x1))
                    py = round(y1 + t * (y2 - y1))
                    dist = px**2+py**2
                    if dist < bestdist:
                        bestdist = dist
                        self.endpoint= [px, py]
                    # pygame.draw.circle(run.screen, pygame.Color('green'), (px, py), 3)
        if len(self.endpoint) == 2:
            pygame.draw.line(run.screen, pygame.Color('white'), run.lamp, self.endpoint)


class Control(object):
    def __init__(self):
        self.winw = 800
        self.winh = 800
        self.screen = pygame.display.set_mode((self.winw, self.winh))
        self.fps = 60
        self.clock = pygame.time.Clock()
        self.lamp = [round(self.winw/2), round(self.winh/2)]
        self.lampr = 13
        self.lines = []
        self.r = 5
        self.Obs = []
        self.angel = 0
        self.fov = 360
        self.scene = np.ones(self.fov)
        self.done = False
        self.makeobs()

    def event_loop(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.done = True
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_F5:
                    self.__init__()
                elif event.key == pygame.K_LEFT:
                    if self.angel <= 0:
                        self.angel = 360
                    else:
                        self.angel -= 5
                elif event.key == pygame.K_RIGHT:
                    if self.angel >= 360:
                        self.angel = 0
                    else:
                        self.angel += 5
                elif event.key == pygame.K_w:
                    self.lamp[1] -= 10
                elif event.key == pygame.K_a:
                    self.lamp[0] -= 10
                elif event.key == pygame.K_s:
                    self.lamp[1] += 10
                elif event.key == pygame.K_d:
                    self.lamp[0] += 10
                elif event.key == pygame.K_y:
                    pass

        if pygame.mouse.get_pressed() == (1, 0, 0):
            if pygame.mouse.get_pos()[0] > 800:
                self.lamp = [799, pygame.mouse.get_pos()[1]]
            else:
                self.lamp[0] = pygame.mouse.get_pos()[0]
                self.lamp[1] = pygame.mouse.get_pos()[1]

    def draw(self):
        self.screen.fill((pygame.Color('black')))
        pygame.draw.circle(self.screen, pygame.Color('white'), self.lamp, self.lampr)
        for obs in self.Obs:
            obs.drawww()
        for line in self.lines:
            line.draw()

    def makeobs(self):
        for i in range(2):
            self.Obs.append(Obs((rd.randint(0, self.winw), rd.randint(0, self.winh)),
                                (rd.randint(0, self.winw), rd.randint(0, self.winh))))
        # self.Obs.append(Obs((0, 0), (self.winw, 0)))
        # self.Obs.append(Obs((0, 0), (0, self.winh)))
        # self.Obs.append(Obs((self.winw, 0), (self.winw, self.winh)))
        # self.Obs.append(Obs((0, self.winh), (self.winw, self.winh)))

    def createlines(self):
        self.lines.clear()
        for angle in range(self.angel, self.angel+self.fov):
            angle = math.radians(angle)
            self.lines.append(rays([self.lamp[0] + math.cos(angle), self.lamp[1] + math.sin(angle)]))

    def main_loop(self):
        while not self.done:
            self.event_loop()
            self.createlines()
            self.draw()
            pygame.display.update()
            self.clock.tick(self.fps)
            pygame.display.set_caption(f"Draw  FPS: {self.clock.get_fps()}")


if __name__ == '__main__':
    run = Control()
    run.main_loop()
    pygame.quit()
    sys.exit()

Expected it to work no matter where the lamp is.

Upvotes: 1

Views: 308

Answers (1)

Rabbid76
Rabbid76

Reputation: 210889

The code

px = round(x1 + t * (x2 - x1))
py = round(y1 + t * (y2 - y1))
dist = px**2+py**2

doesn't make any sense, because (py, py) is a point, and so dist = px**2+py**2 is the squared Euclidean distance from the origin (0, 0) to (py, py).

You've to calculate the distance from (x3, x4) to the intersection point:

vx = u * (x4 - x3)
vy = u * (y4 - y3)
dist = vx**2+vy**2

Further there is an issue when in the calculation of u:

u = ((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / d
u = ((x1 - x3) * (y1 - y2) - (y1 - y3) * (x1 - x2)) / d

Method class rays:

class rays(object):
    def __init__(self, maxendpoint):
        self.maxendpoint = maxendpoint
        self.endpoint = []

    def draw(self):
        bestdist = 1000000000000000000
        for obs in run.Obs:
            x1, y1 = obs.startp
            x2, y2 = obs.endp
            x3, y3 = run.lamp
            x4, y4 = self.maxendpoint

            d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
            if d != 0:
                t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / d
                u = ((x1 - x3) * (y1 - y2) - (y1 - y3) * (x1 - x2)) / d
                if 0 < t < 1 and u > 0:
                    vx = u * (x4 - x3)
                    vy = u * (y4 - y3)
                    dist = vx**2+vy**2
                    if dist < bestdist:
                        px = round(x3 + u * (x4 - x3))
                        py = round(y3 + u * (y4 - y3))
                        bestdist = dist
                        self.endpoint= [px, py]
                    # pygame.draw.circle(run.screen, pygame.Color('green'), (px, py), 3)
        if len(self.endpoint) == 2:
            pygame.draw.line(run.screen, pygame.Color('white'), run.lamp, self.endpoint)

Minimal example: repl.it/@Rabbid76/PyGame-IntersectAndCutLines

import pygame
import math
import random

def intersect(obstacles, P0, P1):
    bestdist = 1000000000000000000
    endpoint = P1
    for Q0, Q1 in obstacles:
        d = (P1[0]-P0[0]) * (Q1[1]-Q0[1]) + (P1[1]-P0[1]) * (Q0[0]-Q1[0]) 
        if d != 0:
            t = ((Q0[0]-P0[0]) * (Q1[1]-Q0[1]) + (Q0[1]-P0[1]) * (Q0[0]-Q1[0])) / d
            u = ((Q0[0]-P0[0]) * (P1[1]-P0[1]) + (Q0[1]-P0[1]) * (P0[0]-P1[0])) / d
            if 0 <= t <= 1 and 0 <= u <= 1:
                vx, vy = (P1[0]-P0[0]) * t, (P1[1]-P0[1]) * t
                dist = vx*vx + vy*vy
                if dist < bestdist:
                    px, py = round(Q1[0] * u + Q0[0] * (1-u)), round(Q1[1] * u + Q0[1] * (1-u))
                    bestdist = dist
                    endpoint = (px, py)
    return endpoint

def createRays(center):
    return [(center[0] + 1200 * math.cos(angle), center[1] + 1200 * math.sin(angle)) for angle in range(0, 360, 10)]

def createObstacles(surface):
    w, h = surface.get_size()
    return [((random.randrange(w), random.randrange(h)), (random.randrange(w), random.randrange(h))) for _ in range(5)]

window = pygame.display.set_mode((800, 800))
clock = pygame.time.Clock()

origin = window.get_rect().center
rays = createRays(origin)
obstacles = createObstacles(window)

move_center = True
run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.MOUSEBUTTONDOWN:
            obstacles = createObstacles(window) 
        if event.type == pygame.KEYDOWN:
            move_center = not move_center

    if move_center:
        origin = pygame.mouse.get_pos()
        rays = createRays(origin) 
        
    window.fill(0)
    for endpoint in rays:
        endpoint = intersect(obstacles, origin, endpoint)
        pygame.draw.line(window, (128, 128, 128), origin, endpoint)
    pygame.draw.circle(window, (255, 255, 255), origin, 10)
    for start, end in obstacles:
        pygame.draw.line(window, (255, 0, 0), start, end)
    pygame.display.flip()

pygame.quit()
exit()

Upvotes: 1

Related Questions