1
0
Fork 0
mirror of https://github.com/HackerPoet/PySpace synced 2025-08-22 10:32:35 +02:00
fractals/ray_marcher_demo.py
2019-07-27 14:50:52 -05:00

432 lines
12 KiB
Python
Executable file

#!/usr/bin/env python
import pygame, sys, math, random, os
import numpy as np
import pyspace
from pyspace.coloring import *
from pyspace.fold import *
from pyspace.geo import *
from pyspace.object import *
from pyspace.shader import Shader
from pyspace.camera import Camera
from ctypes import *
from OpenGL.GL import *
from pygame.locals import *
import os
os.environ['SDL_VIDEO_CENTERED'] = '1'
#Size of the window and rendering
win_size = (1280, 720)
#Maximum frames per second
max_fps = 30
#Forces an 'up' orientation when True, free-camera when False
gimbal_lock = False
#Mouse look speed
look_speed = 0.003
#Use this avoids collisions with the fractal
auto_velocity = True
auto_multiplier = 2.0
#Maximum velocity of the camera
max_velocity = 2.0
#Amount of acceleration when moving
speed_accel = 2.0
#Velocity decay factor when keys are released
speed_decel = 0.6
clicking = False
mouse_pos = None
screen_center = (win_size[0]/2, win_size[1]/2)
start_pos = [0, 0, 12.0]
vel = np.zeros((3,), dtype=np.float32)
look_x = 0.0
look_y = 0.0
#----------------------------------------------
# When building your own fractals, you can
# substitute numbers with string placeholders
# which can be tuned real-time with key bindings.
#
# In this example program:
# '0' +Insert -Delete
# '1' +Home -End
# '2' +PageUp -PageDown
# '3' +NumPad7 -NumPad4
# '4' +NumPad8 -NumPad5
# '5' +NumPad9 -NumPad6
#
# Hold down left-shift to decrease rate 10x
# Hold down right-shift to increase rate 10x
#
# Set initial values of '0' through '6' below
#----------------------------------------------
keyvars = [1.5, 1.5, 2.0, 1.0, 1.0, 1.0]
#----------------------------------------------
# Fractal Examples Below
#----------------------------------------------
def infinite_spheres():
obj = Object()
obj.add(FoldRepeatX(2.0))
obj.add(FoldRepeatY(2.0))
obj.add(FoldRepeatZ(2.0))
obj.add(Sphere(0.5, (1.0, 1.0, 1.0), color=(0.9,0.9,0.5)))
return obj
def butterweed_hills():
obj = Object()
obj.add(OrbitInitZero())
for _ in range(30):
obj.add(FoldAbs())
obj.add(FoldScaleTranslate(1.5, (-1.0,-0.5,-0.2)))
obj.add(OrbitSum((0.5, 0.03, 0.0)))
obj.add(FoldRotateX(3.61))
obj.add(FoldRotateY(2.03))
obj.add(Sphere(1.0, color='orbit'))
return obj
def mandelbox():
obj = Object()
obj.add(OrbitInitInf())
for _ in range(16):
obj.add(FoldBox(1.0))
obj.add(FoldSphere(0.5, 1.0))
obj.add(FoldScaleOrigin(2.0))
obj.add(OrbitMinAbs(1.0))
obj.add(Box(6.0, color='orbit'))
return obj
def mausoleum():
obj = Object()
obj.add(OrbitInitZero())
for _ in range(8):
obj.add(FoldBox(0.34))
obj.add(FoldMenger())
obj.add(FoldScaleTranslate(3.28, (-5.27,-0.34,0.0)))
obj.add(FoldRotateX(math.pi/2))
obj.add(OrbitMax((0.42,0.38,0.19)))
obj.add(Box(2.0, color='orbit'))
return obj
def menger():
obj = Object()
for _ in range(8):
obj.add(FoldAbs())
obj.add(FoldMenger())
obj.add(FoldScaleTranslate(3.0, (-2,-2,0)))
obj.add(FoldPlane((0,0,-1), -1))
obj.add(Box(2.0, color=(.2,.5,1)))
return obj
def tree_planet():
obj = Object()
obj.add(OrbitInitInf())
for _ in range(30):
obj.add(FoldRotateY(0.44))
obj.add(FoldAbs())
obj.add(FoldMenger())
obj.add(OrbitMinAbs((0.24,2.28,7.6)))
obj.add(FoldScaleTranslate(1.3, (-2,-4.8,0)))
obj.add(FoldPlane((0,0,-1), 0))
obj.add(Box(4.8, color='orbit'))
return obj
def sierpinski_tetrahedron():
obj = Object()
obj.add(OrbitInitZero())
for _ in range(9):
obj.add(FoldSierpinski())
obj.add(FoldScaleTranslate(2, -1))
obj.add(Tetrahedron(color=(0.8,0.8,0.5)))
return obj
def snow_stadium():
obj = Object()
obj.add(OrbitInitInf())
for _ in range(30):
obj.add(FoldRotateY(3.33))
obj.add(FoldSierpinski())
obj.add(FoldRotateX(0.15))
obj.add(FoldMenger())
obj.add(FoldScaleTranslate(1.57, (-6.61, -4.0, -2.42)))
obj.add(OrbitMinAbs(1.0))
obj.add(Box(4.8, color='orbit'))
return obj
def test_fractal():
obj = Object()
obj.add(OrbitInitInf())
for _ in range(20):
obj.add(FoldSierpinski())
obj.add(FoldMenger())
obj.add(FoldRotateY(math.pi/2))
obj.add(FoldAbs())
obj.add(FoldRotateZ('0'))
obj.add(FoldScaleTranslate(1.89, (-7.10, 0.396, -6.29)))
obj.add(OrbitMinAbs((1,1,1)))
obj.add(Box(6.0, color='orbit'))
return obj
#----------------------------------------------
# Helper Utilities
#----------------------------------------------
def interp_data(x, f=2.0):
new_dim = int(x.shape[0]*f)
output = np.empty((new_dim,) + x.shape[1:], dtype=np.float32)
for i in range(new_dim):
a, b1 = math.modf(float(i) / f)
b2 = min(b1 + 1, x.shape[0] - 1)
output[i] = x[int(b1)]*(1-a) + x[int(b2)]*a
return output
def make_rot(angle, axis_ix):
s = math.sin(angle)
c = math.cos(angle)
if axis_ix == 0:
return np.array([[ 1, 0, 0],
[ 0, c, -s],
[ 0, s, c]], dtype=np.float32)
elif axis_ix == 1:
return np.array([[ c, 0, s],
[ 0, 1, 0],
[-s, 0, c]], dtype=np.float32)
elif axis_ix == 2:
return np.array([[ c, -s, 0],
[ s, c, 0],
[ 0, 0, 1]], dtype=np.float32)
def reorthogonalize(mat):
u, s, v = np.linalg.svd(mat)
return np.dot(u, v)
# move the cursor back , only if the window is focused
def center_mouse():
if pygame.key.get_focused():
pygame.mouse.set_pos(screen_center)
#--------------------------------------------------
# Video Recording
#
# When you're ready to record a video, press 'r'
# to start recording, and then move around. The
# camera's path and live '0' through '5' parameters
# are recorded to a file. Press 'r' when finished.
#
# Now you can exit the program and turn up the
# camera parameters for better rendering. For
# example; window size, anti-aliasing, motion blur,
# and depth of field are great options.
#
# When you're ready to playback and render, press
# 'p' and the recorded movements are played back.
# Images are saved to a './playback' folder. You
# can import the image sequence to editing software
# to convert it to a video.
#
# You can press 's' anytime for a screenshot.
#---------------------------------------------------
if __name__ == '__main__':
pygame.init()
window = pygame.display.set_mode(win_size, OPENGL | DOUBLEBUF)
pygame.mouse.set_visible(False)
center_mouse()
#======================================================
# Change the fractal here
#======================================================
obj_render = tree_planet()
#======================================================
#======================================================
# Change camera settings here
# See pyspace/camera.py for all camera options
#======================================================
camera = Camera()
camera['ANTIALIASING_SAMPLES'] = 1
camera['AMBIENT_OCCLUSION_STRENGTH'] = 0.01
#======================================================
shader = Shader(obj_render)
program = shader.compile(camera)
print("Compiled!")
matID = glGetUniformLocation(program, "iMat")
prevMatID = glGetUniformLocation(program, "iPrevMat")
resID = glGetUniformLocation(program, "iResolution")
ipdID = glGetUniformLocation(program, "iIPD")
glUseProgram(program)
glUniform2fv(resID, 1, win_size)
glUniform1f(ipdID, 0.04)
fullscreen_quad = np.array([-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 0.0], dtype=np.float32)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, fullscreen_quad)
glEnableVertexAttribArray(0)
mat = np.identity(4, np.float32)
mat[3,:3] = np.array(start_pos)
prevMat = np.copy(mat)
for i in range(len(keyvars)):
shader.set(str(i), keyvars[i])
recording = None
rec_vars = None
playback = None
playback_vars = None
playback_ix = -1
frame_num = 0
def start_playback():
global playback
global playback_vars
global playback_ix
global prevMat
if not os.path.exists('playback'):
os.makedirs('playback')
playback = np.load('recording.npy')
playback_vars = np.load('rec_vars.npy')
playback = interp_data(playback, 2)
playback_vars = interp_data(playback_vars, 2)
playback_ix = 0
prevMat = playback[0]
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit(0)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
if recording is None:
print("Recording...")
recording = []
rec_vars = []
else:
np.save('recording.npy', np.array(recording, dtype=np.float32))
np.save('rec_vars.npy', np.array(rec_vars, dtype=np.float32))
recording = None
rec_vars = None
print("Finished Recording.")
elif event.key == pygame.K_p:
start_playback()
elif event.key == pygame.K_c:
pygame.image.save(window, 'screenshot.png')
elif event.key == pygame.K_ESCAPE:
sys.exit(0)
mat[3,:3] += vel * (clock.get_time() / 1000)
if auto_velocity:
de = obj_render.DE(mat[3]) * auto_multiplier
if not np.isfinite(de):
de = 0.0
else:
de = 1e20
all_keys = pygame.key.get_pressed()
rate = 0.01
if all_keys[pygame.K_LSHIFT]: rate *= 0.1
elif all_keys[pygame.K_RSHIFT]: rate *= 10.0
if all_keys[pygame.K_INSERT]: keyvars[0] += rate; print(keyvars)
if all_keys[pygame.K_DELETE]: keyvars[0] -= rate; print(keyvars)
if all_keys[pygame.K_HOME]: keyvars[1] += rate; print(keyvars)
if all_keys[pygame.K_END]: keyvars[1] -= rate; print(keyvars)
if all_keys[pygame.K_PAGEUP]: keyvars[2] += rate; print(keyvars)
if all_keys[pygame.K_PAGEDOWN]: keyvars[2] -= rate; print(keyvars)
if all_keys[pygame.K_KP7]: keyvars[3] += rate; print(keyvars)
if all_keys[pygame.K_KP4]: keyvars[3] -= rate; print(keyvars)
if all_keys[pygame.K_KP8]: keyvars[4] += rate; print(keyvars)
if all_keys[pygame.K_KP5]: keyvars[4] -= rate; print(keyvars)
if all_keys[pygame.K_KP9]: keyvars[5] += rate; print(keyvars)
if all_keys[pygame.K_KP6]: keyvars[5] -= rate; print(keyvars)
if playback is None:
prev_mouse_pos = mouse_pos
mouse_pos = pygame.mouse.get_pos()
dx,dy = 0,0
if prev_mouse_pos is not None:
center_mouse()
time_rate = (clock.get_time() / 1000.0) / (1 / max_fps)
dx = (mouse_pos[0] - screen_center[0]) * time_rate
dy = (mouse_pos[1] - screen_center[1]) * time_rate
if pygame.key.get_focused():
if gimbal_lock:
look_x += dx * look_speed
look_y += dy * look_speed
look_y = min(max(look_y, -math.pi/2), math.pi/2)
rx = make_rot(look_x, 1)
ry = make_rot(look_y, 0)
mat[:3,:3] = np.dot(ry, rx)
else:
rx = make_rot(dx * look_speed, 1)
ry = make_rot(dy * look_speed, 0)
mat[:3,:3] = np.dot(ry, np.dot(rx, mat[:3,:3]))
mat[:3,:3] = reorthogonalize(mat[:3,:3])
acc = np.zeros((3,), dtype=np.float32)
if all_keys[pygame.K_a]:
acc[0] -= speed_accel / max_fps
if all_keys[pygame.K_d]:
acc[0] += speed_accel / max_fps
if all_keys[pygame.K_w]:
acc[2] -= speed_accel / max_fps
if all_keys[pygame.K_s]:
acc[2] += speed_accel / max_fps
if np.dot(acc, acc) == 0.0:
vel *= speed_decel # TODO
else:
vel += np.dot(mat[:3,:3].T, acc)
vel_ratio = min(max_velocity, de) / (np.linalg.norm(vel) + 1e-12)
if vel_ratio < 1.0:
vel *= vel_ratio
if all_keys[pygame.K_SPACE]:
vel *= 10.0
if recording is not None:
recording.append(np.copy(mat))
rec_vars.append(np.array(keyvars, dtype=np.float32))
else:
if playback_ix >= 0:
ix_str = '%04d' % playback_ix
pygame.image.save(window, 'playback/frame' + ix_str + '.png')
if playback_ix >= playback.shape[0]:
playback = None
break
else:
mat = prevMat * 0.98 + playback[playback_ix] * 0.02
mat[:3,:3] = reorthogonalize(mat[:3,:3])
keyvars = playback_vars[playback_ix].tolist()
playback_ix += 1
for i in range(3):
shader.set(str(i), keyvars[i])
shader.set('v', np.array(keyvars[3:6]))
shader.set('pos', mat[3,:3])
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glUniformMatrix4fv(matID, 1, False, mat)
glUniformMatrix4fv(prevMatID, 1, False, prevMat)
prevMat = np.copy(mat)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
pygame.display.flip()
clock.tick(max_fps)
frame_num += 1
print(clock.get_fps())