diff --git a/src/main/python/ui/viewport.py b/src/main/python/ui/viewport.py index b1df322..5d68819 100644 --- a/src/main/python/ui/viewport.py +++ b/src/main/python/ui/viewport.py @@ -34,6 +34,7 @@ def __init__(self, fps=30, parent=None): # set render resolution? self.camera = camera.freecam(None, None, 128) self.draw_calls = dict() # shader: (index buffer start, end) + self.active_attribs = [] # list of active vertex atrribs def executeGL(self, func, *args, **kwargs): # best hack ever """Execute func(self, *args, **kwargs) in this viewport's glContext""" @@ -203,13 +204,29 @@ def paintGL(self): glVertex(-y, -x) glEnd() - for shader, index_map in self.draw_calls.items(): # DRAW CALLS - start, length = index_map + # print('<<< -- start frame -- >>>') + for shader, params in self.draw_calls.items(): # DRAW CALLS + # index_map, vertex_format = params + start, length = params # index_map glUseProgram(shader) + + # for a in vertex_format: + # if a not in self.active_attribs: + # glEnableVertexAttribArray(a) + # self.active_attribs.append(a) + # print("added", a) + # for a in self.active_attribs[::]: + # if a not in vertex_format: + # glDisableVertexAttribArray(a) + # self.active_attribs.remove(a) + # print("removed", a) + if self.GLES_MODE == True: # plug the MVP matrix into the current shader glUniformMatrix4fv(self.uniforms[shader], 1, GL_FALSE, matrix) + glDrawElements(GL_TRIANGLES, length, GL_UNSIGNED_INT, GLvoidp(start)) + # print("<<< draw call done >>>") def resizeGL(self, width, height): diff --git a/src/main/python/utilities/physics.py b/src/main/python/utilities/physics.py index 691490a..d8c53da 100644 --- a/src/main/python/utilities/physics.py +++ b/src/main/python/utilities/physics.py @@ -1,7 +1,7 @@ from utilities import vector class plane: - def __init__(normal, distance): + def __init__(self, normal, distance): self.normal = normal self.distance = distance diff --git a/src/main/python/utilities/render.py b/src/main/python/utilities/render.py index ba27a34..f55c0b2 100644 --- a/src/main/python/utilities/render.py +++ b/src/main/python/utilities/render.py @@ -83,13 +83,19 @@ def vmf_setup(viewport, vmf_object, ctx): vertices = [] indices = [] solid_map = dict() - displacement_ids = [] - for brush in solids: - if brush.is_displacement: - displacement_ids.append(brush.id) + for brush in [s for s in solids if not s.is_displacement]: solid_map[brush.id] = (len(indices), len(brush.indices)) - vertices += brush.vertices indices += [len(vertices) + i for i in brush.indices] + vertices += brush.vertices + brush_len = len(indices) + for brush in [s for s in solids if s.is_displacement]: # displacements + for side, verts in brush.displacement_vertices.items(): + power = int(brush.source.sides[side].dispinfo.power) + raw_indices = range(len(indices), len(indices) + len(verts)) + indices += solid.disp_tris(raw_indices, power) + vertices += verts + + disp_len = len(indices) - brush_len vertices = tuple(itertools.chain(*vertices)) # Vertex Buffer @@ -102,25 +108,31 @@ def vmf_setup(viewport, vmf_object, ctx): glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(indices) * 4, np.array(indices, dtype=np.uint32), GL_STATIC_DRAW) # Vertex Format + max_attribs = glGetIntegerv(GL_MAX_VERTEX_ATTRIBS) glEnableVertexAttribArray(0) # vertex_position glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 44, GLvoidp(0)) glEnableVertexAttribArray(1) # vertex_normal (brush only) glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, 44, GLvoidp(12)) - # glEnableVertexAttribArray(5) # blend_alpha (displacement only) - glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, 44, GLvoidp(12)) + # glEnableVertexAttribArray(4) # blend_alpha (displacement only) + glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, 44, GLvoidp(12)) # ^ replaces vertex_normal if displacement ^ glEnableVertexAttribArray(2) # vertex_uv glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 44, GLvoidp(24)) - glEnableVertexAttribArray(4) # editor_colour - glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 44, GLvoidp(32)) + glEnableVertexAttribArray(3) # editor_colour + glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 44, GLvoidp(32)) # keep handles to the new GL objects # dictionaries might be more convenient viewport.buffers = [VERTEX_BUFFER, INDEX_BUFFER] viewport.programs = [program_flat_brush, program_flat_displacement, program_stripey_brush] - viewport.draw_calls[viewport.programs[0]] = (0, len(indices)) - viewport.GLES_MODE = GLES_MODE + # brushes + # brush_format = (0, 1, 2, 3) + viewport.draw_calls[viewport.programs[0]] = (0, brush_len) #, brush_format) + # displacements + # disp_format = (0, 4, 2, 3) + # viewport.draw_calls[viewport.programs[1]] = ((brush_len + 1, disp_len), disp_format) + # viewport.GLES_MODE = GLES_MODE if GLES_MODE: viewport.uniforms = {program_flat_brush: uniform_brush_matrix, program_stripey_brush: uniform_stripey_matrix, diff --git a/src/main/python/utilities/solid.py b/src/main/python/utilities/solid.py index 174dd79..e2ee8fb 100644 --- a/src/main/python/utilities/solid.py +++ b/src/main/python/utilities/solid.py @@ -1,16 +1,19 @@ import itertools from . import vector, vmf, physics + def triangle_of(side): "extract triangle from string (returns 3 vec3)" triangle = [[float(i) for i in xyz.split()] for xyz in side.plane[1:-1].split(') (')] return tuple(map(vector.vec3, triangle)) + def plane_of(A, B, C): """returns plane the triangle defined by A, B & C lies on""" normal = ((A - B) * (C - B)).normalise() return (normal, vector.dot(normal, A)) # normal (vec3), distance (float) + def clip(poly, plane): normal, distance = plane split_verts = {"back": [], "front": []} @@ -22,20 +25,19 @@ def clip(poly, plane): B_behind = round(B_distance, 6) < 0 if A_behind: split_verts["back"].append(A) - else: + else: # A is in front of the clipping plane split_verts["front"].append(A) + # does the edge AB intersect the clipping plane? if (A_behind and not B_behind) or (B_behind and not A_behind): - print("splitting", A_distance, B_distance) t = A_distance / (A_distance - B_distance) cut_point = vector.lerp(A, B, t) + cut_point = [round(a, 2) for a in cut_point] + # .vmf floating-point accuracy sucks split_verts["back"].append(cut_point) split_verts["front"].append(cut_point) - if B_behind: - split_verts["back"].append(B) - else: - split_verts["front"].append(B) return split_verts + def loop_fan(vertices): "ploygon to triangle fan" out = vertices[:3] @@ -43,6 +45,7 @@ def loop_fan(vertices): out += [out[0], out[-1], vertex] return out + def loop_fan_indices(vertices): "polygon to triangle fan (indices only) by Exactol" indices = [] @@ -50,6 +53,7 @@ def loop_fan_indices(vertices): indices += [0, i + 1, i + 2] return indices + def disp_tris(verts, power): # copied from snake-biscuits/bsp_tool/bsp_tool.py """takes flat array of verts and arranges them in a patterned triangle grid expects verts to be an array of length ((2 ** power) + 1) ** 2""" @@ -78,7 +82,7 @@ def disp_tris(verts, power): # copied from snake-biscuits/bsp_tool/bsp_tool.py tris.append(verts[offset + power2C]) tris.append(verts[offset + 2]) tris.append(verts[offset + 1]) - else: #|/|\| + else: # |/|\| tris.append(verts[offset + 0]) tris.append(verts[offset + power2A]) tris.append(verts[offset + power2B]) @@ -96,6 +100,7 @@ def disp_tris(verts, power): # copied from snake-biscuits/bsp_tool/bsp_tool.py tris.append(verts[offset + power2B]) return tris + def square_neighbours(x, y, edge_length): # edge_length = (2^power) + 1 """yields the indicies of neighbouring points in a displacement""" for i in range(x - 1, x + 2): @@ -106,11 +111,11 @@ def square_neighbours(x, y, edge_length): # edge_length = (2^power) + 1 yield i * edge_length + j + class solid: - __slots__ = ('aabb', 'center', 'colour', 'displacement_triangles', - 'displacement_vertices', 'faces', 'id', 'index_map', 'indices', - 'is_displacement', 'planes', 'planes', 'sides', 'source', - 'string_triangles', 'vertices') + __slots__ = ('aabb', 'center', 'colour', 'displacement_vertices', 'faces', + 'id', 'index_map', 'indices', 'is_displacement', 'planes', + 'planes', 'source', 'string_triangles', 'vertices') def __init__(self, solid_namespace): # THIS IS FOR IMPORTING FROM VMF """Initialise from namespace""" @@ -121,44 +126,30 @@ def __init__(self, solid_namespace): # THIS IS FOR IMPORTING FROM VMF self.planes = [plane_of(*t) for t in string_planes] self.is_displacement = False -## self.faces = [] -## for i, plane in enumerate(self.planes): -## normal, distance = plane -## non_parallel = vector.vec3(z=-1) if normal.z != 1 else vector.vec3(y=-1) -## local_y = (non_parallel * normal).normalise() -## local_x = (local_y * normal).normalise() -## center = normal * distance -## radius = 10 ** 12 # larger than any reasonable brush -## ngon = [center + ((-local_x + local_y) * radius), -## center + ((local_x + local_y) * radius), -## center + ((local_x + -local_y) * radius), -## center + ((-local_x + -local_y) * radius)] -## print('-' * 80) -## for other_plane in self.planes: -## if other_plane == plane: # what of inverse normal & epsilon? -## continue -## offcut, ngon = clip(ngon, other_plane).values() -## print(len(ngon), len(offcut)) -## self.faces.append(ngon) -## print('=' * 80) -## print(self.faces) - - self.faces = [] # cheap & dirty method - string_vertices = list(itertools.chain(*string_planes)) - for tri, plane in zip(string_planes, self.planes): - this_face = [*tri] + self.faces = [] + for i, plane in enumerate(self.planes): normal, distance = plane - for v in string_vertices: - if v not in tri: - v_dist = vector.dot(v, normal) - if distance - .5 < v_dist < distance + .5: - this_face.append(v) - this_face = vector.sort_clockwise(this_face, normal) - self.faces.append(this_face) + non_parallel = vector.vec3(z=-1) if abs(normal.z) != 1 else vector.vec3(y=-1) + local_y = (non_parallel * normal).normalise() + local_x = (local_y * normal).normalise() + center = normal * distance + radius = 10 ** 4 # larger than any reasonable brush + ngon = [center + ((-local_x + local_y) * radius), + center + ((local_x + local_y) * radius), + center + ((local_x + -local_y) * radius), + center + ((-local_x + -local_y) * radius)] + for other_plane in self.planes: + if other_plane == plane: # what about the inverse plane? + continue + ngon, offcut = clip(ngon, other_plane).values() # back, front + self.faces.append(ngon) self.indices = [] self.vertices = [] # [((position), (normal), (uv), (colour)), ...] + # except it's flat thanks to itertools.chain self.index_map = [] + uvs = {} # side: [(u, v), ...] + side_index = 0 start_index = 0 for face, side, plane in zip(self.faces, self.source.sides, self.planes): face_indices = [] @@ -169,11 +160,13 @@ def __init__(self, solid_namespace): # THIS IS FOR IMPORTING FROM VMF v_axis = side.vaxis.rpartition(' ')[0::2] v_vector = [float(x) for x in v_axis[0][1:-1].split()] v_scale = float(v_axis[1]) + uvs[side_index] = [] for i, vertex in enumerate(face): # regex might help here uv = [vector.dot(vertex, u_vector[:3]) + u_vector[-1], vector.dot(vertex, v_vector[:3]) + v_vector[-1]] uv[0] /= u_scale uv[1] /= v_scale + uvs[side_index].append(uv) assembled_vertex = tuple(itertools.chain(vertex, normal, uv, self.colour)) if assembled_vertex not in self.vertices: @@ -182,6 +175,7 @@ def __init__(self, solid_namespace): # THIS IS FOR IMPORTING FROM VMF else: face_indices.append(self.vertices.index(assembled_vertex)) + side_index += 1 face_indices = loop_fan(face_indices) self.index_map.append((start_index, len(face_indices))) self.indices += face_indices @@ -189,7 +183,6 @@ def __init__(self, solid_namespace): # THIS IS FOR IMPORTING FROM VMF global square_neighbours self.displacement_vertices = {} # {side_index: vertices} - self.displacement_triangles = {} # same but repeat verts to make disp shaped triangles for i, side in enumerate(self.source.sides): if hasattr(side, "dispinfo"): self.source.sides[i].blend_colour = [1 - i for i in self.colour] @@ -197,31 +190,41 @@ def __init__(self, solid_namespace): # THIS IS FOR IMPORTING FROM VMF power = int(side.dispinfo.power) power2 = 2 ** power quad = tuple(vector.vec3(x) for x in self.faces[i]) - start = vector.vec3(eval(side.dispinfo.startposition.replace(" ", ", "))) - if start in quad: - index = quad.index(start) - 1 - quad = quad[index:] + quad[:index] - else: - raise RuntimeError("Couldn't find start of displacement! (side id {})".format(side.id)) + if len(quad) != 4: + raise RuntimeError("displacement brush id {} side id {} has {} sides!".format(self.id, side.id, len(quad))) + quad_uvs = tuple(vector.vec2(x) for x in uvs[i]) + disp_uvs = [] # barymetric uvs for each baryvert + start = vector.vec3(*map(float, side.dispinfo.startposition[1:-1].split())) + if start not in quad: + start = sorted(quad, key=lambda P: (start - P).magnitude())[0] + index = quad.index(start) - 1 + quad = quad[index:] + quad[:index] + quad_uvs = quad_uvs[index:] + quad_uvs[:index] side_dispverts = [] A, B, C, D = quad DA = D - A CB = C - B + Auv, Buv, Cuv, Duv = quad_uvs + DAuv = Duv - Auv + CBuv = Cuv - Buv distance_rows = [v for k, v in side.dispinfo.distances.__dict__.items() if k != "_line"] # skip line number normal_rows = [v for k, v in side.dispinfo.normals.__dict__.items() if k != "_line"] - normals = [] - alphas = [] for y, distance_row, normals_row in zip(itertools.count(), distance_rows, normal_rows): distance_row = [float(x) for x in distance_row.split()] normals_row = [*map(float, normals_row.split())] left_vert = A + (DA * y / power2) + left_uv = Auv + (DAuv * y / power2) right_vert = B + (CB * y / power2) - for x, distance in zip(itertools.count(), distance_row): - k = x * 3 + right_uv = Buv + (CBuv * y / power2) + for x, distance in enumerate(distance_row): + k = x * 3 # index normal = vector.vec3(normals_row[k], normals_row[k + 1], normals_row[k + 2]) baryvert = vector.lerp(right_vert, left_vert, x / power2) + disp_uvs.append(vector.lerp(right_uv, left_uv, x / power2)) side_dispverts.append(vector.vec3(baryvert) + (distance * normal)) + # calculate displacement normals + normals = [] for x in range(power2 + 1): for y in range(power2 + 1): dispvert = side_dispverts[x * (power2 + 1) + y] @@ -231,21 +234,21 @@ def __init__(self, solid_namespace): # THIS IS FOR IMPORTING FROM VMF except Exception as exc: # f"({x}, {y}) {list(square_neighbours(x, y, power2 + 1))=}") # python 3.8 print("({}, {}) {}".format(x, y, list(square_neighbours(x, y, power2 + 1)))) - print(exc) + print(exc) # raise traceback instead normal = vector.vec3(0, 0, 1) if len(neighbours) != 0: normal -= dispvert - sum(neighbours, vector.vec3()) / len(neighbours) normal = normal.normalise() normals.append(normal) - alphas = [float(a) for row in [v for k, v in side.dispinfo.alphas.__dict__.items() if k != "_line"] for a in row.split()] - self.displacement_vertices[i] = [*zip(side_dispverts, alphas, normals)] - self.displacement_triangles[i] = disp_tris(self.displacement_vertices[i], power) - # use disp_tris when assembling vertex buffers - # only store and update verts (full format) - # assemble vertex format (vertex, normal, uv, colour, blend_alpha) - if len(self.displacement_triangles) == 0: - del self.displacement_triangles + self.displacement_vertices[i] = [] + alpha_rows = [v for k, v in side.dispinfo.alphas.__dict__.items() if k != "_line"] + alphas = [float(a) for row in alpha_rows for a in row.split()] + for pos, alpha, uv in zip(side_dispverts, alphas, disp_uvs): + assembled_vertex = tuple(itertools.chain(pos, [alpha, 0.0, 0.0], uv, self.colour)) + self.displacement_vertices[i].append(assembled_vertex) + + if not self.is_displacement: del self.displacement_vertices # all_x = [v[0].x for v in self.vertices] diff --git a/src/main/python/utilities/vmf.py b/src/main/python/utilities/vmf.py index 4853b4a..ec704aa 100644 --- a/src/main/python/utilities/vmf.py +++ b/src/main/python/utilities/vmf.py @@ -76,7 +76,7 @@ def namespace_from(text_file): try: new_namespace = namespace({'_line': line_number}) line = line.rstrip('\n') - line = textwrap.shorten(line, width=200) # cleanup spacing, may break at 200+ chars + line = textwrap.shorten(line, width=2000) # cleanup spacing, broke at 200+ chars, not the right tool for the job if line == '' or line.startswith('//'): # ignore blank / comments continue elif line =='{': # START declaration diff --git a/src/main/resources/base/shaders/GLES_300/brush.vert b/src/main/resources/base/shaders/GLES_300/brush.vert index 2f6c599..9e1d181 100644 --- a/src/main/resources/base/shaders/GLES_300/brush.vert +++ b/src/main/resources/base/shaders/GLES_300/brush.vert @@ -2,7 +2,7 @@ layout(location = 0) in vec3 vertex_position; layout(location = 1) in vec3 vertex_normal; layout(location = 2) in vec2 vertex_uv; -layout(location = 4) in vec3 editor_colour; +layout(location = 3) in vec3 editor_colour; uniform mat4 ModelViewProjectionMatrix; diff --git a/src/main/resources/base/shaders/GLES_300/displacement.vert b/src/main/resources/base/shaders/GLES_300/displacement.vert index 29fcfc4..e95f93d 100644 --- a/src/main/resources/base/shaders/GLES_300/displacement.vert +++ b/src/main/resources/base/shaders/GLES_300/displacement.vert @@ -2,7 +2,7 @@ layout(location = 0) in vec3 vertex_position; layout(location = 1) in float blend_alpha; layout(location = 2) in vec2 vertex_uv; -layout(location = 4) in vec3 editor_colour; +layout(location = 3) in vec3 editor_colour; uniform mat4 ModelViewProjectionMatrix; diff --git a/src/main/resources/base/shaders/GLSL_450/brush.vert b/src/main/resources/base/shaders/GLSL_450/brush.vert index 69e90a7..aa683b2 100644 --- a/src/main/resources/base/shaders/GLSL_450/brush.vert +++ b/src/main/resources/base/shaders/GLSL_450/brush.vert @@ -2,7 +2,7 @@ layout(location = 0) in vec3 vertex_position; layout(location = 1) in vec3 vertex_normal; layout(location = 2) in vec2 vertex_uv; -layout(location = 4) in vec3 editor_colour; +layout(location = 3) in vec3 editor_colour; uniform mat4 gl_ModelViewProjectionMatrix; diff --git a/src/main/resources/base/shaders/GLSL_450/displacement.vert b/src/main/resources/base/shaders/GLSL_450/displacement.vert index 0f4c89f..ce5a5b6 100644 --- a/src/main/resources/base/shaders/GLSL_450/displacement.vert +++ b/src/main/resources/base/shaders/GLSL_450/displacement.vert @@ -2,7 +2,7 @@ layout(location = 0) in vec3 vertex_position; layout(location = 1) in float blend_alpha; layout(location = 2) in vec2 vertex_uv; -layout(location = 4) in vec3 editor_colour; +layout(location = 3) in vec3 editor_colour; uniform mat4 gl_ModelViewProjectionMatrix; diff --git a/tests/utilities/vmf.py b/tests/utilities/vmf.py index 3a86287..910195d 100644 --- a/tests/utilities/vmf.py +++ b/tests/utilities/vmf.py @@ -8,9 +8,9 @@ times = [] for i in range(16): start = time() - # vmf = dict_from(open('mapsrc/test.vmf')) - # vmf = dict_from(open('mapsrc/test2.vmf')) - vmf = dict_from(open('mapsrc/sdk_pl_goldrush.vmf')) + # vmf = namespace_from(open('mapsrc/test.vmf')) + # vmf = namespace_from(open('mapsrc/test2.vmf')) + vmf = namespace_from(open('mapsrc/sdk_pl_goldrush.vmf')) time_taken = time() - start print(f'import took {time_taken:.3f} seconds') times.append(time_taken)