def from_points(cls, point, point_xaxis, point_xyplane): """Constructs a frame from 3 points. Parameters ---------- point : point The origin of the frame. point_xaxis : point A point on the x-axis of the frame. point_xyplane : point A point within the xy-plane of the frame. Returns ------- :class:`compas.geometry.Frame` The constructed frame. Examples -------- >>> frame = Frame.from_points([0, 0, 0], [1, 0, 0], [0, 1, 0]) >>> frame.point Point(0.000, 0.000, 0.000) >>> frame.xaxis Vector(1.000, 0.000, 0.000) >>> frame.yaxis Vector(0.000, 1.000, 0.000) """ xaxis = subtract_vectors(point_xaxis, point) xyvec = subtract_vectors(point_xyplane, point) yaxis = cross_vectors(cross_vectors(xaxis, xyvec), xaxis) return cls(point, xaxis, yaxis)
def barycentric_coordinates(point, triangle): """Compute the barycentric coordinates of a point wrt to a triangle. Parameters ---------- point: list Point location. triangle: (point, point, point) A triangle defined by 3 points. Returns ------- list The barycentric coordinates of the point. """ a, b, c = triangle v0 = subtract_vectors(b, a) v1 = subtract_vectors(c, a) v2 = subtract_vectors(point, a) d00 = dot_vectors(v0, v0) d01 = dot_vectors(v0, v1) d11 = dot_vectors(v1, v1) d20 = dot_vectors(v2, v0) d21 = dot_vectors(v2, v1) D = d00 * d11 - d01 * d01 v = (d11 * d20 - d01 * d21) / D w = (d00 * d21 - d01 * d20) / D u = 1.0 - v - w return u, v, w
def intersection_line_triangle(line, triangle, tol=1e-6): """Computes the intersection point of a line (ray) and a triangle based on the Moeller Trumbore intersection algorithm Parameters ---------- line : [point, point] | :class:`compas.geometry.Line` Two points defining the line. triangle : [point, point, point] XYZ coordinates of the triangle corners. tol : float, optional A tolerance for membership verification. Returns ------- [float, float, float] | None The intersection point between the line and the triangle, or None if the line and the plane are parallel. """ a, b, c = triangle ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) n = cross_vectors(ab, ac) plane = a, n x = intersection_line_plane(line, plane, tol=tol) if x: if is_point_in_triangle(x, triangle): return x
def _cross_edges(edge1, edge2): a, b = edge1 c, d = edge2 edge1_vec = normalize_vector(subtract_vectors(b, a)) edge2_vec = normalize_vector(subtract_vectors(d, c)) cross = cross_vectors(edge1_vec, edge2_vec) return cross
def polygon_normal_oriented(polygon, unitized=True): """Compute the oriented normal of any closed polygon (can be convex, concave or complex). Parameters ---------- polygon : sequence The XYZ coordinates of the vertices/corners of the polygon. The vertices are assumed to be in order. The polygon is assumed to be closed: the first and last vertex in the sequence should not be the same. Returns ------- list The weighted or unitized normal vector of the polygon. """ p = len(polygon) assert p > 2, "At least three points required" w = centroid_points(polygon) normal_sum = (0, 0, 0) for i in range(-1, len(polygon) - 1): u = polygon[i] v = polygon[i + 1] uv = subtract_vectors(v, u) vw = subtract_vectors(w, v) normal = scale_vector(cross_vectors(uv, vw), 0.5) normal_sum = add_vectors(normal_sum, normal) if not unitized: return normal_sum return normalize_vector(normal_sum)
def draw_lines(lines, layer=None, centroid=True): objects = [0] * len(lines) for index, data in enumerate(lines): sp = data['start'] ep = data['end'] mp = centroid_points([sp, ep]) if centroid else [0, 0, 0] name = data.get('name', 'line') curve = bpy.data.curves.new(name, type='CURVE') curve.dimensions = '3D' spline = curve.splines.new('NURBS') spline.points.add(2) spline.points[0].co = list(subtract_vectors(sp, mp)) + [1] spline.points[1].co = list(subtract_vectors(ep, mp)) + [1] spline.order_u = 1 obj = bpy.data.objects.new(name, curve) obj.location = mp obj.data.fill_mode = 'FULL' obj.data.bevel_depth = data.get('width', 0.05) obj.data.bevel_resolution = 0 obj.data.resolution_u = 20 rgb = data.get('color') or [1.0, 1.0, 1.0] set_object_color(obj, rgb) objects[index] = obj link_objects(objects, layer=layer) return objects
def polygon_area_footprint(polygon): """Compute the non-oriented area of a polygon (can be convex or concave). Parameters ---------- polygon : list of lists A list of polygon point coordinates. Returns ------- float The non-oriented area of the polygon. """ area = 0 w = centroid_points(polygon) for i in range(-1, len(polygon) - 1): u = polygon[i] v = polygon[i + 1] uv = subtract_vectors(v, u) vw = subtract_vectors(w, v) normal = scale_vector(cross_vectors(uv, vw), 0.5) area += length_vector(normal) return area
def GetConvexPolygonArea(polygon): center = [0.0,0.0,0.0] for p in polygon: center=[center[0]+p[0],center[1]+p[1],center[2]+p[2]] n = len(polygon.points) center=[center[0]/n,center[1]/n,center[2]/n] areaSum = 0.0 for i in range(0, n): p0 = polygon[i] p1 = polygon[(i+1)%n] p2 = center u = subtract_vectors(p2,p1) v = subtract_vectors(p2,p0) area = length_vector(cross_vectors(u,v))*0.5 areaSum+=area print(areaSum) return areaSum
def separate(self, dst, boids, mxf=0.02, mxs=0.02): count = 0 sum_vec = [0, 0, 0] tol = 1e-6 for boid in boids: d = cg.distance_point_point(self.pos, boid.pos) print('distance is {}'.format(d)) if d > tol and d < dst: dif = subtract_vectors(self.pos, boid.pos) dif = cg.normalize_vector(cg.scale_vector(dif, 1/d)) sum_vec = cg.add_vectors(dif, sum_vec) count += 1 if count > 0: sum_vec = cg.normalize_vector(cg.scale_vector(sum_vec, 1/count) ) sum_vec = cg.scale_vector(sum_vec, mxs) # Reynold's steering formula steer = subtract_vectors(sum_vec, self.vel) steer = ut.limit_vector(steer, mxf) return steer return sum_vec
def draw_lines(lines: List[Dict], collection: Union[Text, bpy.types.Collection] = None, centroid: bool = True) -> List[bpy.types.Object]: """Draw line objects.""" L = len(lines) N = len(str(L)) objects = [0] * L for index, data in enumerate(lines): sp = data['start'] ep = data['end'] origin = centroid_points([sp, ep]) if centroid else [0, 0, 0] name = data.get('name', f'L.{index:0{N}d}') curve = bpy.data.curves.new(name, type='CURVE') curve.dimensions = '3D' spline = curve.splines.new('POLY') spline.points.add(1) spline.points[0].co = subtract_vectors(sp, origin) + [1.0] spline.points[1].co = subtract_vectors(ep, origin) + [1.0] spline.order_u = 1 obj = bpy.data.objects.new(name, curve) obj.location = origin obj.data.fill_mode = 'FULL' obj.data.bevel_depth = data.get('width', 0.05) obj.data.bevel_resolution = 0 obj.data.resolution_u = 20 rgb = data.get('color', [1.0, 1.0, 1.0]) _set_object_color(obj, rgb) objects[index] = obj _link_objects(objects, collection) return objects
def draw_line(start=[0, 0, 0], end=[1, 1, 1], width=0.05, centroid=True, name='line', color=[1, 1, 1], layer=None): mp = centroid_points([start, end]) if centroid else [0, 0, 0] curve = bpy.data.curves.new(name, type='CURVE') curve.dimensions = '3D' object = bpy.data.objects.new(name, curve) object.location = mp spline = curve.splines.new('NURBS') spline.points.add(2) spline.points[0].co = list(subtract_vectors(start, mp)) + [1] spline.points[1].co = list(subtract_vectors(end, mp)) + [1] spline.order_u = 1 object.data.fill_mode = 'FULL' object.data.bevel_depth = width object.data.bevel_resolution = 0 object.data.resolution_u = 20 object.color = color + [1] bpy.context.collection.objects.link(object) if layer: set_objects_layer(objects=[object], layer=layer) return object
def DrawForeground(self, e): draw_dot = e.Display.DrawDot draw_arrows = e.Display.DrawArrows a = self.mouse.p1 b = self.mouse.p2 ab = subtract_vectors(b, a) Lab = length_vector(ab) if not Lab: return for index, vertex in enumerate(self.form_vertex_xyz): c = self.form_vertex_xyz[vertex] D = length_vector( cross_vectors(subtract_vectors(a, c), subtract_vectors(b, c))) if D / Lab < self.tol: point = Point3d(*c) draw_dot(point, str(index), self.dotcolor, self.textcolor) lines = List[Line](len(self.form_vertex_edges[vertex])) for u, v in self.form_vertex_edges[vertex]: lines.Add( Line(Point3d(*self.form_vertex_xyz[u]), Point3d(*self.form_vertex_xyz[v]))) draw_arrows(lines, self.linecolor) lines = List[Line](len(self.force_face_edges[vertex])) for u, v in self.force_face_edges[vertex]: lines.Add( Line(Point3d(*self.force_vertex_xyz[u]), Point3d(*self.force_vertex_xyz[v]))) draw_arrows(lines, self.linecolor) break
def project_point_line(point, line): """Project a point onto a line. Parameters ---------- point : list of float XYZ coordinates of the point. line : tuple Two points defining the projection line. Returns ------- list XYZ coordinates of the projected point. Notes ----- For more info, see [1]_. References ---------- .. [1] Wiki Books. *Linear Algebra/Orthogonal Projection Onto a Line*. Available at: https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line. """ a, b = line ab = subtract_vectors(b, a) ap = subtract_vectors(point, a) c = vector_component(ap, ab) return add_vectors(a, c)
def face_normal(self, fkey, unitized=True): points = self.face_coordinates(fkey) normal = polygon_normal_oriented(points, unitized) if length_vector(normal) == 0: uv = subtract_vectors(points[1], points[0]) vw = subtract_vectors(points[2], points[1]) normal = normalize_vector(cross_vectors(uv, vw)) return normal
def halfface_oriented_normal(self, hfkey, unitized=True): vertices = self.halfface_vertices(hfkey) points = [self.vertex_coordinates(vkey) for vkey in vertices] normal = polygon_normal_oriented(points, unitized) if length_vector(normal) == 0: uv = subtract_vectors(points[1], points[0]) vw = subtract_vectors(points[2], points[1]) normal = normalize_vector(cross_vectors(uv, vw)) return normal
def DrawForeground(self, e): p1 = self.mouse.p1 p2 = self.mouse.p2 v12 = subtract_vectors(p2, p1) l12 = length_vector(v12) # force diagram for ckey in self.volmesh.cell: p0 = self.volmesh.cell_center(ckey) dual_p0 = self.network.vertex_coordinates(ckey) v01 = subtract_vectors(p1, p0) v02 = subtract_vectors(p2, p0) l = length_vector(cross_vectors(v01, v02)) if l12 == 0.0 or (l / l12) < self.tol: hf_colors = {} nbr_vkeys = self.network.vertex_neighbours(ckey) for index, nbr_vkey in enumerate(nbr_vkeys): value = float(index) / (len(nbr_vkeys) - 1) print('boo', value) color = i_to_rgb(value) color = System.Drawing.Color.FromArgb(*color) nbr_xyz = self.network.vertex_coordinates(nbr_vkey) e.Display.DrawLine(Point3d(*dual_p0), Point3d(*nbr_xyz), color, 4) hfkey = self.volmesh.cell_pair_halffaces(ckey, nbr_vkey)[0] hf_colors[hfkey] = color for hfkey in self.volmesh.cell_halffaces(ckey): vkeys = self.volmesh.halfface_vertices(hfkey) face_coordinates = [ self.volmesh.vertex_coordinates(vkey) for vkey in vkeys ] face_coordinates.append(face_coordinates[0]) polygon_xyz = [Point3d(*xyz) for xyz in face_coordinates] e.Display.DrawDot(Point3d(*p0), str(ckey), self.dotcolor, self.textcolor) e.Display.DrawPolyline(polygon_xyz, self.edgecolor, 2) e.Display.DrawDot(Point3d(*dual_p0), str(ckey), self.dotcolor, self.textcolor) if hfkey in hf_colors: hf_color = hf_colors[hfkey] e.Display.DrawPolygon(polygon_xyz, hf_color, filled=True) break
def boundary_triangulation(outer_boundary, inner_boundaries, polyline_features = [], point_features = [], src='numpy_rpc'): """Generate Delaunay triangulation between a planar outer boundary and planar inner boundaries. All vertices lie the boundaries. Parameters ---------- outer_boundary : list Planar outer boundary as list of vertex coordinates. inner_boundaries : list List of planar inner boundaries as lists of vertex coordinates. polyline_features : list List of planar polyline_features as lists of vertex coordinates. point_features : list List of planar point_features as lists of vertex coordinates. src : str Source of Delaunay triangulation. Default is NumPy via RPC. Returns ------- delaunay_mesh : Mesh The Delaunay mesh. """ # generate planar Delaunay triangulation vertices = [pt for boundary in [outer_boundary] + inner_boundaries + polyline_features for pt in boundary] + point_features if src == 'numpy_rpc': faces = delaunay_numpy_rpc(vertices) elif src == 'numpy': faces = delaunay_numpy(vertices) else: delaunay_compas(vertices) delaunay_mesh = Mesh.from_vertices_and_faces(vertices, faces) # delete false faces with aligned vertices for fkey in list(delaunay_mesh.faces()): a, b, c = [delaunay_mesh.vertex_coordinates(vkey) for vkey in delaunay_mesh.face_vertices(fkey)] ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) if length_vector(cross_vectors(ab, ac)) == 0: delaunay_mesh.delete_face(fkey) # delete faces outisde the borders for fkey in list(delaunay_mesh.faces()): centre = trimesh_face_circle(delaunay_mesh, fkey)[0] if not is_point_in_polygon_xy(centre, outer_boundary) or any([is_point_in_polygon_xy(centre, inner_boundary) for inner_boundary in inner_boundaries]): delaunay_mesh.delete_face(fkey) # topological cut along the feature polylines through unwelding vertex_map = {geometric_key(delaunay_mesh.vertex_coordinates(vkey)): vkey for vkey in delaunay_mesh.vertices()} edges = [edge for polyline in polyline_features for edge in pairwise([vertex_map[geometric_key(point)] for point in polyline])] mesh_unweld_edges(delaunay_mesh, edges) return delaunay_mesh
def from_bounding_box(cls, bbox): a = bbox[0] b = bbox[1] d = bbox[3] e = bbox[4] xaxis = Vector(*subtract_vectors(d, a)) yaxis = Vector(*subtract_vectors(b, a)) zaxis = Vector(*subtract_vectors(e, a)) xsize = xaxis.length ysize = yaxis.length zsize = zaxis.length frame = Frame(a, xaxis, yaxis) return cls(frame, xsize, ysize, zsize)
def branches_splitting_flipped_faces(self): """Add new branches to fix the problem of polyline patches that would form flipped faces in the decomposition mesh. Returns ------- new_branches : list List of polylines as list of point XYZ-coordinates. """ new_branches = [] centre_to_fkey = { geometric_key(trimesh_face_circle(self, fkey)[0]): fkey for fkey in self.faces() } # compute total rotation of polyline for polyline in self.branches_singularity_to_singularity(): angles = [ angle_vectors_signed(subtract_vectors(v, u), subtract_vectors(w, v), [0., 0., 1.]) for u, v, w in window(polyline, n=3) ] # subdivide once per angle limit in rotation if abs(sum(angles)) > self.flip_angle_limit: # the step between subdivision points in polylines (+ 2 for the extremities, which will be discarded) alone = len(self.compas_singular_faces()) == 0 n = floor(abs(sum(angles)) / self.flip_angle_limit) + 1 step = int(floor(len(polyline) / n)) # add new branches from corresponding face in Delaunay mesh seams = polyline[::step] if polyline[-1] != seams[-1]: if len(seams) == n + 1: del seams[-1] seams.append(polyline[-1]) if alone: seams = seams[0:-1] else: seams = seams[1:-1] for point in seams: fkey = centre_to_fkey[geometric_key(point)] for edge in self.face_halfedges(fkey): if not self.is_edge_on_boundary(*edge): new_branches += [[ trimesh_face_circle(self, fkey)[0], self.vertex_coordinates(vkey) ] for vkey in edge] break return new_branches
def intersect(self): intersections = [] for u, v in list(self.mesh.edges()): a = self.mesh.vertex_attributes(u, 'xyz') b = self.mesh.vertex_attributes(v, 'xyz') x = intersection_segment_plane((a, b), self.plane) if not x: continue L_ax = length_vector(subtract_vectors(x, a)) L_ab = length_vector(subtract_vectors(b, a)) t = L_ax / L_ab key = self.mesh.split_edge(u, v, t=t, allow_boundary=True) intersections.append(key) self._intersections = intersections
def extrude_normal(polygon, distance, plane=None): if plane is None: x, y, z = cross_vectors(subtract_vectors(polygon[0], polygon[1]), subtract_vectors(polygon[0], polygon[2])) normal = Vector(x, y, z) else: normal = plane.normal Vector.unitize(normal) normal = scale_vector(normal, distance) # normal = Vector(x, y, z) moved = [add_vectors(pt, normal) for pt in polygon] polygon.extend(moved) return polygon
def reflect_line_triangle(line, triangle, tol=1e-6): """Bounce a line of a reflection triangle. Parameters ---------- line : tuple Two points defining the line. triangle : tuple The triangle vertices. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- tuple The reflected line defined by the intersection point of the line and triangle and the mirrored start point of the line with respect to a line perpendicular to the triangle through the intersection. Notes ----- The direction of the line and triangle are important. The line is only reflected if it points towards the front of the triangle. This is true if the dot product of the direction vector of the line and the normal vector of the triangle is smaller than zero. Examples -------- >>> triangle = [1.0, 0, 0], [-1.0, 0, 0], [0, 0, 1.0] >>> line = [-1, 1, 0], [-0.5, 0.5, 0] >>> reflect_line_triangle(line, triangle) ([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) """ x = intersection_line_triangle(line, triangle, tol=tol) if not x: return a, b = line t1, t2, t3 = triangle ab = subtract_vectors(b, a) n = cross_vectors(subtract_vectors(t2, t1), subtract_vectors(t3, t1)) if dot_vectors(ab, n) > 0: # the line does not point towards the front of the triangle return mirror = x, add_vectors(x, n) return x, mirror_point_line(a, mirror)
def branches_splitting_boundary_kinks(self): """Add new branches to fix the problem of boundary kinks not marked by the skeleton Due to a low density that did not spot the change of curvature at the kink. Does not modify the singularites on the contrarty to increasing the density. Returns ------- new_branches : list List of polylines as list of point XYZ-coordinates. """ new_branches = [] compas_singular_faces = set(self.compas_singular_faces()) for boundary in self.boundaries(): angles = {(u, v, w): angle_vectors( subtract_vectors(self.vertex_coordinates(v), self.vertex_coordinates(u)), subtract_vectors(self.vertex_coordinates(w), self.vertex_coordinates(v))) for u, v, w in window(boundary + boundary[:2], n=3)} for u, v, w, x, y in list(window(boundary + boundary[:4], n=5)): # check if not a corner if self.vertex_degree(w) == 2: continue angle = angles[(v, w, x)] adjacent_angles = (angles[(u, v, w)] + angles[(w, x, y)]) / 2 if angle - adjacent_angles > self.relative_kink_angle_limit: # check if not already marked via an adjacent compas_singular face if all([ fkey not in compas_singular_faces for fkey in self.vertex_faces(w) ]): fkeys = list(self.vertex_faces(w, ordered=True)) fkey = fkeys[int(floor(len(fkeys) / 2))] for edge in self.face_halfedges(fkey): if w in edge and not self.is_edge_on_boundary( *edge): new_branches += [[ trimesh_face_circle(self, fkey)[0], self.vertex_coordinates(vkey) ] for vkey in edge] break return new_branches
def DrawForeground(self, e): p1 = self.mouse.p1 p2 = self.mouse.p2 v12 = subtract_vectors(p2, p1) l12 = length_vector(v12) for index, (key, attr) in enumerate(self.mesh.vertices(True)): p0 = attr['x'], attr['y'], attr['z'] text = str(index) v01 = subtract_vectors(p1, p0) v02 = subtract_vectors(p2, p0) l = length_vector(cross_vectors(v01, v02)) # noqa: E741 if l12 == 0.0 or (l / l12) < self.tol: point = Point3d(*p0) e.Display.DrawDot(point, text, self.dotcolor, self.textcolor) break
def CalcAreaPolygon(polygon): totalArea = 0.0 n = len(polygon.points) for i in range(0, n): v0 = polygon[i] v1 = polygon[(i + 1) % n] v2 = polygon.centroid a = subtract_vectors(v2, v1) b = subtract_vectors(v2, v0) cross_ab = cross_vectors(a, b) area = 0.5 * length_vector(cross_ab) totalArea += area return totalArea
def edge_vector(self, u, v, unitized=True): u_xyz = self.vertex_coordinates(u) v_xyz = self.vertex_coordinates(v) vector = subtract_vectors(v_xyz, u_xyz) if unitized: return normalize_vector(vector) return vector
def from_bounding_box(cls, bbox): # this should put the frame at the centroid of the box # not at the bottom left corner a = bbox[0] b = bbox[1] d = bbox[3] e = bbox[4] xaxis = Vector(*subtract_vectors(d, a)) yaxis = Vector(*subtract_vectors(b, a)) zaxis = Vector(*subtract_vectors(e, a)) xsize = xaxis.length ysize = yaxis.length zsize = zaxis.length frame = Frame(a, xaxis, yaxis) frame.point += frame.xaxis * 0.5 * xsize + frame.yaxis * 0.5 * ysize + frame.zaxis * 0.5 * zsize return cls(frame, xsize, ysize, zsize)
def draw_pipes(pipes: List[Dict], collection: Union[Text, bpy.types.Collection] = None, centroid: bool = True, smooth: bool = True) -> List[bpy.types.Object]: """Draw polyline objects.""" P = len(pipes) N = len(str(P)) objects = [0] * P for index, data in enumerate(pipes): points = data['points'] origin = centroid_points(points) if centroid else [0, 0, 0] name = data.get('name', f'POLY.{index:0{N}d}') curve = bpy.data.curves.new(name, type='CURVE') curve.dimensions = '3D' curve.fill_mode = 'FULL' curve.bevel_depth = data.get('width', 0.05) curve.bevel_resolution = 0 curve.resolution_u = 20 curve.use_fill_caps = True if smooth: spline = curve.splines.new('NURBS') else: spline = curve.splines.new('POLY') spline.points.add(len(points) - 1) for i, point in enumerate(points): spline.points[i].co = subtract_vectors(point, origin) + [1.0] spline.order_u = 1 obj = bpy.data.objects.new(name, curve) obj.location = origin rgb = data.get('color', [1.0, 1.0, 1.0]) _set_object_color(obj, rgb) objects[index] = obj _link_objects(objects, collection) return objects
def mirror_vector_vector(v1, v2): """Mirrors vector about vector. Parameters ---------- v1 : [float, float, float] | :class:`compas.geometry.Vector` The vector. v2 : [float, float, float] | :class:`compas.geometry.Vector` The normalized vector as mirror axis Returns ------- [float, float, float] The mirrored vector. Notes ----- For more info, see [1]_. References ---------- .. [1] Math Stack Exchange. *How to get a reflection vector?* Available at: https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector. """ return subtract_vectors(v1, scale_vector(v2, 2 * dot_vectors(v1, v2)))
def tween_points_distance(points1, points2, dist, index=None): """Compute an interpolated set of points between two sets of points, at a given distance. Parameters ---------- points1 : list[[float, float, float] | :class:`compas.geometry.Point`] The first set of points. points2 : list[[float, float, float] | :class:`compas.geometry.Point`] The second set of points. dist : float The distance from the first set to the second at which to compute the interpolated set. index: int, optional The index of the point in the first set from which to calculate the distance to the second set. If no value is given, the first point will be used. Returns ------- list[list[[float, float, float]]] List of points. """ if not index: index = 0 d = distance_point_point(points1[index], points2[index]) scale = float(dist) / d tweens = [] for i in range(len(points1)): tweens.append( add_vectors( points1[i], scale_vector(subtract_vectors(points2[i], points1[i]), scale))) return tweens