[[nichteuklidische_geometrie|← Back to project page]]
====== Sourcecode ======
Download the release version here:\\
{{:ss20:neg-world-engine.zip}}
Or download the newest version from the Git repository:\\
[[https://gitlab.tubit.tu-berlin.de/srather/NEG-World-Engine.git]]
==== Folder Structure: ====
**[[neg_sourcecode|Root]]:** [[neg_sourcecode#readme|README]], [[neg_sourcecode#main|main]]
* **[[neg_datatypes|Datatypes]]:** [[neg_datatypes#vector|Vector]], [[neg_datatypes#matrix|Matrix]]
* **[[neg_drawables|Drawables]]:** [[neg_drawables#drawable|drawable]], [[neg_drawables#ray|Ray]], [[neg_drawables#polygon|Polygon]], [[neg_drawables#floor|Floor]], [[neg_drawables#portal|Portal]]
* **[[neg_utility|Utility]]:** [[neg_utility#colors|colors]], [[neg_utility#generate|generate]], [[neg_utility#draw|draw]]
===== Root =====
----
==== README ====
# NEG-World-Engine
> A 2D Non-Euclidean Engine bases on portals

**Dependencies:**
- Python 3.7.7 or newer (get it [here](https://www.python.org/downloads/))
- PyGame 1.9.6 or newer (get it via `pip install pygame`)
**Setup:**
- Download the sourcecode ([here](https://gitlab.tubit.tu-berlin.de/srather/NEG-World-Engine.git))
- Unpack the downloaded archive
- execute `main.py`
**Controls:**
- Move with WASD, the arrow keys or by clicking with your mouse
- Press the number keys 0-9 to explore different worlds
====== NEG-World-Engine ======
> A 2D Non-Euclidean Engine bases on portals
{{:ss20:icon.png?nolink|}}
**Dependencies:**
* Python 3.7.7 or newer (get it [[https://www.python.org/downloads|here]])
* PyGame 1.9.6 or newer (get it via ''pip install pygame'')
**Setup:**
* Download the sourcecode ([[https://gitlab.tubit.tu-berlin.de/srather/NEG-World-Engine.git|here]])
* Unpack the downloaded archive
* execute ''main.py''
**Controls:**
* Move with WASD, the arrow keys or by clicking with your mouse
* Press the number keys 0-9 to explore different worlds
[[#top|↑ Back to top]]
----
===== main =====
import pygame
import math
import sys
sys.path.append(".\\datatypes")
sys.path.append(".\\drawables")
sys.path.append(".\\utility")
import generate
import draw
from vector import *
from polygon import *
from portal import *
from floor import *
# global variables
WINDOW = Vector(1280, 960)
POSITION = Vector(0, 0)
ZOOM = 25
SPEED = 10
FPS = 70
DEPTH = 0
current_fps = FPS
current_speed = Vector(0, 0)
# generate worlds
def gen4():
"""
Generate world 4 (and 8).
Return a big polygon with sides as mirrors and a
slightly smaller one with polygons on its vertices.
"""
def mirror(m, v):
v=Vector(*v)
return 2 * v.dot(m) / m.dot(m) * m - v
w = []
pol = generate.polygon(4, (0, 0), 500)
for i, _ in enumerate(pol):
w.append(generate.polygon(i+3, 0.5 * pol[i], 50, phase=0.5))
for i, _ in enumerate(pol):
w.append(Portal(pol[i-1], pol[i], M=(mirror(pol[i]-pol[i-1],(1,0)), mirror(pol[i]-pol[i-1],(0,1)))))
return w
def gen6():
"""
Generate additional objects for world 6.
Return the 2 houses with their doors.
"""
w = []
w += generate.border(generate.polygon(4, (0, 0), 200, phase=-0.5))[:-1]
w += generate.border(generate.polygon(4, (1000, 0), 200, phase=-0.5))
w += generate.door(Vector(0, -100*2**0.5), Vector(-100*2**0.5, -100*2**0.5), 0.5)
for point in generate.polygon(4, (0, 0), 100*2**0.5):
d = generate.door(point, Vector(0, 0), 0.5)
w.append(d[0]); w.append(d[1])
for point in generate.polygon(4, (1000, 0), 100*2**0.5):
d = generate.door(point, Vector(1000, 0), 0.5)
w.append(d[0]); w.append(d[1])
return w
worlds = [
# 1: short longer tunnel
[
generate.polygon(3, (-150, -200), 50),
Polygon(( 100,-100), (-100,-100)),
Polygon(( 100,-200), (-100,-200)),
Portal ((-100,-100), (-100,-200), M=((2.0,0), (0,1))),
Portal (( 100,-200), ( 100,-100), M=((2.0,0), (0,1))),
],
# 2: tilted tunnel
[
Polygon(( 200, 100), (-200, 200)),
Polygon(( 200, 200), (-200, 300)),
Portal ((-200, 300), (-200, 200), M=((0.5,0.25), (0,1))),
Portal (( 200, 100), ( 200, 200), M=((0.5,0.25), (0,1))),
],
#3: 1 mirror
[
generate.star(5, (200, -200), 50, phase=0.5),
generate.polygon(4, (0, -200), 50),
generate.polygon(7, (0, 800), 50, phase=0.5),
Portal((-200, -300), (-200, -100), M=((-1,0),(0,1)), M2=((1,0),(0,1)), offset2=(0, 1000)),
Portal((-200, 900), (-200, 700), M=((-1,0),(0,1)), M2=((1,0),(0,1)), offset2=(0, -1000)),
],
# 4: 4 mirrors
[
]
+ gen4(),
# 5: around the pole
[
generate.polygon(3, (0, -200), 50, phase=0.5, color=colors.DARK_RED),
generate.star(5, (2000, -200), 75, phase=0.5, color=colors.DARK_GREEN),
Portal((0, -50), (1000, -50), offset=(2000, 0), offset2=(2000, 0)),
Portal((2000, -50), (3000, -50), offset=(-2000, 0), offset2=(-2000, 0)),
],
# 6: big room
[
Polygon((100*2**0.5, -100*2**0.5), (0, -100*2**0.5)),
Portal((0, 100*2**0.5), (0, 0), offset=(1000,0), offset2=(1000,0)),
Portal((1000, 100*2**0.5), (1000, 0), offset=(-1000,0), offset2=(-1000,0)),
Floor((*generate.polygon(4, ( -100/2**0.5, -100/2**0.5), 99.9, phase=0.5)), color=colors.DARK_RED),
Floor((*generate.polygon(4, ( -100/2**0.5, 100/2**0.5), 99.9, phase=0.5)), color=colors.DARK_ORANGE),
Floor((*generate.polygon(4, (1000+100/2**0.5, 100/2**0.5), 99.9, phase=0.5)), color=colors.DARK_YELLOW),
Floor((*generate.polygon(4, (1000+100/2**0.5, -100/2**0.5), 99.9, phase=0.5)), color=colors.DARK_GREEN),
Floor((*generate.polygon(4, (1000-100/2**0.5, -100/2**0.5), 99.9, phase=0.5)), color=colors.DARK_CYAN),
Floor((*generate.polygon(4, (1000-100/2**0.5, 100/2**0.5), 99.9, phase=0.5)), color=colors.DARK_BLUE),
Floor((*generate.polygon(4, ( 100/2**0.5, 100/2**0.5), 99.9, phase=0.5)), color=colors.DARK_PURPLE),
Floor((*generate.polygon(4, ( 100/2**0.5, -100/2**0.5), 99.9, phase=0.5)), color=colors.DARK_MAGENTA),
]
+ gen6(),
# 7: shorter/longer tunnel bad fps
[
Polygon(( 100,-100), (-100,-100)),
Polygon(( 100,-200), (-100,-200)),
Portal ((-100,-100), (-100,-200), M=((2.0,0), (0,1))),
Portal (( 100,-200), ( 100,-100), M=((2.0,0), (0,1))),
Polygon(( 200, 100), (-200, 100)),
Polygon(( 200, 200), (-200, 200)),
Portal ((-200, 200), (-200, 100), M=((0.5,0), (0,1))),
Portal (( 200, 100), ( 200, 200), M=((0.5,0), (0,1))),
],
# 8: 4 mirors 2 depth
[
]
+ gen4(),
# 9: shoter/longer tunnel depth: 0
[
Polygon(( 100,-100), (-100,-100)),
Polygon(( 100,-200), (-100,-200)),
Portal ((-100,-100), (-100,-200), M=((2.0,0), (0,1))),
Portal (( 100,-200), ( 100,-100), M=((2.0,0), (0,1))),
Polygon(( 200, 100), (-200, 100)),
Polygon(( 200, 200), (-200, 200)),
Portal ((-200, 200), (-200, 100), M=((0.5,0), (0,1))),
Portal (( 200, 100), ( 200, 200), M=((0.5,0), (0,1))),
],
# 0: random assortment
[
Polygon((-30, 0)),
Polygon((10, 30), (30, 100)),
Polygon((100, -100), (200, -100)),
Polygon((-100, -100), (-100, -200), (-200, -100)),
Polygon((100, 100), (100, 200), (200, 200), (200, 100)),
Portal((300, -200), (200, -300), M=((-1,0),(0,-1))),
Portal((-100, 300), (100, 300), M=((1,0),(0,-0.5))),
],
]
def bake_physics(wrld):
"""
Return a list of all lines an points in 'wrld' which have a collision.
"""
lns = []
for object in wrld:
# ignore floor tiles
if type(object) is Floor:
continue
# dissasample polygons into lines
if isinstance(object, Polygon):
if len(object) <= 2:
lns.append(object)
continue
for i, _ in enumerate(object):
lns.append(object.__class__(object[i-1], object[i]))
return lns
# initialise
pygame.init()
window = pygame.display.set_mode(WINDOW)
pygame.display.set_caption("NEG World Engine")
pygame.display.set_icon(pygame.image.load('icon_small.png'))
clock = pygame.time.Clock()
world = [] # empty starting world
lines = bake_physics(world)
# main loop
running = True
while running:
# exit program
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# draw frame
window.fill(colors.GRAY)
draw.world(window, world, POSITION, DEPTH)
draw.player(window, POSITION, ZOOM)
draw.debug(window, 0, f"{current_fps:.0f} FPS", colors.BRIGHT_GREEN)
draw.debug(window, 1, f"{POSITION.x:.0f} X {POSITION.y:.0f} y", colors.BRIGHT_GREEN)
# check inputs
keys = pygame.key.get_pressed()
# change world if numbers are pressed
if keys[pygame.K_1]: world = worlds[0]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 2
if keys[pygame.K_2]: world = worlds[1]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 2
if keys[pygame.K_3]: world = worlds[2]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 1
if keys[pygame.K_4]: world = worlds[3]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 1
if keys[pygame.K_5]: world = worlds[4]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 1
if keys[pygame.K_6]: world = worlds[5]; lines = bake_physics(world); POSITION = Vector(0, -200); current_speed *= 0; DEPTH = 1
if keys[pygame.K_7]: world = worlds[6]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 1
if keys[pygame.K_8]: world = worlds[7]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 2
if keys[pygame.K_9]: world = worlds[8]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 0
if keys[pygame.K_0]: world = worlds[9]; lines = bake_physics(world); POSITION *= 0; current_speed *= 0; DEPTH = 1
# change speed if arrows are pressed
force = Vector(0, 0)
if keys[pygame.K_LEFT ] or keys[pygame.K_a]: force += Vector(-1, 0)
if keys[pygame.K_RIGHT] or keys[pygame.K_d]: force += Vector( 1, 0)
if keys[pygame.K_UP ] or keys[pygame.K_w]: force += Vector( 0, -1)
if keys[pygame.K_DOWN ] or keys[pygame.K_s]: force += Vector( 0, 1)
force = force.normalised()
# change speed if left mouse button is pressed
if pygame.mouse.get_focused():
if pygame.mouse.get_pressed()[0]:
force += (Vector(*pygame.mouse.get_pos()) - WINDOW / 2).normalised()
# apply changes (not opimal, movement should be independant of framerate)
current_speed += force.normalised() * SPEED / max(current_fps, 0.1)
current_speed *= SPEED ** -(1 / max(current_fps, 60))
POSITION += current_speed
# check physics (not optimal, player can glitch into things)
for line in lines:
if line.dist_to(POSITION) < ZOOM/2:
if (POSITION - line[0]).length() < ZOOM/2:
b_a = (POSITION - current_speed - line[0]).normal()
elif (POSITION - line[1]).length() < ZOOM/2:
b_a = (POSITION - current_speed - line[1]).normal()
elif type(line) is Polygon:
b_a = line[1] - line[0]
else:
continue
# removes the part of the speed vector which causes collision
POSITION -= current_speed
current_speed = b_a * (b_a.dot(current_speed) / b_a.dot(b_a))
POSITION += current_speed
# check if gone through portal
move = Ray(POSITION - current_speed, current_speed)
for object in world:
if type(object) is Portal:
# intersect portal with movement ray
port = Ray(object[0], object[1] - object[0])
point = move.intersect(port)
if point: # not empty
if move.value_at(*point) <= 1 and port.value_at(*point) <= 1:
# change world to portal world
world = object.go_through(move.dir, world)
lines = bake_physics(world)
# wait tick and update
clock.tick(FPS)
current_fps = clock.get_fps()
pygame.display.flip()
# exit routine
pygame.quit()
[[#top|↑ Back to top]]