From 4b8eb21634c82c0cd0862b934e3b3129b1450052 Mon Sep 17 00:00:00 2001 From: Lawrence D'Oliveiro Date: Thu, 23 Apr 2020 07:55:13 +0000 Subject: [PATCH] Use separate random sequences for geometry versus materials, and keep seed values on redo (letting user edit them if desired) so spaceship design only has to change on each new invocation. --- __init__.py | 25 +- spaceship_generator.py | 911 +++++++++++++++++++++-------------------- 2 files changed, 481 insertions(+), 455 deletions(-) diff --git a/__init__.py b/__init__.py index 18e17b4..a558869 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ { "name" : "Spaceship Generator", "author" : "Michael Davies, Lawrence D'Oliveiro", - "version" : (1, 2, 2), + "version" : (1, 3, 0), "blender" : (2, 82, 0), "location" : "View3D > Add > Mesh", "description" : "Procedurally generate 3D spaceships from a random seed.", @@ -19,6 +19,7 @@ from . import spaceship_generator #end if +import random import bpy from bpy.props import \ BoolProperty, \ @@ -32,10 +33,15 @@ class GenerateSpaceship(bpy.types.Operator) : bl_options = {"REGISTER", "UNDO"} df = spaceship_generator.parms_defaults # temp short name - random_seed : StringProperty \ + geom_ranseed : StringProperty \ ( - default = df.random_seed, - name = "Seed" + default = df.geom_ranseed, + name = "Geometry Seed" + ) + mat_ranseed : StringProperty \ + ( + default = df.mat_ranseed, + name = "Material Seed" ) num_hull_segments_min : IntProperty \ ( @@ -97,6 +103,17 @@ class GenerateSpaceship(bpy.types.Operator) : ) del df + def invoke(self, context, event) : + maxseed = 1e6 + # [0 .. 999999] is enough to be interesting by + # default. Users can always replace seeds with + # anything they like. + self.geom_ranseed = str(random.randrange(maxseed)) + self.mat_ranseed = str(random.randrange(maxseed)) + spaceship_generator.generate_spaceship(self) + return {"FINISHED"} + #end invoke + def execute(self, context) : spaceship_generator.generate_spaceship(self) return {"FINISHED"} diff --git a/spaceship_generator.py b/spaceship_generator.py index 236b607..021b0cd 100644 --- a/spaceship_generator.py +++ b/spaceship_generator.py @@ -142,19 +142,6 @@ def ribbed_extrude_face(bm, face, translate_forwards, num_ribs = 3, rib_scale = return new_face #end ribbed_extrude_face -def scale_face(bm, face, scale_x, scale_y, scale_z) : - # Scales a face in local face space. Ace! - face_space = get_face_matrix(face) - face_space.invert() - bmesh.ops.scale \ - ( - bm, - vec = Vector((scale_x, scale_y, scale_z)), - space = face_space, - verts = face.verts - ) -#end scale_face - def get_face_matrix(face, pos = None) : # Returns a rough 4x4 transform matrix for a face (doesn't handle # distortion/shear) with optional position override. @@ -187,6 +174,19 @@ def get_face_matrix(face, pos = None) : return mat #end get_face_matrix +def scale_face(bm, face, scale_x, scale_y, scale_z) : + # Scales a face in local face space. Ace! + face_space = get_face_matrix(face) + face_space.invert() + bmesh.ops.scale \ + ( + bm, + vec = Vector((scale_x, scale_y, scale_z)), + space = face_space, + verts = face.verts + ) +#end scale_face + def get_face_width_and_height(face) : # Returns the rough length and width of a quad face. # Assumes a perfect rectangle, but close enough. @@ -224,400 +224,7 @@ class MATERIAL(IntEnum) : GLOW_DISC = 4 # Emissive landing pad disc material #end MATERIAL -def add_exhaust_to_face(bm, face) : - # Given a face, splits it into a uniform grid and extrudes each grid face - # out and back in again, making an exhaust shape. - if not face.is_valid : - return - #end if - - # The more square the face is, the more grid divisions it might have - num_cuts = random.randint(1, int(4 - get_aspect_ratio(face))) - result = bmesh.ops.subdivide_edges \ - ( - bm, - edges = face.edges[:], - cuts = num_cuts, - fractal = 0.02, - use_grid_fill = True - ) - exhaust_length = random.uniform(0.1, 0.2) - scale_outer = 1 / random.uniform(1.3, 1.6) - scale_inner = 1 / random.uniform(1.05, 1.1) - for face in result["geom"] : - if isinstance(face, bmesh.types.BMFace) : - if is_rear_face(face) : - face.material_index = MATERIAL.HULL_DARK - face = extrude_face(bm, face, exhaust_length) - scale_face(bm, face, scale_outer, scale_outer, scale_outer) - extruded_face_list = [] - face = extrude_face(bm, face, -exhaust_length * 0.9, extruded_face_list) - for extruded_face in extruded_face_list : - extruded_face.material_index = MATERIAL.EXHAUST_BURN - #end for - scale_face(bm, face, scale_inner, scale_inner, scale_inner) - #end if - #end if - #end for -#end add_exhaust_to_face - -def add_grid_to_face(bm, face) : - # Given a face, splits it up into a smaller uniform grid and extrudes each grid cell. - if not face.is_valid : - return - #end if - result = bmesh.ops.subdivide_edges \ - ( - bm, - edges = face.edges[:], - cuts = random.randint(2, 4), - fractal = 0.02, - use_grid_fill = True, - use_single_edge = False - ) - grid_length = random.uniform(0.025, 0.15) - scale = 0.8 - for face in result["geom"] : - if isinstance(face, bmesh.types.BMFace) : - material_index = MATERIAL.HULL_LIGHTS if random.random() > 0.5 else MATERIAL.HULL - extruded_face_list = [] - face = extrude_face(bm, face, grid_length, extruded_face_list) - for extruded_face in extruded_face_list : - if abs(face.normal.z) < 0.707 : # side face - extruded_face.material_index = material_index - #end if - #end for - scale_face(bm, face, scale, scale, scale) - #end if - #end for -#end add_grid_to_face - -def add_cylinders_to_face(bm, face) : - # Given a face, adds some cylinders along it in a grid pattern. - if not face.is_valid or len(face.verts[:]) < 4 : - return - #end if - horizontal_step = random.randint(1, 3) - vertical_step = random.randint(1, 3) - num_segments = random.randint(6, 12) - face_width, face_height = get_face_width_and_height(face) - cylinder_depth = \ - ( - 1.3 - * - min - ( - face_width / (horizontal_step + 2), - face_height / (vertical_step + 2) - ) - ) - cylinder_size = cylinder_depth * 0.5 - for h in range(horizontal_step) : - top = face.verts[0].co.lerp \ - ( - face.verts[1].co, - (h + 1) / (horizontal_step + 1) - ) - bottom = face.verts[3].co.lerp \ - ( - face.verts[2].co, - (h + 1) / (horizontal_step + 1) - ) - for v in range(vertical_step) : - pos = top.lerp(bottom, (v + 1) / (vertical_step + 1)) - cylinder_matrix = \ - ( - get_face_matrix(face, pos) - @ - Matrix.Rotation(90 * deg, 3, "X").to_4x4() - ) - bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = num_segments, - diameter1 = cylinder_size, - diameter2 = cylinder_size, - depth = cylinder_depth, - matrix = cylinder_matrix - ) - #end for - #end for -#end add_cylinders_to_face - -def add_weapons_to_face(bm, face) : - # Given a face, adds some weapon turrets to it in a grid pattern. - # Each turret will have a random orientation. - if not face.is_valid or len(face.verts[:]) < 4 : - return - #end if - horizontal_step = random.randint(1, 2) - vertical_step = random.randint(1, 2) - num_segments = 16 - face_width, face_height = get_face_width_and_height(face) - weapon_size = \ - ( - 0.5 - * - min - ( - face_width / (horizontal_step + 2), - face_height / (vertical_step + 2) - ) - ) - weapon_depth = weapon_size * 0.2 - for h in range(horizontal_step) : - top = face.verts[0].co.lerp \ - ( - face.verts[1].co, - (h + 1) / (horizontal_step + 1) - ) - bottom = face.verts[3].co.lerp \ - ( - face.verts[2].co, - (h + 1) / (horizontal_step + 1) - ) - for v in range(vertical_step) : - pos = top.lerp(bottom, (v + 1) / (vertical_step + 1)) - face_matrix = \ - ( - get_face_matrix(face, pos + face.normal * weapon_depth * 0.5) - @ - Matrix.Rotation(random.uniform(0, 90) * deg, 3, "Z").to_4x4() - ) - - # Turret foundation - bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = num_segments, - diameter1 = weapon_size * 0.9, - diameter2 = weapon_size, - depth = weapon_depth, - matrix = face_matrix - ) - # Turret left guard - bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = num_segments, - diameter1 = weapon_size * 0.6, - diameter2 = weapon_size * 0.5, - depth = weapon_depth * 2, - matrix = - face_matrix - @ - Matrix.Rotation(90 * deg, 3, "Y").to_4x4() - @ - Matrix.Translation(Vector((0, 0, weapon_size * 0.6))).to_4x4() - ) - # Turret right guard - bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = num_segments, - diameter1 = weapon_size * 0.5, - diameter2 = weapon_size * 0.6, - depth = weapon_depth * 2, - matrix = - face_matrix - @ - Matrix.Rotation(90 * deg, 3, "Y").to_4x4() - @ - Matrix.Translation(Vector((0, 0, weapon_size * -0.6))).to_4x4() - ) - # Turret housing - upward_angle = random.uniform(0, 45) * deg - turret_house_mat = \ - ( - face_matrix - @ - Matrix.Rotation(upward_angle, 3, "X").to_4x4() - @ - Matrix.Translation(Vector((0, weapon_size * -0.4, 0))).to_4x4() - ) - bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = 8, - diameter1 = weapon_size * 0.4, - diameter2 = weapon_size * 0.4, - depth = weapon_depth * 5, - matrix = turret_house_mat - ) - # Turret barrels L + R - bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = 8, - diameter1 = weapon_size * 0.1, - diameter2 = weapon_size * 0.1, - depth = weapon_depth * 6, - matrix = - turret_house_mat - @ - Matrix.Translation(Vector((weapon_size * 0.2, 0, -weapon_size))).to_4x4() - ) - bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = 8, - diameter1 = weapon_size * 0.1, - diameter2 = weapon_size * 0.1, - depth = weapon_depth * 6, - matrix = - turret_house_mat - @ - Matrix.Translation(Vector((weapon_size * -0.2, 0, -weapon_size))).to_4x4() - ) - #end for v in range(vertical_step) - #end for h in range(horizontal_step) -#end add_weapons_to_face - -def add_sphere_to_face(bm, face) : - # Given a face, adds a sphere on the surface, partially inset. - if not face.is_valid : - return - #end if - face_width, face_height = get_face_width_and_height(face) - sphere_size = random.uniform(0.4, 1.0) * min(face_width, face_height) - sphere_matrix = get_face_matrix \ - ( - face, - face.calc_center_bounds() - face.normal * random.uniform(0, sphere_size * 0.5) - ) - result = bmesh.ops.create_icosphere \ - ( - bm, - subdivisions = 3, - diameter = sphere_size, - matrix = sphere_matrix - ) - for vert in result["verts"] : - for face in vert.link_faces : - face.material_index = MATERIAL.HULL - #end for - #end for -#end add_sphere_to_face - -def add_surface_antenna_to_face(bm, face) : - # Given a face, adds some pointy intimidating antennas. - if not face.is_valid or len(face.verts[:]) < 4 : - return - #end if - horizontal_step = random.randint(4, 10) - vertical_step = random.randint(4, 10) - for h in range(horizontal_step) : - top = face.verts[0].co.lerp \ - ( - face.verts[1].co, - (h + 1) / (horizontal_step + 1) - ) - bottom = face.verts[3].co.lerp \ - ( - face.verts[2].co, - (h + 1) / (horizontal_step + 1) - ) - for v in range(vertical_step) : - if random.random() > 0.9 : - pos = top.lerp(bottom, (v + 1) / (vertical_step + 1)) - face_size = math.sqrt(face.calc_area()) - depth = random.uniform(0.1, 1.5) * face_size - depth_short = depth * random.uniform(0.02, 0.15) - base_diameter = random.uniform(0.005, 0.05) - material_index = MATERIAL.HULL if random.random() > 0.5 else MATERIAL.HULL_DARK - - # Spire - num_segments = random.uniform(3, 6) - result = bmesh.ops.create_cone \ - ( - bm, - cap_ends = False, - cap_tris = False, - segments = num_segments, - diameter1 = 0, - diameter2 = base_diameter, - depth = depth, - matrix = get_face_matrix(face, pos + face.normal * depth * 0.5) - ) - for vert in result["verts"] : - for vert_face in vert.link_faces : - vert_face.material_index = material_index - #end for - #end for - - # Base - result = bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = num_segments, - diameter1 = base_diameter * random.uniform(1, 1.5), - diameter2 = base_diameter * random.uniform(1.5, 2), - depth = depth_short, - matrix = get_face_matrix(face, pos + face.normal * depth_short * 0.45) - ) - for vert in result["verts"] : - for vert_face in vert.link_faces : - vert_face.material_index = material_index - #end for - #end for - #end if random.random() > 0.9 - #end for v in range(vertical_step) - #end for h in range(horizontal_step) -#end add_surface_antenna_to_face - -def add_disc_to_face(bm, face) : - # Given a face, adds a glowing "landing pad" style disc. - if not face.is_valid : - return - #end if - face_width, face_height = get_face_width_and_height(face) - depth = 0.125 * min(face_width, face_height) - bmesh.ops.create_cone \ - ( - bm, - cap_ends = True, - cap_tris = False, - segments = 32, - diameter1 = depth * 3, - diameter2 = depth * 4, - depth=depth, - matrix = get_face_matrix(face, face.calc_center_bounds() + face.normal * depth * 0.5) - ) - result = bmesh.ops.create_cone \ - ( - bm, - cap_ends = False, - cap_tris = False, - segments = 32, - diameter1 = depth * 1.25, - diameter2 = depth * 2.25, - depth = 0.0, - matrix = get_face_matrix(face, face.calc_center_bounds() + face.normal * depth * 1.05) - ) - for vert in result["verts"] : - for face in vert.link_faces : - face.material_index = MATERIAL.GLOW_DISC - #end for - #end for -#end add_disc_to_face - -def create_materials() : +def create_materials(mat_random) : # Creates all our materials and returns them as a list. def define_tex_coords_common() : @@ -629,8 +236,8 @@ def define_tex_coords_common() : group_output = ctx.node("NodeGroupOutput", ctx.step_across(200)) tex_coords_common.outputs.new("NodeSocketVector", "Coords") # work around intermittent crash on following line - ctx.link(tex_coords.outputs["Object"], group_output.inputs[0]) - group_output.inputs[0].name = tex_coords_common.outputs[0].name = "Coords" + ctx.link(tex_coords.outputs["Generated"], group_output.inputs[0]) + group_output.inputs[0].name = tex_coords_common.outputs[0].name return tex_coords_common #end define_tex_coords_common @@ -714,9 +321,9 @@ def set_hull_mat_emissive(mat, color, strength) : ( hls_to_rgb ( - h = random.random(), - l = random.uniform(0.05, 0.5), - s = random.uniform(0, 0.25) + h = mat_random.random(), + l = mat_random.uniform(0.05, 0.5), + s = mat_random.uniform(0, 0.25) ) + (1,) @@ -748,9 +355,9 @@ def set_hull_mat_emissive(mat, color, strength) : ( hls_to_rgb ( - h = random.random(), - l = random.uniform(0.5, 1), - s = random.uniform(0, 0.5) + h = mat_random.random(), + l = mat_random.uniform(0.5, 1), + s = mat_random.uniform(0, 0.5) ) + (1,) @@ -785,7 +392,7 @@ def set_hull_mat_emissive(mat, color, strength) : ) # Choose a glow color for the exhaust + glow discs - glow_color = hls_to_rgb(h = random.random(), l = random.uniform(0.5, 1), s = 1) + (1,) + glow_color = hls_to_rgb(h = mat_random.random(), l = mat_random.uniform(0.5, 1), s = 1) + (1,) # Build the exhaust_burn texture set_hull_mat_emissive(materials[MATERIAL.EXHAUST_BURN], glow_color, 1.0) @@ -798,7 +405,8 @@ def set_hull_mat_emissive(mat, color, strength) : class parms_defaults : "define parameter defaults in a single place for reuse." - random_seed = "" + geom_ranseed = "" + mat_ranseed = "" num_hull_segments_min = 3 num_hull_segments_max = 6 create_asymmetry_segments = True @@ -816,27 +424,422 @@ def generate_spaceship(parms) : # Just uses global cube texture coordinates rather than generating UVs. # Takes an optional random seed value to generate a specific spaceship. # Allows overriding of some parameters that affect generation. - global random # for use by other routines in this module - random = Random() - if parms.random_seed : - random.seed(parms.random_seed) + geom_random = Random() + if parms.geom_ranseed != "" : + geom_random.seed(parms.geom_ranseed) #end if + def add_exhaust_to_face(bm, face) : + # Given a face, splits it into a uniform grid and extrudes each grid face + # out and back in again, making an exhaust shape. + if not face.is_valid : + return + #end if + + # The more square the face is, the more grid divisions it might have + num_cuts = geom_random.randint(1, int(4 - get_aspect_ratio(face))) + result = bmesh.ops.subdivide_edges \ + ( + bm, + edges = face.edges[:], + cuts = num_cuts, + fractal = 0.02, + use_grid_fill = True + ) + exhaust_length = geom_random.uniform(0.1, 0.2) + scale_outer = 1 / geom_random.uniform(1.3, 1.6) + scale_inner = 1 / geom_random.uniform(1.05, 1.1) + for face in result["geom"] : + if isinstance(face, bmesh.types.BMFace) : + if is_rear_face(face) : + face.material_index = MATERIAL.HULL_DARK + face = extrude_face(bm, face, exhaust_length) + scale_face(bm, face, scale_outer, scale_outer, scale_outer) + extruded_face_list = [] + face = extrude_face(bm, face, -exhaust_length * 0.9, extruded_face_list) + for extruded_face in extruded_face_list : + extruded_face.material_index = MATERIAL.EXHAUST_BURN + #end for + scale_face(bm, face, scale_inner, scale_inner, scale_inner) + #end if + #end if + #end for + #end add_exhaust_to_face + + def add_grid_to_face(bm, face) : + # Given a face, splits it up into a smaller uniform grid and extrudes each grid cell. + if not face.is_valid : + return + #end if + result = bmesh.ops.subdivide_edges \ + ( + bm, + edges = face.edges[:], + cuts = geom_random.randint(2, 4), + fractal = 0.02, + use_grid_fill = True, + use_single_edge = False + ) + grid_length = geom_random.uniform(0.025, 0.15) + scale = 0.8 + for face in result["geom"] : + if isinstance(face, bmesh.types.BMFace) : + material_index = MATERIAL.HULL_LIGHTS if geom_random.random() > 0.5 else MATERIAL.HULL + extruded_face_list = [] + face = extrude_face(bm, face, grid_length, extruded_face_list) + for extruded_face in extruded_face_list : + if abs(face.normal.z) < 0.707 : # side face + extruded_face.material_index = material_index + #end if + #end for + scale_face(bm, face, scale, scale, scale) + #end if + #end for + #end add_grid_to_face + + def add_cylinders_to_face(bm, face) : + # Given a face, adds some cylinders along it in a grid pattern. + if not face.is_valid or len(face.verts[:]) < 4 : + return + #end if + horizontal_step = geom_random.randint(1, 3) + vertical_step = geom_random.randint(1, 3) + num_segments = geom_random.randint(6, 12) + face_width, face_height = get_face_width_and_height(face) + cylinder_depth = \ + ( + 1.3 + * + min + ( + face_width / (horizontal_step + 2), + face_height / (vertical_step + 2) + ) + ) + cylinder_size = cylinder_depth * 0.5 + for h in range(horizontal_step) : + top = face.verts[0].co.lerp \ + ( + face.verts[1].co, + (h + 1) / (horizontal_step + 1) + ) + bottom = face.verts[3].co.lerp \ + ( + face.verts[2].co, + (h + 1) / (horizontal_step + 1) + ) + for v in range(vertical_step) : + pos = top.lerp(bottom, (v + 1) / (vertical_step + 1)) + cylinder_matrix = \ + ( + get_face_matrix(face, pos) + @ + Matrix.Rotation(90 * deg, 3, "X").to_4x4() + ) + bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = num_segments, + diameter1 = cylinder_size, + diameter2 = cylinder_size, + depth = cylinder_depth, + matrix = cylinder_matrix + ) + #end for + #end for + #end add_cylinders_to_face + + def add_weapons_to_face(bm, face) : + # Given a face, adds some weapon turrets to it in a grid pattern. + # Each turret will have a random orientation. + if not face.is_valid or len(face.verts[:]) < 4 : + return + #end if + horizontal_step = geom_random.randint(1, 2) + vertical_step = geom_random.randint(1, 2) + num_segments = 16 + face_width, face_height = get_face_width_and_height(face) + weapon_size = \ + ( + 0.5 + * + min + ( + face_width / (horizontal_step + 2), + face_height / (vertical_step + 2) + ) + ) + weapon_depth = weapon_size * 0.2 + for h in range(horizontal_step) : + top = face.verts[0].co.lerp \ + ( + face.verts[1].co, + (h + 1) / (horizontal_step + 1) + ) + bottom = face.verts[3].co.lerp \ + ( + face.verts[2].co, + (h + 1) / (horizontal_step + 1) + ) + for v in range(vertical_step) : + pos = top.lerp(bottom, (v + 1) / (vertical_step + 1)) + face_matrix = \ + ( + get_face_matrix(face, pos + face.normal * weapon_depth * 0.5) + @ + Matrix.Rotation(geom_random.uniform(0, 90) * deg, 3, "Z").to_4x4() + ) + + # Turret foundation + bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = num_segments, + diameter1 = weapon_size * 0.9, + diameter2 = weapon_size, + depth = weapon_depth, + matrix = face_matrix + ) + # Turret left guard + bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = num_segments, + diameter1 = weapon_size * 0.6, + diameter2 = weapon_size * 0.5, + depth = weapon_depth * 2, + matrix = + face_matrix + @ + Matrix.Rotation(90 * deg, 3, "Y").to_4x4() + @ + Matrix.Translation(Vector((0, 0, weapon_size * 0.6))).to_4x4() + ) + # Turret right guard + bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = num_segments, + diameter1 = weapon_size * 0.5, + diameter2 = weapon_size * 0.6, + depth = weapon_depth * 2, + matrix = + face_matrix + @ + Matrix.Rotation(90 * deg, 3, "Y").to_4x4() + @ + Matrix.Translation(Vector((0, 0, weapon_size * -0.6))).to_4x4() + ) + # Turret housing + upward_angle = geom_random.uniform(0, 45) * deg + turret_house_mat = \ + ( + face_matrix + @ + Matrix.Rotation(upward_angle, 3, "X").to_4x4() + @ + Matrix.Translation(Vector((0, weapon_size * -0.4, 0))).to_4x4() + ) + bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = 8, + diameter1 = weapon_size * 0.4, + diameter2 = weapon_size * 0.4, + depth = weapon_depth * 5, + matrix = turret_house_mat + ) + # Turret barrels L + R + bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = 8, + diameter1 = weapon_size * 0.1, + diameter2 = weapon_size * 0.1, + depth = weapon_depth * 6, + matrix = + turret_house_mat + @ + Matrix.Translation(Vector((weapon_size * 0.2, 0, -weapon_size))).to_4x4() + ) + bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = 8, + diameter1 = weapon_size * 0.1, + diameter2 = weapon_size * 0.1, + depth = weapon_depth * 6, + matrix = + turret_house_mat + @ + Matrix.Translation(Vector((weapon_size * -0.2, 0, -weapon_size))).to_4x4() + ) + #end for v in range(vertical_step) + #end for h in range(horizontal_step) + #end add_weapons_to_face + + def add_sphere_to_face(bm, face) : + # Given a face, adds a sphere on the surface, partially inset. + if not face.is_valid : + return + #end if + face_width, face_height = get_face_width_and_height(face) + sphere_size = geom_random.uniform(0.4, 1.0) * min(face_width, face_height) + sphere_matrix = get_face_matrix \ + ( + face, + face.calc_center_bounds() - face.normal * geom_random.uniform(0, sphere_size * 0.5) + ) + result = bmesh.ops.create_icosphere \ + ( + bm, + subdivisions = 3, + diameter = sphere_size, + matrix = sphere_matrix + ) + for vert in result["verts"] : + for face in vert.link_faces : + face.material_index = MATERIAL.HULL + #end for + #end for + #end add_sphere_to_face + + def add_surface_antenna_to_face(bm, face) : + # Given a face, adds some pointy intimidating antennas. + if not face.is_valid or len(face.verts[:]) < 4 : + return + #end if + horizontal_step = geom_random.randint(4, 10) + vertical_step = geom_random.randint(4, 10) + for h in range(horizontal_step) : + top = face.verts[0].co.lerp \ + ( + face.verts[1].co, + (h + 1) / (horizontal_step + 1) + ) + bottom = face.verts[3].co.lerp \ + ( + face.verts[2].co, + (h + 1) / (horizontal_step + 1) + ) + for v in range(vertical_step) : + if geom_random.random() > 0.9 : + pos = top.lerp(bottom, (v + 1) / (vertical_step + 1)) + face_size = math.sqrt(face.calc_area()) + depth = geom_random.uniform(0.1, 1.5) * face_size + depth_short = depth * geom_random.uniform(0.02, 0.15) + base_diameter = geom_random.uniform(0.005, 0.05) + material_index = (MATERIAL.HULL_DARK, MATERIAL.HULL)[geom_random.random() > 0.5] + + # Spire + num_segments = geom_random.uniform(3, 6) + result = bmesh.ops.create_cone \ + ( + bm, + cap_ends = False, + cap_tris = False, + segments = num_segments, + diameter1 = 0, + diameter2 = base_diameter, + depth = depth, + matrix = get_face_matrix(face, pos + face.normal * depth * 0.5) + ) + for vert in result["verts"] : + for vert_face in vert.link_faces : + vert_face.material_index = material_index + #end for + #end for + + # Base + result = bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = num_segments, + diameter1 = base_diameter * geom_random.uniform(1, 1.5), + diameter2 = base_diameter * geom_random.uniform(1.5, 2), + depth = depth_short, + matrix = get_face_matrix(face, pos + face.normal * depth_short * 0.45) + ) + for vert in result["verts"] : + for vert_face in vert.link_faces : + vert_face.material_index = material_index + #end for + #end for + #end if geom_random.random() > 0.9 + #end for v in range(vertical_step) + #end for h in range(horizontal_step) + #end add_surface_antenna_to_face + + def add_disc_to_face(bm, face) : + # Given a face, adds a glowing "landing pad" style disc. + if not face.is_valid : + return + #end if + face_width, face_height = get_face_width_and_height(face) + depth = 0.125 * min(face_width, face_height) + bmesh.ops.create_cone \ + ( + bm, + cap_ends = True, + cap_tris = False, + segments = 32, + diameter1 = depth * 3, + diameter2 = depth * 4, + depth=depth, + matrix = get_face_matrix(face, face.calc_center_bounds() + face.normal * depth * 0.5) + ) + result = bmesh.ops.create_cone \ + ( + bm, + cap_ends = False, + cap_tris = False, + segments = 32, + diameter1 = depth * 1.25, + diameter2 = depth * 2.25, + depth = 0.0, + matrix = get_face_matrix(face, face.calc_center_bounds() + face.normal * depth * 1.05) + ) + for vert in result["verts"] : + for face in vert.link_faces : + face.material_index = MATERIAL.GLOW_DISC + #end for + #end for + #end add_disc_to_face + +#begin generate_spaceship # Let's start with a unit BMesh cube scaled randomly bm = bmesh.new() bmesh.ops.create_cube(bm, size = 1) scale_vector = Vector \ - ( - (random.uniform(0.75, 2.0), random.uniform(0.75, 2.0), random.uniform(0.75, 2.0)) - ) + (( + geom_random.uniform(0.75, 2.0), + geom_random.uniform(0.75, 2.0), + geom_random.uniform(0.75, 2.0), + )) bmesh.ops.scale(bm, vec = scale_vector, verts = bm.verts) # Extrude out the hull along the X axis, adding some semi-random perturbations for face in bm.faces[:] : if abs(face.normal.x) > 0.5 : - hull_segment_length = random.uniform(0.3, 1) + hull_segment_length = geom_random.uniform(0.3, 1) if parms.num_hull_segments_max >= parms.num_hull_segments_min : - num_hull_segments = random.randrange \ + num_hull_segments = geom_random.randrange \ ( parms.num_hull_segments_min, parms.num_hull_segments_max + 1 @@ -847,11 +850,11 @@ def generate_spaceship(parms) : hull_segment_range = range(num_hull_segments) for i in hull_segment_range : is_last_hull_segment = i == hull_segment_range[-1] - val = random.random() + val = geom_random.random() if val > 0.1 : # Most of the time, extrude out the face with some random deviations face = extrude_face(bm, face, hull_segment_length) - if random.random() > 0.75 : + if geom_random.random() > 0.75 : face = extrude_face \ ( bm, @@ -861,22 +864,22 @@ def generate_spaceship(parms) : #end if # Maybe apply some scaling - if random.random() > 0.5 : - sy = random.uniform(1.2, 1.5) - sz = random.uniform(1.2, 1.5) - if is_last_hull_segment or random.random() > 0.5 : + if geom_random.random() > 0.5 : + sy = geom_random.uniform(1.2, 1.5) + sz = geom_random.uniform(1.2, 1.5) + if is_last_hull_segment or geom_random.random() > 0.5 : sy = 1 / sy sz = 1 / sz scale_face(bm, face, 1, sy, sz) #end if # Maybe apply some sideways translation - if random.random() > 0.5 : + if geom_random.random() > 0.5 : sideways_translation = Vector \ ( - (0, 0, random.uniform(0.1, 0.4) * scale_vector.z * hull_segment_length) + (0, 0, geom_random.uniform(0.1, 0.4) * scale_vector.z * hull_segment_length) ) - if random.random() > 0.5 : + if geom_random.random() > 0.5 : sideways_translation = -sideways_translation #end if bmesh.ops.translate \ @@ -888,9 +891,9 @@ def generate_spaceship(parms) : #end if # Maybe add some rotation around Y axis - if random.random() > 0.5 : + if geom_random.random() > 0.5 : angle = 5 * deg - if random.random() > 0.5 : + if geom_random.random() > 0.5 : angle = -angle #end if bmesh.ops.rotate \ @@ -903,13 +906,13 @@ def generate_spaceship(parms) : #end if else : # val <= 0.1 # Rarely, create a ribbed section of the hull - rib_scale = random.uniform(0.75, 0.95) + rib_scale = geom_random.uniform(0.75, 0.95) face = ribbed_extrude_face \ ( bm, face, translate_forwards = hull_segment_length, - num_ribs = random.randint(2, 4), + num_ribs = geom_random.randint(2, 4), rib_scale = rib_scale ) #end if val > 0.1 @@ -928,11 +931,11 @@ def generate_spaceship(parms) : get_aspect_ratio(face) <= 4 # Skip any long thin faces as it'll probably look stupid and - random.random() > 0.85 + geom_random.random() > 0.85 ) : - hull_piece_length = random.uniform(0.1, 0.4) + hull_piece_length = geom_random.uniform(0.1, 0.4) for i in \ - range(random.randrange + range(geom_random.randrange ( parms.num_asymmetry_segments_min, parms.num_asymmetry_segments_max + 1 @@ -940,8 +943,8 @@ def generate_spaceship(parms) : : face = extrude_face(bm, face, hull_piece_length) # Maybe apply some scaling - if random.random() > 0.25 : - s = 1 / random.uniform(1.1, 1.5) + if geom_random.random() > 0.25 : + s = 1 / geom_random.uniform(1.1, 1.5) scale_face(bm, face, s, s, s) #end if #end for @@ -965,7 +968,7 @@ def generate_spaceship(parms) : #end if # Spin the wheel! Let's categorize + assign some materials - val = random.random() + val = geom_random.random() if is_rear_face(face) : if not engine_faces or val > 0.75 : engine_faces.append(face) @@ -1041,11 +1044,11 @@ def generate_spaceship(parms) : #end if parms.create_face_detail # Apply horizontal symmetry sometimes - if parms.allow_horizontal_symmetry and random.random() > 0.5 : + if parms.allow_horizontal_symmetry and geom_random.random() > 0.5 : bmesh.ops.symmetrize(bm, input = bm.verts[:] + bm.edges[:] + bm.faces[:], direction = "Y") #end if # Apply vertical symmetry sometimes - this can cause spaceship "islands", so disabled by default - if parms.allow_vertical_symmetry and random.random() > 0.5 : + if parms.allow_vertical_symmetry and geom_random.random() > 0.5 : bmesh.ops.symmetrize(bm, input = bm.verts[:] + bm.edges[:] + bm.faces[:], direction = "Z") #end if @@ -1067,10 +1070,10 @@ def generate_spaceship(parms) : ob = bpy.context.object ob.location = bpy.context.scene.cursor.location.copy() - # Add a fairly broad bevel modifier to angularize shape if parms.add_bevel_modifier : + # Add a fairly broad bevel modifier to angularize shape bevel_modifier = ob.modifiers.new("Bevel", "BEVEL") - bevel_modifier.width = random.uniform(5, 20) + bevel_modifier.width = geom_random.uniform(5, 20) bevel_modifier.offset_type = "PERCENT" bevel_modifier.segments = 2 bevel_modifier.profile = 0.25 @@ -1079,7 +1082,11 @@ def generate_spaceship(parms) : # Add materials to the spaceship me = ob.data - materials = create_materials() + mat_random = Random() + if parms.mat_ranseed != "" : + mat_random.seed(parms.mat_ranseed) + #end if + materials = create_materials(mat_random) for mat in materials : if parms.assign_materials : me.materials.append(mat) @@ -1120,7 +1127,9 @@ def reset_scene() : if generate_single_spaceship : # Reset the scene, generate a single spaceship and focus on it reset_scene() - parms_defaults.random_seed = "" # add anything here to generate the same spaceship + parms_defaults.geom_ranseed = "" + parms_defaults.mat_ranseed = "" + # add anything here to generate the same spaceship obj = generate_spaceship(parms_defaults) # View the selected object in all views