Created
January 12, 2016 18:41
-
-
Save Centrinia/0d13d2f9f91b11ddae33 to your computer and use it in GitHub Desktop.
Frustum Viewer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 430 | |
struct atlas_entry { | |
ivec2 pos; | |
ivec2 size; | |
}; | |
uniform Atlas { | |
atlas_entry entries[1024]; | |
} uAtlas; | |
uniform vec4 uBoxColor; | |
void main() { | |
gl_FragColor = uBoxColor; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 430 | |
in vec3 aPosition; | |
out vec2 vTexCoord; | |
void main() { | |
vTexCoord = (aPosition.xy + 1) /2; | |
gl_Position = vec4(aPosition.xy, 0, 1); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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