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.
- basilisk/__init__.py +3 -1
- basilisk/collisions/broad/broad_aabb.py +8 -1
- basilisk/collisions/broad/broad_bvh.py +38 -2
- basilisk/collisions/collider.py +20 -10
- basilisk/collisions/collider_handler.py +97 -32
- basilisk/collisions/narrow/contact_manifold.py +91 -0
- basilisk/collisions/narrow/dataclasses.py +27 -0
- basilisk/collisions/narrow/deprecated.py +47 -0
- basilisk/collisions/narrow/epa.py +21 -15
- basilisk/collisions/narrow/gjk.py +15 -14
- basilisk/collisions/narrow/graham_scan.py +25 -0
- basilisk/collisions/narrow/helper.py +14 -7
- basilisk/collisions/narrow/line_intersections.py +107 -0
- basilisk/collisions/narrow/sutherland_hodgman.py +76 -0
- basilisk/draw/draw_handler.py +7 -5
- basilisk/engine.py +28 -6
- basilisk/generic/abstract_custom.py +134 -0
- basilisk/generic/collisions.py +47 -2
- basilisk/generic/quat.py +84 -65
- basilisk/generic/vec3.py +99 -67
- basilisk/input/mouse.py +3 -3
- basilisk/mesh/cube.py +20 -2
- basilisk/mesh/mesh.py +69 -54
- basilisk/mesh/mesh_from_data.py +106 -21
- basilisk/mesh/narrow_aabb.py +10 -1
- basilisk/mesh/narrow_bvh.py +9 -1
- basilisk/nodes/node.py +211 -101
- basilisk/nodes/node_handler.py +58 -33
- basilisk/particles/__init__.py +0 -0
- basilisk/particles/particle_handler.py +55 -0
- basilisk/particles/particle_renderer.py +87 -0
- basilisk/physics/impulse.py +113 -0
- basilisk/physics/physics_body.py +10 -2
- basilisk/physics/physics_engine.py +2 -3
- basilisk/render/batch.py +3 -1
- basilisk/render/camera.py +35 -1
- basilisk/render/chunk.py +19 -4
- basilisk/render/chunk_handler.py +39 -23
- basilisk/render/image.py +1 -1
- basilisk/render/image_handler.py +17 -14
- basilisk/render/light_handler.py +16 -11
- basilisk/render/material.py +38 -14
- basilisk/render/material_handler.py +31 -18
- basilisk/render/shader.py +110 -0
- basilisk/render/shader_handler.py +20 -35
- basilisk/render/sky.py +8 -5
- basilisk/scene.py +116 -33
- basilisk/shaders/batch.frag +40 -11
- basilisk/shaders/batch.vert +14 -7
- basilisk/shaders/geometry.frag +9 -0
- basilisk/shaders/geometry.vert +42 -0
- basilisk/shaders/normal.frag +60 -0
- basilisk/shaders/normal.vert +97 -0
- basilisk/shaders/particle.frag +72 -0
- basilisk/shaders/particle.vert +85 -0
- {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/METADATA +5 -5
- basilisk_engine-0.1.1.dist-info/RECORD +95 -0
- basilisk/shaders/image.png +0 -0
- basilisk_engine-0.0.9.dist-info/RECORD +0 -78
- {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/WHEEL +0 -0
- {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]
|
|
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
|
|
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]
|
|
42
|
-
return False, triple_product(vec_ab, -simplex[0]
|
|
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]
|
|
49
|
-
return False, -dir_vec if glm.dot(dir_vec, -simplex[0]
|
|
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]
|
|
56
|
-
vec_db = simplex[3]
|
|
57
|
-
vec_dc = simplex[3]
|
|
58
|
-
vec_do = -simplex[3]
|
|
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
|
-
|
|
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
|
basilisk/draw/draw_handler.py
CHANGED
|
@@ -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
|
|
31
|
-
|
|
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
|
|
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=
|
|
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:
|
|
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
|
basilisk/generic/collisions.py
CHANGED
|
@@ -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
|