basilisk-engine 0.0.9__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basilisk-engine might be problematic. Click here for more details.

Files changed (61) hide show
  1. basilisk/__init__.py +3 -1
  2. basilisk/collisions/broad/broad_aabb.py +8 -1
  3. basilisk/collisions/broad/broad_bvh.py +38 -2
  4. basilisk/collisions/collider.py +20 -10
  5. basilisk/collisions/collider_handler.py +97 -32
  6. basilisk/collisions/narrow/contact_manifold.py +91 -0
  7. basilisk/collisions/narrow/dataclasses.py +27 -0
  8. basilisk/collisions/narrow/deprecated.py +47 -0
  9. basilisk/collisions/narrow/epa.py +21 -15
  10. basilisk/collisions/narrow/gjk.py +15 -14
  11. basilisk/collisions/narrow/graham_scan.py +25 -0
  12. basilisk/collisions/narrow/helper.py +14 -7
  13. basilisk/collisions/narrow/line_intersections.py +107 -0
  14. basilisk/collisions/narrow/sutherland_hodgman.py +76 -0
  15. basilisk/draw/draw_handler.py +7 -5
  16. basilisk/engine.py +28 -6
  17. basilisk/generic/abstract_custom.py +134 -0
  18. basilisk/generic/collisions.py +47 -2
  19. basilisk/generic/quat.py +84 -65
  20. basilisk/generic/vec3.py +99 -67
  21. basilisk/input/mouse.py +3 -3
  22. basilisk/mesh/cube.py +20 -2
  23. basilisk/mesh/mesh.py +69 -54
  24. basilisk/mesh/mesh_from_data.py +106 -21
  25. basilisk/mesh/narrow_aabb.py +10 -1
  26. basilisk/mesh/narrow_bvh.py +9 -1
  27. basilisk/nodes/node.py +211 -101
  28. basilisk/nodes/node_handler.py +58 -33
  29. basilisk/particles/__init__.py +0 -0
  30. basilisk/particles/particle_handler.py +55 -0
  31. basilisk/particles/particle_renderer.py +87 -0
  32. basilisk/physics/impulse.py +113 -0
  33. basilisk/physics/physics_body.py +10 -2
  34. basilisk/physics/physics_engine.py +2 -3
  35. basilisk/render/batch.py +3 -1
  36. basilisk/render/camera.py +35 -1
  37. basilisk/render/chunk.py +19 -4
  38. basilisk/render/chunk_handler.py +39 -23
  39. basilisk/render/image.py +1 -1
  40. basilisk/render/image_handler.py +17 -14
  41. basilisk/render/light_handler.py +16 -11
  42. basilisk/render/material.py +38 -14
  43. basilisk/render/material_handler.py +31 -18
  44. basilisk/render/shader.py +110 -0
  45. basilisk/render/shader_handler.py +20 -35
  46. basilisk/render/sky.py +8 -5
  47. basilisk/scene.py +116 -33
  48. basilisk/shaders/batch.frag +40 -11
  49. basilisk/shaders/batch.vert +14 -7
  50. basilisk/shaders/geometry.frag +9 -0
  51. basilisk/shaders/geometry.vert +42 -0
  52. basilisk/shaders/normal.frag +60 -0
  53. basilisk/shaders/normal.vert +97 -0
  54. basilisk/shaders/particle.frag +72 -0
  55. basilisk/shaders/particle.vert +85 -0
  56. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/METADATA +5 -5
  57. basilisk_engine-0.1.1.dist-info/RECORD +95 -0
  58. basilisk/shaders/image.png +0 -0
  59. basilisk_engine-0.0.9.dist-info/RECORD +0 -78
  60. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/WHEEL +0 -0
  61. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  import glm
2
2
  from .helper import get_support_point
3
+ from .dataclasses import SupportPoint
3
4
  from ...nodes.node import Node
4
5
  from ...generic.math import triple_product
5
6
 
@@ -11,12 +12,12 @@ def collide_gjk(node1: Node, node2: Node, iterations: int=20) -> tuple: # TODO f
11
12
  # generate starting values
12
13
  dir_vec = node1.position - node2.position
13
14
  simplex = [get_support_point(node1, node2, dir_vec)]
14
- dir_vec = -simplex[0][0] # set direction to point away from starting simplex point
15
+ dir_vec = -simplex[0].support_point # set direction to point away from starting simplex point
15
16
 
16
17
  for _ in range(iterations):
17
18
  # gets support point and checks if its across the origin
18
19
  test_point = get_support_point(node1, node2, dir_vec)
19
- if glm.dot(test_point[0], dir_vec) < -1e-7: return False, simplex
20
+ if glm.dot(test_point.support_point, dir_vec) < -1e-7: return False, simplex
20
21
 
21
22
  # add point and find new direction vector
22
23
  simplex.append(test_point)
@@ -25,7 +26,7 @@ def collide_gjk(node1: Node, node2: Node, iterations: int=20) -> tuple: # TODO f
25
26
  if check: return True, simplex
26
27
  return False, simplex # timeout due to too many checks, usually float errors
27
28
 
28
- def handle_simplex(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
29
+ def handle_simplex(simplex: list[SupportPoint]) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
29
30
  """
30
31
  Call proper function based on number of support points
31
32
  """
@@ -34,28 +35,28 @@ def handle_simplex(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3,
34
35
  if num == 3: return handle_simplex_triangle(simplex)
35
36
  return handle_simplex_tetrahedron(simplex) # simplex must be 4 points
36
37
 
37
- def handle_simplex_line(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
38
+ def handle_simplex_line(simplex: list[SupportPoint]) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
38
39
  """
39
40
  Returns the perpendicular vector to the simplex line
40
41
  """
41
- vec_ab = simplex[1][0] - simplex[0][0]
42
- return False, triple_product(vec_ab, -simplex[0][0], vec_ab), simplex
42
+ vec_ab = simplex[1].support_point - simplex[0].support_point
43
+ return False, triple_product(vec_ab, -simplex[0].support_point, vec_ab), simplex
43
44
 
44
- def handle_simplex_triangle(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
45
+ def handle_simplex_triangle(simplex: list[SupportPoint]) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
45
46
  """
46
47
  Returns the normal vector of the triangoe pointed towards the origin
47
48
  """
48
- dir_vec = glm.cross(simplex[1][0] - simplex[0][0], simplex[2][0] - simplex[0][0])
49
- return False, -dir_vec if glm.dot(dir_vec, -simplex[0][0]) < 0 else dir_vec, simplex
49
+ dir_vec = glm.cross(simplex[1].support_point - simplex[0].support_point, simplex[2].support_point - simplex[0].support_point)
50
+ return False, -dir_vec if glm.dot(dir_vec, -simplex[0].support_point) < 0 else dir_vec, simplex
50
51
 
51
- def handle_simplex_tetrahedron(simplex: list, epsilon: float=0) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
52
+ def handle_simplex_tetrahedron(simplex: list[SupportPoint], epsilon: float=0) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
52
53
  """
53
54
  Perform collision check and remove support point if no collision is found
54
55
  """
55
- vec_da = simplex[3][0] - simplex[0][0]
56
- vec_db = simplex[3][0] - simplex[1][0]
57
- vec_dc = simplex[3][0] - simplex[2][0]
58
- vec_do = -simplex[3][0]
56
+ vec_da = simplex[3].support_point - simplex[0].support_point
57
+ vec_db = simplex[3].support_point - simplex[1].support_point
58
+ vec_dc = simplex[3].support_point - simplex[2].support_point
59
+ vec_do = -simplex[3].support_point
59
60
 
60
61
  vectors = [(glm.cross(vec_da, vec_db), 2), (glm.cross(vec_dc, vec_da), 1), (glm.cross(vec_db, vec_dc), 0)] # TODO determine if this is the best way to do this
61
62
  for normal_vec, index in vectors:
@@ -0,0 +1,25 @@
1
+ import glm
2
+ from math import atan2
3
+ from .helper import is_ccw_turn
4
+
5
+ def graham_scan(points:list[glm.vec2]) -> None:
6
+ """converts list of arbitrary points into polygon sorted ccw"""
7
+ # get pivot point
8
+ pivot = min(points, key=lambda p: (p.y, p.x))
9
+ points.remove(pivot)
10
+
11
+ # sort points by polar angle and start hull
12
+ points = sorted(points, key=lambda p: (get_polar_angle(pivot, p), glm.length(pivot - p)))
13
+ hull = [pivot, points.pop(0)]
14
+
15
+ for point in points:
16
+ while len(hull) > 1 and not is_ccw_turn(hull[-2], hull[-1], point):
17
+ hull.pop()
18
+ hull.append(point)
19
+
20
+ return hull
21
+
22
+ def get_polar_angle(pivot:glm.vec2, point:glm.vec2) -> float:
23
+ """gets the polar angle between two points from the horizontal"""
24
+ vector = point - pivot
25
+ return atan2(vector.y, vector.x)
@@ -1,14 +1,14 @@
1
1
  import glm
2
2
  from ...nodes.node import Node
3
+ from .dataclasses import SupportPoint
3
4
 
4
-
5
- def get_support_point(node1: Node, node2: Node, dir_vec: glm.vec3) -> tuple[glm.vec3, glm.vec3, glm.vec3]:
5
+ def get_support_point(node1: Node, node2: Node, dir_vec: glm.vec3) -> SupportPoint:
6
6
  """
7
7
  Outputs the best support point to be added to the polytop based on the direction vector.
8
8
  """
9
- vertex1 = get_furthest_point(node1, dir_vec)
10
- vertex2 = get_furthest_point(node2, -dir_vec)
11
- return (vertex1 - vertex2, vertex1, vertex2)
9
+ vertex1, index1 = get_furthest_point(node1, dir_vec)
10
+ vertex2, index2 = get_furthest_point(node2, -dir_vec)
11
+ return SupportPoint(vertex1 - vertex2, index1, vertex1, index2, vertex2)
12
12
 
13
13
  def get_furthest_point(node: Node, dir_vec: glm.vec3) -> glm.vec3:
14
14
  """
@@ -16,8 +16,15 @@ def get_furthest_point(node: Node, dir_vec: glm.vec3) -> glm.vec3:
16
16
  """
17
17
  # determine furthest point by using untransformed mesh
18
18
  node_dir_vec = node.rotation * dir_vec # rotate the world space vector to node space
19
- vertex = node.mesh.get_best_dot(node_dir_vec)
19
+ index = node.mesh.get_best_dot(node_dir_vec)
20
+ vertex = node.mesh.points[index]
20
21
  vertex = node.model_matrix * glm.vec4(vertex, 1.0)
21
22
 
22
23
  # transform point to world space
23
- return glm.vec3(vertex)
24
+ return glm.vec3(vertex), index
25
+
26
+ def is_ccw_turn(a:glm.vec2, b:glm.vec2, c:glm.vec2) -> bool:
27
+ """
28
+ Determines if the series of points results in a left hand turn
29
+ """
30
+ return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) > 0 # TODO check formula
@@ -0,0 +1,107 @@
1
+ import glm
2
+ from .helper import is_ccw_turn
3
+
4
+ # intersectionn algorithms for lines
5
+ def line_line_intersect(points1:list[glm.vec2], points2:list[glm.vec2]) -> list[glm.vec2]:
6
+ """gets the intersection of 2 2d lines. if the lines are parallel, returns the second line"""
7
+ # orders points from smallest x to greatest x
8
+ points1 = sorted(points1, key=lambda p: (p.x, p.y))
9
+ points2 = sorted(points2, key=lambda p: (p.x, p.y))
10
+ vec1, vec2 = points1[1] - points1[0], points2[1] - points2[0]
11
+
12
+ # if vectors have the same slope return the smallest line
13
+ if have_same_slope(vec1, vec2): return sorted(points1 + points2, key=lambda p: (p.x, p.y))[1:3]
14
+
15
+ # line - line intersection
16
+ det = vec1.x * vec2.y - vec1.y * vec2.x
17
+ if det == 0: return []
18
+ t = (points2[0].x - points1[0].x) * vec2.y - (points2[0].y - points1[0].y) * vec2.x
19
+ t /= det
20
+ return [points1[0] + t * vec1]
21
+
22
+ def have_same_slope(vec1:glm.vec2, vec2:glm.vec2, epsilon:float=1e-5) -> bool:
23
+ """determines if two vectors moving in the positive x direction have the same slope"""
24
+ return abs(vec1.y * vec2.x - vec2.y * vec1.x) < epsilon
25
+
26
+ def line_poly_intersect(line:list[glm.vec2], polygon:list[glm.vec2]) -> list[glm.vec2]: #TODO Reseach into faster algorithm < O(2n)
27
+ """computes which parts of the line clip with the polygon"""
28
+ # calculate the center of the polygon
29
+ assert len(polygon) > 2, 'polygon is does not contain engough points'
30
+ center = glm.vec2(0,0)
31
+ for point in polygon: center += point
32
+ center /= len(polygon)
33
+ orig_line = line[:]
34
+ # determine which points are in or out of the polygon
35
+ exterior_points = []
36
+ for i in range(len(polygon)): # nearest even number below n
37
+ for line_point in line[:]:
38
+ if not is_ccw_turn(polygon[i], polygon[(i + 1) % len(polygon)], line_point): # if point is on the outside
39
+ exterior_points.append((polygon[i], polygon[(i + 1) % len(polygon)], line_point)) # polypoint1, polypoint2, linepoint
40
+ line.remove(line_point) # removes line point if it is confirmed to be outside
41
+
42
+ # determine what to with line based on number of points found outside
43
+ if len(exterior_points) == 0:
44
+ return line
45
+ if len(exterior_points) == 1:
46
+ return line_line_intersect(line + [exterior_points[0][2]], exterior_points[0][0:2]) + [line[0]] # [intersecting point, exterior point]
47
+ if len(exterior_points) == 2: # line must intersect with two edges
48
+ points = []
49
+ for i in range(len(polygon)):
50
+ intersection = line_line_intersect(orig_line, [polygon[i], polygon[(i + 1) % len(polygon)]])
51
+ if len(intersection) > 0: points += intersection
52
+ if len(points) > 1: break # exit if two intersections have been found
53
+ else: return [] # fallback if 0 or one intersections found
54
+ return points
55
+
56
+ def closest_two_lines(p1: glm.vec3, q1: glm.vec3, p2: glm.vec3, q2: glm.vec3, epsilon: float=1e-7) -> tuple[glm.vec3, glm.vec3]:
57
+ """
58
+ Determines the closest point on each line segment to the other line segment.
59
+ """
60
+ # create direction vector
61
+ d1 = q1 - p1
62
+ d2 = q2 - p2
63
+ r = p1 - p2
64
+
65
+ # get lengths of line segments
66
+ a = glm.dot(d1, d1)
67
+ e = glm.dot(d2, d2)
68
+ f = glm.dot(d2, r)
69
+
70
+ # check if either or both line segment degenerate into points
71
+ if a <= epsilon and e <= epsilon:
72
+ # both segments degenerate
73
+ return p1, p2
74
+
75
+ if a <= epsilon:
76
+ s = 0
77
+ t = glm.clamp(f / e, 0, 1)
78
+ else:
79
+ c = glm.dot(d1, r)
80
+ if e <= epsilon:
81
+ # the second line degenerates to a point
82
+ t = 0
83
+ s = glm.clamp(-c / a, 0, 1)
84
+ else:
85
+ # if neither of them degenerate to a point
86
+ b = glm.dot(d1, d2)
87
+ denom = a * e - b ** 2 # this will always be non-negative
88
+
89
+ # if segments are not parallel, compute closest point from l1 to l2
90
+ s = glm.clamp((b * f - c * e) / denom, 0, 1) if denom else 0
91
+
92
+ # compute closest point from l2 on s1(s)
93
+ t = (b * s + f) / e
94
+
95
+ # if t is not in [0, 1], clamp and recompute s
96
+ if t < 0:
97
+ t = 0
98
+ s = glm.clamp(-c / a, 0, 1)
99
+ elif t > 1:
100
+ t = 1
101
+ s = glm.clamp((b - c) / a, 0, 1)
102
+
103
+ c1 = p1 + d1 * s
104
+ c2 = p2 + d2 * t
105
+ return c1, c2
106
+
107
+
@@ -0,0 +1,76 @@
1
+ import glm
2
+ from .helper import is_ccw_turn
3
+ from .line_intersections import line_line_intersect
4
+
5
+ def sutherland_hodgman(subject:list[glm.vec2], clip:list[glm.vec2]) -> list[glm.vec2]:
6
+ """determines the clipped polygon vertices from ccw oriented polygons"""
7
+ output_poly = subject
8
+
9
+ for i in range(len(clip)):
10
+ input_poly = output_poly
11
+ output_poly = []
12
+
13
+ edge_start, edge_end = clip[i], clip[(i + 1) % len(clip)]
14
+ for j in range(len(input_poly)):
15
+ prev_point, curr_point = input_poly[j - 1], input_poly[j]
16
+
17
+ if is_ccw_turn(curr_point, edge_start, edge_end):
18
+ if not is_ccw_turn(prev_point, edge_start, edge_end):
19
+ output_poly += line_line_intersect([edge_end, edge_start], [prev_point, curr_point])
20
+ output_poly.append(curr_point)
21
+ elif is_ccw_turn(prev_point, edge_start, edge_end):
22
+ output_poly += line_line_intersect([edge_end, edge_start], [prev_point, curr_point])
23
+
24
+ return output_poly
25
+
26
+ # def get_intersect(one: glm.vec2, two: glm.vec2, thr: glm.vec2, fou: glm.vec2) -> glm.vec2:
27
+ # """
28
+ # Gets the intersection point between two lines
29
+ # """
30
+ # deno = (one.x - two.x) * (thr.y - fou.y) - (one.y - two.y) * (thr.x - fou.x)
31
+ # if deno == 0: # TODO determine if this happens
32
+ # print('sutherland-hodgman line intersection had zero denominator')
33
+ # return None
34
+ # x_num = (one.x * two.y - one.y * two.x) * (thr.x - fou.x) - (one.x - two.x) * (thr.x * fou.y - thr.y * fou.x)
35
+ # y_num = (one.x * two.y - one.y * two.x) * (thr.y - fou.y) - (one.y - two.y) * (thr.x * fou.y - thr.y * fou.x)
36
+ # return glm.vec2(x_num / deno, y_num / deno)
37
+
38
+ # def clip(poly: list[glm.vec2], one: glm.vec2, two: glm.vec2) -> list[glm.vec2]:
39
+ # """
40
+ # Clip all edges of polygon with one of the clipping edges
41
+ # """
42
+ # num_points = len(poly)
43
+ # new_points = []
44
+
45
+ # for i in range(num_points):
46
+ # k = (i + 1) % num_points
47
+ # veci = poly[i]
48
+ # veck = poly[k]
49
+
50
+ # posi = (two.x - one.x) * (veci.y - one.y) - (two.y - one.y) * (veci.x - one.x)
51
+ # posk = (two.x - one.x) * (veck.y - one.y) - (two.y - one.y) * (veck.x - one.x)
52
+
53
+ # if posi < 0 and posk < 0: new_points.append(veck)
54
+ # elif posi >= 0 and posk < 0:
55
+
56
+ # new_points.append(get_intersect(one, two, veci, veck))
57
+ # new_points.append(veck)
58
+
59
+ # elif posi < 0 and posk >= 0:
60
+
61
+ # new_points.append(get_intersect(one, two, veci, veck))
62
+
63
+ # return new_points
64
+
65
+ # def sutherland_hodgman(subj_poly:list[glm.vec2], clip_poly:list[glm.vec2]) -> list[glm.vec2]:
66
+ # """
67
+ # Determines the clipped polygon vertices from ccw oriented polygons.
68
+ # """
69
+ # num_clip = len(clip_poly)
70
+
71
+ # for i in range(num_clip):
72
+ # k = (i + 1) % num_clip
73
+
74
+ # subj_poly = clip(subj_poly, clip_poly[i], clip_poly[k])
75
+
76
+ # return subj_poly
@@ -4,6 +4,7 @@ import glm
4
4
  from math import cos, sin, atan2
5
5
  from ..render.image import Image
6
6
  from .font_renderer import FontRenderer
7
+ from ..render.shader import Shader
7
8
 
8
9
  class DrawHandler():
9
10
  engine: ...
@@ -16,9 +17,9 @@ class DrawHandler():
16
17
  """2D draw program"""
17
18
  draw_data: list[float]
18
19
  """Temporary buffer for user draw calls"""
19
- vbo: mgl.Buffer
20
+ vbo: mgl.Buffer=None
20
21
  """Buffer for all 2D draws"""
21
- vao: mgl.VertexArray
22
+ vao: mgl.VertexArray=None
22
23
  """VAO for rendering all 2D draw calls"""
23
24
 
24
25
  def __init__(self, scene) -> None:
@@ -27,8 +28,9 @@ class DrawHandler():
27
28
  self.engine = scene.engine
28
29
  self.ctx = scene.engine.ctx
29
30
 
30
- # Get the program
31
- self.program = self.scene.shader_handler.programs['draw']
31
+ # Get the shader
32
+ root = self.engine.root
33
+ self.shader = self.scene.shader_handler.add(Shader(self.engine, root + '/shaders/draw.vert', root + '/shaders/draw.frag'))
32
34
 
33
35
  # Initialize draw data as blank
34
36
  self.draw_data = []
@@ -52,7 +54,7 @@ class DrawHandler():
52
54
 
53
55
  # Create buffer and VAO
54
56
  self.vbo = self.ctx.buffer(data)
55
- self.vao = self.ctx.vertex_array(self.program, [(self.vbo, '2f 4f 1i', *['in_position', 'in_color', 'in_uses_image'])], skip_errors=True)
57
+ self.vao = self.ctx.vertex_array(self.shader.program, [(self.vbo, '2f 4f 1i', *['in_position', 'in_color', 'in_uses_image'])], skip_errors=True)
56
58
 
57
59
  # Render the VAO
58
60
  self.ctx.enable(mgl.BLEND)
basilisk/engine.py CHANGED
@@ -1,11 +1,13 @@
1
1
  import os
2
+ from sys import platform
2
3
  os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
3
4
  import pygame as pg
4
5
  import moderngl as mgl
5
6
  from .config import Config
6
7
  from .input.mouse import Mouse
7
8
  from .mesh.cube import Cube
8
- import time
9
+ from .render.shader import Shader
10
+
9
11
 
10
12
  class Engine():
11
13
  win_size: tuple
@@ -35,7 +37,7 @@ class Engine():
35
37
  root: str
36
38
  """Path to the root directory containing internal data"""
37
39
 
38
- def __init__(self, win_size=(800, 800), title="Basilisk Engine", vsync=False, grab_mouse=True, headless=False) -> None:
40
+ def __init__(self, win_size=(800, 800), title="Basilisk Engine", vsync=None, grab_mouse=True, headless=False) -> None:
39
41
  """
40
42
  Basilisk Engine Class. Sets up the engine enviornment and allows the user to interact with Basilisk
41
43
  Args:
@@ -48,6 +50,11 @@ class Engine():
48
50
  headless: bool
49
51
  Flag for headless rendering
50
52
  """
53
+
54
+ if platform == 'win32' : self.platform = 'windows'
55
+ elif platform == 'darwin': self.platform = 'mac'
56
+ else: self.platform = 'linux'
57
+
51
58
  # Save the window size
52
59
  self.win_size = win_size
53
60
 
@@ -56,6 +63,8 @@ class Engine():
56
63
  pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION, 3)
57
64
  pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION, 3)
58
65
  pg.display.gl_set_attribute(pg.GL_CONTEXT_PROFILE_MASK, pg.GL_CONTEXT_PROFILE_CORE)
66
+ # Check vsync against platform defaults
67
+ if vsync == None: vsync = True if platform == 'linux' else False
59
68
  # Pygame display init
60
69
  if headless:
61
70
  pg.display.set_mode((300, 50), vsync=vsync, flags=pg.OPENGL | pg.DOUBLEBUF)
@@ -90,6 +99,10 @@ class Engine():
90
99
  # Scene being used by the engine
91
100
  self.scene = None
92
101
 
102
+ # Load a default shader
103
+ self.shader = Shader(self, self.root + '/shaders/batch.vert', self.root + '/shaders/batch.frag')
104
+ self.shader.hash = self.shader.hash + hash('engine_shader')
105
+
93
106
  # Set the scene to running
94
107
  self.running = True
95
108
 
@@ -103,6 +116,9 @@ class Engine():
103
116
  self.time += self.delta_time
104
117
  pg.display.set_caption(f"FPS: {round(self.clock.get_fps())}")
105
118
 
119
+ # Update the previous input lists for the next frame
120
+ self.previous_keys = self.keys
121
+
106
122
  # Get inputs and events
107
123
  self.events = pg.event.get()
108
124
  self.keys = pg.key.get_pressed()
@@ -126,8 +142,6 @@ class Engine():
126
142
  # Render after the scene and engine has been updated
127
143
  self.render()
128
144
 
129
- # Update the previous input lists for the next frame
130
- self.previous_keys = self.keys
131
145
 
132
146
  def render(self) -> None:
133
147
  """
@@ -167,8 +181,16 @@ class Engine():
167
181
 
168
182
  @property
169
183
  def scene(self): return self._scene
170
-
184
+ @property
185
+ def shader(self): return self._shader
186
+
171
187
  @scene.setter
172
188
  def scene(self, value):
173
189
  self._scene = value
174
- if self._scene: self._scene.set_engine(self)
190
+ if self._scene:
191
+ self._scene.set_engine(self)
192
+
193
+ @shader.setter
194
+ def shader(self, value):
195
+ self._shader = value
196
+ if self.scene: value.set_main()
@@ -0,0 +1,134 @@
1
+ import glm
2
+ import numpy as np
3
+
4
+
5
+ class Custom():
6
+
7
+ def normalize(self):
8
+ """
9
+ Inplace normalizes the vector
10
+ """
11
+ self.data = glm.normalize(self.data)
12
+ return self
13
+
14
+ def apply_function(): ... # will be overridden by child custom classes
15
+
16
+ # math functions
17
+ def add(self, other):
18
+ def func(a, b): return a + b
19
+ return self.apply_function(other, func, 'addition')
20
+
21
+ def subtract(self, other):
22
+ def func(a, b): return a - b
23
+ return self.apply_function(other, func, 'subtraction')
24
+
25
+ def rhs_subtract(self, other):
26
+ def func(a, b): return b - a
27
+ return self.apply_function(other, func, 'subtraction')
28
+
29
+ def multiply(self, other):
30
+ def func(a, b): return a * b
31
+ return self.apply_function(other, func, 'multiplication')
32
+
33
+ def divide(self, other):
34
+ def func(a, b): return a / b
35
+ return self.apply_function(other, func, 'division')
36
+
37
+ def rhs_divide(self, other):
38
+ def func(a, b): return b / a
39
+ return self.apply_function(other, func, 'division')
40
+
41
+ def floor_divide(self, other):
42
+ def func(a, b): return a // b
43
+ return self.apply_function(other, func, 'division')
44
+
45
+ def rhs_floor_divide(self, other):
46
+ def func(a, b): return b // a
47
+ return self.apply_function(other, func, 'division')
48
+
49
+ def mod(self, other):
50
+ def func(a, b): return a % b
51
+ return self.apply_function(other, func, 'modulus')
52
+
53
+ def rhs_mod(self, other):
54
+ def func(a, b): return b % a
55
+ return self.apply_function(other, func, 'modulus')
56
+
57
+ def pow(self, other):
58
+ def func(a, b): return a ** b
59
+ return self.apply_function(other, func, 'power')
60
+
61
+ def rhs_pow(self, other):
62
+ def func(a, b): return b ** a
63
+ return self.apply_function(other, func, 'power')
64
+
65
+ def __add__(self, other): return self.add(other) # this + that
66
+ def __radd__(self, other): return self.add(other) # that + this
67
+ def __iadd__(self, other): # this += that
68
+ self = self.add(other)
69
+ return self
70
+
71
+ def __sub__(self, other): return self.subtract(other)
72
+ def __rsub__(self, other): return self.rhs_subtract(other) # non-commutative
73
+ def __isub__(self, other):
74
+ self = self.subtract(other)
75
+ return self
76
+
77
+ def __mul__(self, other): return self.multiply(other)
78
+ def __rmul__(self, other): return self.multiply(other)
79
+ def __imul__(self, other):
80
+ self = self.multiply(other)
81
+ return self
82
+
83
+ def __truediv__(self, other): return self.divide(other)
84
+ def __rtruediv__(self, other): return self.rhs_divide(other) # non-commutative
85
+ def __itruediv__(self, other):
86
+ self = self.divide(other)
87
+ return self
88
+
89
+ def __floordiv__(self, other): return self.floor_divide(other)
90
+ def __rfloordiv__(self, other): return self.rhs_floor_divide(other) # non-commutative
91
+ def __ifloordiv__(self, other):
92
+ self = self.floor_divide(other)
93
+ return self
94
+
95
+ def __mod__(self, other): return self.mod(other)
96
+ def __rmod__(self, other): return self.rhs_mod(other)
97
+ def __imod__(self, other):
98
+ self = self.mod(other)
99
+ return self
100
+
101
+ def __pow__(self, other): return self.pow(other)
102
+ def __rpow__(self, other): return self.rhs_pow(other)
103
+ def __ipow__(self, other):
104
+ self = self.pow(other)
105
+ return self
106
+
107
+ # comparison functions
108
+ def __eq__(self, other):
109
+ if isinstance(other, Custom): return self.data == other.data
110
+ return self.data == other
111
+
112
+ def __ne__(self, other):
113
+ if isinstance(other, Custom): return self.data != other.data
114
+ return self.data != other
115
+
116
+ def __lt__(self, other):
117
+ if isinstance(other, Custom): return self.data < other.data
118
+ return self.data < other
119
+
120
+ def __gt__(self, other):
121
+ if isinstance(other, Custom): return self.data > other.data
122
+ return self.data > other
123
+
124
+ def __le__(self, other):
125
+ if isinstance(other, Custom): return self.data <= other.data
126
+ return self.data <= other
127
+
128
+ def __ge__(self, other):
129
+ if isinstance(other, Custom): return self.data >= other.data
130
+ return self.data >= other
131
+
132
+ # unary operators
133
+ def __pos__(self):
134
+ return self
@@ -7,6 +7,52 @@ def collide_aabb_aabb(top_right1: glm.vec3, bottom_left1: glm.vec3, top_right2:
7
7
  """
8
8
  return all(bottom_left1[i] <= top_right2[i] + epsilon and epsilon + top_right1[i] >= bottom_left2[i] for i in range(3))
9
9
 
10
+ def collide_aabb_line(top_right: glm.vec3, bottom_left: glm.vec3, position: glm.vec3, forward: glm.vec3) -> bool: # TODO check algorithm
11
+ """
12
+ Determines if an infinite line intersects with an AABB
13
+ """
14
+ tmin, tmax = -1e10, 1e10
15
+ for i in range(3):
16
+ if forward[i]: # if forward[i] is not 0 to avoid division errors
17
+
18
+ deno = 1 / forward[i]
19
+ tlow = (bottom_left[i] - position[i]) * deno
20
+ thigh = (top_right[i] - position[i]) * deno
21
+ if deno < 0: tlow, thigh = thigh, tlow
22
+ tmin = max(tmin, tlow)
23
+ tmax = min(tmax, thigh)
24
+ if tmax <= tmin: return False
25
+
26
+ elif position[i] + 1e-7 < bottom_left[i] or position[i] > top_right[i] + 1e-7: return False
27
+
28
+ return True
29
+
30
+ def moller_trumbore(point:glm.vec3, vec:glm.vec3, triangle:list[glm.vec3], epsilon:float=1e-7) -> glm.vec3:
31
+ """
32
+ Determines where a line intersects with a triangle and where that intersection occurred
33
+ """
34
+ edge1, edge2 = triangle[1] - triangle[0], triangle[2] - triangle[0]
35
+ ray_cross = glm.cross(vec, edge2)
36
+ det = glm.dot(edge1, ray_cross)
37
+
38
+ # if the ray is parallel to the triangle
39
+ if abs(det) < epsilon: return None
40
+
41
+ inv_det = 1 / det
42
+ s = point - triangle[0]
43
+ u = glm.dot(s, ray_cross) * inv_det
44
+
45
+ if (u < 0 and abs(u) > epsilon) or (u > 1 and abs(u - 1) > epsilon): return None
46
+
47
+ s_cross = glm.cross(s, edge1)
48
+ v = glm.dot(vec, s_cross) * inv_det
49
+
50
+ if (v < 0 and abs(v) > epsilon) or (u + v > 1 and abs(u + v - 1) > epsilon): return None
51
+
52
+ t = glm.dot(edge2, s_cross) * inv_det
53
+ if t > epsilon: return point + vec * t
54
+ return None
55
+
10
56
  def get_sat_axes(rotation1: glm.quat, rotation2: glm.quat) -> list[glm.vec3]:
11
57
  """
12
58
  Gets the axes for SAT from obb rotation matrices
@@ -14,13 +60,12 @@ def get_sat_axes(rotation1: glm.quat, rotation2: glm.quat) -> list[glm.vec3]:
14
60
  axes = []
15
61
  axes.extend(glm.transpose(glm.mat3_cast(rotation1)))
16
62
  axes.extend(glm.transpose(glm.mat3_cast(rotation2)))
17
- # axes.extend(glm.mat3_cast(rotation1))
18
- # axes.extend(glm.mat3_cast(rotation2))
19
63
 
20
64
  # crossed roots
21
65
  for i in range(0, 3):
22
66
  for j in range(3, 6):
23
67
  cross = glm.cross(axes[i], axes[j])
68
+ if glm.length2(cross) < 1e-6: continue
24
69
  axes.append(glm.normalize(cross))
25
70
 
26
71
  return axes