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.
- aether/__init__.py +54 -0
- aether/audio/__init__.py +0 -0
- aether/audio/audio_manager.py +211 -0
- aether/audio/music.py +6 -0
- aether/audio/sound.py +6 -0
- aether/core/__init__.py +211 -0
- aether/core/achievement_system.py +304 -0
- aether/core/application.py +19 -0
- aether/core/config.py +75 -0
- aether/core/console.py +366 -0
- aether/core/engine.py +50 -0
- aether/core/event_bus.py +24 -0
- aether/core/input.py +58 -0
- aether/core/plugin_system.py +93 -0
- aether/core/save_system.py +409 -0
- aether/core/time_manager.py +25 -0
- aether/core/window.py +57 -0
- aether/graphics/__init__.py +0 -0
- aether/graphics/camera.py +69 -0
- aether/graphics/framebuffer.py +119 -0
- aether/graphics/light.py +32 -0
- aether/graphics/lod_system.py +170 -0
- aether/graphics/material.py +25 -0
- aether/graphics/mesh.py +83 -0
- aether/graphics/mesh_factory.py +67 -0
- aether/graphics/particle_system.py +268 -0
- aether/graphics/post_process.py +279 -0
- aether/graphics/renderer.py +135 -0
- aether/graphics/shader.py +61 -0
- aether/graphics/shader_library.py +157 -0
- aether/graphics/sky_atmosphere.py +309 -0
- aether/graphics/terrain.py +408 -0
- aether/graphics/texture.py +34 -0
- aether/graphics/water.py +432 -0
- aether/physics/__init__.py +0 -0
- aether/physics/bullet_backend.py +395 -0
- aether/physics/collider.py +22 -0
- aether/physics/physics_world.py +436 -0
- aether/physics/raycast.py +25 -0
- aether/physics/rigidbody.py +7 -0
- aether/resources/__init__.py +0 -0
- aether/resources/asset_database.py +76 -0
- aether/resources/resource_manager.py +147 -0
- aether/resources/serialization.py +114 -0
- aether/scene/__init__.py +0 -0
- aether/scene/component.py +368 -0
- aether/scene/entity.py +46 -0
- aether/scene/scene.py +33 -0
- aether/scene/scene_graph.py +252 -0
- aether/scene/scene_manager.py +191 -0
- aether/ui/__init__.py +0 -0
- aether/ui/button.py +297 -0
- aether/ui/canvas.py +11 -0
- aether/ui/text.py +286 -0
- aether/utils/__init__.py +0 -0
- aether/utils/logger.py +16 -0
- aether/utils/math_utils.py +31 -0
- aether/utils/object_pool.py +137 -0
- aether/utils/profiler.py +187 -0
- aether_engine-1.0.0.dist-info/METADATA +71 -0
- aether_engine-1.0.0.dist-info/RECORD +64 -0
- aether_engine-1.0.0.dist-info/WHEEL +5 -0
- aether_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
- aether_engine-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Система террайна с LOD и текстурированием"""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from OpenGL.GL import *
|
|
4
|
+
from typing import List, Tuple, Optional
|
|
5
|
+
from aether.graphics.mesh import Mesh
|
|
6
|
+
from aether.graphics.texture import Texture
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TerrainChunk:
|
|
10
|
+
"""Чанк террайна"""
|
|
11
|
+
|
|
12
|
+
def __init__(self,
|
|
13
|
+
x: int, z: int,
|
|
14
|
+
size: int,
|
|
15
|
+
resolution: int,
|
|
16
|
+
height_map: np.ndarray):
|
|
17
|
+
self.grid_x = x
|
|
18
|
+
self.grid_z = z
|
|
19
|
+
self.size = size
|
|
20
|
+
self.resolution = resolution
|
|
21
|
+
self.height_map = height_map
|
|
22
|
+
|
|
23
|
+
# Позиция в мире
|
|
24
|
+
self.world_x = x * size
|
|
25
|
+
self.world_z = z * size
|
|
26
|
+
|
|
27
|
+
# Меш
|
|
28
|
+
self.mesh: Optional[Mesh] = None
|
|
29
|
+
self._generate_mesh()
|
|
30
|
+
|
|
31
|
+
def _generate_mesh(self):
|
|
32
|
+
"""Генерирует меш чанка"""
|
|
33
|
+
vertices = []
|
|
34
|
+
normals = []
|
|
35
|
+
uvs = []
|
|
36
|
+
indices = []
|
|
37
|
+
|
|
38
|
+
step = self.size / (self.resolution - 1)
|
|
39
|
+
|
|
40
|
+
# Вершины
|
|
41
|
+
for iz in range(self.resolution):
|
|
42
|
+
for ix in range(self.resolution):
|
|
43
|
+
# Локальные координаты
|
|
44
|
+
lx = ix * step
|
|
45
|
+
lz = iz * step
|
|
46
|
+
|
|
47
|
+
# Высота из height_map
|
|
48
|
+
hx = int(ix * self.height_map.shape[0] / self.resolution)
|
|
49
|
+
hz = int(iz * self.height_map.shape[1] / self.resolution)
|
|
50
|
+
|
|
51
|
+
height = 0.0
|
|
52
|
+
if 0 <= hx < self.height_map.shape[0] and 0 <= hz < self.height_map.shape[1]:
|
|
53
|
+
height = self.height_map[hx, hz]
|
|
54
|
+
|
|
55
|
+
vertices.append([lx, height, lz])
|
|
56
|
+
|
|
57
|
+
# UV координаты
|
|
58
|
+
uvs.append([ix / (self.resolution - 1), iz / (self.resolution - 1)])
|
|
59
|
+
|
|
60
|
+
vertices = np.array(vertices, dtype=np.float32)
|
|
61
|
+
|
|
62
|
+
# Нормали (вычисляем из соседних вершин)
|
|
63
|
+
normals = self._calculate_normals(vertices)
|
|
64
|
+
|
|
65
|
+
# Индексы (треугольники)
|
|
66
|
+
for iz in range(self.resolution - 1):
|
|
67
|
+
for ix in range(self.resolution - 1):
|
|
68
|
+
top_left = iz * self.resolution + ix
|
|
69
|
+
top_right = top_left + 1
|
|
70
|
+
bottom_left = (iz + 1) * self.resolution + ix
|
|
71
|
+
bottom_right = bottom_left + 1
|
|
72
|
+
|
|
73
|
+
# Два треугольника
|
|
74
|
+
indices.extend([top_left, bottom_left, top_right])
|
|
75
|
+
indices.extend([top_right, bottom_left, bottom_right])
|
|
76
|
+
|
|
77
|
+
indices = np.array(indices, dtype=np.uint32)
|
|
78
|
+
|
|
79
|
+
# Создаём меш
|
|
80
|
+
self.mesh = Mesh(vertices, indices, normals, np.array(uvs, dtype=np.float32))
|
|
81
|
+
|
|
82
|
+
def _calculate_normals(self, vertices: np.ndarray) -> np.ndarray:
|
|
83
|
+
"""Вычисляет нормали из вершин"""
|
|
84
|
+
normals = np.zeros_like(vertices)
|
|
85
|
+
|
|
86
|
+
for iz in range(1, self.resolution - 1):
|
|
87
|
+
for ix in range(1, self.resolution - 1):
|
|
88
|
+
i = iz * self.resolution + ix
|
|
89
|
+
|
|
90
|
+
# Соседние вершины
|
|
91
|
+
left = vertices[iz * self.resolution + (ix - 1)]
|
|
92
|
+
right = vertices[iz * self.resolution + (ix + 1)]
|
|
93
|
+
top = vertices[(iz - 1) * self.resolution + ix]
|
|
94
|
+
bottom = vertices[(iz + 1) * self.resolution + ix]
|
|
95
|
+
|
|
96
|
+
# Вычисляем нормаль
|
|
97
|
+
dx = right - left
|
|
98
|
+
dz = bottom - top
|
|
99
|
+
|
|
100
|
+
normal = np.cross(dz, dx)
|
|
101
|
+
norm = np.linalg.norm(normal)
|
|
102
|
+
|
|
103
|
+
if norm > 0:
|
|
104
|
+
normals[i] = normal / norm
|
|
105
|
+
else:
|
|
106
|
+
normals[i] = np.array([0, 1, 0])
|
|
107
|
+
|
|
108
|
+
return normals
|
|
109
|
+
|
|
110
|
+
def get_height_at(self, world_x: float, world_z: float) -> float:
|
|
111
|
+
"""Получает высоту в точке мира"""
|
|
112
|
+
# Локальные координаты
|
|
113
|
+
local_x = world_x - self.world_x
|
|
114
|
+
local_z = world_z - self.world_z
|
|
115
|
+
|
|
116
|
+
if local_x < 0 or local_x > self.size or local_z < 0 or local_z > self.size:
|
|
117
|
+
return 0.0
|
|
118
|
+
|
|
119
|
+
# Нормализованные координаты
|
|
120
|
+
nx = local_x / self.size
|
|
121
|
+
nz = local_z / self.size
|
|
122
|
+
|
|
123
|
+
# Индексы в height_map
|
|
124
|
+
hx = int(nx * (self.height_map.shape[0] - 1))
|
|
125
|
+
hz = int(nz * (self.height_map.shape[1] - 1))
|
|
126
|
+
|
|
127
|
+
if 0 <= hx < self.height_map.shape[0] - 1 and 0 <= hz < self.height_map.shape[1] - 1:
|
|
128
|
+
# Билинейная интерполяция
|
|
129
|
+
fx = nx * (self.height_map.shape[0] - 1) - hx
|
|
130
|
+
fz = nz * (self.height_map.shape[1] - 1) - hz
|
|
131
|
+
|
|
132
|
+
h00 = self.height_map[hx, hz]
|
|
133
|
+
h10 = self.height_map[hx + 1, hz]
|
|
134
|
+
h01 = self.height_map[hx, hz + 1]
|
|
135
|
+
h11 = self.height_map[hx + 1, hz + 1]
|
|
136
|
+
|
|
137
|
+
return (h00 * (1 - fx) * (1 - fz) +
|
|
138
|
+
h10 * fx * (1 - fz) +
|
|
139
|
+
h01 * (1 - fx) * fz +
|
|
140
|
+
h11 * fx * fz)
|
|
141
|
+
|
|
142
|
+
return 0.0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Terrain:
|
|
146
|
+
"""Террайн (ландшафт)"""
|
|
147
|
+
|
|
148
|
+
def __init__(self,
|
|
149
|
+
width: int = 512,
|
|
150
|
+
depth: int = 512,
|
|
151
|
+
max_height: float = 100.0,
|
|
152
|
+
chunk_size: int = 64,
|
|
153
|
+
chunk_resolution: int = 65):
|
|
154
|
+
|
|
155
|
+
self.width = width
|
|
156
|
+
self.depth = depth
|
|
157
|
+
self.max_height = max_height
|
|
158
|
+
self.chunk_size = chunk_size
|
|
159
|
+
self.chunk_resolution = chunk_resolution
|
|
160
|
+
|
|
161
|
+
# Чанки
|
|
162
|
+
self.chunks: dict = {}
|
|
163
|
+
self._chunks_x = width // chunk_size
|
|
164
|
+
self._chunks_z = depth // chunk_size
|
|
165
|
+
|
|
166
|
+
# Текстуры
|
|
167
|
+
self.splat_map: Optional[Texture] = None
|
|
168
|
+
self.detail_textures: List[Texture] = []
|
|
169
|
+
|
|
170
|
+
# Height map всего террайна
|
|
171
|
+
self.height_map = np.zeros((width + 1, depth + 1), dtype=np.float32)
|
|
172
|
+
|
|
173
|
+
# Настройки
|
|
174
|
+
self.tesselation_enabled = False
|
|
175
|
+
self.tesselation_factor = 1.0
|
|
176
|
+
self.wireframe = False
|
|
177
|
+
|
|
178
|
+
print(f"[Terrain] Created {width}x{depth} ({self._chunks_x * self._chunks_z} chunks)")
|
|
179
|
+
|
|
180
|
+
def generate_height_map(self,
|
|
181
|
+
seed: int = 0,
|
|
182
|
+
octaves: int = 6,
|
|
183
|
+
persistence: float = 0.5,
|
|
184
|
+
lacunarity: float = 2.0,
|
|
185
|
+
scale: float = 100.0):
|
|
186
|
+
"""Генерирует height map с помощью шума Перлина"""
|
|
187
|
+
try:
|
|
188
|
+
from noise import pnoise2
|
|
189
|
+
|
|
190
|
+
np.random.seed(seed)
|
|
191
|
+
|
|
192
|
+
for x in range(self.width + 1):
|
|
193
|
+
for z in range(self.depth + 1):
|
|
194
|
+
height = pnoise2(
|
|
195
|
+
x / scale,
|
|
196
|
+
z / scale,
|
|
197
|
+
octaves=octaves,
|
|
198
|
+
persistence=persistence,
|
|
199
|
+
lacunarity=lacunarity,
|
|
200
|
+
base=seed
|
|
201
|
+
)
|
|
202
|
+
# Нормализуем от [-1, 1] к [0, max_height]
|
|
203
|
+
self.height_map[x, z] = (height + 1) * 0.5 * self.max_height
|
|
204
|
+
|
|
205
|
+
print(f"[Terrain] Height map generated (seed: {seed})")
|
|
206
|
+
|
|
207
|
+
except ImportError:
|
|
208
|
+
print("[Terrain] 'noise' module not found. Using random heights.")
|
|
209
|
+
self._generate_random_height_map(seed)
|
|
210
|
+
|
|
211
|
+
def _generate_random_height_map(self, seed: int):
|
|
212
|
+
"""Генерирует случайную height map"""
|
|
213
|
+
np.random.seed(seed)
|
|
214
|
+
|
|
215
|
+
# Простой шум через интерполяцию
|
|
216
|
+
points = np.random.rand(16, 16) * self.max_height
|
|
217
|
+
|
|
218
|
+
for x in range(self.width + 1):
|
|
219
|
+
for z in range(self.depth + 1):
|
|
220
|
+
fx = (x / self.width) * (points.shape[0] - 1)
|
|
221
|
+
fz = (z / self.depth) * (points.shape[1] - 1)
|
|
222
|
+
|
|
223
|
+
ix, iz = int(fx), int(fz)
|
|
224
|
+
fx -= ix
|
|
225
|
+
fz -= iz
|
|
226
|
+
|
|
227
|
+
ix = min(ix, points.shape[0] - 2)
|
|
228
|
+
iz = min(iz, points.shape[1] - 2)
|
|
229
|
+
|
|
230
|
+
h = (points[ix, iz] * (1 - fx) * (1 - fz) +
|
|
231
|
+
points[ix + 1, iz] * fx * (1 - fz) +
|
|
232
|
+
points[ix, iz + 1] * (1 - fx) * fz +
|
|
233
|
+
points[ix + 1, iz + 1] * fx * fz)
|
|
234
|
+
|
|
235
|
+
self.height_map[x, z] = h
|
|
236
|
+
|
|
237
|
+
def generate_chunks(self):
|
|
238
|
+
"""Генерирует все чанки"""
|
|
239
|
+
for cx in range(self._chunks_x):
|
|
240
|
+
for cz in range(self._chunks_z):
|
|
241
|
+
self._generate_chunk(cx, cz)
|
|
242
|
+
|
|
243
|
+
print(f"[Terrain] Generated {len(self.chunks)} chunks")
|
|
244
|
+
|
|
245
|
+
def _generate_chunk(self, cx: int, cz: int):
|
|
246
|
+
"""Генерирует один чанк"""
|
|
247
|
+
# Извлекаем часть height map для чанка
|
|
248
|
+
x_start = cx * self.chunk_size
|
|
249
|
+
z_start = cz * self.chunk_size
|
|
250
|
+
|
|
251
|
+
chunk_hmap = np.zeros((self.chunk_size + 1, self.chunk_size + 1), dtype=np.float32)
|
|
252
|
+
|
|
253
|
+
for x in range(self.chunk_size + 1):
|
|
254
|
+
for z in range(self.chunk_size + 1):
|
|
255
|
+
world_x = x_start + x
|
|
256
|
+
world_z = z_start + z
|
|
257
|
+
|
|
258
|
+
if world_x <= self.width and world_z <= self.depth:
|
|
259
|
+
chunk_hmap[x, z] = self.height_map[world_x, world_z]
|
|
260
|
+
|
|
261
|
+
chunk = TerrainChunk(
|
|
262
|
+
cx, cz,
|
|
263
|
+
self.chunk_size,
|
|
264
|
+
self.chunk_resolution,
|
|
265
|
+
chunk_hmap
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
self.chunks[(cx, cz)] = chunk
|
|
269
|
+
|
|
270
|
+
def get_height(self, world_x: float, world_z: float) -> float:
|
|
271
|
+
"""Получает высоту в точке мира"""
|
|
272
|
+
# Определяем чанк
|
|
273
|
+
cx = int(world_x // self.chunk_size)
|
|
274
|
+
cz = int(world_z // self.chunk_size)
|
|
275
|
+
|
|
276
|
+
chunk = self.chunks.get((cx, cz))
|
|
277
|
+
if chunk:
|
|
278
|
+
return chunk.get_height_at(world_x, world_z)
|
|
279
|
+
|
|
280
|
+
return 0.0
|
|
281
|
+
|
|
282
|
+
def get_normal(self, world_x: float, world_z: float) -> np.ndarray:
|
|
283
|
+
"""Получает нормаль в точке мира"""
|
|
284
|
+
eps = 0.1
|
|
285
|
+
|
|
286
|
+
h_left = self.get_height(world_x - eps, world_z)
|
|
287
|
+
h_right = self.get_height(world_x + eps, world_z)
|
|
288
|
+
h_down = self.get_height(world_x, world_z - eps)
|
|
289
|
+
h_up = self.get_height(world_x, world_z + eps)
|
|
290
|
+
|
|
291
|
+
normal = np.array([
|
|
292
|
+
(h_left - h_right) / (2 * eps),
|
|
293
|
+
1.0,
|
|
294
|
+
(h_down - h_up) / (2 * eps)
|
|
295
|
+
], dtype=np.float32)
|
|
296
|
+
|
|
297
|
+
return normal / np.linalg.norm(normal)
|
|
298
|
+
|
|
299
|
+
def add_texture_layer(self, texture: Texture,
|
|
300
|
+
min_height: float = 0.0,
|
|
301
|
+
max_height: float = 1.0,
|
|
302
|
+
min_slope: float = 0.0,
|
|
303
|
+
max_slope: float = 90.0):
|
|
304
|
+
"""Добавляет слой текстуры"""
|
|
305
|
+
self.detail_textures.append(texture)
|
|
306
|
+
|
|
307
|
+
def render(self, view: np.ndarray, projection: np.ndarray,
|
|
308
|
+
camera_position: np.ndarray, shader=None):
|
|
309
|
+
"""Рендерит террайн"""
|
|
310
|
+
if self.wireframe:
|
|
311
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
|
|
312
|
+
|
|
313
|
+
for chunk in self.chunks.values():
|
|
314
|
+
if chunk.mesh:
|
|
315
|
+
# Здесь был бы рендеринг с шейдером
|
|
316
|
+
chunk.mesh.draw()
|
|
317
|
+
|
|
318
|
+
if self.wireframe:
|
|
319
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
|
320
|
+
|
|
321
|
+
def cleanup(self):
|
|
322
|
+
"""Очищает ресурсы"""
|
|
323
|
+
for chunk in self.chunks.values():
|
|
324
|
+
if chunk.mesh:
|
|
325
|
+
chunk.mesh.cleanup()
|
|
326
|
+
self.chunks.clear()
|
|
327
|
+
print("[Terrain] Cleanup complete")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class TerrainEditor:
|
|
331
|
+
"""Инструменты редактирования террайна"""
|
|
332
|
+
|
|
333
|
+
def __init__(self, terrain: Terrain):
|
|
334
|
+
self.terrain = terrain
|
|
335
|
+
self.brush_size = 5.0
|
|
336
|
+
self.brush_strength = 1.0
|
|
337
|
+
self.brush_falloff = 2.0
|
|
338
|
+
|
|
339
|
+
def raise_terrain(self, world_x: float, world_z: float, amount: float):
|
|
340
|
+
"""Поднимает террайн в точке"""
|
|
341
|
+
self._modify_terrain(world_x, world_z, amount)
|
|
342
|
+
|
|
343
|
+
def lower_terrain(self, world_x: float, world_z: float, amount: float):
|
|
344
|
+
"""Опускает террайн в точке"""
|
|
345
|
+
self._modify_terrain(world_x, world_z, -amount)
|
|
346
|
+
|
|
347
|
+
def smooth_terrain(self, world_x: float, world_z: float, radius: float):
|
|
348
|
+
"""Сглаживает террайн"""
|
|
349
|
+
cx = int(world_x)
|
|
350
|
+
cz = int(world_z)
|
|
351
|
+
r = int(radius)
|
|
352
|
+
|
|
353
|
+
heights = []
|
|
354
|
+
|
|
355
|
+
for x in range(max(0, cx - r), min(self.terrain.width, cx + r + 1)):
|
|
356
|
+
for z in range(max(0, cz - r), min(self.terrain.depth, cz + r + 1)):
|
|
357
|
+
dist = np.sqrt((x - cx)**2 + (z - cz)**2)
|
|
358
|
+
if dist <= r:
|
|
359
|
+
heights.append(self.terrain.height_map[x, z])
|
|
360
|
+
|
|
361
|
+
if heights:
|
|
362
|
+
avg = np.mean(heights)
|
|
363
|
+
|
|
364
|
+
for x in range(max(0, cx - r), min(self.terrain.width, cx + r + 1)):
|
|
365
|
+
for z in range(max(0, cz - r), min(self.terrain.depth, cz + r + 1)):
|
|
366
|
+
dist = np.sqrt((x - cx)**2 + (z - cz)**2)
|
|
367
|
+
if dist <= r:
|
|
368
|
+
t = dist / r
|
|
369
|
+
self.terrain.height_map[x, z] = (
|
|
370
|
+
self.terrain.height_map[x, z] * t + avg * (1 - t)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def flatten_terrain(self, world_x: float, world_z: float,
|
|
374
|
+
radius: float, target_height: float):
|
|
375
|
+
"""Выравнивает террайн"""
|
|
376
|
+
cx = int(world_x)
|
|
377
|
+
cz = int(world_z)
|
|
378
|
+
r = int(radius)
|
|
379
|
+
|
|
380
|
+
for x in range(max(0, cx - r), min(self.terrain.width, cx + r + 1)):
|
|
381
|
+
for z in range(max(0, cz - r), min(self.terrain.depth, cz + r + 1)):
|
|
382
|
+
dist = np.sqrt((x - cx)**2 + (z - cz)**2)
|
|
383
|
+
if dist <= r:
|
|
384
|
+
t = dist / r
|
|
385
|
+
self.terrain.height_map[x, z] = (
|
|
386
|
+
self.terrain.height_map[x, z] * t + target_height * (1 - t)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
def _modify_terrain(self, world_x: float, world_z: float, amount: float):
|
|
390
|
+
"""Модифицирует террайн в точке"""
|
|
391
|
+
cx = int(world_x)
|
|
392
|
+
cz = int(world_z)
|
|
393
|
+
r = int(self.brush_size)
|
|
394
|
+
|
|
395
|
+
for x in range(max(0, cx - r), min(self.terrain.width, cx + r + 1)):
|
|
396
|
+
for z in range(max(0, cz - r), min(self.terrain.depth, cz + r + 1)):
|
|
397
|
+
dist = np.sqrt((x - cx)**2 + (z - cz)**2)
|
|
398
|
+
|
|
399
|
+
if dist <= r:
|
|
400
|
+
# Функция falloff
|
|
401
|
+
falloff = 1.0 - (dist / r) ** self.brush_falloff
|
|
402
|
+
self.terrain.height_map[x, z] += amount * falloff * self.brush_strength
|
|
403
|
+
|
|
404
|
+
# Ограничиваем высоту
|
|
405
|
+
self.terrain.height_map[x, z] = max(0.0, min(
|
|
406
|
+
self.terrain.max_height,
|
|
407
|
+
self.terrain.height_map[x, z]
|
|
408
|
+
))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Текстура"""
|
|
2
|
+
from OpenGL.GL import *
|
|
3
|
+
from PIL import Image
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Texture:
|
|
8
|
+
def __init__(self, path=None):
|
|
9
|
+
self.id = glGenTextures(1)
|
|
10
|
+
self.width = 0
|
|
11
|
+
self.height = 0
|
|
12
|
+
|
|
13
|
+
if path:
|
|
14
|
+
self.load(path)
|
|
15
|
+
|
|
16
|
+
def load(self, path: str):
|
|
17
|
+
image = Image.open(path).convert("RGBA")
|
|
18
|
+
image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
|
19
|
+
self.width, self.height = image.size
|
|
20
|
+
data = np.array(image, dtype=np.uint8)
|
|
21
|
+
|
|
22
|
+
glBindTexture(GL_TEXTURE_2D, self.id)
|
|
23
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height,
|
|
24
|
+
0, GL_RGBA, GL_UNSIGNED_BYTE, data)
|
|
25
|
+
glGenerateMipmap(GL_TEXTURE_2D)
|
|
26
|
+
|
|
27
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
|
28
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
|
|
29
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
|
|
30
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
|
31
|
+
|
|
32
|
+
def bind(self, unit=0):
|
|
33
|
+
glActiveTexture(GL_TEXTURE0 + unit)
|
|
34
|
+
glBindTexture(GL_TEXTURE_2D, self.id)
|