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