nodes/particle.pyΒΆ

Part of Q1K3.

 1"""Particle entities for blood, gibs, explosion fragments."""
 2
 3from __future__ import annotations
 4
 5import random
 6from typing import TYPE_CHECKING
 7
 8from simvx.core import Material, MeshInstance3D, Node3D, Vec3
 9
10from . import meshes, textures
11from .physics import PhysicsBody, update_physics
12
13if TYPE_CHECKING:  # pragma: no cover
14    from .root import Q1K3Root
15
16
17_PART_MAT_CACHE: dict[int, Material] = {}
18
19
20def _mat_for(tex_id: int) -> Material:
21    if tex_id not in _PART_MAT_CACHE:
22        _PART_MAT_CACHE[tex_id] = Material(albedo_map=textures.get(tex_id), roughness=0.6, metallic=0.0)
23    return _PART_MAT_CACHE[tex_id]
24
25
26class Particle(Node3D, PhysicsBody):
27    def __init__(self, game: Q1K3Root, pos: Vec3, vel: Vec3, lifetime: float, tex_id: int) -> None:
28        super().__init__()
29        self._physics_init()
30        self.game = game
31        self.position = pos
32        self.p = Vec3(pos.x, pos.y, pos.z)
33        self.v = Vec3(vel.x, vel.y, vel.z)
34        self.s = Vec3(2, 2, 2)
35        self.f = 0.1
36        self._gravity = 1.0
37        self._bounciness = 0.5
38        self._die_at = game.game_time + lifetime
39        self._dead = False
40        self._yaw = random.random()
41        self._pitch = random.random()
42
43        self._inst = MeshInstance3D(
44            mesh=meshes.cube(),
45            material=_mat_for(tex_id),
46            scale=(2.5, 2.5, 2.5),
47        )
48        self.add_child(self._inst)
49
50    def on_update(self, dt: float) -> None:
51        if self._dead:
52            return
53        if self._die_at <= self.game.game_time:
54            self._kill()
55            return
56        self._yaw += float(self.v.y) * 0.001
57        self._pitch += float(self.v.x) * 0.001
58        update_physics(self, self.game.world, [], [], dt, self.game.game_time)
59        self.position = self.p
60        from simvx.core.math.types import Quat
61
62        self.rotation = Quat.from_axis_angle(Vec3(0, 1, 0), self._yaw)
63
64    def _kill(self) -> None:
65        if self._dead:
66            return
67        self._dead = True
68        self.game.queue_remove(self)
69
70
71def spawn_burst(game: Q1K3Root, pos: Vec3, count: int, speed: float, lifetime: float, tex_id: int) -> None:
72    for _ in range(count):
73        v = Vec3(
74            (random.random() - 0.5) * speed,
75            random.random() * speed,
76            (random.random() - 0.5) * speed,
77        )
78        p = Particle(game, pos, v, lifetime + random.random() * lifetime * 0.2, tex_id)
79        game.add_entity(p)