aether-engine 1.0.0__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.
Files changed (64) hide show
  1. aether/__init__.py +54 -0
  2. aether/audio/__init__.py +0 -0
  3. aether/audio/audio_manager.py +211 -0
  4. aether/audio/music.py +6 -0
  5. aether/audio/sound.py +6 -0
  6. aether/core/__init__.py +211 -0
  7. aether/core/achievement_system.py +304 -0
  8. aether/core/application.py +19 -0
  9. aether/core/config.py +75 -0
  10. aether/core/console.py +366 -0
  11. aether/core/engine.py +50 -0
  12. aether/core/event_bus.py +24 -0
  13. aether/core/input.py +58 -0
  14. aether/core/plugin_system.py +93 -0
  15. aether/core/save_system.py +409 -0
  16. aether/core/time_manager.py +25 -0
  17. aether/core/window.py +57 -0
  18. aether/graphics/__init__.py +0 -0
  19. aether/graphics/camera.py +69 -0
  20. aether/graphics/framebuffer.py +119 -0
  21. aether/graphics/light.py +32 -0
  22. aether/graphics/lod_system.py +170 -0
  23. aether/graphics/material.py +25 -0
  24. aether/graphics/mesh.py +83 -0
  25. aether/graphics/mesh_factory.py +67 -0
  26. aether/graphics/particle_system.py +268 -0
  27. aether/graphics/post_process.py +279 -0
  28. aether/graphics/renderer.py +135 -0
  29. aether/graphics/shader.py +61 -0
  30. aether/graphics/shader_library.py +157 -0
  31. aether/graphics/sky_atmosphere.py +309 -0
  32. aether/graphics/terrain.py +408 -0
  33. aether/graphics/texture.py +34 -0
  34. aether/graphics/water.py +432 -0
  35. aether/physics/__init__.py +0 -0
  36. aether/physics/bullet_backend.py +395 -0
  37. aether/physics/collider.py +22 -0
  38. aether/physics/physics_world.py +436 -0
  39. aether/physics/raycast.py +25 -0
  40. aether/physics/rigidbody.py +7 -0
  41. aether/resources/__init__.py +0 -0
  42. aether/resources/asset_database.py +76 -0
  43. aether/resources/resource_manager.py +147 -0
  44. aether/resources/serialization.py +114 -0
  45. aether/scene/__init__.py +0 -0
  46. aether/scene/component.py +368 -0
  47. aether/scene/entity.py +46 -0
  48. aether/scene/scene.py +33 -0
  49. aether/scene/scene_graph.py +252 -0
  50. aether/scene/scene_manager.py +191 -0
  51. aether/ui/__init__.py +0 -0
  52. aether/ui/button.py +297 -0
  53. aether/ui/canvas.py +11 -0
  54. aether/ui/text.py +286 -0
  55. aether/utils/__init__.py +0 -0
  56. aether/utils/logger.py +16 -0
  57. aether/utils/math_utils.py +31 -0
  58. aether/utils/object_pool.py +137 -0
  59. aether/utils/profiler.py +187 -0
  60. aether_engine-1.0.0.dist-info/METADATA +71 -0
  61. aether_engine-1.0.0.dist-info/RECORD +64 -0
  62. aether_engine-1.0.0.dist-info/WHEEL +5 -0
  63. aether_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
  64. aether_engine-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,252 @@
1
+ """Граф сцены с иерархией объектов и трансформациями"""
2
+ import numpy as np
3
+ from typing import Optional, List, Set, Dict
4
+ from aether.scene.entity import Entity
5
+
6
+
7
+ class SceneNode:
8
+ """Узел графа сцены"""
9
+
10
+ def __init__(self, entity: Entity):
11
+ self.entity = entity
12
+ self.parent: Optional['SceneNode'] = None
13
+ self.children: List['SceneNode'] = []
14
+
15
+ # Кэшированная мировая матрица
16
+ self._world_matrix_dirty = True
17
+ self._cached_world_matrix = np.identity(4, dtype=np.float32)
18
+
19
+ # Мировые трансформации (кэш)
20
+ self._world_position = np.array([0.0, 0.0, 0.0], dtype=np.float32)
21
+ self._world_rotation = np.array([0.0, 0.0, 0.0], dtype=np.float32)
22
+ self._world_scale = np.array([1.0, 1.0, 1.0], dtype=np.float32)
23
+
24
+ def add_child(self, child: 'SceneNode'):
25
+ """Добавляет дочерний узел"""
26
+ if child.parent:
27
+ child.parent.remove_child(child)
28
+
29
+ child.parent = self
30
+ self.children.append(child)
31
+ child._mark_dirty()
32
+
33
+ def remove_child(self, child: 'SceneNode'):
34
+ """Удаляет дочерний узел"""
35
+ if child in self.children:
36
+ self.children.remove(child)
37
+ child.parent = None
38
+ child._mark_dirty()
39
+
40
+ def set_parent(self, parent: Optional['SceneNode']):
41
+ """Устанавливает родительский узел"""
42
+ if parent:
43
+ parent.add_child(self)
44
+ elif self.parent:
45
+ self.parent.remove_child(self)
46
+
47
+ def _mark_dirty(self):
48
+ """Помечает матрицу как устаревшую (рекурсивно)"""
49
+ self._world_matrix_dirty = True
50
+ for child in self.children:
51
+ child._mark_dirty()
52
+
53
+ def get_world_matrix(self) -> np.ndarray:
54
+ """Получает мировую матрицу с учётом иерархии"""
55
+ if not self._world_matrix_dirty:
56
+ return self._cached_world_matrix
57
+
58
+ # Получаем локальную матрицу
59
+ transform = self.entity.get_component("Transform")
60
+ if transform:
61
+ local_matrix = transform.get_model_matrix()
62
+ else:
63
+ local_matrix = np.identity(4, dtype=np.float32)
64
+
65
+ # Применяем родительскую матрицу
66
+ if self.parent:
67
+ parent_matrix = self.parent.get_world_matrix()
68
+ self._cached_world_matrix = parent_matrix @ local_matrix
69
+ else:
70
+ self._cached_world_matrix = local_matrix.copy()
71
+
72
+ # Извлекаем мировые компоненты
73
+ self._extract_world_components()
74
+
75
+ self._world_matrix_dirty = False
76
+ return self._cached_world_matrix
77
+
78
+ def _extract_world_components(self):
79
+ """Извлекает позицию, поворот и масштаб из мировой матрицы"""
80
+ m = self._cached_world_matrix
81
+
82
+ # Позиция
83
+ self._world_position = m[3, :3].copy()
84
+
85
+ # Масштаб
86
+ scale_x = np.linalg.norm(m[0, :3])
87
+ scale_y = np.linalg.norm(m[1, :3])
88
+ scale_z = np.linalg.norm(m[2, :3])
89
+ self._world_scale = np.array([scale_x, scale_y, scale_z], dtype=np.float32)
90
+
91
+ # Поворот (упрощённо)
92
+ if scale_x > 0 and scale_y > 0 and scale_z > 0:
93
+ rot_matrix = m[:3, :3].copy()
94
+ rot_matrix[0, :3] /= scale_x
95
+ rot_matrix[1, :3] /= scale_y
96
+ rot_matrix[2, :3] /= scale_z
97
+
98
+ # Извлекаем углы Эйлера
99
+ self._world_rotation = self._matrix_to_euler(rot_matrix)
100
+
101
+ def _matrix_to_euler(self, m: np.ndarray) -> np.ndarray:
102
+ """Конвертирует матрицу поворота в углы Эйлера"""
103
+ sy = np.sqrt(m[0, 0] ** 2 + m[1, 0] ** 2)
104
+
105
+ if sy > 1e-6:
106
+ x = np.arctan2(m[2, 1], m[2, 2])
107
+ y = np.arctan2(-m[2, 0], sy)
108
+ z = np.arctan2(m[1, 0], m[0, 0])
109
+ else:
110
+ x = np.arctan2(-m[1, 2], m[1, 1])
111
+ y = np.arctan2(-m[2, 0], sy)
112
+ z = 0.0
113
+
114
+ return np.degrees(np.array([x, y, z], dtype=np.float32))
115
+
116
+ @property
117
+ def world_position(self) -> np.ndarray:
118
+ """Мировая позиция"""
119
+ self.get_world_matrix()
120
+ return self._world_position.copy()
121
+
122
+ @property
123
+ def world_rotation(self) -> np.ndarray:
124
+ """Мировой поворот"""
125
+ self.get_world_matrix()
126
+ return self._world_rotation.copy()
127
+
128
+ @property
129
+ def world_scale(self) -> np.ndarray:
130
+ """Мировой масштаб"""
131
+ self.get_world_matrix()
132
+ return self._world_scale.copy()
133
+
134
+ def forward(self) -> np.ndarray:
135
+ """Направление вперёд в мировом пространстве"""
136
+ m = self.get_world_matrix()
137
+ forward = -m[2, :3]
138
+ return forward / np.linalg.norm(forward)
139
+
140
+ def right(self) -> np.ndarray:
141
+ """Направление вправо в мировом пространстве"""
142
+ m = self.get_world_matrix()
143
+ right = m[0, :3]
144
+ return right / np.linalg.norm(right)
145
+
146
+ def up(self) -> np.ndarray:
147
+ """Направление вверх в мировом пространстве"""
148
+ m = self.get_world_matrix()
149
+ up = m[1, :3]
150
+ return up / np.linalg.norm(up)
151
+
152
+
153
+ class SceneGraph:
154
+ """Граф сцены"""
155
+
156
+ def __init__(self):
157
+ self._nodes: Dict[str, SceneNode] = {}
158
+ self._root_nodes: List[SceneNode] = []
159
+ self._dirty = True
160
+
161
+ def register_entity(self, entity: Entity) -> SceneNode:
162
+ """Регистрирует Entity в графе"""
163
+ if entity.id in self._nodes:
164
+ return self._nodes[entity.id]
165
+
166
+ node = SceneNode(entity)
167
+ self._nodes[entity.id] = node
168
+ self._root_nodes.append(node)
169
+ self._dirty = True
170
+
171
+ return node
172
+
173
+ def unregister_entity(self, entity: Entity):
174
+ """Удаляет Entity из графа"""
175
+ if entity.id in self._nodes:
176
+ node = self._nodes[entity.id]
177
+
178
+ # Отсоединяем от родителя
179
+ if node.parent:
180
+ node.parent.remove_child(node)
181
+
182
+ # Удаляем из корневых
183
+ if node in self._root_nodes:
184
+ self._root_nodes.remove(node)
185
+
186
+ # Переносим детей в корень
187
+ for child in node.children[:]:
188
+ node.remove_child(child)
189
+ self._root_nodes.append(child)
190
+
191
+ del self._nodes[entity.id]
192
+ self._dirty = True
193
+
194
+ def set_parent(self, child_entity: Entity, parent_entity: Entity):
195
+ """Устанавливает родительскую связь"""
196
+ child_node = self._nodes.get(child_entity.id)
197
+ parent_node = self._nodes.get(parent_entity.id)
198
+
199
+ if child_node and parent_node:
200
+ if child_node in self._root_nodes:
201
+ self._root_nodes.remove(child_node)
202
+
203
+ parent_node.add_child(child_node)
204
+ self._dirty = True
205
+
206
+ def get_node(self, entity: Entity) -> Optional[SceneNode]:
207
+ """Получает узел по Entity"""
208
+ return self._nodes.get(entity.id)
209
+
210
+ def get_root_nodes(self) -> List[SceneNode]:
211
+ """Возвращает корневые узлы"""
212
+ return self._root_nodes.copy()
213
+
214
+ def get_all_nodes(self) -> List[SceneNode]:
215
+ """Возвращает все узлы"""
216
+ return list(self._nodes.values())
217
+
218
+ def update(self):
219
+ """Обновляет все мировые матрицы"""
220
+ for root in self._root_nodes:
221
+ self._update_node_recursive(root)
222
+
223
+ self._dirty = False
224
+
225
+ def _update_node_recursive(self, node: SceneNode):
226
+ """Рекурсивно обновляет узел и его детей"""
227
+ node._mark_dirty()
228
+ node.get_world_matrix()
229
+
230
+ for child in node.children:
231
+ self._update_node_recursive(child)
232
+
233
+ def find_entity_at_point(self, point: np.ndarray) -> Optional[Entity]:
234
+ """Находит Entity в заданной точке (упрощённо)"""
235
+ for node in reversed(self.get_all_nodes()):
236
+ collider = node.entity.get_component("Collider") or \
237
+ node.entity.get_component("BoxCollider") or \
238
+ node.entity.get_component("SphereCollider")
239
+
240
+ if collider:
241
+ world_pos = node.world_position
242
+ # Простая проверка AABB (можно улучшить)
243
+ if isinstance(collider, BoxCollider):
244
+ min_point = world_pos + collider.center - collider.size / 2
245
+ max_point = world_pos + collider.center + collider.size / 2
246
+
247
+ if (min_point[0] <= point[0] <= max_point[0] and
248
+ min_point[1] <= point[1] <= max_point[1] and
249
+ min_point[2] <= point[2] <= max_point[2]):
250
+ return node.entity
251
+
252
+ return None
@@ -0,0 +1,191 @@
1
+ """Менеджер сцен — загрузка, выгрузка, переходы"""
2
+ from typing import Dict, Optional, List
3
+ from aether.scene.scene import Scene
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+
8
+
9
+ class SceneTransition:
10
+ """Переход между сценами"""
11
+
12
+ def __init__(self, from_scene: str, to_scene: str,
13
+ transition_time: float = 1.0):
14
+ self.from_scene = from_scene
15
+ self.to_scene = to_scene
16
+ self.transition_time = transition_time
17
+ self.elapsed = 0.0
18
+ self.completed = False
19
+ self.progress = 0.0
20
+
21
+ def update(self, dt: float):
22
+ """Обновляет прогресс перехода"""
23
+ self.elapsed += dt
24
+ self.progress = min(1.0, self.elapsed / self.transition_time)
25
+ if self.progress >= 1.0:
26
+ self.completed = True
27
+
28
+
29
+ class SceneManager:
30
+ """Менеджер сцен"""
31
+
32
+ _instance = None
33
+
34
+ def __new__(cls):
35
+ if cls._instance is None:
36
+ cls._instance = super().__new__(cls)
37
+ cls._instance._initialize()
38
+ return cls._instance
39
+
40
+ def _initialize(self):
41
+ """Инициализация менеджера"""
42
+ self._scenes: Dict[str, Scene] = {}
43
+ self._active_scene: Optional[Scene] = None
44
+ self._scene_queue: List[str] = []
45
+ self._transition: Optional[SceneTransition] = None
46
+ self._loading = False
47
+
48
+ # Пути для сохранения сцен
49
+ self._scenes_path = Path("./scenes")
50
+
51
+ print("[SceneManager] Initialized")
52
+
53
+ def register_scene(self, scene: Scene):
54
+ """Регистрирует сцену"""
55
+ self._scenes[scene.name] = scene
56
+ print(f"[SceneManager] Registered scene: {scene.name}")
57
+
58
+ def unregister_scene(self, name: str):
59
+ """Удаляет сцену"""
60
+ if name in self._scenes:
61
+ if self._active_scene and self._active_scene.name == name:
62
+ self._active_scene.destroy()
63
+ self._active_scene = None
64
+
65
+ del self._scenes[name]
66
+
67
+ def load_scene(self, name: str, additive: bool = False):
68
+ """Загружает сцену"""
69
+ if name not in self._scenes:
70
+ print(f"[SceneManager] Scene '{name}' not found!")
71
+ return False
72
+
73
+ # Если есть активная сцена и не аддитивная загрузка
74
+ if self._active_scene and not additive:
75
+ self._active_scene.destroy()
76
+
77
+ scene = self._scenes[name]
78
+
79
+ if not additive:
80
+ self._active_scene = scene
81
+ else:
82
+ # Аддитивная загрузка — добавляем объекты к текущей сцене
83
+ if self._active_scene:
84
+ for entity in scene.entities:
85
+ self._active_scene.spawn(entity)
86
+
87
+ scene.awake()
88
+ scene.start()
89
+
90
+ print(f"[SceneManager] Loaded scene: {name}")
91
+ return True
92
+
93
+ def load_scene_async(self, name: str):
94
+ """Асинхронная загрузка сцены"""
95
+ import threading
96
+
97
+ def _load():
98
+ self._loading = True
99
+ success = self.load_scene(name)
100
+ self._loading = False
101
+ if success:
102
+ print(f"[SceneManager] Async load complete: {name}")
103
+
104
+ thread = threading.Thread(target=_load, daemon=True)
105
+ thread.start()
106
+
107
+ def transition_to(self, name: str, transition_time: float = 1.0):
108
+ """Переход к новой сцене с эффектом"""
109
+ if name not in self._scenes:
110
+ print(f"[SceneManager] Scene '{name}' not found!")
111
+ return
112
+
113
+ if self._active_scene:
114
+ self._transition = SceneTransition(
115
+ self._active_scene.name, name, transition_time
116
+ )
117
+
118
+ def update(self, dt: float):
119
+ """Обновляет менеджер"""
120
+ if self._transition:
121
+ self._transition.update(dt)
122
+
123
+ if self._transition.completed:
124
+ self.load_scene(self._transition.to_scene)
125
+ self._transition = None
126
+
127
+ def get_active_scene(self) -> Optional[Scene]:
128
+ """Возвращает активную сцену"""
129
+ return self._active_scene
130
+
131
+ def get_scene(self, name: str) -> Optional[Scene]:
132
+ """Возвращает сцену по имени"""
133
+ return self._scenes.get(name)
134
+
135
+ def get_all_scenes(self) -> Dict[str, Scene]:
136
+ """Возвращает все сцены"""
137
+ return self._scenes.copy()
138
+
139
+ def save_scene(self, name: str, path: Optional[str] = None):
140
+ """Сохраняет сцену в файл"""
141
+ scene = self._scenes.get(name)
142
+ if not scene:
143
+ print(f"[SceneManager] Scene '{name}' not found!")
144
+ return
145
+
146
+ if path is None:
147
+ os.makedirs(self._scenes_path, exist_ok=True)
148
+ path = self._scenes_path / f"{name}.json"
149
+
150
+ try:
151
+ from aether.resources.serialization import SceneSerializer, Serializer
152
+
153
+ serializer = Serializer()
154
+ scene_serializer = SceneSerializer(serializer)
155
+ scene_serializer.save_scene(scene, str(path))
156
+
157
+ print(f"[SceneManager] Saved scene '{name}' to {path}")
158
+ except Exception as e:
159
+ print(f"[SceneManager] Failed to save scene: {e}")
160
+
161
+ def load_scene_from_file(self, path: str) -> Optional[Scene]:
162
+ """Загружает сцену из файла"""
163
+ try:
164
+ from aether.resources.serialization import SceneSerializer, Serializer
165
+
166
+ serializer = Serializer()
167
+ scene_serializer = SceneSerializer(serializer)
168
+ scene = scene_serializer.load_scene(path)
169
+
170
+ self.register_scene(scene)
171
+ print(f"[SceneManager] Loaded scene from {path}")
172
+
173
+ return scene
174
+ except Exception as e:
175
+ print(f"[SceneManager] Failed to load scene: {e}")
176
+ return None
177
+
178
+ @property
179
+ def is_transitioning(self) -> bool:
180
+ """Идёт ли переход между сценами"""
181
+ return self._transition is not None
182
+
183
+ @property
184
+ def is_loading(self) -> bool:
185
+ """Идёт ли загрузка сцены"""
186
+ return self._loading
187
+
188
+ @property
189
+ def transition_progress(self) -> float:
190
+ """Прогресс перехода (0.0 - 1.0)"""
191
+ return self._transition.progress if self._transition else 0.0
aether/ui/__init__.py ADDED
File without changes