expre-gen/blender/3.4/scripts/addons/ant_landscape/ant_functions.py
2025-10-17 09:47:23 +08:00

1046 lines
35 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
# Another Noise Tool - Functions
# Jimmy Hazevoet
# ErosionR:
# Michel Anders, Ian Huish
# import modules
import bpy
from bpy.props import (
BoolProperty,
FloatProperty,
StringProperty,
EnumProperty,
IntProperty,
PointerProperty,
)
from math import (
sin, cos, pi,
)
from .ant_noise import noise_gen
# ------------------------------------------------------------
# Create a new mesh (object) from verts/edges/faces.
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata).
# name ... Name of the new mesh (& object)
from bpy_extras import object_utils
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, [], faces)
# Update mesh geometry after adding stuff.
mesh.update()
return object_utils.object_data_add(context, mesh, operator=None)
# Generate XY Grid
def grid_gen(sub_d_x, sub_d_y, tri, meshsize_x, meshsize_y, props, water_plane, water_level):
verts = []
faces = []
vappend = verts.append
fappend = faces.append
for i in range(0, sub_d_x):
x = meshsize_x * (i / (sub_d_x - 1) - 1 / 2)
for j in range(0, sub_d_y):
y = meshsize_y * (j / (sub_d_y - 1) - 1 / 2)
if not water_plane:
z = noise_gen((x, y, 0), props)
else:
z = water_level
vappend((x, y, z))
if i > 0 and j > 0:
A = i * sub_d_y + (j - 1)
B = i * sub_d_y + j
C = (i - 1) * sub_d_y + j
D = (i - 1) * sub_d_y + (j - 1)
if not tri:
fappend((A, B, C, D))
else:
fappend((A, B, D))
fappend((B, C, D))
return verts, faces
# Generate UV Sphere
def sphere_gen(sub_d_x, sub_d_y, tri, meshsize, props, water_plane, water_level):
verts = []
faces = []
vappend = verts.append
fappend = faces.append
sub_d_x += 1
sub_d_y += 1
for i in range(0, sub_d_x):
for j in range(0, sub_d_y):
u = sin(j * pi * 2 / (sub_d_y - 1)) * cos(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
v = cos(j * pi * 2 / (sub_d_y - 1)) * cos(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
w = sin(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
if water_plane:
h = water_level
else:
h = noise_gen((u, v, w), props) / meshsize
vappend(((u + u * h), (v + v * h), (w + w * h)))
count = 0
for i in range(0, sub_d_y * (sub_d_x - 1)):
if count < sub_d_y - 1:
A = i + 1
B = i
C = (i + sub_d_y)
D = (i + sub_d_y) + 1
if tri:
fappend((A, B, D))
fappend((B, C, D))
else:
fappend((A, B, C, D))
count = count + 1
else:
count = 0
return verts, faces
# ------------------------------------------------------------
# Do refresh - redraw
class AntLandscapeRefresh(bpy.types.Operator):
bl_idname = "mesh.ant_landscape_refresh"
bl_label = "Refresh"
bl_description = "Refresh landscape with current settings"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
ob = bpy.context.active_object
return (ob.ant_landscape and not ob.ant_landscape.sphere_mesh)
def execute(self, context):
# ant object items
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
keys = obj.ant_landscape.keys()
if keys:
ob = obj.ant_landscape
prop = []
for key in keys:
prop.append(getattr(ob, key))
# redraw verts
mesh = obj.data
if ob['vert_group'] != "" and ob['vert_group'] in obj.vertex_groups:
vertex_group = obj.vertex_groups[ob['vert_group']]
gi = vertex_group.index
for v in mesh.vertices:
for g in v.groups:
if g.group == gi:
v.co[2] = 0.0
v.co[2] = vertex_group.weight(v.index) * noise_gen(v.co, prop)
else:
for v in mesh.vertices:
v.co[2] = 0.0
v.co[2] = noise_gen(v.co, prop)
mesh.update()
else:
pass
return {'FINISHED'}
# ------------------------------------------------------------
# Do regenerate
class AntLandscapeRegenerate(bpy.types.Operator):
bl_idname = "mesh.ant_landscape_regenerate"
bl_label = "Regenerate"
bl_description = "Regenerate landscape with current settings"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
ob = bpy.context.active_object
if ob.mode == 'EDIT':
return False
return ob.ant_landscape
def execute(self, context):
view_layer = bpy.context.view_layer
# ant object items
obj = bpy.context.active_object
keys = obj.ant_landscape.keys()
if keys:
ob = obj.ant_landscape
ant_props = []
for key in keys:
ant_props.append(getattr(ob, key))
new_name = obj.name
# Main function, create landscape mesh object
if ob['sphere_mesh']:
# sphere
verts, faces = sphere_gen(
ob['subdivision_y'],
ob['subdivision_x'],
ob['tri_face'],
ob['mesh_size'],
ant_props,
False,
0.0,
)
new_ob = create_mesh_object(context, verts, [], faces, new_name)
if ob['remove_double']:
new_ob.select_set(True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
bpy.ops.object.mode_set(mode='OBJECT')
else:
# grid
verts, faces = grid_gen(
ob['subdivision_x'],
ob['subdivision_y'],
ob['tri_face'],
ob['mesh_size_x'],
ob['mesh_size_y'],
ant_props,
False,
0.0,
)
new_ob = create_mesh_object(context, verts, [], faces, new_name)
new_ob.select_set(True)
if ob['smooth_mesh']:
bpy.ops.object.shade_smooth()
# Landscape Material
if ob['land_material'] != "" and ob['land_material'] in bpy.data.materials:
mat = bpy.data.materials[ob['land_material']]
bpy.context.object.data.materials.append(mat)
# Water plane
if ob['water_plane']:
if ob['sphere_mesh']:
# sphere
verts, faces = sphere_gen(
ob['subdivision_y'],
ob['subdivision_x'],
ob['tri_face'],
ob['mesh_size'],
ant_props,
ob['water_plane'],
ob['water_level'],
)
wobj = create_mesh_object(context, verts, [], faces, new_name + "_plane")
if ob['remove_double']:
wobj.select_set(True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
bpy.ops.object.mode_set(mode='OBJECT')
else:
# grid
verts, faces = grid_gen(
2,
2,
ob['tri_face'],
ob['mesh_size_x'],
ob['mesh_size_y'],
ant_props,
ob['water_plane'],
ob['water_level'],
)
wobj = create_mesh_object(context, verts, [], faces, new_name + "_plane")
wobj.select_set(True)
if ob['smooth_mesh']:
bpy.ops.object.shade_smooth()
# Water Material
if ob['water_material'] != "" and ob['water_material'] in bpy.data.materials:
mat = bpy.data.materials[ob['water_material']]
bpy.context.object.data.materials.append(mat)
# Loc Rot Scale
if ob['water_plane']:
wobj.location = obj.location
wobj.rotation_euler = obj.rotation_euler
wobj.scale = obj.scale
wobj.select_set(False)
new_ob.location = obj.location
new_ob.rotation_euler = obj.rotation_euler
new_ob.scale = obj.scale
# Store props
new_ob = store_properties(ob, new_ob)
# Delete old object
new_ob.select_set(False)
obj.select_set(True)
view_layer.objects.active = obj
bpy.ops.object.delete(use_global=False)
# Select landscape and make active
new_ob.select_set(True)
view_layer.objects.active = new_ob
return {'FINISHED'}
# ------------------------------------------------------------
# Z normal value to vertex group (Slope map)
class AntVgSlopeMap(bpy.types.Operator):
bl_idname = "mesh.ant_slope_map"
bl_label = "Weight from Slope"
bl_description = "A.N.T. Slope Map - z normal value to vertex group weight"
bl_options = {'REGISTER', 'UNDO'}
z_method: EnumProperty(
name="Method:",
default='SLOPE_Z',
items=[
('SLOPE_Z', "Z Slope", "Slope for planar mesh"),
('SLOPE_XYZ', "Sphere Slope", "Slope for spherical mesh")
])
group_name: StringProperty(
name="Vertex Group Name:",
default="Slope",
description="Name"
)
select_flat: BoolProperty(
name="Vert Select:",
default=True,
description="Select vertices on flat surface"
)
select_range: FloatProperty(
name="Vert Select Range:",
default=0.0,
min=0.0,
max=1.0,
description="Increase to select more vertices on slopes"
)
@classmethod
def poll(cls, context):
ob = context.object
return (ob and ob.type == 'MESH')
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def execute(self, context):
message = "Popup Values: %d, %f, %s, %s" % \
(self.select_flat, self.select_range, self.group_name, self.z_method)
self.report({'INFO'}, message)
bpy.ops.object.mode_set(mode='OBJECT')
ob = bpy.context.active_object
dim = ob.dimensions
if self.select_flat:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.context.tool_settings.mesh_select_mode = [True, False, False]
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.vertex_group_add()
vg_normal = ob.vertex_groups.active
for v in ob.data.vertices:
if self.z_method == 'SLOPE_XYZ':
zval = (v.co.normalized() * v.normal.normalized()) * 2 - 1
else:
zval = v.normal[2]
vg_normal.add([v.index], zval, 'REPLACE')
if self.select_flat:
if zval >= (1.0 - self.select_range):
v.select = True
vg_normal.name = self.group_name
bpy.ops.paint.weight_paint_toggle()
return {'FINISHED'}
# ------------------------------------------------------------
# draw properties
def draw_ant_refresh(self, context):
layout = self.layout
if self.auto_refresh is False:
self.refresh = False
elif self.auto_refresh is True:
self.refresh = True
row = layout.box().row()
split = row.split()
split.scale_y = 1.5
split.prop(self, "auto_refresh", toggle=True, icon_only=True, icon='AUTO')
split.prop(self, "refresh", toggle=True, icon_only=True, icon='FILE_REFRESH')
def draw_ant_main(self, context, generate=True):
layout = self.layout
box = layout.box()
box.prop(self, "show_main_settings", toggle=True)
if self.show_main_settings:
if generate:
row = box.row(align=True)
split = row.split(align=True)
split.prop(self, "at_cursor", toggle=True, icon_only=True, icon='PIVOT_CURSOR')
split.prop(self, "smooth_mesh", toggle=True, icon_only=True, icon='SHADING_SOLID')
split.prop(self, "tri_face", toggle=True, text="Triangulate", icon='MESH_DATA')
if not self.sphere_mesh:
row = box.row(align=True)
row.prop(self, "sphere_mesh", toggle=True)
else:
row = box.row(align=True)
split = row.split(factor=0.5, align=True)
split.prop(self, "sphere_mesh", toggle=True)
split.prop(self, "remove_double", toggle=True)
box.prop(self, "ant_terrain_name")
box.prop_search(self, "land_material", bpy.data, "materials")
col = box.column(align=True)
col.prop(self, "subdivision_x")
col.prop(self, "subdivision_y")
col = box.column(align=True)
if self.sphere_mesh:
col.prop(self, "mesh_size")
else:
col.prop(self, "mesh_size_x")
col.prop(self, "mesh_size_y")
def draw_ant_noise(self, context, generate=True):
layout = self.layout
box = layout.box()
box.prop(self, "show_noise_settings", toggle=True)
if self.show_noise_settings:
box.prop(self, "noise_type")
if self.noise_type == "blender_texture":
box.prop_search(self, "texture_block", bpy.data, "textures")
else:
box.prop(self, "basis_type")
col = box.column(align=True)
col.prop(self, "random_seed")
col = box.column(align=True)
col.prop(self, "noise_offset_x")
col.prop(self, "noise_offset_y")
if self.sphere_mesh or generate == False:
col.prop(self, "noise_offset_z")
col.prop(self, "noise_size_x")
col.prop(self, "noise_size_y")
if self.sphere_mesh or generate == False:
col.prop(self, "noise_size_z")
col = box.column(align=True)
col.prop(self, "noise_size")
col = box.column(align=True)
if self.noise_type == "multi_fractal":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
elif self.noise_type == "ridged_multi_fractal":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "offset")
col.prop(self, "gain")
elif self.noise_type == "hybrid_multi_fractal":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "offset")
col.prop(self, "gain")
elif self.noise_type == "hetero_terrain":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "offset")
elif self.noise_type == "fractal":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
elif self.noise_type == "turbulence_vector":
col.prop(self, "noise_depth")
col.prop(self, "amplitude")
col.prop(self, "frequency")
col.separator()
row = col.row(align=True)
row.prop(self, "hard_noise", expand=True)
elif self.noise_type == "variable_lacunarity":
box.prop(self, "vl_basis_type")
box.prop(self, "distortion")
elif self.noise_type == "marble_noise":
box.prop(self, "marble_shape")
box.prop(self, "marble_bias")
box.prop(self, "marble_sharp")
col = box.column(align=True)
col.prop(self, "distortion")
col.prop(self, "noise_depth")
col.separator()
row = col.row(align=True)
row.prop(self, "hard_noise", expand=True)
elif self.noise_type == "shattered_hterrain":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "offset")
col.prop(self, "distortion")
elif self.noise_type == "strata_hterrain":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "offset")
col.prop(self, "distortion", text="Strata")
elif self.noise_type == "ant_turbulence":
col.prop(self, "noise_depth")
col.prop(self, "amplitude")
col.prop(self, "frequency")
col.prop(self, "distortion")
col.separator()
row = col.row(align=True)
row.prop(self, "hard_noise", expand=True)
elif self.noise_type == "vl_noise_turbulence":
col.prop(self, "noise_depth")
col.prop(self, "amplitude")
col.prop(self, "frequency")
col.prop(self, "distortion")
col.separator()
box.prop(self, "vl_basis_type")
col.separator()
row = col.row(align=True)
row.prop(self, "hard_noise", expand=True)
elif self.noise_type == "vl_hTerrain":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "offset")
col.prop(self, "distortion")
col.separator()
box.prop(self, "vl_basis_type")
elif self.noise_type == "distorted_heteroTerrain":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "offset")
col.prop(self, "distortion")
col.separator()
box.prop(self, "vl_basis_type")
elif self.noise_type == "double_multiFractal":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "offset")
col.prop(self, "gain")
col.separator()
box.prop(self, "vl_basis_type")
elif self.noise_type == "rocks_noise":
col.prop(self, "noise_depth")
col.prop(self, "distortion")
col.separator()
row = col.row(align=True)
row.prop(self, "hard_noise", expand=True)
elif self.noise_type == "slick_rock":
col.prop(self, "noise_depth")
col.prop(self, "dimension")
col.prop(self, "lacunarity")
col.prop(self, "gain")
col.prop(self, "offset")
col.prop(self, "distortion")
col.separator()
box.prop(self, "vl_basis_type")
elif self.noise_type == "planet_noise":
col.prop(self, "noise_depth")
col.separator()
row = col.row(align=True)
row.prop(self, "hard_noise", expand=True)
# Effects mix
col = box.column(align=False)
box.prop(self, "fx_type")
if self.fx_type != "0":
if int(self.fx_type) <= 12:
box.prop(self, "fx_bias")
box.prop(self, "fx_mix_mode")
col = box.column(align=True)
col.prop(self, "fx_mixfactor")
col = box.column(align=True)
col.prop(self, "fx_loc_x")
col.prop(self, "fx_loc_y")
col.prop(self, "fx_size")
col = box.column(align=True)
col.prop(self, "fx_depth")
if self.fx_depth != 0:
col.prop(self, "fx_frequency")
col.prop(self, "fx_amplitude")
col.prop(self, "fx_turb")
col = box.column(align=True)
row = col.row(align=True).split(factor=0.92, align=True)
row.prop(self, "fx_height")
row.prop(self, "fx_invert", toggle=True, text="", icon='ARROW_LEFTRIGHT')
col.prop(self, "fx_offset")
def draw_ant_displace(self, context, generate=True):
layout = self.layout
box = layout.box()
box.prop(self, "show_displace_settings", toggle=True)
if self.show_displace_settings:
if not generate:
col = box.column(align=False)
col.prop(self, "direction", toggle=True)
col = box.column(align=True)
row = col.row(align=True).split(factor=0.92, align=True)
row.prop(self, "height")
row.prop(self, "height_invert", toggle=True, text="", icon='ARROW_LEFTRIGHT')
col.prop(self, "height_offset")
col.prop(self, "maximum")
col.prop(self, "minimum")
if generate:
if not self.sphere_mesh:
col = box.column()
col.prop(self, "edge_falloff")
if self.edge_falloff != "0":
col = box.column(align=True)
col.prop(self, "edge_level")
if self.edge_falloff in ["2", "3"]:
col.prop(self, "falloff_x")
if self.edge_falloff in ["1", "3"]:
col.prop(self, "falloff_y")
col = box.column()
col.prop(self, "strata_type")
if self.strata_type != "0":
col = box.column()
col.prop(self, "strata")
if not generate:
col = box.column(align=False)
col.prop_search(self, "vert_group", bpy.context.object, "vertex_groups")
def draw_ant_water(self, context):
layout = self.layout
box = layout.box()
col = box.column()
col.prop(self, "water_plane", toggle=True)
if self.water_plane:
col = box.column(align=True)
col.prop_search(self, "water_material", bpy.data, "materials")
col = box.column()
col.prop(self, "water_level")
# Store properties
def store_properties(operator, ob):
ob.ant_landscape.ant_terrain_name = operator.ant_terrain_name
ob.ant_landscape.at_cursor = operator.at_cursor
ob.ant_landscape.smooth_mesh = operator.smooth_mesh
ob.ant_landscape.tri_face = operator.tri_face
ob.ant_landscape.sphere_mesh = operator.sphere_mesh
ob.ant_landscape.land_material = operator.land_material
ob.ant_landscape.water_material = operator.water_material
ob.ant_landscape.texture_block = operator.texture_block
ob.ant_landscape.subdivision_x = operator.subdivision_x
ob.ant_landscape.subdivision_y = operator.subdivision_y
ob.ant_landscape.mesh_size_x = operator.mesh_size_x
ob.ant_landscape.mesh_size_y = operator.mesh_size_y
ob.ant_landscape.mesh_size = operator.mesh_size
ob.ant_landscape.random_seed = operator.random_seed
ob.ant_landscape.noise_offset_x = operator.noise_offset_x
ob.ant_landscape.noise_offset_y = operator.noise_offset_y
ob.ant_landscape.noise_offset_z = operator.noise_offset_z
ob.ant_landscape.noise_size_x = operator.noise_size_x
ob.ant_landscape.noise_size_y = operator.noise_size_y
ob.ant_landscape.noise_size_z = operator.noise_size_z
ob.ant_landscape.noise_size = operator.noise_size
ob.ant_landscape.noise_type = operator.noise_type
ob.ant_landscape.basis_type = operator.basis_type
ob.ant_landscape.vl_basis_type = operator.vl_basis_type
ob.ant_landscape.distortion = operator.distortion
ob.ant_landscape.hard_noise = operator.hard_noise
ob.ant_landscape.noise_depth = operator.noise_depth
ob.ant_landscape.amplitude = operator.amplitude
ob.ant_landscape.frequency = operator.frequency
ob.ant_landscape.dimension = operator.dimension
ob.ant_landscape.lacunarity = operator.lacunarity
ob.ant_landscape.offset = operator.offset
ob.ant_landscape.gain = operator.gain
ob.ant_landscape.marble_bias = operator.marble_bias
ob.ant_landscape.marble_sharp = operator.marble_sharp
ob.ant_landscape.marble_shape = operator.marble_shape
ob.ant_landscape.height = operator.height
ob.ant_landscape.height_invert = operator.height_invert
ob.ant_landscape.height_offset = operator.height_offset
ob.ant_landscape.maximum = operator.maximum
ob.ant_landscape.minimum = operator.minimum
ob.ant_landscape.edge_falloff = operator.edge_falloff
ob.ant_landscape.edge_level = operator.edge_level
ob.ant_landscape.falloff_x = operator.falloff_x
ob.ant_landscape.falloff_y = operator.falloff_y
ob.ant_landscape.strata_type = operator.strata_type
ob.ant_landscape.strata = operator.strata
ob.ant_landscape.water_plane = operator.water_plane
ob.ant_landscape.water_level = operator.water_level
ob.ant_landscape.vert_group = operator.vert_group
ob.ant_landscape.remove_double = operator.remove_double
ob.ant_landscape.fx_mixfactor = operator.fx_mixfactor
ob.ant_landscape.fx_mix_mode = operator.fx_mix_mode
ob.ant_landscape.fx_type = operator.fx_type
ob.ant_landscape.fx_bias = operator.fx_bias
ob.ant_landscape.fx_turb = operator.fx_turb
ob.ant_landscape.fx_depth = operator.fx_depth
ob.ant_landscape.fx_frequency = operator.fx_frequency
ob.ant_landscape.fx_amplitude = operator.fx_amplitude
ob.ant_landscape.fx_size = operator.fx_size
ob.ant_landscape.fx_loc_x = operator.fx_loc_x
ob.ant_landscape.fx_loc_y = operator.fx_loc_y
ob.ant_landscape.fx_height = operator.fx_height
ob.ant_landscape.fx_offset = operator.fx_offset
ob.ant_landscape.fx_invert = operator.fx_invert
return ob
# ------------------------------------------------------------
# "name": "ErosionR"
# "author": "Michel Anders, Ian Huish"
from random import random as rand
from math import tan, radians
from .eroder import Grid
from .stats import Stats
from .utils import numexpr_available
def availableVertexGroupsOrNone(self, context):
groups = [('None', 'None', 'None', 1)]
return groups + [(name, name, name, n + 1) for n, name in enumerate(context.active_object.vertex_groups.keys())]
class Eroder(bpy.types.Operator):
bl_idname = "mesh.eroder"
bl_label = "ErosionR"
bl_description = "Apply various kinds of erosion to a square ANT-Landscape grid. Also available in Weight Paint mode > Weights menu"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Iterations: IntProperty(
name="Iterations",
description="Number of overall iterations",
default=1,
min=1,
soft_max=100
)
IterRiver: IntProperty(
name="River Iterations",
description="Number of river iterations",
default=30,
min=1,
soft_max=1000
)
IterAva: IntProperty(
name="Avalanche Iterations",
description="Number of avalanche iterations",
default=5,
min=1,
soft_max=10
)
IterDiffuse: IntProperty(
name="Diffuse Iterations",
description="Number of diffuse iterations",
default=5,
min=1,
soft_max=10
)
Ef: FloatProperty(
name="Rain on Plains",
description="1 gives equal rain across the terrain, 0 rains more at the mountain tops",
default=0.0,
min=0,
max=1
)
Kd: FloatProperty(
name="Kd",
description="Thermal diffusion rate (1.0 is a fairly high rate)",
default=0.1,
min=0,
soft_max=100
)
Kt: FloatProperty(
name="Kt",
description="Maximum stable talus angle",
default=radians(60),
min=0,
max=radians(90),
subtype='ANGLE'
)
Kr: FloatProperty(
name="Rain amount",
description="Total Rain amount",
default=.01,
min=0,
soft_max=1,
precision=3
)
Kv: FloatProperty(
name="Rain variance",
description="Rain variance (0 is constant, 1 is uniform)",
default=0,
min=0,
max=1
)
userainmap: BoolProperty(
name="Use rain map",
description="Use active vertex group as a rain map",
default=True
)
Ks: FloatProperty(
name="Soil solubility",
description="Soil solubility - how quickly water quickly reaches saturation point",
default=0.5,
min=0,
soft_max=1
)
Kdep: FloatProperty(
name="Deposition rate",
description="Sediment deposition rate - how quickly silt is laid down once water stops flowing quickly",
default=0.1,
min=0,
soft_max=1
)
Kz: FloatProperty(
name="Fluvial Erosion Rate",
description="Amount of sediment moved each main iteration - if 0, then rivers are formed but the mesh is not changed",
default=0.3,
min=0,
soft_max=20)
Kc: FloatProperty(
name="Carrying capacity",
description="Base sediment carrying capacity",
default=0.9,
min=0,
soft_max=1
)
Ka: FloatProperty(
name="Slope dependence",
description="Slope dependence of carrying capacity (not used)",
default=1.0,
min=0,
soft_max=2
)
Kev: FloatProperty(
name="Evaporation",
description="Evaporation Rate per grid square in % - causes sediment to be dropped closer to the hills",
default=.5,
min=0,
soft_max=2
)
numexpr: BoolProperty(
name="Numexpr",
description="Use numexpr module (if available)",
default=True
)
Pd: FloatProperty(
name="Diffusion Amount",
description="Diffusion probability",
default=0.2,
min=0,
max=1
)
Pa: FloatProperty(
name="Avalanche Amount",
description="Avalanche amount",
default=0.5,
min=0,
max=1
)
Pw: FloatProperty(
name="River Amount",
description="Water erosion probability",
default=1,
min=0,
max=1
)
smooth: BoolProperty(
name="Smooth",
description="Set smooth shading",
default=True
)
showiterstats: BoolProperty(
name="Iteration Stats",
description="Show iteraration statistics",
default=False
)
showmeshstats: BoolProperty(
name="Mesh Stats",
description="Show mesh statistics",
default=False
)
stats = Stats()
counts = {}
maps = {
'rainmap': lambda g, r, c: g.rainmap[r, c],
'scree': lambda g, r, c: g.avalanced[r, c],
'avalanced': lambda g, r, c: -g.avalanced[r, c],
'water': lambda g, r, c: g.water[r, c] / g.watermax,
'scour': lambda g, r, c: g.scour[r, c] / max(g.scourmax, -g.scourmin),
'deposit': lambda g, r, c: g.scour[r, c] / min(-g.scourmax, g.scourmin),
'flowrate': lambda g, r, c: g.flowrate[r, c],
'sediment': lambda g, r, c: g.sediment[r, c],
'sedimentpct': lambda g, r, c: g.sedimentpct[r, c],
'capacity': lambda g, r, c: g.capacity[r, c]
}
def execute(self, context):
ob = context.active_object
oldMesh = ob.data
self.stats.reset()
index_to_name = {}
for name in self.maps:
try:
ob.vertex_groups[name]
except:
ob.vertex_groups.new(name=name)
# Save a mapping from index to name, in case,
# the next iteration is different.
index_to_name[ob.vertex_groups[name].index] = name
g = Grid.fromBlenderMesh(oldMesh, ob.vertex_groups['rainmap'], self.Ef)
self.counts['diffuse'] = 0
self.counts['avalanche'] = 0
self.counts['water'] = 0
for i in range(self.Iterations):
if self.IterRiver > 0:
for i in range(self.IterRiver):
g.rivergeneration(
self.Kr,
self.Kv,
self.userainmap,
self.Kc,
self.Ks,
self.Kdep,
self.Ka,
self.Kev / 100,
0,
0,
0,
0,
self.numexpr,
)
if self.Kd > 0.0:
for k in range(self.IterDiffuse):
g.diffuse(self.Kd / 5, self.IterDiffuse, self.numexpr)
self.counts['diffuse'] += 1
if self.Kt < radians(90) and self.Pa > 0:
for k in range(self.IterAva):
# since dx and dy are scaled to 1, tan(Kt) is the height for a given angle
g.avalanche(tan(self.Kt), self.IterAva, self.Pa, self.numexpr)
self.counts['avalanche'] += 1
if self.Kz > 0:
g.fluvial_erosion(self.Kr, self.Kv, self.userainmap, self.Kc, self.Ks,
self.Kz * 50, self.Ka, 0, 0, 0, 0, self.numexpr)
self.counts['water'] += 1
newMesh = bpy.data.meshes.new(oldMesh.name)
g.toBlenderMesh(newMesh)
# This empties ob.vertex_groups.
ob.data = newMesh
# Copy vertex groups from the old mesh.
for name in self.maps:
ob.vertex_groups.new(name=name)
for vert in oldMesh.vertices:
for group in vert.groups:
name = index_to_name[group.group]
if name:
ob.vertex_groups[name].add([vert.index], group.weight, 'REPLACE')
# Add the new data.
for row in range(g.rainmap.shape[0]):
for col in range(g.rainmap.shape[1]):
i = row * g.rainmap.shape[1] + col
for name, fn in self.maps.items():
ob.vertex_groups[name].add([i], fn(g, row, col), 'ADD')
ob.vertex_groups.active = ob.vertex_groups['capacity']
if self.smooth:
bpy.ops.object.shade_smooth()
self.stats.time()
self.stats.memory()
if self.showmeshstats:
self.stats.meshstats = g.analyze()
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.operator('screen.repeat_last', text="Repeat", icon='FILE_REFRESH')
layout.prop(self, 'Iterations')
box = layout.box()
col = box.column(align=True)
col.label(text="Thermal (Diffusion)")
col.prop(self, 'Kd')
col.prop(self, 'IterDiffuse')
box = layout.box()
col = box.column(align=True)
col.label(text="Avalanche (Talus)")
col.prop(self, 'Pa')
col.prop(self, 'IterAva')
col.prop(self, 'Kt')
box = layout.box()
col = box.column(align=True)
col.label(text="River erosion")
col.prop(self, 'IterRiver')
col.prop(self, 'Kz')
col.prop(self, 'Ks')
col.prop(self, 'Kc')
col.prop(self, 'Kdep')
col.prop(self, 'Kr')
col.prop(self, 'Kv')
col.prop(self, 'Kev')
col.prop(self, 'Ef')
layout.prop(self, 'smooth')