1046 lines
35 KiB
Python
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')
|