File headers: use SPDX license identifiers
[blender-addons.git] / ant_landscape / ant_functions.py
blobf63747aa6cb1210932637c796c7d620433842bf9
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Another Noise Tool - Functions
4 # Jimmy Hazevoet
6 # ErosionR:
7 # Michel Anders, Ian Huish
9 # import modules
10 import bpy
11 from bpy.props import (
12 BoolProperty,
13 FloatProperty,
14 StringProperty,
15 EnumProperty,
16 IntProperty,
17 PointerProperty,
19 from math import (
20 sin, cos, pi,
22 from .ant_noise import noise_gen
24 # ------------------------------------------------------------
25 # Create a new mesh (object) from verts/edges/faces.
26 # verts/edges/faces ... List of vertices/edges/faces for the
27 # new mesh (as used in from_pydata).
28 # name ... Name of the new mesh (& object)
30 from bpy_extras import object_utils
32 def create_mesh_object(context, verts, edges, faces, name):
33 # Create new mesh
34 mesh = bpy.data.meshes.new(name)
35 # Make a mesh from a list of verts/edges/faces.
36 mesh.from_pydata(verts, [], faces)
37 # Update mesh geometry after adding stuff.
38 mesh.update()
39 return object_utils.object_data_add(context, mesh, operator=None)
42 # Generate XY Grid
43 def grid_gen(sub_d_x, sub_d_y, tri, meshsize_x, meshsize_y, props, water_plane, water_level):
44 verts = []
45 faces = []
46 vappend = verts.append
47 fappend = faces.append
48 for i in range (0, sub_d_x):
49 x = meshsize_x * (i / (sub_d_x - 1) - 1 / 2)
50 for j in range(0, sub_d_y):
51 y = meshsize_y * (j / (sub_d_y - 1) - 1 / 2)
52 if not water_plane:
53 z = noise_gen((x, y, 0), props)
54 else:
55 z = water_level
56 vappend((x,y,z))
58 if i > 0 and j > 0:
59 A = i * sub_d_y + (j - 1)
60 B = i * sub_d_y + j
61 C = (i - 1) * sub_d_y + j
62 D = (i - 1) * sub_d_y + (j - 1)
63 if not tri:
64 fappend((A, B, C, D))
65 else:
66 fappend((A, B, D))
67 fappend((B, C, D))
69 return verts, faces
72 # Generate UV Sphere
73 def sphere_gen(sub_d_x, sub_d_y, tri, meshsize, props, water_plane, water_level):
74 verts = []
75 faces = []
76 vappend = verts.append
77 fappend = faces.append
78 sub_d_x += 1
79 sub_d_y += 1
80 for i in range(0, sub_d_x):
81 for j in range(0, sub_d_y):
82 u = sin(j * pi * 2 / (sub_d_y - 1)) * cos(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
83 v = cos(j * pi * 2 / (sub_d_y - 1)) * cos(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
84 w = sin(-pi / 2 + i * pi / (sub_d_x - 1)) * meshsize / 2
85 if water_plane:
86 h = water_level
87 else:
88 h = noise_gen((u, v, w), props) / meshsize
89 vappend(((u + u * h), (v + v * h), (w + w * h)))
91 count = 0
92 for i in range (0, sub_d_y * (sub_d_x - 1)):
93 if count < sub_d_y - 1 :
94 A = i + 1
95 B = i
96 C = (i + sub_d_y)
97 D = (i + sub_d_y) + 1
98 if tri:
99 fappend((A, B, D))
100 fappend((B, C, D))
101 else:
102 fappend((A, B, C, D))
103 count = count + 1
104 else:
105 count = 0
107 return verts, faces
110 # ------------------------------------------------------------
111 # Do refresh - redraw
112 class AntLandscapeRefresh(bpy.types.Operator):
113 bl_idname = "mesh.ant_landscape_refresh"
114 bl_label = "Refresh"
115 bl_description = "Refresh landscape with current settings"
116 bl_options = {'REGISTER', 'UNDO'}
119 @classmethod
120 def poll(cls, context):
121 ob = bpy.context.active_object
122 return (ob.ant_landscape and not ob.ant_landscape.sphere_mesh)
124 def execute(self, context):
125 # ant object items
126 obj = bpy.context.active_object
128 bpy.ops.object.mode_set(mode = 'EDIT')
129 bpy.ops.object.mode_set(mode = 'OBJECT')
131 keys = obj.ant_landscape.keys()
132 if keys:
133 ob = obj.ant_landscape
134 prop = []
135 for key in keys:
136 prop.append(getattr(ob, key))
138 # redraw verts
139 mesh = obj.data
141 if ob['vert_group'] != "" and ob['vert_group'] in obj.vertex_groups:
142 vertex_group = obj.vertex_groups[ob['vert_group']]
143 gi = vertex_group.index
144 for v in mesh.vertices:
145 for g in v.groups:
146 if g.group == gi:
147 v.co[2] = 0.0
148 v.co[2] = vertex_group.weight(v.index) * noise_gen(v.co, prop)
149 else:
150 for v in mesh.vertices:
151 v.co[2] = 0.0
152 v.co[2] = noise_gen(v.co, prop)
153 mesh.update()
154 else:
155 pass
157 return {'FINISHED'}
159 # ------------------------------------------------------------
160 # Do regenerate
161 class AntLandscapeRegenerate(bpy.types.Operator):
162 bl_idname = "mesh.ant_landscape_regenerate"
163 bl_label = "Regenerate"
164 bl_description = "Regenerate landscape with current settings"
165 bl_options = {'REGISTER', 'UNDO'}
168 @classmethod
169 def poll(cls, context):
170 ob = bpy.context.active_object
171 if ob.mode == 'EDIT':
172 return False
173 return ob.ant_landscape
176 def execute(self, context):
178 view_layer = bpy.context.view_layer
179 # ant object items
180 obj = bpy.context.active_object
182 keys = obj.ant_landscape.keys()
183 if keys:
184 ob = obj.ant_landscape
185 ant_props = []
186 for key in keys:
187 ant_props.append(getattr(ob, key))
189 new_name = obj.name
191 # Main function, create landscape mesh object
192 if ob['sphere_mesh']:
193 # sphere
194 verts, faces = sphere_gen(
195 ob['subdivision_y'],
196 ob['subdivision_x'],
197 ob['tri_face'],
198 ob['mesh_size'],
199 ant_props,
200 False,
203 new_ob = create_mesh_object(context, verts, [], faces, new_name)
204 if ob['remove_double']:
205 new_ob.select_set(True)
206 bpy.ops.object.mode_set(mode = 'EDIT')
207 bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
208 bpy.ops.object.mode_set(mode = 'OBJECT')
209 else:
210 # grid
211 verts, faces = grid_gen(
212 ob['subdivision_x'],
213 ob['subdivision_y'],
214 ob['tri_face'],
215 ob['mesh_size_x'],
216 ob['mesh_size_y'],
217 ant_props,
218 False,
221 new_ob = create_mesh_object(context, verts, [], faces, new_name)
223 new_ob.select_set(True)
225 if ob['smooth_mesh']:
226 bpy.ops.object.shade_smooth()
228 # Landscape Material
229 if ob['land_material'] != "" and ob['land_material'] in bpy.data.materials:
230 mat = bpy.data.materials[ob['land_material']]
231 bpy.context.object.data.materials.append(mat)
233 # Water plane
234 if ob['water_plane']:
235 if ob['sphere_mesh']:
236 # sphere
237 verts, faces = sphere_gen(
238 ob['subdivision_y'],
239 ob['subdivision_x'],
240 ob['tri_face'],
241 ob['mesh_size'],
242 ant_props,
243 ob['water_plane'],
244 ob['water_level']
246 wobj = create_mesh_object(context, verts, [], faces, new_name+"_plane")
247 if ob['remove_double']:
248 wobj.select_set(True)
249 bpy.ops.object.mode_set(mode = 'EDIT')
250 bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False)
251 bpy.ops.object.mode_set(mode = 'OBJECT')
252 else:
253 # grid
254 verts, faces = grid_gen(
257 ob['tri_face'],
258 ob['mesh_size_x'],
259 ob['mesh_size_y'],
260 ant_props,
261 ob['water_plane'],
262 ob['water_level']
264 wobj = create_mesh_object(context, verts, [], faces, new_name+"_plane")
266 wobj.select_set(True)
268 if ob['smooth_mesh']:
269 bpy.ops.object.shade_smooth()
271 # Water Material
272 if ob['water_material'] != "" and ob['water_material'] in bpy.data.materials:
273 mat = bpy.data.materials[ob['water_material']]
274 bpy.context.object.data.materials.append(mat)
276 # Loc Rot Scale
277 if ob['water_plane']:
278 wobj.location = obj.location
279 wobj.rotation_euler = obj.rotation_euler
280 wobj.scale = obj.scale
281 wobj.select_set(False)
283 new_ob.location = obj.location
284 new_ob.rotation_euler = obj.rotation_euler
285 new_ob.scale = obj.scale
287 # Store props
288 new_ob = store_properties(ob, new_ob)
290 # Delete old object
291 new_ob.select_set(False)
293 obj.select_set(True)
294 view_layer.objects.active = obj
295 bpy.ops.object.delete(use_global=False)
297 # Select landscape and make active
298 new_ob.select_set(True)
299 view_layer.objects.active = new_ob
301 return {'FINISHED'}
304 # ------------------------------------------------------------
305 # Z normal value to vertex group (Slope map)
306 class AntVgSlopeMap(bpy.types.Operator):
307 bl_idname = "mesh.ant_slope_map"
308 bl_label = "Weight from Slope"
309 bl_description = "A.N.T. Slope Map - z normal value to vertex group weight"
310 bl_options = {'REGISTER', 'UNDO'}
312 z_method: EnumProperty(
313 name="Method:",
314 default='SLOPE_Z',
315 items=[
316 ('SLOPE_Z', "Z Slope", "Slope for planar mesh"),
317 ('SLOPE_XYZ', "Sphere Slope", "Slope for spherical mesh")
319 group_name: StringProperty(
320 name="Vertex Group Name:",
321 default="Slope",
322 description="Name"
324 select_flat: BoolProperty(
325 name="Vert Select:",
326 default=True,
327 description="Select vertices on flat surface"
329 select_range: FloatProperty(
330 name="Vert Select Range:",
331 default=0.0,
332 min=0.0,
333 max=1.0,
334 description="Increase to select more vertices on slopes"
337 @classmethod
338 def poll(cls, context):
339 ob = context.object
340 return (ob and ob.type == 'MESH')
343 def invoke(self, context, event):
344 wm = context.window_manager
345 return wm.invoke_props_dialog(self)
348 def execute(self, context):
349 message = "Popup Values: %d, %f, %s, %s" % \
350 (self.select_flat, self.select_range, self.group_name, self.z_method)
351 self.report({'INFO'}, message)
353 bpy.ops.object.mode_set(mode='OBJECT')
354 ob = bpy.context.active_object
355 dim = ob.dimensions
357 if self.select_flat:
358 bpy.ops.object.mode_set(mode='EDIT')
359 bpy.ops.mesh.select_all(action='DESELECT')
360 bpy.context.tool_settings.mesh_select_mode = [True, False, False]
361 bpy.ops.object.mode_set(mode='OBJECT')
363 bpy.ops.object.vertex_group_add()
364 vg_normal = ob.vertex_groups.active
366 for v in ob.data.vertices:
367 if self.z_method == 'SLOPE_XYZ':
368 zval = (v.co.normalized() * v.normal.normalized()) * 2 - 1
369 else:
370 zval = v.normal[2]
372 vg_normal.add([v.index], zval, 'REPLACE')
374 if self.select_flat:
375 if zval >= (1.0 - self.select_range):
376 v.select = True
378 vg_normal.name = self.group_name
380 bpy.ops.paint.weight_paint_toggle()
381 return {'FINISHED'}
384 # ------------------------------------------------------------
385 # draw properties
387 def draw_ant_refresh(self, context):
388 layout = self.layout
389 if self.auto_refresh is False:
390 self.refresh = False
391 elif self.auto_refresh is True:
392 self.refresh = True
393 row = layout.box().row()
394 split = row.split()
395 split.scale_y = 1.5
396 split.prop(self, "auto_refresh", toggle=True, icon_only=True, icon='AUTO')
397 split.prop(self, "refresh", toggle=True, icon_only=True, icon='FILE_REFRESH')
400 def draw_ant_main(self, context, generate=True):
401 layout = self.layout
402 box = layout.box()
403 box.prop(self, "show_main_settings", toggle=True)
404 if self.show_main_settings:
405 if generate:
406 row = box.row(align=True)
407 split = row.split(align=True)
409 split.prop(self, "at_cursor", toggle=True, icon_only=True, icon='PIVOT_CURSOR')
411 split.prop(self, "smooth_mesh", toggle=True, icon_only=True, icon='SHADING_SOLID')
412 split.prop(self, "tri_face", toggle=True, text="Triangulate", icon='MESH_DATA')
414 if not self.sphere_mesh:
415 row = box.row(align=True)
416 row.prop(self, "sphere_mesh", toggle=True)
417 else:
418 row = box.row(align=True)
419 split = row.split(factor=0.5, align=True)
420 split.prop(self, "sphere_mesh", toggle=True)
421 split.prop(self, "remove_double", toggle=True)
423 box.prop(self, "ant_terrain_name")
424 box.prop_search(self, "land_material", bpy.data, "materials")
426 col = box.column(align=True)
427 col.prop(self, "subdivision_x")
428 col.prop(self, "subdivision_y")
429 col = box.column(align=True)
430 if self.sphere_mesh:
431 col.prop(self, "mesh_size")
432 else:
433 col.prop(self, "mesh_size_x")
434 col.prop(self, "mesh_size_y")
437 def draw_ant_noise(self, context, generate=True):
438 layout = self.layout
439 box = layout.box()
440 box.prop(self, "show_noise_settings", toggle=True)
441 if self.show_noise_settings:
442 box.prop(self, "noise_type")
443 if self.noise_type == "blender_texture":
444 box.prop_search(self, "texture_block", bpy.data, "textures")
445 else:
446 box.prop(self, "basis_type")
448 col = box.column(align=True)
449 col.prop(self, "random_seed")
450 col = box.column(align=True)
451 col.prop(self, "noise_offset_x")
452 col.prop(self, "noise_offset_y")
453 if self.sphere_mesh == True or generate == False:
454 col.prop(self, "noise_offset_z")
455 col.prop(self, "noise_size_x")
456 col.prop(self, "noise_size_y")
457 if self.sphere_mesh == True or generate == False:
458 col.prop(self, "noise_size_z")
460 col = box.column(align=True)
461 col.prop(self, "noise_size")
463 col = box.column(align=True)
464 if self.noise_type == "multi_fractal":
465 col.prop(self, "noise_depth")
466 col.prop(self, "dimension")
467 col.prop(self, "lacunarity")
468 elif self.noise_type == "ridged_multi_fractal":
469 col.prop(self, "noise_depth")
470 col.prop(self, "dimension")
471 col.prop(self, "lacunarity")
472 col.prop(self, "offset")
473 col.prop(self, "gain")
474 elif self.noise_type == "hybrid_multi_fractal":
475 col.prop(self, "noise_depth")
476 col.prop(self, "dimension")
477 col.prop(self, "lacunarity")
478 col.prop(self, "offset")
479 col.prop(self, "gain")
480 elif self.noise_type == "hetero_terrain":
481 col.prop(self, "noise_depth")
482 col.prop(self, "dimension")
483 col.prop(self, "lacunarity")
484 col.prop(self, "offset")
485 elif self.noise_type == "fractal":
486 col.prop(self, "noise_depth")
487 col.prop(self, "dimension")
488 col.prop(self, "lacunarity")
489 elif self.noise_type == "turbulence_vector":
490 col.prop(self, "noise_depth")
491 col.prop(self, "amplitude")
492 col.prop(self, "frequency")
493 col.separator()
494 row = col.row(align=True)
495 row.prop(self, "hard_noise", expand=True)
496 elif self.noise_type == "variable_lacunarity":
497 box.prop(self, "vl_basis_type")
498 box.prop(self, "distortion")
499 elif self.noise_type == "marble_noise":
500 box.prop(self, "marble_shape")
501 box.prop(self, "marble_bias")
502 box.prop(self, "marble_sharp")
503 col = box.column(align=True)
504 col.prop(self, "distortion")
505 col.prop(self, "noise_depth")
506 col.separator()
507 row = col.row(align=True)
508 row.prop(self, "hard_noise", expand=True)
509 elif self.noise_type == "shattered_hterrain":
510 col.prop(self, "noise_depth")
511 col.prop(self, "dimension")
512 col.prop(self, "lacunarity")
513 col.prop(self, "offset")
514 col.prop(self, "distortion")
515 elif self.noise_type == "strata_hterrain":
516 col.prop(self, "noise_depth")
517 col.prop(self, "dimension")
518 col.prop(self, "lacunarity")
519 col.prop(self, "offset")
520 col.prop(self, "distortion", text="Strata")
521 elif self.noise_type == "ant_turbulence":
522 col.prop(self, "noise_depth")
523 col.prop(self, "amplitude")
524 col.prop(self, "frequency")
525 col.prop(self, "distortion")
526 col.separator()
527 row = col.row(align=True)
528 row.prop(self, "hard_noise", expand=True)
529 elif self.noise_type == "vl_noise_turbulence":
530 col.prop(self, "noise_depth")
531 col.prop(self, "amplitude")
532 col.prop(self, "frequency")
533 col.prop(self, "distortion")
534 col.separator()
535 box.prop(self, "vl_basis_type")
536 col.separator()
537 row = col.row(align=True)
538 row.prop(self, "hard_noise", expand=True)
539 elif self.noise_type == "vl_hTerrain":
540 col.prop(self, "noise_depth")
541 col.prop(self, "dimension")
542 col.prop(self, "lacunarity")
543 col.prop(self, "offset")
544 col.prop(self, "distortion")
545 col.separator()
546 box.prop(self, "vl_basis_type")
547 elif self.noise_type == "distorted_heteroTerrain":
548 col.prop(self, "noise_depth")
549 col.prop(self, "dimension")
550 col.prop(self, "lacunarity")
551 col.prop(self, "offset")
552 col.prop(self, "distortion")
553 col.separator()
554 box.prop(self, "vl_basis_type")
555 elif self.noise_type == "double_multiFractal":
556 col.prop(self, "noise_depth")
557 col.prop(self, "dimension")
558 col.prop(self, "lacunarity")
559 col.prop(self, "offset")
560 col.prop(self, "gain")
561 col.separator()
562 box.prop(self, "vl_basis_type")
563 elif self.noise_type == "rocks_noise":
564 col.prop(self, "noise_depth")
565 col.prop(self, "distortion")
566 col.separator()
567 row = col.row(align=True)
568 row.prop(self, "hard_noise", expand=True)
569 elif self.noise_type == "slick_rock":
570 col.prop(self, "noise_depth")
571 col.prop(self, "dimension")
572 col.prop(self, "lacunarity")
573 col.prop(self, "gain")
574 col.prop(self, "offset")
575 col.prop(self, "distortion")
576 col.separator()
577 box.prop(self, "vl_basis_type")
578 elif self.noise_type == "planet_noise":
579 col.prop(self, "noise_depth")
580 col.separator()
581 row = col.row(align=True)
582 row.prop(self, "hard_noise", expand=True)
584 # Effects mix
585 col = box.column(align=False)
586 box.prop(self, "fx_type")
587 if self.fx_type != "0":
588 if int(self.fx_type) <= 12:
589 box.prop(self, "fx_bias")
591 box.prop(self, "fx_mix_mode")
592 col = box.column(align=True)
593 col.prop(self, "fx_mixfactor")
595 col = box.column(align=True)
596 col.prop(self, "fx_loc_x")
597 col.prop(self, "fx_loc_y")
598 col.prop(self, "fx_size")
600 col = box.column(align=True)
601 col.prop(self, "fx_depth")
602 if self.fx_depth != 0:
603 col.prop(self, "fx_frequency")
604 col.prop(self, "fx_amplitude")
605 col.prop(self, "fx_turb")
607 col = box.column(align=True)
608 row = col.row(align=True).split(factor=0.92, align=True)
609 row.prop(self, "fx_height")
610 row.prop(self, "fx_invert", toggle=True, text="", icon='ARROW_LEFTRIGHT')
611 col.prop(self, "fx_offset")
614 def draw_ant_displace(self, context, generate=True):
615 layout = self.layout
616 box = layout.box()
617 box.prop(self, "show_displace_settings", toggle=True)
618 if self.show_displace_settings:
619 if not generate:
620 col = box.column(align=False)
621 col.prop(self, "direction", toggle=True)
623 col = box.column(align=True)
624 row = col.row(align=True).split(factor=0.92, align=True)
625 row.prop(self, "height")
626 row.prop(self, "height_invert", toggle=True, text="", icon='ARROW_LEFTRIGHT')
627 col.prop(self, "height_offset")
628 col.prop(self, "maximum")
629 col.prop(self, "minimum")
630 if generate:
631 if not self.sphere_mesh:
632 col = box.column()
633 col.prop(self, "edge_falloff")
634 if self.edge_falloff != "0":
635 col = box.column(align=True)
636 col.prop(self, "edge_level")
637 if self.edge_falloff in ["2", "3"]:
638 col.prop(self, "falloff_x")
639 if self.edge_falloff in ["1", "3"]:
640 col.prop(self, "falloff_y")
642 col = box.column()
643 col.prop(self, "strata_type")
644 if self.strata_type != "0":
645 col = box.column()
646 col.prop(self, "strata")
648 if not generate:
649 col = box.column(align=False)
650 col.prop_search(self, "vert_group", bpy.context.object, "vertex_groups")
653 def draw_ant_water(self, context):
654 layout = self.layout
655 box = layout.box()
656 col = box.column()
657 col.prop(self, "water_plane", toggle=True)
658 if self.water_plane:
659 col = box.column(align=True)
660 col.prop_search(self, "water_material", bpy.data, "materials")
661 col = box.column()
662 col.prop(self, "water_level")
665 # Store propereties
666 def store_properties(operator, ob):
667 ob.ant_landscape.ant_terrain_name = operator.ant_terrain_name
668 ob.ant_landscape.at_cursor = operator.at_cursor
669 ob.ant_landscape.smooth_mesh = operator.smooth_mesh
670 ob.ant_landscape.tri_face = operator.tri_face
671 ob.ant_landscape.sphere_mesh = operator.sphere_mesh
672 ob.ant_landscape.land_material = operator.land_material
673 ob.ant_landscape.water_material = operator.water_material
674 ob.ant_landscape.texture_block = operator.texture_block
675 ob.ant_landscape.subdivision_x = operator.subdivision_x
676 ob.ant_landscape.subdivision_y = operator.subdivision_y
677 ob.ant_landscape.mesh_size_x = operator.mesh_size_x
678 ob.ant_landscape.mesh_size_y = operator.mesh_size_y
679 ob.ant_landscape.mesh_size = operator.mesh_size
680 ob.ant_landscape.random_seed = operator.random_seed
681 ob.ant_landscape.noise_offset_x = operator.noise_offset_x
682 ob.ant_landscape.noise_offset_y = operator.noise_offset_y
683 ob.ant_landscape.noise_offset_z = operator.noise_offset_z
684 ob.ant_landscape.noise_size_x = operator.noise_size_x
685 ob.ant_landscape.noise_size_y = operator.noise_size_y
686 ob.ant_landscape.noise_size_z = operator.noise_size_z
687 ob.ant_landscape.noise_size = operator.noise_size
688 ob.ant_landscape.noise_type = operator.noise_type
689 ob.ant_landscape.basis_type = operator.basis_type
690 ob.ant_landscape.vl_basis_type = operator.vl_basis_type
691 ob.ant_landscape.distortion = operator.distortion
692 ob.ant_landscape.hard_noise = operator.hard_noise
693 ob.ant_landscape.noise_depth = operator.noise_depth
694 ob.ant_landscape.amplitude = operator.amplitude
695 ob.ant_landscape.frequency = operator.frequency
696 ob.ant_landscape.dimension = operator.dimension
697 ob.ant_landscape.lacunarity = operator.lacunarity
698 ob.ant_landscape.offset = operator.offset
699 ob.ant_landscape.gain = operator.gain
700 ob.ant_landscape.marble_bias = operator.marble_bias
701 ob.ant_landscape.marble_sharp = operator.marble_sharp
702 ob.ant_landscape.marble_shape = operator.marble_shape
703 ob.ant_landscape.height = operator.height
704 ob.ant_landscape.height_invert = operator.height_invert
705 ob.ant_landscape.height_offset = operator.height_offset
706 ob.ant_landscape.maximum = operator.maximum
707 ob.ant_landscape.minimum = operator.minimum
708 ob.ant_landscape.edge_falloff = operator.edge_falloff
709 ob.ant_landscape.edge_level = operator.edge_level
710 ob.ant_landscape.falloff_x = operator.falloff_x
711 ob.ant_landscape.falloff_y = operator.falloff_y
712 ob.ant_landscape.strata_type = operator.strata_type
713 ob.ant_landscape.strata = operator.strata
714 ob.ant_landscape.water_plane = operator.water_plane
715 ob.ant_landscape.water_level = operator.water_level
716 ob.ant_landscape.vert_group = operator.vert_group
717 ob.ant_landscape.remove_double = operator.remove_double
718 ob.ant_landscape.fx_mixfactor = operator.fx_mixfactor
719 ob.ant_landscape.fx_mix_mode = operator.fx_mix_mode
720 ob.ant_landscape.fx_type = operator.fx_type
721 ob.ant_landscape.fx_bias = operator.fx_bias
722 ob.ant_landscape.fx_turb = operator.fx_turb
723 ob.ant_landscape.fx_depth = operator.fx_depth
724 ob.ant_landscape.fx_frequency = operator.fx_frequency
725 ob.ant_landscape.fx_amplitude = operator.fx_amplitude
726 ob.ant_landscape.fx_size = operator.fx_size
727 ob.ant_landscape.fx_loc_x = operator.fx_loc_x
728 ob.ant_landscape.fx_loc_y = operator.fx_loc_y
729 ob.ant_landscape.fx_height = operator.fx_height
730 ob.ant_landscape.fx_offset = operator.fx_offset
731 ob.ant_landscape.fx_invert = operator.fx_invert
732 return ob
735 # ------------------------------------------------------------
736 # "name": "ErosionR"
737 # "author": "Michel Anders, Ian Huish"
739 from random import random as rand
740 from math import tan, radians
741 from .eroder import Grid
742 from .stats import Stats
743 from .utils import numexpr_available
746 def availableVertexGroupsOrNone(self, context):
747 groups = [ ('None', 'None', 'None', 1) ]
748 return groups + [(name, name, name, n+1) for n,name in enumerate(context.active_object.vertex_groups.keys())]
751 class Eroder(bpy.types.Operator):
752 bl_idname = "mesh.eroder"
753 bl_label = "ErosionR"
754 bl_description = "Apply various kinds of erosion to a square ANT-Landscape grid. Also available in Weight Paint mode > Weights menu"
755 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
757 Iterations: IntProperty(
758 name="Iterations",
759 description="Number of overall iterations",
760 default=1,
761 min=1,
762 soft_max=100
764 IterRiver: IntProperty(
765 name="River Iterations",
766 description="Number of river iterations",
767 default=30,
768 min=1,
769 soft_max=1000
771 IterAva: IntProperty(
772 name="Avalanche Iterations",
773 description="Number of avalanche iterations",
774 default=5,
775 min=1,
776 soft_max=10
778 IterDiffuse: IntProperty(
779 name="Diffuse Iterations",
780 description="Number of diffuse iterations",
781 default=5,
782 min=1,
783 soft_max=10
785 Ef: FloatProperty(
786 name="Rain on Plains",
787 description="1 gives equal rain across the terrain, 0 rains more at the mountain tops",
788 default=0.0,
789 min=0,
790 max=1
792 Kd: FloatProperty(
793 name="Kd",
794 description="Thermal diffusion rate (1.0 is a fairly high rate)",
795 default=0.1,
796 min=0,
797 soft_max=100
799 Kt: FloatProperty(
800 name="Kt",
801 description="Maximum stable talus angle",
802 default=radians(60),
803 min=0,
804 max=radians(90),
805 subtype='ANGLE'
807 Kr: FloatProperty(
808 name="Rain amount",
809 description="Total Rain amount",
810 default=.01,
811 min=0,
812 soft_max=1,
813 precision=3
815 Kv: FloatProperty(
816 name="Rain variance",
817 description="Rain variance (0 is constant, 1 is uniform)",
818 default=0,
819 min=0,
820 max=1
822 userainmap: BoolProperty(
823 name="Use rain map",
824 description="Use active vertex group as a rain map",
825 default=True
827 Ks: FloatProperty(
828 name="Soil solubility",
829 description="Soil solubility - how quickly water quickly reaches saturation point",
830 default=0.5,
831 min=0,
832 soft_max=1
834 Kdep: FloatProperty(
835 name="Deposition rate",
836 description="Sediment deposition rate - how quickly silt is laid down once water stops flowing quickly",
837 default=0.1,
838 min=0,
839 soft_max=1
841 Kz: FloatProperty(name="Fluvial Erosion Rate",
842 description="Amount of sediment moved each main iteration - if 0, then rivers are formed but the mesh is not changed",
843 default=0.3,
844 min=0,
845 soft_max=20
847 Kc: FloatProperty(
848 name="Carrying capacity",
849 description="Base sediment carrying capacity",
850 default=0.9,
851 min=0,
852 soft_max=1
854 Ka: FloatProperty(
855 name="Slope dependence",
856 description="Slope dependence of carrying capacity (not used)",
857 default=1.0,
858 min=0,
859 soft_max=2
861 Kev: FloatProperty(
862 name="Evaporation",
863 description="Evaporation Rate per grid square in % - causes sediment to be dropped closer to the hills",
864 default=.5,
865 min=0,
866 soft_max=2
868 numexpr: BoolProperty(
869 name="Numexpr",
870 description="Use numexpr module (if available)",
871 default=True
873 Pd: FloatProperty(
874 name="Diffusion Amount",
875 description="Diffusion probability",
876 default=0.2,
877 min=0,
878 max=1
880 Pa: FloatProperty(
881 name="Avalanche Amount",
882 description="Avalanche amount",
883 default=0.5,
884 min=0,
885 max=1
887 Pw: FloatProperty(
888 name="River Amount",
889 description="Water erosion probability",
890 default=1,
891 min=0,
892 max=1
894 smooth: BoolProperty(
895 name="Smooth",
896 description="Set smooth shading",
897 default=True
899 showiterstats: BoolProperty(
900 name="Iteration Stats",
901 description="Show iteraration statistics",
902 default=False
904 showmeshstats: BoolProperty(name="Mesh Stats",
905 description="Show mesh statistics",
906 default=False
909 stats = Stats()
910 counts= {}
912 def execute(self, context):
914 ob = context.active_object
915 me = ob.data
916 self.stats.reset()
917 try:
918 vgActive = ob.vertex_groups.active.name
919 except:
920 vgActive = "capacity"
921 print("ActiveGroup", vgActive)
922 try:
923 vg=ob.vertex_groups["rainmap"]
924 except:
925 vg=ob.vertex_groups.new(name="rainmap")
926 try:
927 vgscree=ob.vertex_groups["scree"]
928 except:
929 vgscree=ob.vertex_groups.new(name="scree")
930 try:
931 vgavalanced=ob.vertex_groups["avalanced"]
932 except:
933 vgavalanced=ob.vertex_groups.new(name="avalanced")
934 try:
935 vgw=ob.vertex_groups["water"]
936 except:
937 vgw=ob.vertex_groups.new(name="water")
938 try:
939 vgscour=ob.vertex_groups["scour"]
940 except:
941 vgscour=ob.vertex_groups.new(name="scour")
942 try:
943 vgdeposit=ob.vertex_groups["deposit"]
944 except:
945 vgdeposit=ob.vertex_groups.new(name="deposit")
946 try:
947 vgflowrate=ob.vertex_groups["flowrate"]
948 except:
949 vgflowrate=ob.vertex_groups.new(name="flowrate")
950 try:
951 vgsediment=ob.vertex_groups["sediment"]
952 except:
953 vgsediment=ob.vertex_groups.new(name="sediment")
954 try:
955 vgsedimentpct=ob.vertex_groups["sedimentpct"]
956 except:
957 vgsedimentpct=ob.vertex_groups.new(name="sedimentpct")
958 try:
959 vgcapacity=ob.vertex_groups["capacity"]
960 except:
961 vgcapacity=ob.vertex_groups.new(name="capacity")
963 g = Grid.fromBlenderMesh(me, vg, self.Ef)
965 me = bpy.data.meshes.new(me.name)
967 self.counts['diffuse'] = 0
968 self.counts['avalanche'] = 0
969 self.counts['water'] = 0
970 for i in range(self.Iterations):
971 if self.IterRiver > 0:
972 for i in range(self.IterRiver):
973 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)
975 if self.Kd > 0.0:
976 for k in range(self.IterDiffuse):
977 g.diffuse(self.Kd / 5, self.IterDiffuse, self.numexpr)
978 self.counts['diffuse']+=1
980 if self.Kt < radians(90) and self.Pa > 0:
981 for k in range(self.IterAva):
982 # since dx and dy are scaled to 1, tan(Kt) is the height for a given angle
983 g.avalanche(tan(self.Kt), self.IterAva, self.Pa, self.numexpr)
984 self.counts['avalanche']+=1
985 if self.Kz > 0:
986 g.fluvial_erosion(self.Kr, self.Kv, self.userainmap, self.Kc, self.Ks, self.Kz*50, self.Ka, 0,0,0,0, self.numexpr)
987 self.counts['water']+=1
989 g.toBlenderMesh(me)
990 ob.data = me
992 if vg:
993 for row in range(g.rainmap.shape[0]):
994 for col in range(g.rainmap.shape[1]):
995 i = row * g.rainmap.shape[1] + col
996 vg.add([i],g.rainmap[row,col],'ADD')
997 if vgscree:
998 for row in range(g.rainmap.shape[0]):
999 for col in range(g.rainmap.shape[1]):
1000 i = row * g.rainmap.shape[1] + col
1001 vgscree.add([i],g.avalanced[row,col],'ADD')
1002 if vgavalanced:
1003 for row in range(g.rainmap.shape[0]):
1004 for col in range(g.rainmap.shape[1]):
1005 i = row * g.rainmap.shape[1] + col
1006 vgavalanced.add([i],-g.avalanced[row,col],'ADD')
1007 if vgw:
1008 for row in range(g.rainmap.shape[0]):
1009 for col in range(g.rainmap.shape[1]):
1010 i = row * g.rainmap.shape[1] + col
1011 vgw.add([i],g.water[row,col]/g.watermax,'ADD')
1012 if vgscour:
1013 for row in range(g.rainmap.shape[0]):
1014 for col in range(g.rainmap.shape[1]):
1015 i = row * g.rainmap.shape[1] + col
1016 vgscour.add([i],g.scour[row,col]/max(g.scourmax, -g.scourmin),'ADD')
1017 if vgdeposit:
1018 for row in range(g.rainmap.shape[0]):
1019 for col in range(g.rainmap.shape[1]):
1020 i = row * g.rainmap.shape[1] + col
1021 vgdeposit.add([i],g.scour[row,col]/min(-g.scourmax, g.scourmin),'ADD')
1022 if vgflowrate:
1023 for row in range(g.rainmap.shape[0]):
1024 for col in range(g.rainmap.shape[1]):
1025 i = row * g.rainmap.shape[1] + col
1026 vgflowrate.add([i],g.flowrate[row,col],'ADD')
1027 if vgsediment:
1028 for row in range(g.rainmap.shape[0]):
1029 for col in range(g.rainmap.shape[1]):
1030 i = row * g.rainmap.shape[1] + col
1031 vgsediment.add([i],g.sediment[row,col],'ADD')
1032 if vgsedimentpct:
1033 for row in range(g.rainmap.shape[0]):
1034 for col in range(g.rainmap.shape[1]):
1035 i = row * g.rainmap.shape[1] + col
1036 vgsedimentpct.add([i],g.sedimentpct[row,col],'ADD')
1037 if vgcapacity:
1038 for row in range(g.rainmap.shape[0]):
1039 for col in range(g.rainmap.shape[1]):
1040 i = row * g.rainmap.shape[1] + col
1041 vgcapacity.add([i],g.capacity[row,col],'ADD')
1042 try:
1043 vg = ob.vertex_groups["vgActive"]
1044 except:
1045 vg = vgcapacity
1046 ob.vertex_groups.active = vg
1048 if self.smooth:
1049 bpy.ops.object.shade_smooth()
1050 self.stats.time()
1051 self.stats.memory()
1052 if self.showmeshstats:
1053 self.stats.meshstats = g.analyze()
1055 return {'FINISHED'}
1058 def draw(self,context):
1059 layout = self.layout
1061 layout.operator('screen.repeat_last', text="Repeat", icon='FILE_REFRESH' )
1063 layout.prop(self, 'Iterations')
1065 box = layout.box()
1066 col = box.column(align=True)
1067 col.label(text="Thermal (Diffusion)")
1068 col.prop(self, 'Kd')
1069 col.prop(self, 'IterDiffuse')
1071 box = layout.box()
1072 col = box.column(align=True)
1073 col.label(text="Avalanche (Talus)")
1074 col.prop(self, 'Pa')
1075 col.prop(self, 'IterAva')
1076 col.prop(self, 'Kt')
1078 box = layout.box()
1079 col = box.column(align=True)
1080 col.label(text="River erosion")
1081 col.prop(self, 'IterRiver')
1082 col.prop(self, 'Kz')
1083 col.prop(self, 'Ks')
1084 col.prop(self, 'Kc')
1085 col.prop(self, 'Kdep')
1086 col.prop(self, 'Kr')
1087 col.prop(self, 'Kv')
1088 col.prop(self, 'Kev')
1090 col.prop(self, 'Ef')
1092 layout.prop(self,'smooth')