Skip to content

Instantly share code, notes, and snippets.

@Centrinia
Created January 12, 2016 18:41
Show Gist options
  • Save Centrinia/0d13d2f9f91b11ddae33 to your computer and use it in GitHub Desktop.
Save Centrinia/0d13d2f9f91b11ddae33 to your computer and use it in GitHub Desktop.
Frustum Viewer
#version 430
struct atlas_entry {
ivec2 pos;
ivec2 size;
};
uniform Atlas {
atlas_entry entries[1024];
} uAtlas;
uniform vec4 uBoxColor;
void main() {
gl_FragColor = uBoxColor;
}
#version 430
in vec3 aPosition;
uniform mat4 uModelView;
uniform mat4 uPerspective;
uniform bool uProjectiveToggle;
uniform float uBoxNear;
uniform float uBoxFar;
void main() {
vec4 t = vec4(aPosition,1);
/* TODO: Perhaps specify the coordinates in the vertex buffer instead of here. */
float f = (t.z < 1) ? uBoxNear : uBoxFar;
t *= f;
/* If the rendering mode is not projective then project from clip space to R^3. */
if(!uProjectiveToggle) {
t.w = 1;
}
gl_Position = uPerspective * uModelView * t;
}
#version 430
in vec3 vNormal;
in vec2 vTextureCoordinate;
in vec4 vClipPosition;
flat in int vTextureId;
uniform sampler2D uShadowmap;
struct atlas_entry {
ivec2 pos;
ivec2 size;
};
uniform Atlas {
atlas_entry entries[1024];
} uAtlas;
void main() {
/* Clip this fragment against the primary clip planes. */
for(int i=0;i<3;i++) {
for(int j=0;j<2;j++) {
float t = j == 0 ? 1 : -1;
if(t * vClipPosition[i] + vClipPosition.w <= 0) {
discard;
}
}
}
/* Compute the moments from the shadowmap. */
vec2 moments = texture2D(uShadowmap, (vClipPosition.xy/vClipPosition.w + 1) / 2, 0).rg;
moments.g -= moments.r * moments.r;
float t = vClipPosition.z;
float p = step(t, moments.r);
float d = t - moments.r;
float p_max = moments.g/ (moments.g+d*d);
/* Compute the expected attenuation. */
float upper_bound = max(p,p_max);
/* Use the normal to flat color the faces. */
vec3 color = (vNormal+1)/2;
float attenuation = mix(0.1,1.0,upper_bound);
gl_FragColor = vec4(color*attenuation,1);
}
#version 430
in vec3 aPosition;
in vec3 aNormal;
in vec2 aTextureCoordinate;
in int aTextureId;
out vec3 vNormal;
out vec2 vTextureCoordinate;
out vec4 vClipPosition;
flat out int vTextureId;
//out float gl_ClipDistance[1];
uniform bool uProjectiveToggle;
uniform mat4 uModelView;
uniform mat4 uPerspective;
uniform mat4 uPrimaryModelView;
uniform mat4 uPrimaryPerspective;
void main() {
vTextureCoordinate = aTextureCoordinate;
vNormal = aNormal;
vTextureId = aTextureId;
vClipPosition = uPrimaryPerspective * uPrimaryModelView * vec4(aPosition,1);
vec4 t = vClipPosition;
/* If the rendering mode is not projective then project from clip space to R^3. */
if(!uProjectiveToggle) {
t.w = 1;
}
gl_Position = uPerspective * uModelView * t;
}
#version 430
in vec4 vClipPosition;
struct atlas_entry {
ivec2 pos;
ivec2 size;
};
uniform Atlas {
atlas_entry entries[1024];
} uAtlas;
void main() {
bool inside = true;
/* Clip the fragment against the primary clip planes. */
for(int i=0;i<3;i++) {
for(int j=0;j<2;j++) {
float t = j == 0 ? 1 : -1;
inside = inside && (t * vClipPosition[i] + vClipPosition.w > 0);
}
}
/* This fragment should be outside of the primary frustum. */
if(inside) {
discard;
}
/* TODO: Perhaps have the color in an uniform. */
gl_FragColor = vec4(vec3(1,1,1)/2,1);
}
#version 430
in vec3 aPosition;
in vec3 aNormal;
in vec2 aTextureCoordinate;
in int aTextureId;
out vec3 vNormal;
out vec2 vTextureCoordinate;
out vec4 vClipPosition;
flat out int vTextureId;
//out float gl_ClipDistance[1];
uniform bool uProjectiveToggle;
uniform mat4 uModelView;
uniform mat4 uPerspective;
uniform mat4 uPrimaryModelView;
uniform mat4 uPrimaryPerspective;
void main() {
vClipPosition = uPrimaryPerspective * uPrimaryModelView * vec4(aPosition,1);
vec4 t = vClipPosition;
if(!uProjectiveToggle) {
t.w = 1;
}
gl_Position = uPerspective * uModelView * t;
}
import pprint
import os
import sys
import numpy
import random
def pack_rectangles(shape,rects,mask=None):
dt = numpy.dtype([
('pos',numpy.int32,2),
('dim',numpy.int32,2)
])
F = numpy.array([((0,0),shape)],dtype=dt)
for R_index,R_dim in sorted(enumerate(rects),key=lambda x: (-x[1][0],-x[1][1])):
# Decide the free rectangle to pack R into.
candidate_indexes = numpy.all(R_dim <= F['dim'],axis=1)
F_candidates = F[candidate_indexes]
if F_candidates.size == 0:
continue
R_area = numpy.product(R_dim)
F0 = numpy.repeat(numpy.reshape(F_candidates['dim'],(-1,2,1)),3,axis=2)
F0[:,0,0] -= R_dim[0]
F0[:,1,0] = R_dim[1]
F0[:,0,1] = R_dim[0]
F0[:,1,1] -= R_dim[1]
F0[:,:,2] -= R_dim
F0 = numpy.min(F0,axis=1)
Fi_areas = numpy.min(F0, axis=1)
i = numpy.argmin(Fi_areas)
# Best Area Fit
Fi = F_candidates[i]
B = numpy.array((Fi['pos'],R_dim),dtype=dt)
yield R_index,B
# Subdivide Fi into F' and F''
F0 = Fi.copy()
F1 = Fi.copy()
F0['pos'][0] += R_dim[0]
F0['dim'][0] -= R_dim[0]
F1['pos'][1] += R_dim[1]
F1['dim'][1] -= R_dim[1]
if numpy.prod(F0['dim']) < numpy.prod(F1['dim']):
F0['dim'][1] = R_dim[1]
else:
F1['dim'][0] = R_dim[0]
F = numpy.delete(F,numpy.flatnonzero(candidate_indexes)[i],axis=0)
if numpy.all(F0['dim'] != 0):
F = numpy.append(F,F0)
if numpy.all(F1['dim'] != 0):
F = numpy.append(F,F1)
n = F.shape[0]
for i in range(n):
for idx in range(2):
mergable = (F['pos'][i+1:n,idx] == F['pos'][i,idx]) & (F['dim'][i+1:n,idx] == F['dim'][i,idx])
Fm = F[i+1:n][mergable]
pred = (Fm['pos'][:,1-idx] + Fm['dim'][:,1-idx] == F['pos'][i,1-idx]) | (Fm['pos'][:,1-idx] == F['pos'][i,1-idx] + F['dim'][i,1-idx])
mergable[mergable] = pred
for j in numpy.nonzero(mergable)[0]+i+1:
if F[i]['pos'][idx] != F[j]['pos'][idx] or F[i]['dim'][idx] != F[j]['dim'][idx] or (F[i]['pos'][1-idx]+F[i]['dim'][1-idx] != F[j]['pos'][1-idx] and F[j]['pos'][1-idx]+F[j]['dim'][1-idx] != F[i]['pos'][1-idx]):
print('error')
print(F[i],F[j])
sys.exit(1)
F[i]['pos'][1-idx] = min(F[i]['pos'][1-idx],F[j]['pos'][1-idx])
F[i]['dim'][1-idx] += F[j]['dim'][1-idx]
F[j] = F[n-1]
n -= 1
F = F[:n]
if mask is not None:
for i,Fi in enumerate(F):
x,y = Fi['pos']
w,h = Fi['dim']
mask[x:x+w,y:y+h] = i+1
def pack_rectangles_maximal(shape,rects,mask=None):
dt = numpy.dtype([
('pos',numpy.int32,2),
('dim',numpy.int32,2)
])
F = numpy.array([((0,0),shape)],dtype=dt)
for R_index,R_dim in sorted(enumerate(rects),key=lambda x: (-x[1][0],-x[1][1])):
if F.size == 0:
break
# Decide the free rectangle to pack R into.
F_candidates = F[numpy.all(R_dim <= F['dim'],axis=1)]
if F_candidates.size == 0:
continue
R_area = numpy.product(R_dim)
Fi_areas = numpy.product(F_candidates['dim'],axis=1)
# Best Area Fit
i = numpy.argmin(Fi_areas - R_area)
Fi = F_candidates[i]
B = numpy.array((Fi['pos'],R_dim),dtype=dt)
yield R_index,B
# Subdivide Fi into F' and F''
F0 = Fi.copy()
F1 = Fi.copy()
F0['pos'][0] += R_dim[0]
F0['dim'][0] -= R_dim[0]
F1['pos'][1] += R_dim[1]
F1['dim'][1] -= R_dim[1]
if R_dim[0] < R_dim[1]:
F1['dim'][0] = R_dim[0]
else:
F0['dim'][1] = R_dim[1]
F[F == Fi] = F0
F = numpy.append(F,F1)
B0 = B['pos']
B1 = B0 + B['dim']
def inside_rectangle(S,X):
for rect in S:
if numpy.all(rect['pos'] <= X['pos']) and numpy.all(X['pos']+X['dim'] <= rect['pos'] + rect['dim']):
#print(rect,X)
return True
return False
Fs = []
for Fi in F:
Gs = []
F0 = Fi['pos']
F1 = F0 + Fi['dim']
# Left splitting line.
if F0[0] < B0[0]:
G = Fi.copy()
G['dim'][0] = min(B0[0],F1[0]) - F0[0]
if not inside_rectangle(Gs,G):
Gs += [G]
#Gs += [G]
# Bottom splitting line
if F0[1] < B0[1]:
G = Fi.copy()
G['dim'][1] = min(B0[1],F1[1]) - F0[1]
#Gs += [G]
if not inside_rectangle(Gs,G):
Gs += [G]
# Right splitting line.
if B1[0] < F1[0]:
G = Fi.copy()
G['pos'][0] = max(B1[0],F0[0])
G['dim'][0] = F1[0] - G['pos'][0]
#Gs += [G]
if not inside_rectangle(Gs,G):
Gs += [G]
# Top splitting line
if B1[1] < F1[1]:
G = Fi.copy()
G['pos'][1] = max(B1[1],F0[1])
G['dim'][1] = F1[1] - G['pos'][1]
#Gs += [G]
if not inside_rectangle(Gs,G):
Gs += [G]
#print('Gs:',Gs)
for G in Gs:
if not inside_rectangle(Fs,G):
Fs += [G]
F = numpy.array(Fs)
def main():
import scipy.misc
IMG_PATH=[]
IMG_PATH+=['img/mip_0']
print(IMG_PATH)
dirs = []
for path in IMG_PATH:
dirs += map(lambda t: path+'/'+t,os.listdir(path))
rects = []
images = []
image_names = []
total_area = 0
for filename in dirs:
if filename.split('.')[-1] != 'png':
continue
img = scipy.misc.imread(filename)
image_names += [filename]
images += [img]
rects += [img.shape[:2]]
total_area += numpy.prod(img.shape[:2])
print(total_area,numpy.sqrt(total_area))
D = int(numpy.sqrt(total_area)*1.01)
print('D:',D)
img = numpy.zeros((D,D,4),dtype=numpy.uint8)
mask = numpy.zeros((D,D),dtype=numpy.int32)
holdouts = set(range(len(rects)))
atlas = {}
for progress,(index,rect) in enumerate(pack_rectangles((D,D),rects,mask)):
holdouts.remove(index)
x,y = rect['pos']
w,h = rect['dim']
if images[index].shape[2] == 3:
img[x:x+w,y:y+h,:3] = images[index]
img[x:x+w,y:y+h,3] = 255
else:
img[x:x+w,y:y+h] = images[index]
atlas[dirs[index]] = {
'filename': dirs[index].split('/')[-1],
'start': tuple(rect['pos']),
'size': tuple(rect['dim'])
}
n = numpy.max(mask)
colors = numpy.random.randint(0,256,(n+1,4))
colors[:,3] = 192
img[mask != 0,:] = colors[mask,:][mask!=0,:]
for index in holdouts:
print(image_names[index])
scipy.misc.imsave('pack.png',img)
with open('pack.json','w') as f:
pprint.pprint(atlas,stream=f)
sys.exit(0)
if __name__ == '__main__':
main()
#!/usr/bin/python3
import numpy
import ctypes
import math
import time
import quake
import sys
import OpenGL.GL as gl
import pygame
import packing
DEGREES_TO_RADIANS = numpy.pi / 180
PRIMITIVE_RESTART = -1
SHADOWMAP_SIZE = 1024
def reflect(n, x):
return x - 2 * (n * numpy.dot(n, x)) / numpy.dot(n, n)
def normalize(x):
return x / numpy.linalg.norm(x)
def rotate(a, b, x, bias=None):
if len(x.shape) == 1:
if bias is not None:
p = normalize(bias)
return reflect(p-normalize(b), reflect(p-normalize(a), x))
else:
p = normalize(a)+normalize(b)
return reflect(p, reflect(a, x))
else:
y = numpy.empty(x.shape,dtype=x.dtype)
for i in range(x.shape[1]):
y[:,i] = rotate(a,b, x[:,i], bias)
return y
QUAKE_FORWARD_VECTOR = numpy.array([1,0,0], dtype=numpy.float32)
QUAKE_UP_VECTOR = numpy.array([0,0,1], dtype=numpy.float32)
OPENGL_FORWARD_VECTOR = numpy.array([0, 0, -1], dtype=numpy.float32)
OPENGL_UP_VECTOR = numpy.array([0, 1, 0], dtype=numpy.float32)
def euler_vector(idx, angle):
cs = numpy.cos(angle)
sn = numpy.sin(angle)
y = numpy.zeros(3, dtype=numpy.float32)
y[idx[0]] = cs
y[idx[1]] = sn
return y
class Player:
def __init__(self, viewpoint=[0.0, 0.0, 0.0], direction_angle=0.0, pitch_angle=0.0, up_vector = QUAKE_UP_VECTOR, forward_vector=QUAKE_FORWARD_VECTOR):
self.__direction = forward_vector.copy()
self.__up_vector = up_vector.copy()
self.turn_left(direction_angle)
self.look_up(pitch_angle)
self.__viewpoint = numpy.array(viewpoint, dtype=numpy.float32)
def position(self):
return self.__viewpoint.copy()
def move_forward(self, step):
self.__viewpoint += self.__direction * step
def move_left(self, step):
left_vector = -numpy.cross(self.__direction, self.__up_vector)
self.__viewpoint += left_vector * step
def move_up(self, step):
left_vector = -normalize(numpy.cross(self.__direction, self.__up_vector))
up_vector = normalize(numpy.cross(self.__direction, left_vector))
self.__viewpoint += up_vector * step
def turn_left(self, angle):
left_vector = -normalize(numpy.cross(self.__direction, self.__up_vector))
cs = numpy.cos(angle)
sn = numpy.sin(angle)
self.__direction = normalize(self.__direction)* cs + left_vector*sn
def look_up(self, angle):
left_vector = -normalize(numpy.cross(self.__direction, self.__up_vector))
up_vector = normalize(numpy.cross(self.__direction, left_vector))
cs = numpy.cos(angle)
sn = numpy.sin(angle)
new_direction = self.__direction*cs + up_vector*sn
if abs(numpy.dot(new_direction, self.__up_vector)) < (1-1e-2):
self.__direction = new_direction
def modelview(self):
left_vector = -normalize(numpy.cross(self.__direction, self.__up_vector))
up_vector = normalize(numpy.cross(self.__direction, left_vector))
m = numpy.eye(4, dtype=numpy.float32)
m[:3, 3] = -self.__viewpoint
rotated_up = rotate(self.__direction, OPENGL_FORWARD_VECTOR, up_vector)
projected_up = normalize(rotated_up - numpy.dot(rotated_up, OPENGL_FORWARD_VECTOR)*OPENGL_FORWARD_VECTOR)
m[:3, :] = rotate(self.__direction, OPENGL_FORWARD_VECTOR, m[:3,:])
m[:3, :] = rotate(projected_up, OPENGL_UP_VECTOR, m[:3, :])
return m
class Engine:
def __init__(self, viewpoint = [0, 0, 0],direction_angle=0):
self.__players = {
'primary': Player(viewpoint,direction_angle),
'meta': Player(forward_vector=[0,0,-1],up_vector=[0,1,0])
}
self.__modes = {
'view': 'primary',
'move': 'primary'
}
self.__view_modes = {
'primary': {},
'meta': {},
'box': {},
'metawire': {},
'primary_shadowmap': {},
'postprocessed': {}
}
self.__config = {
'start': (viewpoint,direction_angle),
'projective': True,
'wireframe': False,
'box width': 16,
'box near': 1,
'box far': 1000,
'meta near': 1,
'meta far': 10000,
'clear color': (0, 0, 0, 0.2)
}
return
def __del__(self):
for view_mode in self.__view_modes.values():
gl.glDeleteVertexArrays(1, [view_mode['vao']])
gl.glDeleteBuffers(len(view_mode['vbos']), view_mode['vbos'])
for shadowmap in self.__shadowmaps.values():
gl.glDeleteFramebuffers(1, [shadowmap['fbo']])
gl.glDeleteTextures([shadowmap['texture']])
gl.glDeleteTextures([shadowmap['depth texture']])
def init_gl(self):
pygame.init()
pygame.key.set_repeat(10, 5)
self.__config['screen size'] = (1600, 900)
gl.glViewport(0,0, *self.__config['screen size'])
pygame.display.set_mode(self.__config['screen size'], pygame.OPENGL|pygame.DOUBLEBUF)
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
gl.glEnable(gl.GL_DEPTH_TEST)
pygame.joystick.init()
if pygame.joystick.get_count() > 0:
self.__joystick = pygame.joystick.Joystick(0)
if self.__joystick.get_name() == 'Microsoft X-Box 360 pad':
self.__joystick.init()
else:
self.__joystick = None
else:
self.__joystick = None
def __load_shaders(self, files):
program = gl.glCreateProgram()
for filename, shader_type in files:
with open(filename, 'r') as f:
code = f.read()
try:
shader = gl.glCreateShader(shader_type)
gl.glShaderSource(shader, code.encode())
gl.glCompileShader(shader)
if gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
info = gl.glGetShaderInfoLog(shader)
print(info.decode())
raise RuntimeError('Shader compilation failed:\n{}'.format(info))
except:
gl.glDeleteShader(shader)
raise
gl.glAttachShader(program, shader)
gl.glLinkProgram(program)
if gl.glGetProgramiv(program, gl.GL_LINK_STATUS) != gl.GL_TRUE:
info = gl.glGetProgramInfoLog(program)
print(info.decode())
raise RuntimeError('Shader linking failed:\n{}'.format(info))
return program
def load_data(self, bsp):
self.__generate_vertices(bsp)
def enable_attribute(name, program, vbo, index, data_type, data, count):
location = gl.glGetAttribLocation(program, name.encode())
if location >= 0:
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
gl.glBufferData(gl.GL_ARRAY_BUFFER, gl.arrays.ArrayDatatype.arrayByteCount(data), data, gl.GL_STATIC_DRAW)
gl.glVertexAttribPointer(location, count, data_type, gl.GL_FALSE, 0, None)
gl.glEnableVertexAttribArray(index)
return location
def enable_indexes(vbo, data):
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, vbo)
data_pointer = gl.arrays.ArrayDatatype.voidDataPointer(data)
gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, gl.arrays.ArrayDatatype.arrayByteCount(data), data_pointer, gl.GL_STATIC_DRAW)
def enable_uniform_block(name, program, vbo, data):
block_index = gl.glGetUniformBlockIndex(program, name)
if block_index >= 0:
ptr = ctypes.c_int()
gl.glGetActiveUniformBlockiv(program, block_index, gl.GL_UNIFORM_BLOCK_DATA_SIZE, ptr)
block_size = ptr.value
gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, vbo)
gl.glBufferData(gl.GL_UNIFORM_BUFFER, gl.arrays.ArrayDatatype.arrayByteCount(data), data, gl.GL_STATIC_DRAW)
gl.glBindBufferBase(gl.GL_UNIFORM_BUFFER, block_index, vbo)
#self.make_texture_atlas(bsp)
self.__view_modes['primary']['vertices'] = self.__vertices
self.__view_modes['meta']['vertices'] = self.__vertices
self.__view_modes['metawire']['vertices'] = self.__vertices
self.__view_modes['primary_shadowmap']['vertices'] = self.__vertices
def make_box_vertices(extents):
box_vertices = numpy.empty(3*2*4,dtype=self.__vertices.dtype)
box_vertices['position'] = 0
index = 0
corners = [
(0,0),
(1,0),
(1,1),
(0,1)
]
for dimension in range(3):
indexes = list(range(3))
indexes.remove(dimension)
for z in range(2):
for (i,j) in (corners if (z+dimension)%2 == 1 else reversed(corners)):
box_vertices[index]['position'][dimension] = extents[dimension][z]
box_vertices[index]['position'][indexes[0]] = extents[dimension][i]
box_vertices[index]['position'][indexes[1]] = extents[dimension][j]
index += 1
return box_vertices
for dimension in range(3):
indexes = list(range(3))
indexes.remove(dimension)
for x in range(2):
for y in range(2):
i,j = indexes[:2]
box_vertices[index]['position'][i] = extents[i][x]
break
box_vertices[index]['position'][i] = extents[i][x]
box_vertices[index]['position'][j] = extents[j][y]
box_vertices[index]['position'][dimension] = extents[dimension][0]
box_vertices[index+1]['position'][i] = extents[i][x]
box_vertices[index+1]['position'][j] = extents[j][y]
box_vertices[index+1]['position'][dimension] = extents[dimension][1]
return box_vertices
rectangle_vertices = numpy.empty(4,dtype=self.__vertices.dtype)
rectangle_vertices['position'] = 0
rectangle_vertices['position'][0][:2] = [1,1]
rectangle_vertices['position'][1][:2] = [-1,1]
rectangle_vertices['position'][2][:2] = [-1,-1]
rectangle_vertices['position'][3][:2] = [1,-1]
self.__view_modes['postprocessed']['vertices'] = rectangle_vertices
self.__view_modes['box']['vertices'] = make_box_vertices([
[-1,1],
[-1,1],
[-1,1]
])
for shader_name,view_mode in self.__view_modes.items():
vao = gl.glGenVertexArrays(1)
gl.glBindVertexArray(vao)
program = self.__load_shaders([
(shader_name + '_vertex.glsl', gl.GL_VERTEX_SHADER),
(shader_name + '_fragment.glsl', gl.GL_FRAGMENT_SHADER),
])
gl.glUseProgram(program)
vbos = gl.glGenBuffers(6)
attributes = {}
vertices = view_mode['vertices']
attributes['position'] = enable_attribute('aPosition', program, vbos[1], 0, gl.GL_FLOAT, vertices['position'], 3)
attributes['normal'] = enable_attribute('aNormal', program, vbos[2], 1, gl.GL_FLOAT, vertices['normal'], 3)
attributes['texcoord'] = enable_attribute('aTextureCoordinate', program, vbos[3], 2, gl.GL_FLOAT, vertices['texcoord'], 2)
attributes['textureid'] = enable_attribute('aTextureId', program, vbos[4], 3, gl.GL_FLOAT, vertices['texture_id'], 1)
#enable_uniform_block('Atlas', program, vbos[5], self.__atlas)
enable_indexes(vbos[0], self.__indexes)
view_mode['program'] = program
view_mode['attributes'] = attributes
view_mode['vbos'] = vbos
view_mode['vao'] = vao
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
gl.glBindVertexArray(0)
# Generate framebuffer for the shadow map.
self.__shadowmaps = {}
def make_shadowmap(color_format,size):
shadowmap = {}
# TODO: Considate the texture list.
shadowmap['fbo'] = gl.glGenFramebuffers(1)
shadowmap['texture'] = gl.glGenTextures(1)
shadowmap['size'] = size
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, shadowmap['fbo'])
gl.glBindTexture(gl.GL_TEXTURE_2D, shadowmap['texture'])
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
gl.glTexStorage2D(gl.GL_TEXTURE_2D, 1, color_format, size,size)
shadowmap['depth texture'] = gl.glGenTextures(1)
gl.glBindTexture(gl.GL_TEXTURE_2D, shadowmap['depth texture'])
gl.glTexStorage2D(gl.GL_TEXTURE_2D, 1, gl.GL_DEPTH_COMPONENT32F, size,size)
gl.glFramebufferTexture(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, shadowmap['texture'], 0)
gl.glFramebufferTexture(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT, shadowmap['depth texture'], 0)
gl.glDrawBuffers(1, [gl.GL_COLOR_ATTACHMENT0])
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
return shadowmap
self.__shadowmaps['primary'] = make_shadowmap(color_format=gl.GL_RG32F, size=1024)
self.__shadowmaps['postprocessed'] = make_shadowmap(color_format=gl.GL_RG32F, size=1024)
def __generate_vertices(self, bsp):
# A list of textures that are not displayed.
EXCLUDED_TEXTURES = set(['trigger'])
vertex_dtype = numpy.dtype([
('position', numpy.float32, 3),
('normal', numpy.float32, 3),
('texcoord', numpy.float32, 2),
('texture_id', numpy.int32)
])
index_range_dtype = numpy.dtype([
('start', numpy.int32),
('count', numpy.int32)
])
vertices_list = []
leaf_dtype = numpy.dtype([
('indexes',index_range_dtype),
('interiors',numpy.float32,3)
])
self.__leaves = numpy.empty(bsp['leaves'].size, dtype=leaf_dtype)
self.__leaves['interiors'] = 0
element_indexes = []
starting_index = 0
for leaf_index, leaf in enumerate(bsp['leaves']):
face_start = leaf['face_list_start']
face_count = leaf['face_list_count']
vertex_count = 0
for face in bsp['faces'][bsp['face_list'][face_start:face_start+face_count]]:
edge_start = face['edge_list_start']
edge_count = face['edge_list_count']
edge_indexes = bsp['edge_list'][edge_start:edge_start+edge_count]
# Get the vertex indexes by computing the edge index's absolute value and the reversing the edge if the index was negative.
vertex_indexes = bsp['edges'][numpy.absolute(edge_indexes)]
vertex_indexes[edge_indexes < 0, :] = vertex_indexes[edge_indexes<0, ::-1]
face_positions = bsp['vertices'][vertex_indexes[::-1, 0]]['position']
texinfo = bsp['texinfo'][face['texinfo_id']]
texture_name = bsp['miptexes']['names'][texinfo['texture_id']]
if texture_name.decode() in EXCLUDED_TEXTURES:
continue
dimensions = bsp['miptexes']['textures'][texture_name.decode()][0].shape
# Compute the texture coordinates by dotting the vertex position with the direction vector and adding the offset.
texture_coord = numpy.dot(face_positions, numpy.transpose(texinfo['directions'][:]['direction']))
texture_coord += numpy.transpose(texinfo['directions'][:]['offset'])
# Reduce the texture coordinate by moduloing by the texture size.
normal = compute_normal(face_positions)
vertex_entry = numpy.empty(face_positions.shape[0], dtype=vertex_dtype)
vertex_entry['position'] = face_positions
vertex_entry['normal'] = normal
vertex_entry['texcoord'] = texture_coord / dimensions[:2]
vertex_entry['texture_id'] = face['texinfo_id']
self.__leaves['interiors'][leaf_index] += numpy.sum(face_positions,axis=0)
vertices_list += [vertex_entry]
vertex_count += edge_count+1
element_indexes += [numpy.append(numpy.arange(edge_count, dtype=numpy.uint32)+starting_index, PRIMITIVE_RESTART)]
starting_index += edge_count
total_vertices = vertex_count - face_count
if total_vertices > 0:
self.__leaves['interiors'][leaf_index] /= total_vertices
self.__leaves['indexes'][leaf_index]['count'] = vertex_count
self.__leaves['indexes']['start'][0] = 0
self.__leaves['indexes']['start'][1:] = numpy.cumsum(self.__leaves['indexes']['count'][:-1])
self.__leaves = self.__leaves[:-1]
self.__vertices = numpy.array(numpy.concatenate(vertices_list), dtype=vertex_dtype)
self.__indexes = numpy.array(numpy.concatenate(element_indexes), dtype=numpy.uint32)
plane_dt = numpy.dtype([
('normal',numpy.float32,3),
('dist',numpy.float32),
])
bsp_dt = numpy.dtype([
#('plane_id',numpy.int32),
('plane',plane_dt),
('children',numpy.uint32,2),
('children leaf',numpy.bool,2),
#('front',numpy.uint16),
#('back',numpy.uint16),
#('bbox',bbox_short_type),
#('face_id',numpy.uint16),
#('face_num',numpy.uint16)
])
self.__bsp_tree = numpy.empty(bsp['nodes'].size, dtype=bsp_dt)
self.__bsp_tree[:]['plane'] = bsp['planes']['eq'][bsp['nodes']['plane_id']]
self.__bsp_tree[:]['children leaf'][:,0] = (bsp['nodes'][:]['front'] >> 15) != 0
self.__bsp_tree[:]['children leaf'][:,1] = (bsp['nodes'][:]['back'] >> 15) != 0
self.__bsp_tree['children'][:,0] = bsp['nodes'][:]['front']
self.__bsp_tree['children'][:,1] = bsp['nodes'][:]['back']
self.__bsp_tree['children'][self.__bsp_tree['children leaf']] = (~ self.__bsp_tree['children'][self.__bsp_tree['children leaf']]) & 0xffff
def make_leaf_paths(node_index,path=[]):
out = [None] * 2
for i in range(2):
newpath = path + [i!=0]
node = self.__bsp_tree[node_index]
if node['children leaf'][i]:
#bitpath = sum([(1 if b else 0)<<i for (i,b) in enumerate(newpath)])
out[i] = [numpy.array(newpath,dtype=numpy.bool)]
#out[i] = [bitpath]
else:
out[i] = make_leaf_paths(node['children'][i], newpath)
#return numpy.array(out)
#return numpy.concatenate(out)
return out[0] + out[1]
#self.__leaf_paths = make_leaf_paths(self.__bsp_tree.size-1)
self.__leaf_paths = make_leaf_paths(0)
def status_update(self):
JOYSTICK_SCALE = 1.0
JOYSTICK_TURN_SCALE = 5e-1
JOYSTICK_DEADZONE = 3e-1
STRAFE_DISTANCE = 5.0
FORWARD_DISTANCE = 5.0
TURN_ANGLE = 10*DEGREES_TO_RADIANS
FOV = 80*DEGREES_TO_RADIANS
ASPECT = 1200/900
events = pygame.event.get()
for event in events:
if event.type == pygame.KEYDOWN and pygame.key.get_mods() & pygame.KMOD_LALT:
scale = 20 if pygame.key.get_mods() & pygame.KMOD_LSHIFT else 1
if event.key == pygame.K_LEFT:
self.__players[self.__modes['move']].move_left(STRAFE_DISTANCE*scale)
elif event.key == pygame.K_RIGHT:
self.__players[self.__modes['move']].move_left(-STRAFE_DISTANCE*scale)
elif event.key == pygame.K_UP:
self.__players[self.__modes['move']].move_up(STRAFE_DISTANCE*scale)
elif event.key == pygame.K_DOWN:
self.__players[self.__modes['move']].move_up(-STRAFE_DISTANCE*scale)
elif event.type == pygame.KEYDOWN and pygame.key.get_mods() & pygame.KMOD_LCTRL:
if event.key == pygame.K_UP:
self.__players[self.__modes['move']].look_up(TURN_ANGLE)
elif event.key == pygame.K_DOWN:
self.__players[self.__modes['move']].look_up(-TURN_ANGLE)
elif event.type==pygame.KEYDOWN:
scale = 20 if pygame.key.get_mods() & pygame.KMOD_LSHIFT else 1
if event.key == pygame.K_LEFT:
self.__players[self.__modes['move']].turn_left(TURN_ANGLE)
elif event.key == pygame.K_RIGHT:
self.__players[self.__modes['move']].turn_left(-TURN_ANGLE)
elif event.key == pygame.K_UP:
self.__players[self.__modes['move']].move_forward(FORWARD_DISTANCE * scale)
elif event.key == pygame.K_DOWN:
self.__players[self.__modes['move']].move_forward(-FORWARD_DISTANCE *scale)
elif event.key == pygame.K_c:
if self.__modes['view'] == 'primary':
(viewpoint,direction_angle) = self.__config['start']
player = Player(viewpoint,direction_angle),
elif self.__modes['view'] == 'meta':
player = Player(forward_vector=OPENGL_FORWARD_VECTOR,up_vector=OPENGL_UP_VECTOR)
self.__players[self.__modes['view']] = player
elif event.key == pygame.K_q:
return False
elif event.type == pygame.KEYUP:
if event.key == pygame.K_t:
self.__config['projective'] = not self.__config['projective']
if event.key == pygame.K_m:
if self.__modes['move'] == 'meta':
self.__modes['move'] = 'primary'
else:
self.__modes['move'] = 'meta'
if event.key == pygame.K_v:
if self.__modes['view'] == 'meta':
self.__modes['view'] = 'primary'
else:
self.__modes['view'] = 'meta'
if event.key == pygame.K_w:
self.__config['wireframe'] = not self.__config['wireframe']
previous_modes = self.__modes.copy()
gl.glEnable(gl.GL_PRIMITIVE_RESTART)
gl.glPrimitiveRestartIndex(PRIMITIVE_RESTART)
if self.__config['wireframe']:
gl.glDisable(gl.GL_CULL_FACE)
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)
else:
gl.glEnable(gl.GL_CULL_FACE)
#gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL)
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
# Buttons 0,1,2,3 are A, B, X, and Y respectively
# Button 4,5 are the left and right shoulder buttons respectively.
# Button 6, 7, 8 are the back, start, and dasboard buttons.
# Button 9, 10 are the left and right analog stick buttons
def joystick_deadzone(x):
return x if abs(x) > JOYSTICK_DEADZONE else 0
if self.__joystick is not None:
joystick_boost = math.pow(2, joystick_deadzone(self.__joystick.get_axis(2)-self.__joystick.get_axis(5))*3)
adjust = 4e1 if self.__modes['move'] == 'meta' else 1
self.__players[self.__modes['move']].move_forward(FORWARD_DISTANCE * adjust * JOYSTICK_SCALE * -joystick_deadzone(self.__joystick.get_axis(1))*joystick_boost)
self.__players[self.__modes['move']].move_left(STRAFE_DISTANCE * adjust * JOYSTICK_SCALE * -joystick_deadzone(self.__joystick.get_axis(0))*joystick_boost)
self.__players[self.__modes['move']].turn_left(TURN_ANGLE * JOYSTICK_TURN_SCALE * -joystick_deadzone(self.__joystick.get_axis(3)))
self.__players[self.__modes['move']].look_up(TURN_ANGLE * JOYSTICK_TURN_SCALE * -joystick_deadzone(self.__joystick.get_axis(4)))
program = self.__view_modes[self.__modes['view']]['program']
if self.__modes['view'] == 'meta':
#gl.glEnable(gl.GL_CULL_FACE)
gl.glDisable(gl.GL_CULL_FACE)
perspective = perspective_matrix(FOV, self.__config['meta near'], self.__config['meta far'], ASPECT)
modelview = self.__players[self.__modes['view']].modelview()
#modelview[:,2] *= 128
if self.__config['projective']:
#modelview[:,:3] *= (self.__config['box far']-self.__config['box near'])/2
modelview[:,:3] *= self.__config['box width']/2
modelview[:,2] *= -1
modelview[:,:3] *= 4
primary_perspective = perspective_matrix(FOV, self.__config['box near'], self.__config['box far'], ASPECT)
primary_modelview = self.__players['primary'].modelview()
# Set the uniforms for the shadow map rendering.
primary_program = self.__view_modes['primary_shadowmap']['program']
gl.glUseProgram(primary_program)
gl.glUniformMatrix4fv(gl.glGetUniformLocation(primary_program, b'uPerspective'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*primary_perspective.transpose().flatten()))
gl.glUniformMatrix4fv(gl.glGetUniformLocation(primary_program, b'uModelView'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*primary_modelview.transpose().flatten()))
self.__view_modes['meta']['modelview'] = primary_modelview
self.__view_modes['meta']['perspective'] = primary_perspective
# Set the frustum boundary uniforms.
box_program = self.__view_modes['box']['program']
gl.glUseProgram(box_program)
gl.glUniformMatrix4fv(gl.glGetUniformLocation(box_program, b'uPerspective'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*perspective.transpose().flatten()))
gl.glUniformMatrix4fv(gl.glGetUniformLocation(box_program, b'uModelView'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*modelview.transpose().flatten()))
gl.glUniform1i(gl.glGetUniformLocation(box_program, b'uProjectiveToggle'), self.__config['projective'])
gl.glUniform1f(gl.glGetUniformLocation(box_program, b'uBoxNear'), self.__config['box near'])
gl.glUniform1f(gl.glGetUniformLocation(box_program, b'uBoxFar'), self.__config['box far'])
# Set the uniforms for the program that shades the region outside the frustum.
metawire_program = self.__view_modes['metawire']['program']
gl.glUseProgram(metawire_program)
gl.glUniformMatrix4fv(gl.glGetUniformLocation(metawire_program, b'uPrimaryPerspective'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*primary_perspective.transpose().flatten()))
gl.glUniformMatrix4fv(gl.glGetUniformLocation(metawire_program, b'uPrimaryModelView'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*primary_modelview.transpose().flatten()))
gl.glUniformMatrix4fv(gl.glGetUniformLocation(metawire_program, b'uPerspective'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*perspective.transpose().flatten()))
gl.glUniformMatrix4fv(gl.glGetUniformLocation(metawire_program, b'uModelView'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*modelview.transpose().flatten()))
gl.glUniform1i(gl.glGetUniformLocation(metawire_program, b'uProjectiveToggle'), self.__config['projective'])
# Set the uniforms for the frustum region program.
gl.glUseProgram(program)
gl.glUniform1i(gl.glGetUniformLocation(program, b'uProjectiveToggle'), self.__config['projective'])
#for i in range(6):
#gl.glEnable(gl.GL_CLIP_DISTANCE0 + i)
# gl.glDisable(gl.GL_CLIP_DISTANCE0 + i)
gl.glUniformMatrix4fv(gl.glGetUniformLocation(program, b'uPrimaryPerspective'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*primary_perspective.transpose().flatten()))
gl.glUniformMatrix4fv(gl.glGetUniformLocation(program, b'uPrimaryModelView'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*primary_modelview.transpose().flatten()))
else:
gl.glEnable(gl.GL_CULL_FACE)
gl.glUseProgram(program)
for i in range(6):
gl.glDisable(gl.GL_CLIP_DISTANCE0 + i)
perspective = perspective_matrix(FOV, self.__config['box near'], self.__config['box far'], ASPECT)
modelview = self.__players[self.__modes['view']].modelview()
gl.glUniformMatrix4fv(gl.glGetUniformLocation(program, b'uPerspective'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*perspective.transpose().flatten()))
gl.glUniformMatrix4fv(gl.glGetUniformLocation(program, b'uModelView'), 1, gl.GL_FALSE, (ctypes.c_float * 16)(*modelview.transpose().flatten()))
gl.glUseProgram(0)
return True
def make_texture_atlas(self, bsp):
PADDING=2
image_names = []
images = []
rects = []
total_area = 0
for image_name, mips in bsp['miptexes']['textures'].items():
img = mips[0]
image_names += [image_name]
images += [img]
w, h = img.shape[:2]
rects += [[w+2*PADDING, h+2*PADDING]]
total_area += numpy.prod(img.shape[:2])
Ds = int(math.floor(math.log(numpy.sqrt(total_area))/math.log(2)))
for D_exp in range(Ds, Ds+3):
D = 2**D_exp
img = numpy.zeros((D, D, 4), dtype=numpy.uint8)
atlas = {}
holdouts = set(range(len(rects)))
#for progress, (index, rect) in enumerate(packing.pack_rectangles_maximal((D, D), rects)):
for progress, (index, rect) in enumerate(packing.pack_rectangles((D, D), rects)):
holdouts.remove(index)
x, y = rect['pos']
w, h = rect['dim']
w -= 2*PADDING
h -= 2*PADDING
image = numpy.empty((w, h, 3), dtype=numpy.uint8)
image = images[index].copy()
image = numpy.roll(image, PADDING, axis=0)
image = numpy.roll(image, PADDING, axis=1)
image = numpy.tile(image, (2, 2, 1))
img[x:x+w+2*PADDING, y:y+h+2*PADDING, :3] = image[0:w+2*PADDING, 0:h+2*PADDING, :]
img[x:x+w+2*PADDING, y:y+h+2*PADDING, 3] = 255
name = image_names[index]
atlas[name] = {
'start': tuple(rect['pos']),
'size': (w, h)
}
if len(holdouts) == 0:
break
import scipy.misc
scipy.misc.imsave('atlas.png', img)
dt = numpy.dtype([
('start', numpy.uint16, 2),
('size', numpy.uint16, 2),
])
self.__atlas = numpy.empty(len(rects), dtype=dt)
for i, name in enumerate(image_names):
self.__atlas[i]['start'][:] = atlas[name]['start']
self.__atlas[i]['size'][:] = atlas[name]['size']
def render(self):
gl.glEnable(gl.GL_CULL_FACE)
def draw_leaves(primitive_type, permutation=None):
for leaf_index in self.__leaves['indexes'] if permutation is None else self.__leaves['indexes'][permutation]:
if leaf_index['count'] > 0:
s = leaf_index['start']
c = leaf_index['count']
# TODO: Get the size of an index instead of using 4
gl.glDrawElements(primitive_type, c, gl.GL_UNSIGNED_INT, ctypes.c_void_p(int(s)*4))
def draw_all_leaves(primitive_type):
gl.glDrawElements(primitive_type, self.__leaves['indexes']['start'][-1]+self.__leaves['indexes']['count'][-1], gl.GL_UNSIGNED_INT, None)
def draw_bsp(primitive_type, position):
def traverse(node_id):
node = self.__bsp_tree[node_id]
same_side = numpy.dot(node['plane']['normal'],position[:3]) - node['plane']['dist'] >= 0
for i in range(2):
if same_side:
index = 1-i
else:
index = i
child_index = node['children'][index]
if node['children leaf'][index]:
if child_index != 0:
leaf_indexes = self.__leaves['indexes'][child_index]
s = leaf_indexes['start']
c = leaf_indexes['count']
# TODO: Get the size of an index instead of using 4
gl.glDrawElements(primitive_type, c, gl.GL_UNSIGNED_INT, ctypes.c_void_p(int(s)*4))
else:
traverse(child_index)
traverse(0)
if self.__modes['view'] == 'meta':
gl.glDisable(gl.GL_BLEND)
#gl.glDisable(gl.GL_DEPTH_TEST)
gl.glEnable(gl.GL_CULL_FACE)
gl.glEnable(gl.GL_DEPTH_TEST)
# Make the shadowmap.
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.__shadowmaps['primary']['fbo'])
gl.glViewport(0,0, self.__shadowmaps['primary']['size'], self.__shadowmaps['primary']['size'])
gl.glClearBufferfv(gl.GL_COLOR, 0, [1,1])
gl.glClearBufferfv(gl.GL_DEPTH, 0, [1])
gl.glBindVertexArray(self.__view_modes['primary_shadowmap']['vao'])
gl.glUseProgram(self.__view_modes['primary_shadowmap']['program'])
draw_all_leaves(gl.GL_TRIANGLE_FAN)
#draw_leaves(gl.GL_TRIANGLE_FAN)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
gl.glUseProgram(0)
gl.glBindVertexArray(0)
gl.glEnable(gl.GL_CULL_FACE)
# Postprocess.
gl.glBindVertexArray(self.__view_modes['postprocessed']['vao'])
gl.glUseProgram(self.__view_modes['postprocessed']['program'])
pixel_size = 1/self.__shadowmaps['primary']['size']
gl.glUniform1i(gl.glGetUniformLocation(self.__view_modes['postprocessed']['program'], b'uKernelSize'), 3)
gl.glUniform2f(gl.glGetUniformLocation(self.__view_modes['postprocessed']['program'], b'uKernelDirection'), *tuple([pixel_size,0]))
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.__shadowmaps['postprocessed']['fbo'])
gl.glBindTexture(gl.GL_TEXTURE_2D, self.__shadowmaps['primary']['texture'])
gl.glDisable(gl.GL_DEPTH_TEST)
gl.glClearBufferfv(gl.GL_COLOR, 0, [0,0])
gl.glClearBufferfv(gl.GL_DEPTH, 0, [1])
gl.glDrawArrays(gl.GL_TRIANGLE_FAN, 0, 4)
# Map back.
gl.glUniform2f(gl.glGetUniformLocation(self.__view_modes['postprocessed']['program'], b'uKernelDirection'), *tuple([0,pixel_size]))
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.__shadowmaps['primary']['fbo'])
gl.glBindTexture(gl.GL_TEXTURE_2D, self.__shadowmaps['postprocessed']['texture'])
gl.glDisable(gl.GL_DEPTH_TEST)
gl.glClearBufferfv(gl.GL_COLOR, 0, [0,0])
gl.glClearBufferfv(gl.GL_DEPTH, 0, [1])
gl.glDrawArrays(gl.GL_TRIANGLE_FAN, 0, 4)
gl.glUseProgram(0)
gl.glBindVertexArray(0)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
gl.glViewport(0,0, *self.__config['screen size'])
gl.glEnable(gl.GL_DEPTH_TEST)
gl.glClearBufferfv(gl.GL_COLOR, 0, self.__config['clear color'])
gl.glClearBufferfv(gl.GL_DEPTH, 0, [1])
if self.__modes['view'] == 'meta':
#gl.glUseProgram(0)
#gl.glBindVertexArray(0)
gl.glDisable(gl.GL_CULL_FACE)
gl.glBindVertexArray(self.__view_modes['metawire']['vao'])
gl.glUseProgram(self.__view_modes['metawire']['program'])
#draw_leaves(gl.GL_LINE_LOOP)
draw_all_leaves(gl.GL_LINE_LOOP)
gl.glUseProgram(0)
gl.glBindVertexArray(0)
gl.glBindVertexArray(self.__view_modes[self.__modes['view']]['vao'])
gl.glUseProgram(self.__view_modes[self.__modes['view']]['program'])
if self.__config['wireframe']:
draw_all_leaves(gl.GL_LINE_LOOP)
else:
if self.__modes['view'] == 'meta':
gl.glEnable(gl.GL_CULL_FACE)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.__shadowmaps['primary']['texture'])
gl.glDisable(gl.GL_DEPTH_TEST)
gl.glDisable(gl.GL_BLEND)
gl.glBlendFunc(gl.GL_ZERO, gl.GL_SRC_COLOR)
pos = numpy.append(self.__players[self.__modes['view']].position(),1)
pos = numpy.dot(numpy.linalg.inv(self.__view_modes['meta']['perspective']), pos)
pos = numpy.dot(numpy.linalg.inv(self.__view_modes['meta']['modelview']), pos)
pos /= pos[3]
gl.glEnable(gl.GL_DEPTH_TEST)
draw_all_leaves(gl.GL_TRIANGLE_FAN)
gl.glEnable(gl.GL_DEPTH_TEST)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
gl.glDisable(gl.GL_BLEND)
else:
gl.glEnable(gl.GL_DEPTH_TEST)
pos = numpy.append(self.__players[self.__modes['view']].position(),1)
draw_all_leaves(gl.GL_TRIANGLE_FAN)
gl.glUseProgram(0)
gl.glBindVertexArray(0)
if self.__modes['view'] == 'meta':
BOX_SIDE_COLOR = numpy.array([0.5,0.5,0.5,0.2],dtype=numpy.float32)
BOX_LINE_COLOR = numpy.array([1,1,1,0.8],dtype=numpy.float32)
gl.glBindVertexArray(self.__view_modes['box']['vao'])
box_program = self.__view_modes['box']['program']
gl.glUseProgram(box_program)
# Draw the edges
gl.glLineWidth(5)
for i in range(3*2):
gl.glDrawArrays(gl.GL_LINE_LOOP, i*4, 4)
gl.glLineWidth(1)
gl.glEnable(gl.GL_BLEND)
# Draw the faces
gl.glBlendFunc(gl.GL_ONE_MINUS_DST_ALPHA, gl.GL_DST_ALPHA)
gl.glUniform4f(gl.glGetUniformLocation(box_program, b'uBoxColor'), *BOX_SIDE_COLOR)
for i in range(3*2):
gl.glDrawArrays(gl.GL_TRIANGLE_FAN, i*4, 4)
gl.glUniform4f(gl.glGetUniformLocation(box_program, b'uBoxColor'), *BOX_LINE_COLOR)
gl.glDisable(gl.GL_BLEND)
gl.glUseProgram(0)
gl.glBindVertexArray(0)
pygame.display.flip()
def compute_normal(positions):
'''Compute the normal vector by finding the first triple of positions that are not colinear.'''
TOL = 1e-2
for i in range(2, positions.shape[0]):
for j in range(1, i):
for k in range(j):
normal = numpy.cross(positions[j] - positions[k], positions[i] - positions[k])
dist = numpy.linalg.norm(normal)
if dist > TOL:
return normal / dist
return None
def perspective_matrix(fov, zN, zF, aspect=3/4):
f = 1/math.tan(fov/2.0)
r = aspect
pMatrix = numpy.array([
[f/r, 0, 0, 0],
[0, f, 0, 0],
[0, 0, -(zF+zN)/(zF-zN), -2*zF*zN/(zF-zN)],
[0, 0, -1, 0]], dtype=numpy.float32)
return pMatrix
def main():
if len(sys.argv) <= 1:
print('Usage: quake_json.py [bsp]')
sys.exit(1)
with open(sys.argv[1], 'rb') as bsp_file:
bsp = quake.load_bsp(bsp_file)
entities = quake.parseEntities(bsp['entities'])
startPos = numpy.array(list(map(float,entities['info_player_start'][0]['origin'].split(' '))),dtype=numpy.float32)
startAngle = float(entities['info_player_start'][0]['angle'])*DEGREES_TO_RADIANS
engine = Engine(viewpoint=startPos, direction_angle=startAngle)
engine.init_gl()
engine.load_data(bsp)
INTERVAL = 1/35
done = False
while not done:
if not engine.status_update():
done = True
engine.render()
time.sleep(INTERVAL)
if __name__ == '__main__':
main()
#version 430
in vec2 vTexCoord;
struct atlas_entry {
ivec2 pos;
ivec2 size;
};
uniform Atlas {
atlas_entry entries[1024];
} uAtlas;
uniform sampler2D uSourceMap;
uniform vec2 uKernelDirection;
uniform int uKernelSize;
uniform int uPixelSize;
int choose(int n, int k) {
int p = 1;
for(int i=0;i<k;i++) {
p = p*(n-i)/(k-i);
}
return p;
}
/* Do a Gaussian blur. */
void main() {
vec2 d = uKernelDirection;
int n = 2*uKernelSize;
vec4 t = choose(n,uKernelSize) * texture2D(uSourceMap, vTexCoord);
for(int i=1;i<=uKernelSize;i++) {
vec4 ti = texture2D(uSourceMap, vTexCoord - i*d) + texture2D(uSourceMap, vTexCoord + i*d);
t += choose(n,uKernelSize-i) * ti;
}
t /= 1<<n;
gl_FragColor = t;
}
#version 430
in vec3 aPosition;
out vec2 vTexCoord;
void main() {
vTexCoord = (aPosition.xy + 1) /2;
gl_Position = vec4(aPosition.xy, 0, 1);
}
#version 430
in vec3 vNormal;
in vec2 vTextureCoordinate;
flat in int vTextureId;
struct atlas_entry {
ivec2 pos;
ivec2 size;
};
uniform Atlas {
atlas_entry entries[1024];
} uAtlas;
void main() {
/* Let the vertex normal flat color the face. */
gl_FragColor = vec4((vNormal+1)/2,1);
}
#version 430
in vec4 vClipCoordinates;
struct atlas_entry {
ivec2 pos;
ivec2 size;
};
uniform Atlas {
atlas_entry entries[1024];
} uAtlas;
/* Compute the variance shadow map. */
void main() {
float depth = vClipCoordinates.z;
float moment2 = depth*depth;
float dx = dFdx(depth);
float dy = dFdx(depth);
moment2 += (dx*dx+dy*dy)/4;
gl_FragColor.rg = vec2(depth, moment2);
}
#version 430
in vec3 aPosition;
out vec4 vClipCoordinates;
uniform mat4 uModelView;
uniform mat4 uPerspective;
void main() {
vClipCoordinates = uPerspective * uModelView * vec4(aPosition,1);
gl_Position = vClipCoordinates;
}
#version 430
in vec3 aPosition;
in vec3 aNormal;
in vec2 aTextureCoordinate;
in int aTextureId;
out vec3 vNormal;
out vec2 vTextureCoordinate;
flat out int vTextureId;
uniform mat4 uModelView;
uniform mat4 uPerspective;
void main() {
vTextureCoordinate = aTextureCoordinate;
vNormal = aNormal;
vTextureId = aTextureId;
gl_Position = uPerspective * uModelView * vec4(aPosition,1);
}
#!/usr/bin/python3
import parse
import sys
import struct
import numpy
import pprint
import scipy.misc
import os
IMAGE_PATH = 'img'
def load_bsp(bsp_file):
structs = {}
def struct_read(f,fmt):
if fmt in structs:
s = structs[fmt]
else:
s = struct.Struct(fmt)
structs[fmt] = s
return s.unpack(f.read(s.size))
def read_entry(f,func):
(offset,size) = struct_read(f,'ii')
loc = f.tell()
f.seek(offset)
dat = func(f,size)
f.seek(loc)
return dat
def skip_entry(f):
(offset,size) = struct_read(f,'ii')
bsp = {}
magic = struct_read(bsp_file,'4B')
if magic == tuple(map(ord,'IBSP')):
bsp['magic'] = magic
bsp['version'] = struct_read(bsp_file,'4B')
else:
bsp['version'] = magic
bbox_short_type = numpy.dtype([
('mins',numpy.int16,3),
('maxs',numpy.int16,3)
])
def read_entities(f,size):
s = f.read(size)
return s.decode()
bsp['entities'] = read_entry(bsp_file,read_entities)
def read_planes(f,size):
dt = numpy.dtype([
('eq',
[('normal',numpy.float32,3),
('dist',numpy.float32)
]),
('type',numpy.int32)
])
count = size // dt.itemsize
planes = numpy.fromfile(bsp_file,dtype=dt,count=count)
return planes
def read_miptexes(bsp_file,size):
miptex_start = bsp_file.tell()
numtex, = struct_read(bsp_file,'I')
offsets = numpy.fromfile(bsp_file,dtype=numpy.int32,count=numtex)
offsets += miptex_start
dt = numpy.dtype([
('name','S16'),
#('name',numpy.int8,16),
('dims',numpy.int32,2),
('offsets',numpy.int32,4),
])
palette = numpy.fromfile('palette.lmp',dtype=(numpy.uint8,3),count=256)
names = numpy.empty(offsets.size,dtype='S16')
miptexes = {}
for index,offset in enumerate(offsets):
if offset < miptex_start:
continue
bsp_file.seek(offset)
miptex = numpy.fromfile(bsp_file,dtype=dt,count=1)[0]
#print(miptex)
name = miptex['name'].decode().split('\0')[0]
names[index] = name
images = [None] * miptex['offsets'].size
for miplevel,miplevel_offset in enumerate(miptex['offsets']):
bsp_file.seek(miplevel_offset+offset)
[w,h] = miptex['dims'] >> miplevel
pixels = numpy.reshape(numpy.fromfile(bsp_file,dtype=numpy.uint8,count=w*h),(h,w))
pixels = palette[pixels]
images[miplevel] = pixels
miptexes[name] = images
return {
'names': names,
'textures': miptexes
}
def read_vertices(bsp_file,size):
dt = numpy.dtype([
('position',numpy.float32,3)
])
return numpy.fromfile(bsp_file,dtype=dt,count=size//dt.itemsize)
def read_visilist(bsp_file,size):
compressed = numpy.fromfile(bsp_file,dtype=numpy.uint8,count=size)
compressed = numpy.reshape(numpy.unpackbits(compressed),(size,8))
return {
'compressed': compressed
}
def decompress_visilist(visilist,leaves):
compressed = visilist['compressed']
numleaves = leaves.size
visilist['numleaves'] = numleaves
uncompressed = numpy.zeros((numleaves,numleaves))
for K,index in enumerate(leaves['visilist']):
if index < 0:
continue
v = index
L = 1
while L < numleaves:
if v >= visilist['compressed'].shape[0]:
break
if numpy.all(visilist['compressed'][v,:] == 0):
repeats = numpy.packbits(visilist['compressed'][v+1,:]) * 8
L += repeats[0]
v += 1
else:
nonzeros = numpy.flatnonzero(visilist['compressed'][v,:] == 1) + L
nonzeros = nonzeros[nonzeros<numleaves]
uncompressed[K,nonzeros] = 1
L += 8
v += 1
uncompressed = numpy.random.randint(0,255,size=uncompressed.shape)
t = uncompressed.copy()
indexes = numpy.lexsort(t,axis=0).flatten()
t[indexes,:] = t
t[:,indexes] = t
def read_nodes(bsp_file,size):
dt = numpy.dtype([
('plane_id',numpy.int32),
('front',numpy.uint16),
('back',numpy.uint16),
('bbox',bbox_short_type),
('face_id',numpy.uint16),
('face_num',numpy.uint16)
])
nodes = numpy.fromfile(bsp_file,dtype=dt,count=size//dt.itemsize)
return nodes
def read_texinfo(bsp_file,size):
vdist_type = numpy.dtype([
('direction',numpy.float32,3),
('offset',numpy.float32)
])
dt = numpy.dtype([
('directions',vdist_type,2),
('texture_id',numpy.uint32),
('animated',numpy.uint32)
])
texinfo = numpy.fromfile(bsp_file,dtype=dt,count=size//dt.itemsize)
return texinfo
def read_faces(bsp_file,size):
dt = numpy.dtype([
('plane_id',numpy.uint16),
('side',numpy.uint16),
('edge_list_start',numpy.int32),
('edge_list_count',numpy.uint16),
('texinfo_id',numpy.uint16),
('typelight',numpy.uint8),
('baselight',numpy.uint8),
('light',numpy.uint8,2),
('lightmap',numpy.int32)
])
faces = numpy.fromfile(bsp_file,dtype=dt,count=size//dt.itemsize)
return faces
def read_lightmaps(bsp_file,size):
return None
def read_clipnodes(bsp_file,size):
return None
def read_leaves(bsp_file,size):
dt = numpy.dtype([
('type',numpy.int32),
('visilist',numpy.int32),
('bound',bbox_short_type),
('face_list_start',numpy.uint16),
('face_list_count',numpy.uint16),
('sounds',numpy.uint8,4)
])
leaves = numpy.fromfile(bsp_file,dtype=dt,count=size//dt.itemsize)
return leaves
def read_face_list(bsp_file,size):
dt = numpy.dtype(numpy.uint16)
face_list = numpy.fromfile(bsp_file,dtype=dt,count=size//dt.itemsize)
return face_list
def read_edges(bsp_file,size):
dt = numpy.dtype((numpy.int16,2))
edges = numpy.fromfile(bsp_file,dtype=dt,count=size//dt.itemsize)
return edges
def read_edge_list(bsp_file,size):
dt = numpy.dtype(numpy.int32)
edge_list = numpy.fromfile(bsp_file,dtype=dt,count=size//dt.itemsize)
return edge_list
bsp['planes'] = read_entry(bsp_file,read_planes)
if 'magic' in bsp:
bsp['vertices'] = read_entry(bsp_file,read_vertices)
bsp['visilist'] = read_entry(bsp_file,read_visilist)
bsp['nodes'] = read_entry(bsp_file,read_nodes)
skip_entry(bsp_file)
#bsp['miptexes'] = read_entry(bsp_file,read_miptexes)
else:
bsp['miptexes'] = read_entry(bsp_file,read_miptexes)
bsp['vertices'] = read_entry(bsp_file,read_vertices)
bsp['visilist'] = read_entry(bsp_file,read_visilist)
bsp['nodes'] = read_entry(bsp_file,read_nodes)
bsp['texinfo'] = read_entry(bsp_file,read_texinfo)
bsp['faces'] = read_entry(bsp_file,read_faces)
bsp['lightmaps'] = read_entry(bsp_file,read_lightmaps)
bsp['clipnodes'] = read_entry(bsp_file,read_clipnodes)
bsp['leaves'] = read_entry(bsp_file,read_leaves)
#decompress_visilist(bsp['visilist'], bsp['leaves'])
bsp['face_list'] = read_entry(bsp_file,read_face_list)
bsp['edges'] = read_entry(bsp_file,read_edges)
bsp['edge_list'] = read_entry(bsp_file,read_edge_list)
return bsp
def write_images(name,images):
for miplevel,image in enumerate(images):
image_filename = '{}/mip_{}/img_{}.png'.format(IMAGE_PATH,miplevel,name)
scipy.misc.imsave(image_filename,image)
def write_wavefront(bsp):
OUTNAME = 'out'
with open('{}.obj'.format(OUTNAME),'w') as obj_file:
vertices = bsp['vertices'].copy()
m = numpy.mean(vertices)
s = numpy.std(vertices)
vertices = 64*((vertices[:,:]-m)/s)
vertices = vertices[:,[0,2,1]]
print('mtllib {}'.format('{}.mtl'.format(OUTNAME)),file=obj_file)
for vertex in vertices:
print('v {:.6} {:.6} {:.6}'.format(*vertex[:3]),file=obj_file)
for leaf_index,leaf in enumerate(bsp['leaves']):
face_start = leaf['face_list_start']
face_count = leaf['face_list_count']
print('# {}'.format(leaf),file=obj_file)
print('usemtl leaf_{}'.format(leaf_index),file=obj_file)
for face in bsp['faces'][face_start:face_start+face_count]:
start = face['edge_list_start']
count = face['edge_list_count']
edge_indexes = bsp['edge_list'][start:start+count]
vertex_indexes = bsp['edges'][numpy.absolute(edge_indexes)]
vertex_indexes[edge_indexes < 0,:] = vertex_indexes[edge_indexes<0,::-1]
indexes = vertex_indexes[:,0]+1
print('f {}'.format(' '.join(map(str,indexes[:]))),file=obj_file)
with open('{}.mtl'.format(OUTNAME),'w') as mtl_file:
colors = numpy.random.rand(bsp['leaves'].size,3)
for index,color in enumerate(colors):
print('newmtl leaf_{}'.format(index),file=mtl_file)
print('illum {}'.format(0),file=mtl_file)
print('Kd {:.4} {:.4} {:.4}'.format(*color),file=mtl_file)
def doSVG(bsp):
with open('out.svg','w') as svg:
print('<svg xmlns="http://www.w3.org/2000/svg" version="1.1">',file=svg)
for edge in bsp['edges']:
v0,v1 = bsp['vertices'][edge][:]['position']/40
print('\t<line x1="{}" y1="{}" x2="{}" y2="{}" stroke="{}" stroke-width="{}" />'.format(v0[0],v0[1],v1[0],v1[1],'blue',1),file=svg)
print('</svg>',file=svg)
def parseEntities(entities):
currentBlock = None
outmap = {}
for line in entities.split('\n'):
l = line.strip()
if l.startswith('{'):
currentBlock = {}
elif l.startswith('}'):
classname = currentBlock['classname']
if classname not in outmap:
outmap[classname] = []
del currentBlock['classname']
outmap[classname] += [currentBlock]
currentBlock = None
elif currentBlock is not None:
contents = parse.parse('"{name}" "{values}"', l)
currentBlock[contents['name']] = contents['values']
return outmap
def main():
if len(sys.argv) <= 2:
print('Usage: quake_json.py [bsp] [json]')
sys.exit(1)
with open(sys.argv[1], 'rb') as bsp_file:
with open(sys.argv[2], 'w') as json_file:
bsp = load_bsp(bsp_file)
for name,images in bsp['miptexes']['textures'].items():
for i in range(len(images)):
pathname = '{}/mip_{}'.format(IMAGE_PATH,i)
if not os.access(pathname,os.F_OK):
os.makedirs(pathname)
write_images(name,images)
sys.exit(0)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment