def compute_verts(nodes: Iterable[Node], bone: Bone) -> None: """Build the initial vertexes of each node.""" bone_weight = [(bone, 1.0)] todo = set(nodes) def vert(pos: Vec, off: Vec, u: float, v: float) -> Vertex: """Make a vertex, with appropriate UVs.""" if config.flip_uv: v, u = u, v return Vertex(pos + off, off.norm(), u, v, bone_weight) while todo: start = todo.pop() start = start.find_start() if start.next is None: continue v_start = 0.0 for node1 in start.follow(): todo.discard(node1) node2 = node1.next if node1.next is not None else node1 config = node1.config count = node1.config.side_count v_end = v_start + config.v_scale * (node2.pos - node1.pos).mag() for i in range(count): ang = lerp(i, 0, count, 0, 2 * math.pi) local = Vec(0, math.cos(ang), math.sin(ang)) u = lerp(i, 0, count, config.u_min, config.u_max) node1.points_next.append( vert(node1.pos, node1.radius * local @ node1.orient, u, v_start)) if node1.next is not None: node1.next.points_prev.append( vert(node2.pos, node2.radius * local @ node2.orient, u, v_end)) v_start = v_end
def vec_point(self, t: float, dest: DestType = DestType.PRIMARY) -> Vec: """Return the position along the curve.""" assert dest is DestType.PRIMARY if self.reversed: t = 1.0 - t if t < self.STRAIGHT_PERC: x = 0 y = lerp(t, 0.0, self.STRAIGHT_PERC, 128, 72) else: ang = lerp(t, self.STRAIGHT_PERC, 1.0, 0.0, math.pi / 4) x = lerp(math.cos(ang), 1.0, math.cos(math.pi / 4), 0, 64) y = lerp(math.sin(ang), 0.0, math.sin(math.pi / 4), 72, -64) return Vec(x, self.y * y, 0) @ self.matrix + self.origin
def make_cap(orig, norm): # Recompute the UVs to use the first bit of the cable. points = [ Vertex( point.pos, norm, lerp(Vec.dot(point.norm, node.orient.up()), -1, 1, node.config.u_min, node.config.u_max), lerp(Vec.dot(point.norm, node.orient.left()), -1, 1, 0, v_max), point.links, ) for point in orig ] mesh.triangles.append(Triangle(mat, points[0], points[1], points[2])) for a, b in zip(points[2:], points[3:]): mesh.triangles.append(Triangle(mat, points[0], a, b))
def interpolate_straight(node1: Node, node2: Node, seg_count: int) -> List[Node]: """Simply interpolate in a straight line.""" diff = (node2.pos - node1.pos) / (seg_count + 1) return [ Node(node1.pos + diff * i, node1.config, lerp(i, 0, seg_count + 1, node1.radius, node2.radius)) for i in range(1, seg_count + 1) ]
def interpolate_rope(node1: Node, node2: Node, seg_count: int) -> List[Node]: """Compute the move_rope style hanging points. This uses a quite unusual implementation in Source, doing a physics simulation. See the following files for this code: - src/public/keyframe.cpp - src/public/simple_physics.cpp - src/public/rope_physics.cpp """ diff = node2.pos - node1.pos total_len = diff.mag() + max(0.0, node1.config.slack - 100.0) max_len = total_len / (seg_count + 1) max_len_sqr = max_len**2 interp_diff = diff / (seg_count + 1) points = [ RopePhys( node1.pos + interp_diff * i, lerp(i, 0, seg_count + 2, node1.radius, node2.radius), ) for i in range(0, seg_count + 2) ] springs = list(zip(points, points[1:])) time = 0 step = TIME_STEP gravity = Vec(z=ROPE_GRAVITY) * step**2 # Start/end doesn't move. anchor1, *moveable, anchor2 = points while time < SIM_TIME: time += step points[0].pos = node1.pos.copy() points[-1].pos = node2.pos.copy() # Gravity. for node in moveable: node.prev_pos, node.pos = node.pos, ( node.pos + (node.pos - node.prev_pos) * 0.98 + gravity) # Spring constraints. for _ in range(3): for phys1, phys2 in springs: diff = phys1.pos - phys2.pos dist = diff.mag_sq() if dist > max_len_sqr: diff *= 0.5 * (1 - (max_len / math.sqrt(dist))) if phys1 is not anchor1: phys1.pos -= diff if phys2 is not anchor2: phys2.pos += diff return [Node(point.pos, node1.config, point.radius) for point in moveable]
def interpolate_catmull_rom(node1: Node, node2: Node, seg_count: int) -> List[Node]: """Interpolate a spline curve, matching Valve's implementation.""" # If no points are found, extrapolate out the line. diff = (node2.pos - node1.pos).norm() if node1.prev is None: p0 = node1.pos - diff else: p0 = node1.prev.pos p1 = node1.pos p2 = node2.pos if node2.next is None: p3 = node2.pos + diff else: p3 = node2.next.pos t0 = 0 t1 = t0 + (p1 - p0).mag() t2 = t1 + (p2 - p1).mag() t3 = t2 + (p3 - p2).mag() points: List[Node] = [] for i in range(1, seg_count + 1): t = lerp(i, 0, seg_count + 1, t1, t2) A1 = (t1 - t) / (t1 - t0) * p0 + (t - t0) / (t1 - t0) * p1 A2 = (t2 - t) / (t2 - t1) * p1 + (t - t1) / (t2 - t1) * p2 A3 = (t3 - t) / (t3 - t2) * p2 + (t - t2) / (t3 - t2) * p3 B1 = (t2 - t) / (t2 - t0) * A1 + (t - t0) / (t2 - t0) * A2 B2 = (t3 - t) / (t3 - t1) * A2 + (t - t1) / (t3 - t1) * A3 points.append( Node( (t2 - t) / (t2 - t1) * B1 + (t - t1) / (t2 - t1) * B2, node1.config, lerp(i, 0, seg_count + 1, node1.radius, node2.radius), )) return points