MODULE; IMPORT HullViewClass, Filter, FloatMode, GraphVBT, MyColors, R2, RefList, RealFloat, View, ZeusPanel, IntList, Site, SiteList, VBT, Thread; CONST Big : REAL = 6.0; dMul: REAL = 1.0; TYPE MyVertex = GraphVBT.Vertex BRANDED OBJECT rel: BOOLEAN := FALSE END; Sites = REF ARRAY OF MyVertex; Edges = REF ARRAY OF GraphVBT.Edge; HP = REF RECORD tail, head: INTEGER; (* HP = HalfPlane *) vFront, vRight, vBack: GraphVBT.Vertex; pRight: GraphVBT.Polygon END; T = HullViewClass.T BRANDED OBJECT mg: GraphVBT.T; nSites: INTEGER; sites: Sites; edges: Edges; testLite: GraphVBT.VertexHighlight; testLiteOn: BOOLEAN; tailLite, headLite: GraphVBT.VertexHighlight; tailLiteOn, headLiteOn: BOOLEAN; limbo: GraphVBT.Vertex; tail, head: INTEGER; vTail, vHead, vFront, vRight, vBack, vLeft: GraphVBT.Vertex; pRight, pLeft: GraphVBT.Polygon; eBack, eShaft, eFront: GraphVBT.Edge; confirmed: RefList.T; (* of HP's *) hanging: RefList.T; (* of HP's *) bandData: BandData; OVERRIDES startrun := Startrun; oeSetup := Setup; oeSetHalfPlane := SetHalfPlane; oeTestSite := TestSite; oeMoveHalfPlane := MoveHalfPlane; oeSwap := Swap; oeConfirm := Confirm; oeSentinel := Sentinel; oeStretch := Stretch; oeSnap := Snap; oeClearHead := ClearHead; oeClearTest := ClearTest; oeSetTail := SetTail; END; ConstPath = GraphVBT.AnimationPath BRANDED OBJECT p0: R2.T OVERRIDES pos := ConstPos END; AffinePath = GraphVBT.AnimationPath BRANDED OBJECT p0, p1: R2.T OVERRIDES pos := AffinePos END; JointPath = GraphVBT.AnimationPath BRANDED OBJECT pStart, pStop: R2.T; tStop: REAL OVERRIDES pos := JointPos END; ShoulderPath = GraphVBT.AnimationPath BRANDED OBJECT pStart, pBump, pStop: R2.T; tBump, tStop: REAL OVERRIDES pos := ShoulderPos END; BezierPath = GraphVBT.AnimationPath BRANDED OBJECT p0, p1, p2, p3: R2.T OVERRIDES pos := BezierPos END; Movie = REF RECORD head, tail: GraphVBT.AnimationPath; curTime: REAL; curHeadPos, curTailPos, curFrontPos, curBackPos, curRightPos, curLeftPos: R2.T; END; FrontPath = GraphVBT.AnimationPath BRANDED OBJECT m: Movie OVERRIDES pos := FrontPos; END; BackPath = GraphVBT.AnimationPath BRANDED OBJECT m: Movie OVERRIDES pos := BackPos; END; RightPath = GraphVBT.AnimationPath BRANDED OBJECT m: Movie OVERRIDES pos := RightPos; END; LeftPath = GraphVBT.AnimationPath BRANDED OBJECT m: Movie OVERRIDES pos := LeftPos; END; PROCEDURE GeomView New (): View.T = BEGIN RETURN NEW(T).init(NEW(GraphVBT.T).init()) END New; PROCEDUREStartrun (view: T) = BEGIN LOCK VBT.mu DO EVAL Filter.Replace(view, NEW(GraphVBT.T).init()) END; HullViewClass.T.startrun(view); END Startrun; PROCEDURESetup (view: T; trueSites, auxSites: SiteList.T) = VAR nTrueSites, nAuxSites: INTEGER; wc := GraphVBT.WorldRectangle{ w := -1.3, s := -1.3, e := 1.3, n := 1.3}; r : SiteList.T; s: Site.T; font: GraphVBT.WorldFont; BEGIN nTrueSites := SiteList.Length(trueSites); nAuxSites := SiteList.Length(auxSites); view.nSites := nTrueSites+2+nAuxSites; view.mg := NEW(GraphVBT.T, world := wc).init(); font := view.mg.font(family := "Helvetica", weight := "bold", slant := GraphVBT.Slant.Roman, size := 0.1); LOCK VBT.mu DO EVAL Filter.Replace(view, view.mg); END; view.sites := NEW(Sites, view.nSites); r := SiteList.Append(trueSites, auxSites); WHILE r # NIL DO s := r.head; r := r.tail; IF s.bool THEN view.sites[s.uid]:=NEW(MyVertex, graph := view.mg, pos := R2.T {s.x, s.y}, shape := GraphVBT.VertexShape.Ellipse, rel := TRUE).init() ELSE view.sites[s.uid]:=NEW(MyVertex, graph := view.mg, pos := R2.T{s.x, s.y}, shape := GraphVBT.VertexShape.Ellipse, color := MyColors.White(), font := font, fontColor := MyColors.Black(), border := 0.01, label := s.lab, size := R2.T{0.11, 0.11}).init() END; END; view.limbo := NEW(GraphVBT.Vertex, graph := view.mg, pos := R2.T{0.0, 1.5}, shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(); view.sites[0] := NEW(MyVertex, graph := view.mg, pos := R2.T{0.0, 1.5}, shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(); view.sites[nTrueSites+1] := view.sites[0]; view.edges := NEW(Edges, view.nSites); view.vHead := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.limbo.pos, shape := GraphVBT.VertexShape.Ellipse, color := MyColors.Head(), size := R2.T{0.169, 0.169}).init(); LOCK view.mg.mu DO view.vHead.toBack() END; view.vTail := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.limbo.pos, shape := GraphVBT.VertexShape.Rectangle, size := R2.T{0.0, 0.0}).init(); view.vFront := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.limbo.pos, shape := GraphVBT.VertexShape.Rectangle, size := R2.T{0.0, 0.0}).init(); view.vBack := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.limbo.pos, shape := GraphVBT.VertexShape.Rectangle, size := R2.T{0.0, 0.0}).init(); view.vLeft := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.limbo.pos, shape := GraphVBT.VertexShape.Rectangle, size := R2.T{0.0, 0.0}).init(); view.vRight := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.limbo.pos, shape := GraphVBT.VertexShape.Rectangle, size := R2.T{0.0, 0.0}).init(); view.pRight := NEW(GraphVBT.Polygon, vertices := RefList.List3(view.vFront, view.vRight, view.vBack), color := MyColors.Right()).init(); view.pLeft := NEW(GraphVBT.Polygon, vertices := RefList.List3(view.vFront, view.vLeft, view.vBack), color := MyColors.Left()).init(); view.eShaft := NEW(GraphVBT.Edge, vertex0 := view.vTail, vertex1 := view.vHead, arrow := ARRAY [0..1] OF BOOLEAN{FALSE,TRUE}, color := MyColors.Shaft()).init(); view.eFront := NEW(GraphVBT.Edge, vertex0 := view.vHead, vertex1 := view.vFront, color := MyColors.Front()).init(); view.eBack := NEW(GraphVBT.Edge, vertex0 := view.vBack, vertex1 := view.vTail, color := MyColors.Back()).init(); view.testLite := NEW(GraphVBT.VertexHighlight, vertex := view.limbo, color := MyColors.Test(), border := R2.T{0.042, 0.042}).init(); view.testLiteOn := FALSE; view.tailLite := NEW(GraphVBT.VertexHighlight, vertex := view.limbo, color := MyColors.Tail(), border := R2.T{0.042, 0.042}).init(); view.tailLiteOn := FALSE; view.headLite := NEW(GraphVBT.VertexHighlight, vertex := view.limbo, color := MyColors.Head(), border := R2.T{0.021, 0.021}).init(); view.headLiteOn := FALSE; view.mg.redisplay(); END Setup; PROCEDURESetHalfPlane (view: T; tail, head: INTEGER) RAISES {Thread.Alerted} = VAR tailPos, headPos, center, del, delta, delta90: R2.T; BEGIN tailPos := view.sites[tail].pos; headPos := view.sites[head].pos; IF view.sites[tail].rel THEN <* ASSERT NOT view.sites[head].rel *> tailPos := R2.Add(headPos, tailPos); END; IF view.sites[head].rel THEN <* ASSERT NOT view.sites[tail].rel *> headPos := R2.Add(tailPos, headPos); END; IF NOT view.headLiteOn THEN LOCK view.mg.mu DO view.headLite.move(view.sites[head], FALSE) END; view.headLiteOn := TRUE; END; IF view.tailLiteOn AND NOT view.sites[view.tail].rel THEN LOCK view.mg.mu DO view.tailLite.move(view.sites[tail], TRUE) END; view.mg.animate(0.0, 1.0); ELSE LOCK view.mg.mu DO view.tailLite.move(view.sites[tail], FALSE) END; view.tailLiteOn := TRUE END; view.tail := tail; view.head := head; IF head # tail THEN del := R2.Sub(headPos, tailPos); delta := R2.Scale(Big/R2.L2Norm(del), del); delta90 := R2.T{delta[1], -delta[0]}; center := R2.Mix(headPos, 0.5, tailPos, 0.5); view.vHead.move(headPos); view.vTail.move(tailPos); view.vFront.move(R2.Add(headPos, delta)); view.vRight.move(R2.Add(center, delta90)); view.vBack.move(R2.Sub(tailPos, delta)); view.vLeft.move(R2.Sub(center, delta90)); END; view.mg.redisplay(); END SetHalfPlane; PROCEDUREClearHead (view: T) = BEGIN LOCK view.mg.mu DO view.headLiteOn := FALSE; view.headLite.move(view.limbo, FALSE); END; view.mg.redisplay(); END ClearHead; PROCEDUREClearTest (view: T) = BEGIN LOCK view.mg.mu DO view.testLiteOn := FALSE; view.testLite.move(view.limbo, FALSE); END; view.mg.redisplay(); END ClearTest; PROCEDURESetTail (view: T; i: INTEGER) RAISES {Thread.Alerted} = BEGIN IF view.tailLiteOn AND NOT view.sites[view.tail].rel THEN LOCK view.mg.mu DO view.tailLite.move(view.sites[i], TRUE); END; view.mg.animate(0.0, 1.0); ELSE LOCK view.mg.mu DO view.tailLite.move(view.sites[i], FALSE); END; view.tailLiteOn := TRUE; view.mg.redisplay(); END; END SetTail; PROCEDURESwap (view: T; i, j: INTEGER) RAISES {Thread.Alerted} = VAR iList : RefList.T := view.sites[i].vertexHighlights; jList : RefList.T := view.sites[j].vertexHighlights; h : GraphVBT.VertexHighlight; BEGIN LOCK view.mg.mu DO WHILE iList # NIL DO h := iList.head; h.move (view.sites[j], TRUE); iList := iList.tail; END; WHILE jList # NIL DO h := jList.head; h.move (view.sites[i], TRUE); jList := jList.tail; END; END; view.mg.animate (0.5, 1.5); END Swap; PROCEDURETestSite (view: T; i: INTEGER) RAISES {Thread.Alerted} = <*FATAL FloatMode.Trap*> VAR p1, p2: R2.T; dist: REAL; BEGIN IF view.testLiteOn THEN p1 := view.testLite.vertex.pos; p2 := view.sites[i].pos; dist := R2.L2Dist(p1,p2); LOCK view.mg.mu DO view.testLite.move(view.sites[i], TRUE) END; view.mg.animate(0.0, RealFloat.Sqrt(RealFloat.Sqrt(dist))); ELSE LOCK view.mg.mu DO view.testLite.move(view.sites[i], FALSE) END; view.mg.redisplay(); view.testLiteOn := TRUE END; END TestSite; PROCEDUREMoveHalfPlane (view: T; tail, head: INTEGER) RAISES {Thread.Alerted} = VAR headPos, tailPos, center, del, delta, delta90: R2.T; m: Movie; headPath, tailPath: GraphVBT.AnimationPath; BEGIN IF view.head = view.tail THEN LOCK view.mg.mu DO view.headLite.move(view.sites[head], TRUE) END; view.mg.animate(0.0, 1.0); SetHalfPlane(view, tail, head); ELSE tailPos := view.sites[tail].pos; headPos := view.sites[head].pos; IF view.sites[tail].rel THEN <* ASSERT NOT view.sites[head].rel *> tailPos := R2.Add(headPos, tailPos); END; IF view.sites[head].rel THEN <* ASSERT NOT view.sites[tail].rel *> headPos := R2.Add(tailPos, headPos); END; del := R2.Sub(headPos, tailPos); delta := R2.Scale(Big/R2.L2Norm(del), del); delta90 := R2.T{delta[1], -delta[0]}; center := R2.Mix(headPos, 0.5, tailPos, 0.5); IF view.sites[view.head].rel THEN headPath := NEW(BezierPath, p0 := view.vHead.pos, p1 := R2.Add(view.vHead.pos, R2.T{-0.1, 0.0}), p2 := R2.Add(view.vHead.pos, R2.T{-0.1, 0.0}), p3 := headPos); ELSE headPath := NEW(AffinePath, p0 := view.vHead.pos, p1 := headPos); END; tailPath := NEW(AffinePath, p0 := view.vTail.pos, p1 := tailPos); m := NEW(Movie, head := headPath, tail := tailPath, curTime := -1.0); LOCK view.mg.mu DO view.headLite.move(view.sites[head], TRUE); view.vHead.move(headPos, TRUE, path := headPath); view.vTail.move(tailPos, TRUE, path := tailPath); view.vFront.move(R2.Add(headPos, delta), TRUE, path := NEW(FrontPath, m := m)); view.vRight.move(R2.Add(center, delta90), TRUE, path := NEW(RightPath, m := m)); view.vBack.move(R2.Sub(tailPos, delta), TRUE, path := NEW(BackPath, m := m)); view.vLeft.move(R2.Sub(center, delta90), TRUE, path := NEW(LeftPath, m := m)); END; view.mg.animate(0.0, 1.0); view.head := head; view.tail := tail; END; END MoveHalfPlane; PROCEDUREConfirm (view: T; tail, head: INTEGER) = VAR hp: HP; BEGIN <*ASSERT tail = view.tail AND head = view.head *> LOCK view.mg.mu DO view.testLiteOn := FALSE; view.testLite.move(view.limbo, FALSE); END; hp := NEW(HP, tail := view.tail, head := view.head, vFront := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.vFront.pos, shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(), vRight := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.vRight.pos, shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(), vBack := NEW(GraphVBT.Vertex, graph := view.mg, pos := view.vBack.pos, shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init()); hp.pRight := NEW (GraphVBT.Polygon, vertices := RefList.List3(hp.vFront, hp.vRight, hp.vBack), color := MyColors.Outside()).init(); view.confirmed := RefList.Cons (hp, view.confirmed); IF NOT view.sites[tail].rel AND NOT view.sites[head].rel THEN EVAL NEW(GraphVBT.Edge, vertex0 := view.sites[hp.tail], vertex1 := view.sites[hp.head]).init() END; LOCK view.mg.mu DO view.sites[hp.tail].setColor(MyColors.Black()); view.sites[hp.tail].setFontColor(MyColors.White()); view.sites[hp.tail].setBorder(0.0); view.sites[hp.head].setColor(MyColors.Black()); view.sites[hp.head].setFontColor(MyColors.White()); view.sites[hp.head].setBorder(0.0); view.vFront.move(view.limbo.pos, FALSE); view.vRight.move(view.limbo.pos, FALSE); view.vBack.move(view.limbo.pos, FALSE); view.vLeft.move(view.limbo.pos, FALSE); view.vHead.move(view.limbo.pos, FALSE); view.vTail.move(view.limbo.pos, FALSE); END; view.mg.redisplay(); END Confirm; PROCEDUREConfirmOne (view: T; tail, head: INTEGER) = VAR hp: HP; del, delta, delta90, center, headPos, tailPos: R2.T; BEGIN headPos := view.sites[head].pos; tailPos := view.sites[tail].pos; del := R2.Sub(headPos, tailPos); delta := R2.Scale(Big/R2.L2Norm(del), del); delta90 := R2.T{delta[1], -delta[0]}; center := R2.Mix(headPos, 0.5, tailPos, 0.5); hp := NEW(HP, tail := tail, head := head, vFront := NEW(GraphVBT.Vertex, graph := view.mg, pos := R2.Add(headPos, delta), shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(), vRight := NEW(GraphVBT.Vertex, graph := view.mg, pos := R2.Add(center, delta90), shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(), vBack := NEW(GraphVBT.Vertex, graph := view.mg, pos := R2.Sub(tailPos, delta), shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init()); hp.pRight := NEW (GraphVBT.Polygon, vertices := RefList.List3 (hp.vFront, hp.vRight, hp.vBack), color := MyColors.Outside()).init(); view.confirmed := RefList.Cons (hp, view.confirmed); IF NOT view.sites[tail].rel AND NOT view.sites[head].rel THEN EVAL NEW(GraphVBT.Edge, vertex0 := view.sites[tail], vertex1 := view.sites[head]).init() END; LOCK view.mg.mu DO view.sites[hp.tail].setColor(MyColors.Black()); view.sites[hp.tail].setFontColor(MyColors.White()); view.sites[hp.tail].setBorder(0.0); view.sites[hp.head].setColor(MyColors.Black()); view.sites[hp.head].setFontColor(MyColors.White()); view.sites[hp.head].setBorder(0.0); END; END ConfirmOne; PROCEDURESentinel (view: T; i, j: INTEGER) = BEGIN view.sites[i]:=view.sites[j]; END Sentinel; TYPE BandData = REF RECORD jntStarts, jntStops, lshStarts, lshBumps, lshStops, rshStarts, rshBumps, rshStops: REF ARRAY OF R2.T; jntStopTimes, lshStopTimes, rshStopTimes: REF ARRAY OF REAL; jnt, lsh, rsh: REF ARRAY OF GraphVBT.Vertex; hullSiteUids: REF ARRAY OF INTEGER; END; PROCEDUREStretch ( view : T; hullSites : IntList.T; <* UNUSED *> otherSites: IntList.T) = VAR b: BandData := NEW(BandData); n: INTEGER; hs: REF ARRAY OF R2.T; hsl: IntList.T; minX, maxX, minY, maxY, rad, len: REAL; center, del, del90: R2.T; band: Edges; BEGIN view.bandData := b; n := IntList.Length (hullSites); hsl := hullSites; hs := NEW (REF ARRAY OF R2.T, n+2); b.hullSiteUids := NEW (REF ARRAY OF INTEGER, n+2); FOR i := 1 TO n DO b.hullSiteUids[i] := hsl.head; hs[i] := view.sites[hsl.head].pos; hsl := hsl.tail; END; hs[0] := hs[n]; hs[n+1] := hs[1]; b.hullSiteUids[0] := b.hullSiteUids[n]; b.hullSiteUids[n+1] := b.hullSiteUids[1]; minX := 100.0; maxX := -100.0; minY := 100.0; maxY := -100.0; FOR i := 1 TO n DO minX := MIN(minX, hs[i][0]); maxX := MAX(maxX, hs[i][0]); minY := MIN(minY, hs[i][1]); maxY := MAX(maxY, hs[i][1]); END; center := R2.Mix(R2.T{minX, minY}, 0.5, R2.T{maxX, maxY}, 0.5); b.jntStarts := NEW(REF ARRAY OF R2.T, n+2); b.jntStops := NEW(REF ARRAY OF R2.T, n+2); b.lshStarts := NEW(REF ARRAY OF R2.T, n+2); b.lshBumps := NEW(REF ARRAY OF R2.T, n+2); b.lshStops := NEW(REF ARRAY OF R2.T, n+2); b.rshStarts := NEW(REF ARRAY OF R2.T, n+2); b.rshBumps := NEW(REF ARRAY OF R2.T, n+2); b.rshStops := NEW(REF ARRAY OF R2.T, n+2); b.jntStopTimes := NEW(REF ARRAY OF REAL, n+2); b.lshStopTimes := NEW(REF ARRAY OF REAL, n+2); b.rshStopTimes := NEW(REF ARRAY OF REAL, n+2); rad := 0.0; FOR i := 1 TO n DO del := R2.Sub(hs[i], center); rad := MAX(rad, R2.L2Norm(del)); END; rad := MAX(1.1 * rad, 1.2); FOR i := 0 TO n+1 DO del := R2.Sub(hs[i], center); b.jntStopTimes[i] := dMul * (rad - R2.L2Norm(del)); b.jntStarts[i] := R2.Add(center, R2.Scale(rad/R2.L2Norm(del), del)); b.jntStops[i] := hs[i]; END; FOR i := 1 TO n DO del := R2.Sub(hs[i], center); len := R2.L2Norm(del); del := R2.Scale(1.0/len, del); del90 := R2.T{del[1], -del[0]}; del := R2.Scale(0.5 * R2.L2Dist(b.jntStarts[i-1], b.jntStarts[i]), del90); b.lshStarts[i] := R2.Add(b.jntStarts[i], del); b.lshBumps[i] := R2.Mix(center, 1.0 - len/rad, b.lshStarts[i], len/rad); b.lshStops[i] := R2.Mix(b.jntStops[i], 2.0/3.0, b.jntStops[i-1], 1.0/3.0); b.lshStopTimes[i] := dMul * (rad - R2.L2Dist(center, b.lshStops[i])); del := R2.Scale(0.5 * R2.L2Dist(b.jntStarts[i+1], b.jntStarts[i]), del90); b.rshStarts[i] := R2.Sub(b.jntStarts[i], del); b.rshBumps[i] := R2.Mix(center, 1.0 - len/rad, b.rshStarts[i], len/rad); b.rshStops[i] := R2.Mix(b.jntStops[i], 2.0/3.0, b.jntStops[i+1], 1.0/3.0); b.rshStopTimes[i] := dMul * (rad - R2.L2Dist(center, b.rshStops[i])); END; b.jntStarts[0] := b.jntStarts[n]; b.jntStops[0] := b.jntStops[n]; b.lshStarts[0] := b.lshStarts[n]; b.lshBumps[0] := b.lshBumps[n]; b.lshStops[0] := b.lshStops[n]; b.rshStarts[0] := b.rshStarts[n]; b.rshBumps[0] := b.rshBumps[n]; b.rshStops[0] := b.rshStops[n]; b.jntStopTimes[0] := b.jntStopTimes[n]; b.lshStopTimes[0] := b.lshStopTimes[n]; b.rshStopTimes[0] := b.rshStopTimes[n]; b.jntStarts[n+1] := b.jntStarts[1]; b.jntStops[n+1] := b.jntStops[1]; b.lshStarts[n+1] := b.lshStarts[1]; b.lshBumps[n+1] := b.lshBumps[1]; b.lshStops[n+1] := b.lshStops[1]; b.rshStarts[n+1] := b.rshStarts[1]; b.rshBumps[n+1] := b.rshBumps[1]; b.rshStops[n+1] := b.rshStops[1]; b.jntStopTimes[n+1] := b.jntStopTimes[1]; b.lshStopTimes[n+1] := b.lshStopTimes[1]; b.rshStopTimes[n+1] := b.rshStopTimes[1]; b.jnt := NEW(REF ARRAY OF GraphVBT.Vertex, n+2); b.lsh := NEW(REF ARRAY OF GraphVBT.Vertex, n+2); b.rsh := NEW(REF ARRAY OF GraphVBT.Vertex, n+2); FOR i := 1 TO n DO b.jnt[i]:=NEW(GraphVBT.Vertex, graph := view.mg, pos := b.jntStarts[i], shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(); b.lsh[i]:=NEW(GraphVBT.Vertex, graph := view.mg, pos := b.lshStarts[i], shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(); b.rsh[i]:=NEW(GraphVBT.Vertex, graph := view.mg, pos := b.rshStarts[i], shape := GraphVBT.VertexShape.Ellipse, size := R2.T{0.0, 0.0}).init(); END; b.jnt[0] := b.jnt[n]; b.jnt[n+1] := b.jnt[1]; b.lsh[0] := b.lsh[n]; b.lsh[n+1] := b.lsh[1]; b.rsh[0] := b.rsh[n]; b.rsh[n+1] := b.rsh[1]; band := NEW(Edges, n+2); FOR i := 1 TO n DO band[i] := NEW(GraphVBT.Edge, vertex0 := b.jnt[i], vertex1 := b.jnt[i+1], control0 := b.rsh[i], control1 := b.lsh[i+1], color := MyColors.Band()).init(); END; view.mg.redisplay(); END Stretch; PROCEDURESnap ( view : T; hullSites : IntList.T; <* UNUSED *> otherSites: IntList.T) RAISES {Thread.Alerted} = VAR n: INTEGER; b: BandData; BEGIN b := view.bandData; n := IntList.Length (hullSites); LOCK view.mg.mu DO FOR i := 1 TO n DO b.jnt[i].move(b.jntStops[i], TRUE, path := NEW(JointPath, pStart := b.jntStarts[i], pStop := b.jntStops[i], tStop := b.jntStopTimes[i])); b.lsh[i].move(b.lshStops[i], TRUE, path := NEW(ShoulderPath, pStart := b.lshStarts[i], pBump := b.lshBumps[i], pStop := b.lshStops[i], tBump := b.jntStopTimes[i], tStop := b.lshStopTimes[i])); b.rsh[i].move(b.rshStops[i], TRUE, path := NEW(ShoulderPath, pStart := b.rshStarts[i], pBump := b.rshBumps[i], pStop := b.rshStops[i], tBump := b.jntStopTimes[i], tStop := b.rshStopTimes[i])); END; END; view.mg.animate(0.0, 2.0); FOR i := 1 TO n DO ConfirmOne(view, b.hullSiteUids[i], b.hullSiteUids[i+1]); END; view.mg.redisplay(); END Snap; PROCEDUREConstPos (path: ConstPath; <* UNUSED *> t: REAL): R2.T = BEGIN RETURN(path.p0) END ConstPos; PROCEDUREAffinePos (path: AffinePath; t: REAL): R2.T = BEGIN RETURN(R2.Mix(path.p0, 1.0-t, path.p1, t)) END AffinePos; PROCEDUREJointPos (path: JointPath; t: REAL): R2.T = BEGIN IF t < path.tStop THEN RETURN(R2.Mix(path.pStart, (path.tStop - t)/path.tStop, path.pStop, t/path.tStop)) ELSE RETURN(path.pStop); END; END JointPos; PROCEDUREShoulderPos (path: ShoulderPath; t: REAL): R2.T = BEGIN IF t < path.tBump THEN RETURN(R2.Mix(path.pStart, (path.tBump - t)/path.tBump, path.pBump, t/path.tBump)) ELSIF t < path.tStop THEN RETURN(R2.Mix(path.pBump, (path.tStop - t)/(path.tStop - path.tBump), path.pStop, (t - path.tBump)/(path.tStop - path.tBump))) ELSE RETURN(path.pStop); END; END ShoulderPos; PROCEDUREBezierPos (path: BezierPath; t: REAL): R2.T = VAR q0, q1, q2, r0, r1: R2.T; BEGIN q0 := R2.Mix(path.p0, 1.0-t, path.p1, t); q1 := R2.Mix(path.p1, 1.0-t, path.p2, t); q2 := R2.Mix(path.p2, 1.0-t, path.p3, t); r0 := R2.Mix(q0, 1.0-t, q1, t); r1 := R2.Mix(q1, 1.0-t, q2, t); RETURN(R2.Mix(r0, 1.0-t, r1, t)); END BezierPos; PROCEDUREFrontPos (path: FrontPath; t: REAL): R2.T = BEGIN IF path.m.curTime # t THEN Update(path.m, t) END; RETURN(path.m.curFrontPos) END FrontPos; PROCEDUREBackPos (path: BackPath; t: REAL): R2.T = BEGIN IF path.m.curTime # t THEN Update(path.m, t) END; RETURN(path.m.curBackPos) END BackPos; PROCEDURERightPos (path: RightPath; t: REAL): R2.T = BEGIN IF path.m.curTime # t THEN Update(path.m, t) END; RETURN(path.m.curRightPos) END RightPos; PROCEDURELeftPos (path: LeftPath; t: REAL): R2.T = BEGIN IF path.m.curTime # t THEN Update(path.m, t) END; RETURN(path.m.curLeftPos) END LeftPos; PROCEDUREUpdate (m: Movie; t: REAL) = VAR center, del, delta, delta90: R2.T; BEGIN m.curHeadPos := m.head.pos(t); m.curTailPos := m.tail.pos(t); del := R2.Sub(m.curHeadPos, m.curTailPos); delta := R2.Scale(Big/R2.L2Norm(del), del); delta90 := R2.T{delta[1], -delta[0]}; center := R2.Mix(m.curHeadPos, 0.5, m.curTailPos, 0.5); m.curFrontPos := R2.Add(m.curHeadPos, delta); m.curRightPos := R2.Add(center, delta90); m.curBackPos := R2.Sub(m.curTailPos, delta); m.curLeftPos := R2.Sub(center, delta90); m.curTime := t; END Update; BEGIN ZeusPanel.RegisterView (New, "Geometry View", "Hull"); END GeomView.