MODULELyle suggest that for a bezier with control points P, Q, R, S estimate the length by the length of the lines connecting: P, (P+Q)/2, (Q+R)/2, S; IMPORT GraphVBT, RefList, R2; REVEAL StraightPath = StraightPathPublic BRANDED OBJECT p0, p1: R2.T; OVERRIDES init := StraightInit; pos := StraightPos; END; PROCEDURE AnimationPath StraightInit (self: StraightPath; p0, p1: R2.T): T = BEGIN self.p0 := p0; self.p1 := p1; RETURN self END StraightInit; PROCEDUREStraightPos (self: StraightPath; t: REAL): R2.T = BEGIN RETURN R2.Add(self.p0, R2.Scale(t, R2.Sub(self.p1, self.p0))) END StraightPos; REVEAL BezierPath = BezierPathPublic BRANDED OBJECT p0, p1, p2, p3: R2.T; OVERRIDES init := BezierInit; pos := BezierPos; END; PROCEDUREBezierInit (self: BezierPath; p0, p1, p2, p3: R2.T):T = BEGIN self.p0 := p0; self.p1 := p1; self.p2 := p2; self.p3 := p3; RETURN self END BezierInit; PROCEDUREBezierPos (self: BezierPath; t: REAL): R2.T = BEGIN RETURN R2.T{ Cubic(self.p0[0], self.p1[0], self.p2[0], self.p3[0], t), Cubic(self.p0[1], self.p1[1], self.p2[1], self.p3[1], t)} END BezierPos; REVEAL EdgePath = EdgePathPublic BRANDED OBJECT e: GraphVBT.Edge OVERRIDES init := EdgeInit; pos := EdgePos; END; PROCEDUREEdgeInit (self: EdgePath; e: GraphVBT.Edge): T = BEGIN self.e := e; RETURN self END EdgeInit; PROCEDUREEdgePos (self: EdgePath; t: REAL): R2.T = VAR p0, p1, p2, p3: R2.T; BEGIN IF (self.e.control0 = NIL) OR (self.e.control1 = NIL) THEN p0 := self.e.vertex0.pos; p1 := self.e.vertex1.pos; RETURN R2.Mix(p0, 1.0 - t, p1, t); ELSE p0 := self.e.vertex0.pos; p1 := self.e.control0.pos; p2 := self.e.control1.pos; p3 := self.e.vertex1.pos; RETURN R2.T{Cubic(p0[0], p1[0], p2[0], p3[0], t), Cubic(p0[1], p1[1], p2[1], p3[1], t)} END; END EdgePos; PROCEDURECubic (c0, c1, c2, c3: REAL; t: REAL): REAL = BEGIN RETURN c0 + t * (3.0 * (c1 - c0) + t * (3.0 * ((c2 - c1) - (c1 - c0)) + t * ((c3 - c0) - 3.0 * (c2 - c1)))) END Cubic; TYPE Edges = REF ARRAY OF RECORD e: GraphVBT.Edge; l: REAL END; REVEAL MultipleEdgePath = MultipleEdgePathPublic BRANDED OBJECT edges: Edges; totalLength: REAL; OVERRIDES init := MultipleEdgeInit; pos := MultipleEdgePos; END; PROCEDUREMultipleEdgeInit (self: MultipleEdgePath; edges: RefList.T): T = VAR total := 0.0; BEGIN self.edges := NEW(Edges, RefList.Length(edges)); FOR i := 0 TO LAST(self.edges^) DO WITH e = edges.head, l = EdgeLength(e) DO edges := edges.tail; self.edges[i].e := e; self.edges[i].l := l; total := total + l; END; END; self.totalLength := total; RETURN self END MultipleEdgeInit;
PROCEDUREEdgeLength (e: GraphVBT.Edge): REAL = BEGIN IF e.control0 = NIL THEN RETURN R2.Length(R2.Sub(e.vertex0.pos, e.vertex1.pos)) ELSE WITH p = e.vertex0.pos, q = e.control0.pos, r = e.control1.pos, s = e.vertex1.pos, q1 = R2.T{(p[0] + q[0]) / 2.0, (p[1] + q[1]) / 2.0}, r1 = R2.T{(r[0] + s[0]) / 2.0, (r[1] + s[1]) / 2.0} DO RETURN R2.Length(R2.Sub(p, q1)) + R2.Length(R2.Sub(q1, r1)) + R2.Length(R2.Sub(r1, s)) END; END; END EdgeLength; PROCEDUREMultipleEdgePos (self: MultipleEdgePath; t: REAL): R2.T = VAR p0, p1, p2, p3: R2.T; total, goal : REAL := 0.0; e : GraphVBT.Edge; BEGIN goal := t * self.totalLength; FOR i := 0 TO LAST(self.edges^) DO WITH l = self.edges[i].l DO IF total + l >= goal THEN e := self.edges[i].e; IF l = 0.0 THEN t := 0.0 ELSE t := (goal - total) / l; END; EXIT; END; total := total + l; END; END; IF (e.control0 = NIL) OR (e.control1 = NIL) THEN p0 := e.vertex0.pos; p1 := e.vertex1.pos; RETURN R2.Mix(p0, 1.0 - t, p1, t); ELSE p0 := e.vertex0.pos; p1 := e.control0.pos; p2 := e.control1.pos; p3 := e.vertex1.pos; RETURN R2.T{Cubic(p0[0], p1[0], p2[0], p3[0], t), Cubic(p0[1], p1[1], p2[1], p3[1], t)} END; END MultipleEdgePos; BEGIN END AnimationPath.