# 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 propereties 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')