def discard_outside_of_view(self, view): # Assume that the mesh object's bounding box is fully contained in the # view projection, and test this assumption. bounding_box = mesh_object.bound_box bounding_box_contained_in_projection = True # Transform the coordinates of each vertex of the bounding box from # object space to clip space. As soon as any single vertex is # found to be outside of the view projection, duly indicate, and exit # the loop. projection_matrix = view.projection_matrix for vertex in bounding_box: co = Vector(vertex) co.resize(4) co.w = 1 co.xyzw = projection_matrix * co w = co.w # Determine if the coordinates are within the view projection. if abs(co.x) > w or\ abs(co.y) > w or\ abs(co.z) > w: bounding_box_contained_in_projection = False break # If the bounding box is not entirely contained within the view # projection then some vertices may exist outside of the view # projection. if not bounding_box_contained_in_projection: clip_space_map = self.coordinate_map # Retain each clip space vertex that is inside of the view # projection. indices = self.indices self.indices = [ index for index in indices if abs(clip_space_map[index].x) < clip_space_map[index].w and\ abs(clip_space_map[index].y) < clip_space_map[index].w and\ abs(clip_space_map[index].z) < clip_space_map[index].w ]
def resize_primary_brush(self, radius): context = bpy.context primary_brush = self.primary_brush region_height = context.region.height region_width = context.region.width # Determine the world space radius of the primary brush necessary to # project a circle onto the view plane with the specified region space # radius. # Determine the z-depth of the primary brush's center in normalized # device coordinates. projection_matrix = context.region_data.perspective_matrix co = primary_brush.center.copy() co.resize(4) co.w = 1 co.xyzw = projection_matrix * co w = co.w co.xyz /= w NDC_z_depth = co.z # Determine the region space coordinates of the primary brush's center. region_x = (co.x + 1) * region_width / 2 region_y = (co.y + 1) * region_height / 2 # Determine the NDC coordinates of a point on the edge of the # circle that should result from projecting the brush onto the view # plane. co = Vector((region_x, region_y)) + Vector((radius, 0)) co.x = co.x * 2 / region_width - 1 co.y = co.y * 2 / region_height - 1 co.resize(3) co.z = NDC_z_depth # Calculate the world space radius of the primary brush. co.resize(4) co.w = 1 co.xyzw = projection_matrix.inverted() * co w = co.w co.resize(3) co.xyz /= w primary_brush.radius = (co - primary_brush.center).length
def stroke(self, region_x, region_y): props = self.props brushes = props.brushes derived_brushes = brushes.derived_brushes primary_brush = brushes.primary_brush active_object = bpy.context.active_object indices_affected_by_stroke = self.indices_affected_by_stroke vertices = active_object.data.vertices # The primary brush must be on the mesh in order to execute a stroke. if not primary_brush.is_on_mesh: return # Only proceed if at least one vertex is affected by the stroke. if not indices_affected_by_stroke: return # Determine the terminal position of the stroke in world space # coordinates. inverted_perspective_matrix =\ bpy.context.region_data.perspective_matrix.inverted() co = Vector((region_x, region_y)) co.x = co.x * 2 / bpy.context.region.width - 1 co.y = co.y * 2 / bpy.context.region.height - 1 co.resize(4) co.z = self.stroke_z_depth co.w = 1 co = inverted_perspective_matrix @ co w = co.w co.resize(3) co /= w stroke_terminal_co = co # Apply the stroke to each brush. inverted_model_matrix = active_object.matrix_world.inverted() for brush in [primary_brush] + brushes.derived_brushes: # Determine the displaced position of the brush caused by the # stroke. displaced_brush_center =\ brush.transformation_matrix @ stroke_terminal_co # Calculate an object space displacement vector between the brush's # original position and its displaced position. object_space_displacement = ( inverted_model_matrix @ displaced_brush_center - inverted_model_matrix @ brush.center ) # Move the brush to its displaced position. brush.center = displaced_brush_center # Add the displacement vector to the coordinates of the brush's # affected vertices, taking into account brush falloff. falloff_map = brush.falloff_map for index in brush.indices: vertices[index].co +=\ falloff_map[index] * object_space_displacement # Constrain the vertices to the specified target, if necessary. if self.target: surface_constraint_props = self.surface_constraint_props apply_shrinkwrap( offset = self.offset, target = self.target, wrap_method = surface_constraint_props.wrap_method_map[ surface_constraint_props.direction ], affected_indices = list(indices_affected_by_stroke) ) # Update the octree's coordinate map. model_matrix = active_object.matrix_world world_space_submap = { index : model_matrix @ vertices[index].co for index in indices_affected_by_stroke } self.props.octree.coordinate_map.update(world_space_submap)
def stroke(self, region_x, region_y): props = self.props brushes = props.brushes derived_brushes = brushes.derived_brushes primary_brush = brushes.primary_brush active_object = bpy.context.active_object indices_affected_by_stroke = self.indices_affected_by_stroke vertices = active_object.data.vertices # The primary brush must be on the mesh in order to execute a stroke. if not primary_brush.is_on_mesh: return # Only proceed if at least one vertex is affected by the stroke. if not indices_affected_by_stroke: return # Determine the terminal position of the stroke in world space # coordinates. inverted_perspective_matrix =\ bpy.context.region_data.perspective_matrix.inverted() co = Vector((region_x, region_y)) co.x = co.x * 2 / bpy.context.region.width - 1 co.y = co.y * 2 / bpy.context.region.height - 1 co.resize(4) co.z = self.stroke_z_depth co.w = 1 co = inverted_perspective_matrix * co w = co.w co.resize(3) co /= w stroke_terminal_co = co # Apply the stroke to each brush. inverted_model_matrix = active_object.matrix_world.inverted() for brush in [primary_brush] + brushes.derived_brushes: # Determine the displaced position of the brush caused by the # stroke. displaced_brush_center =\ brush.transformation_matrix * stroke_terminal_co # Calculate an object space displacement vector between the brush's # original position and its displaced position. object_space_displacement = ( inverted_model_matrix * displaced_brush_center - inverted_model_matrix * brush.center ) # Move the brush to its displaced position. brush.center = displaced_brush_center # Add the displacement vector to the coordinates of the brush's # affected vertices, taking into account brush falloff. falloff_map = brush.falloff_map for index in brush.indices: vertices[index].co +=\ falloff_map[index] * object_space_displacement # Constrain the vertices to the specified target, if necessary. if self.target: surface_constraint_props = self.surface_constraint_props apply_shrinkwrap( offset = self.offset, target = self.target, wrap_method = surface_constraint_props.wrap_method_map[ surface_constraint_props.direction ], affected_indices = list(indices_affected_by_stroke) ) # Update the octree's coordinate map. model_matrix = active_object.matrix_world world_space_submap = { index : model_matrix * vertices[index].co for index in indices_affected_by_stroke } self.props.octree.coordinate_map.update(world_space_submap)