112 lines
4.4 KiB
Python
112 lines
4.4 KiB
Python
import bpy
|
||
import bmesh
|
||
import mathutils
|
||
import random
|
||
import math
|
||
|
||
class SolidTreeGenerator:
|
||
def __init__(self, levels=5, length=3.0, radius=0.3):
|
||
self.levels = levels
|
||
self.base_length = length
|
||
self.base_radius = radius
|
||
|
||
# Хранилище для данных: (start_pos, end_pos, radius_start, radius_end)
|
||
self.branches_data = []
|
||
|
||
def calculate_tree(self, start_pos, direction, length, radius, level):
|
||
if level <= 0 or length < 0.1:
|
||
return
|
||
|
||
end_pos = start_pos + direction * length
|
||
# Сохраняем данные сегмента
|
||
self.branches_data.append({
|
||
'start': start_pos.copy(),
|
||
'end': end_pos.copy(),
|
||
'r_start': radius,
|
||
'r_end': radius * 0.7
|
||
})
|
||
|
||
# 1. Основной ствол (продолжение)
|
||
trunk_dir = (direction + self.get_random_vector(0.1)).normalized()
|
||
self.calculate_tree(end_pos, trunk_dir, length * 0.8, radius * 0.7, level - 1)
|
||
|
||
# 2. Боковые ветки (ветвление)
|
||
if level > 1:
|
||
num_sides = random.randint(2, 3) # Минимум 2 ветки для видимости
|
||
for _ in range(num_sides):
|
||
# Создаем вектор, сильно отклоненный от ствола (30-60 градусов)
|
||
axis = self.get_random_vector(1.0).normalized()
|
||
angle = math.radians(random.uniform(30, 60))
|
||
|
||
side_dir = direction.copy()
|
||
side_dir.rotate(mathutils.Quaternion(axis, angle))
|
||
|
||
# Боковые ветки короче
|
||
self.calculate_tree(end_pos, side_dir, length * 0.6, radius * 0.5, level - 1)
|
||
|
||
def get_random_vector(self, intensity):
|
||
return mathutils.Vector((
|
||
random.uniform(-intensity, intensity),
|
||
random.uniform(-intensity, intensity),
|
||
random.uniform(-intensity, intensity)
|
||
))
|
||
|
||
def build_mesh(self):
|
||
mesh = bpy.data.meshes.new("TreeMesh")
|
||
obj = bpy.data.objects.new("Tree", mesh)
|
||
bpy.context.collection.objects.link(obj)
|
||
|
||
bm = bmesh.new()
|
||
skin_layer = bm.verts.layers.skin.verify()
|
||
|
||
# Словарь для предотвращения дублирования вершин в одной точке
|
||
# Ключ - кортеж координат, Значение - объект вершины BMesh
|
||
vert_map = {}
|
||
|
||
for b in self.branches_data:
|
||
# Превращаем координаты в кортежи для словаря
|
||
s_key = tuple(round(v, 4) for v in b['start'])
|
||
e_key = tuple(round(v, 4) for v in b['end'])
|
||
|
||
# Получаем или создаем начальную вершину
|
||
if s_key not in vert_map:
|
||
v_start = bm.verts.new(b['start'])
|
||
v_start[skin_layer].radius = (b['r_start'], b['r_start'])
|
||
vert_map[s_key] = v_start
|
||
else:
|
||
v_start = vert_map[s_key]
|
||
|
||
# Получаем или создаем конечную вершину
|
||
if e_key not in vert_map:
|
||
v_end = bm.verts.new(b['end'])
|
||
v_end[skin_layer].radius = (b['r_end'], b['r_end'])
|
||
vert_map[e_key] = v_end
|
||
else:
|
||
v_end = vert_map[e_key]
|
||
|
||
# Создаем ребро, если его еще нет
|
||
if not bm.edges.get((v_start, v_end)):
|
||
bm.edges.new((v_start, v_end))
|
||
|
||
# Находим корень (самую нижнюю точку) и помечаем его
|
||
root_v = min(bm.verts, key=lambda v: v.co.z)
|
||
root_v[skin_layer].use_root = True
|
||
|
||
bm.to_mesh(mesh)
|
||
bm.free()
|
||
|
||
# Модификаторы
|
||
obj.modifiers.new(name="Skin", type='SKIN')
|
||
sub = obj.modifiers.new(name="Subdiv", type='SUBSURF')
|
||
sub.levels = 1 # Для начала 1, чтобы не тормозило
|
||
|
||
# Очистка сцены
|
||
bpy.ops.object.select_all(action='SELECT')
|
||
bpy.ops.object.delete()
|
||
|
||
# Запуск
|
||
generator = SolidTreeGenerator(levels=5, length=3.0, radius=0.4)
|
||
generator.calculate_tree(mathutils.Vector((0,0,0)), mathutils.Vector((0,0,1)), 3.0, 0.4, 5)
|
||
generator.build_mesh()
|
||
|