Save unfinished work

This commit is contained in:
Tipragot 2024-01-07 10:48:49 +01:00
parent e3c24ee9d6
commit d05d4a33fa

View file

@ -1,204 +1,204 @@
"""
Plugin implémentant une physique exacte pour des collisions AABB.
"""
from typing import Callable
from engine import GlobalPlugin
from engine.ecs import Entity, World
from engine.math import Vec2
from plugins.render import Origin, Position, Scale
from plugins.timing import Delta
class Solid:
"""
Composant représentant un objet (de préférence imobille pour que la simulation soit prédictible) qui ne laisse pas passer les objets dynamiques.
"""
class Velocity(Vec2):
"""
Composant donnant la vélocité d'un objet.
"""
class CollisionHandler:
"""
Composant permettant de traiter les collisions.
"""
def __init__(self, callback: Callable[[Entity, Entity], bool]):
self.callback = callback
class AABB:
"""
Définit une boite.
"""
def __init__(self, min: Vec2, max: Vec2, entity: Entity):
self.min = min
self.max = max
self.entity = entity
@staticmethod
def from_entity(entity: Entity):
min = entity[Position] - entity[Origin] * entity[Scale]
return AABB(min, min + entity[Scale], entity)
def entity_position(self, entity: Entity):
scale = self.max - self.min
entity[Position] = self.min + entity[Origin] * scale
entity[Scale] = scale
def __contains__(self, point: Vec2):
return (
self.min.x <= point.x <= self.max.x and self.min.y <= point.y <= self.max.y
)
def move(self, movement: Vec2):
self.min += movement
self.max += movement
def collide(self, other: "AABB"):
return (
self.min.x < other.max.x
and self.max.x > other.min.x
and self.min.y < other.max.y
and self.max.y > other.min.y
)
def line_to_line(sa: Vec2, ea: Vec2, sb: Vec2, eb: Vec2):
"""
Renvoie la collision entre deux lignes.
"""
if sa.x == ea.x:
sa.x += 0.0001
if sb.x == eb.x:
sb.x += 0.0001
if sa.y == ea.y:
sa.y += 0.0001
if sb.y == eb.y:
sb.y += 0.0001
divisor = (eb.y - sb.y) * (ea.x - sa.x) - (eb.x - sb.x) * (ea.y - sa.y)
if divisor == 0:
uA = 0
else:
uA = ((eb.x - sb.x) * (sa.y - sb.y) - (eb.y - sb.y) * (sa.x - sb.x)) / (divisor)
divisor = (eb.y - sb.y) * (ea.x - sa.x) - (eb.x - sb.x) * (ea.y - sa.y)
if divisor == 0:
uB = 0
else:
uB = ((ea.x - sa.x) * (sa.y - sb.y) - (ea.y - sa.y) * (sa.x - sb.x)) / (divisor)
if uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1:
return (
Vec2((uA * (ea.x - sa.x)), (uA * (ea.y - sa.y))).length / (ea - sa).length
)
return 1.0
def line_to_aabb(start: Vec2, end: Vec2, aabb: AABB):
"""
Renvoie la collision entre une ligne et une AABB.
"""
left = line_to_line(start, end, aabb.min, Vec2(aabb.min.x, aabb.max.y))
right = line_to_line(start, end, Vec2(aabb.max.x, aabb.min.y), aabb.max)
bottom = line_to_line(start, end, aabb.min, Vec2(aabb.max.x, aabb.min.y))
top = line_to_line(start, end, Vec2(aabb.min.x, aabb.max.y), aabb.max)
t = min([left, right, bottom, top])
if t == left:
normal = Vec2(-1, 0)
elif t == right:
normal = Vec2(1, 0)
elif t == bottom:
normal = Vec2(0, -1)
elif t == top:
normal = Vec2(0, 1)
else:
normal = Vec2(0, 0)
return t, normal
def aabb_to_aabb(moving: AABB, static: AABB, movement: Vec2):
"""
Renvoie la collision entre deux AABB.
"""
if moving.collide(static):
return 1.0, Vec2(0, 0)
size = (moving.max - moving.min) / 2
static = AABB(static.min - size, static.max + size, static.entity)
start_pos = moving.min + size
return line_to_aabb(start_pos, start_pos + movement, static)
def aabb_to_aabbs(moving: AABB, statics: list[AABB], movement: Vec2):
"""
Renvoie la collision entre deux AABB.
"""
t = 1.0
normal = Vec2(0, 0)
entity = None
for static in statics:
if static.entity == moving.entity:
continue
result = aabb_to_aabb(moving, static, movement)
if result[0] < t:
t = result[0]
normal = result[1]
entity = static.entity
return t, normal, entity
def move_entity(entity: Entity, movement: Vec2, disable_callback: bool = False):
world = entity.world
aabb = AABB.from_entity(entity)
others = [
AABB.from_entity(other) for other in world.query(Solid, Position, Scale, Origin)
]
counter = 0
while movement.length > 0.0001 and counter < 50:
t, normal, obstacle = aabb_to_aabbs(aabb, others, movement)
if t == 1.0:
step = movement
else:
step = movement * max(t - 0.000001, 0)
aabb.move(step)
aabb.entity_position(entity)
movement -= step
if normal.x != 0:
movement.x *= -1
entity[Velocity].x *= -1
if normal.y != 0:
movement.y *= -1
entity[Velocity].y *= -1
movement /= entity[Velocity]
if obstacle is not None and not disable_callback:
if not entity.get(
CollisionHandler, CollisionHandler(lambda e, o: True)
).callback(entity, obstacle):
break
if not obstacle.get(
CollisionHandler, CollisionHandler(lambda e, o: True)
).callback(obstacle, entity):
break
movement *= entity[Velocity]
counter += 1
def __apply_velocity(world: World):
"""
Applique la vélocité a toutes les entitées.
"""
delta = world[Delta]
for entity in world.query(Velocity, Position, Scale, Origin):
move_entity(entity, entity[Velocity] * delta)
PLUGIN = GlobalPlugin(
[],
[__apply_velocity],
[],
[],
)
"""
Plugin implémentant une physique exacte pour des collisions AABB.
"""
from typing import Callable
from engine import GlobalPlugin
from engine.ecs import Entity, World
from engine.math import Vec2
from plugins.render import Origin, Position, Scale
from plugins.timing import Delta
class Solid:
"""
Composant représentant un objet (de préférence imobille pour que la simulation soit prédictible) qui ne laisse pas passer les objets dynamiques.
"""
class Velocity(Vec2):
"""
Composant donnant la vélocité d'un objet.
"""
class CollisionHandler:
"""
Composant permettant de traiter les collisions.
"""
def __init__(self, callback: Callable[[Entity, Entity], bool]):
self.callback = callback
class AABB:
"""
Définit une boite.
"""
def __init__(self, min: Vec2, max: Vec2, entity: Entity):
self.min = min
self.max = max
self.entity = entity
@staticmethod
def from_entity(entity: Entity):
min = entity[Position] - entity[Origin] * entity[Scale]
return AABB(min, min + entity[Scale], entity)
def entity_position(self, entity: Entity):
scale = self.max - self.min
entity[Position] = self.min + entity[Origin] * scale
entity[Scale] = scale
def __contains__(self, point: Vec2):
return (
self.min.x <= point.x <= self.max.x and self.min.y <= point.y <= self.max.y
)
def move(self, movement: Vec2):
self.min += movement
self.max += movement
def collide(self, other: "AABB"):
return (
self.min.x < other.max.x
and self.max.x > other.min.x
and self.min.y < other.max.y
and self.max.y > other.min.y
)
def line_to_line(sa: Vec2, ea: Vec2, sb: Vec2, eb: Vec2):
"""
Renvoie la collision entre deux lignes.
"""
if sa.x == ea.x:
sa.x += 0.0001
if sb.x == eb.x:
sb.x += 0.0001
if sa.y == ea.y:
sa.y += 0.0001
if sb.y == eb.y:
sb.y += 0.0001
divisor = (eb.y - sb.y) * (ea.x - sa.x) - (eb.x - sb.x) * (ea.y - sa.y)
if divisor == 0:
uA = 0
else:
uA = ((eb.x - sb.x) * (sa.y - sb.y) - (eb.y - sb.y) * (sa.x - sb.x)) / (divisor)
divisor = (eb.y - sb.y) * (ea.x - sa.x) - (eb.x - sb.x) * (ea.y - sa.y)
if divisor == 0:
uB = 0
else:
uB = ((ea.x - sa.x) * (sa.y - sb.y) - (ea.y - sa.y) * (sa.x - sb.x)) / (divisor)
if uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1:
return (
Vec2((uA * (ea.x - sa.x)), (uA * (ea.y - sa.y))).length / (ea - sa).length
)
return 1.0
def line_to_aabb(start: Vec2, end: Vec2, aabb: AABB):
"""
Renvoie la collision entre une ligne et une AABB.
"""
left = line_to_line(start, end, aabb.min, Vec2(aabb.min.x, aabb.max.y))
right = line_to_line(start, end, Vec2(aabb.max.x, aabb.min.y), aabb.max)
bottom = line_to_line(start, end, aabb.min, Vec2(aabb.max.x, aabb.min.y))
top = line_to_line(start, end, Vec2(aabb.min.x, aabb.max.y), aabb.max)
t = min([left, right, bottom, top])
if t == left:
normal = Vec2(-1, 0)
elif t == right:
normal = Vec2(1, 0)
elif t == bottom:
normal = Vec2(0, -1)
elif t == top:
normal = Vec2(0, 1)
else:
normal = Vec2(0, 0)
return t, normal
def aabb_to_aabb(moving: AABB, static: AABB, movement: Vec2):
"""
Renvoie la collision entre deux AABB.
"""
if moving.collide(static):
return 1.0, Vec2(0, 0)
size = (moving.max - moving.min) / 2
static = AABB(static.min - size, static.max + size, static.entity)
start_pos = moving.min + size
return line_to_aabb(start_pos, start_pos + movement, static)
def aabb_to_aabbs(moving: AABB, statics: list[AABB], movement: Vec2):
"""
Renvoie la collision entre deux AABB.
"""
t = 1.0
normal = Vec2(0, 0)
entity = None
for static in statics:
if static.entity == moving.entity:
continue
result = aabb_to_aabb(moving, static, movement)
if result[0] < t:
t = result[0]
normal = result[1]
entity = static.entity
return t, normal, entity
def move_entity(entity: Entity, movement: Vec2, disable_callback: bool = False):
world = entity.world
aabb = AABB.from_entity(entity)
others = [
AABB.from_entity(other) for other in world.query(Solid, Position, Scale, Origin)
]
counter = 0
while movement.length > 0.0001 and counter < 50:
t, normal, obstacle = aabb_to_aabbs(aabb, others, movement)
if t == 1.0:
step = movement
else:
step = movement * max(t - 0.000001, 0)
aabb.move(step)
aabb.entity_position(entity)
movement -= step
if normal.x != 0:
movement.x *= -1
entity[Velocity].x *= -1
if normal.y != 0:
movement.y *= -1
entity[Velocity].y *= -1
movement /= entity[Velocity]
if obstacle is not None and not disable_callback:
if not entity.get(
CollisionHandler, CollisionHandler(lambda e, o: True)
).callback(entity, obstacle):
break
if not obstacle.get(
CollisionHandler, CollisionHandler(lambda e, o: True)
).callback(obstacle, entity):
break
movement *= entity[Velocity]
counter += 1
def __apply_velocity(world: World):
"""
Applique la vélocité a toutes les entitées.
"""
delta = world[Delta]
for entity in world.query(Velocity, Position, Scale, Origin):
move_entity(entity, entity[Velocity] * delta)
PLUGIN = GlobalPlugin(
[],
[__apply_velocity],
[],
[],
)