<> <> <> <<>> <> <> DIRECTORY Convert USING [RopeFromReal], PathEditor, PEAlgebra USING [ClosestPointOnLineSegment, DistanceSquared], PEBetaSpline USING [BetaSplinePoints], PEBezier USING [Bezier, BezierToSegment, SegmentToBezier, SplitBezier], PEConstraints USING [Constrain], PEConstructSpline USING [LinkBetaSplineSegments, BuildBetaSplineTrajectory], PEDisplay USING [DrawControlPoints, DrawSegment, DrawSegmentAndVertices, DrawVertex, DrawVertices], PEFileOps USING [ReadTrajectoryFile, WriteTrajectoryFile], PEHitTest USING [SegmentHitTest, TrajectoryHitTest, VertexHitTest], PERefresh USING [CreateRefreshProcess, DestroyRefreshProcess, DisableSegmentRefresh, DisableTrajectoryRefresh, EnableSegmentRefresh, EnableTrajectoryRefresh, NewRefreshData, RefreshData, RequestRefresh], PESimplify USING [WeightedMidpoint], PETrajectoryOps, PETypes, PEViewer USING [BuildViewer, ButtonProc, DrawInViewer, DrawProc, QuitProc, RedrawProc], Rope USING [ROPE], ViewerClasses USING [Viewer], ViewerTools USING [SetContents]; PathEditorImpl: CEDAR PROGRAM IMPORTS Convert, PEAlgebra, PEBetaSpline, PEBezier, PEConstraints, PEConstructSpline, PEDisplay, PEFileOps, PEHitTest, PERefresh, PESimplify, PETrajectoryOps, PEViewer, ViewerTools EXPORTS PathEditor = BEGIN OPEN PathEditor, PEAlgebra, PEBezier, PEConstraints, PEDisplay, PEFileOps, PEHitTest, PERefresh, PETrajectoryOps, PETypes, PEViewer; Handle: TYPE = REF PathEditorRec; PathEditorRec: TYPE = RECORD [ refreshData: RefreshData _ NIL, -- data for refresh process buttonProc: ButtonHitProc _ NIL, -- client's button procedure redrawProc: DrawProc _ NIL, -- client's redraw procedure pathViewer: ViewerClasses.Viewer, -- path editor viewer biasViewer: ViewerClasses.Viewer, -- bias viewer tensionViewer: ViewerClasses.Viewer, -- tension viewer trajectoryList: TrajectoryList _ NIL, -- list of segments in the path newTrajectory: BOOLEAN _ TRUE, -- create a new trajectory? activeTrajectory: TrajectoryNode _ NIL, -- trajectory currently being manipulated activeVertex: VertexNode _ NIL, -- vertex currently being manipulated activeSegment: SegmentNode _ NIL, -- segment containing activeVertex splitSegment: SegmentNode _ NIL, -- segment being split by a vertex addition splitReplacements: SegmentNode _ NIL, -- replacement segments for segment being split vertexHit: RECORD [ -- cache for hits in vertex hit testing hitCached: BOOLEAN _ FALSE, where: Point, closeToVertex: BOOLEAN, vertexType: VertexType, vertex: Point ], curveHit: RECORD [ -- cache for hits in curve hit testing hitCached: BOOLEAN _ FALSE, where: Point, closeToCurve: BOOLEAN, curveType: CurveType, p0, p1, p2, p3: Point, hitPoint: Point ] ]; <> CreatePathViewer: PUBLIC PROCEDURE [name: Rope.ROPE, menuLabels: LIST OF MenuLabel, buttonProc: ButtonHitProc _ NIL, redrawProc: DrawProc _ NIL] RETURNS [pathData: PathData] = { <> handle: Handle_ NEW[PathEditorRec]; handle.buttonProc _ buttonProc; handle.redrawProc _ redrawProc; [handle.pathViewer, handle.biasViewer, handle.tensionViewer] _ BuildViewer[ name: name, menuLabels: menuLabels, clientData: handle, redrawProc: DoRedraw, quitProc: Quit, buttonProc: ButtonHit ]; handle.refreshData _ CreateRefreshProcess[handle.pathViewer, handle.redrawProc]; pathData _ handle; }; DoRedraw: RedrawProc = { <> handle: Handle _ NARROW[clientData]; Redraw[pathData: clientData, erase: TRUE]; }; Quit: QuitProc = { <> handle: Handle _ NARROW[clientData]; DestroyRefreshProcess[handle.refreshData]; <> FreeTrajectoryList[handle.trajectoryList]; }; ButtonHit: ButtonProc = { <> handle: Handle _ NARROW[clientData]; IF handle.buttonProc # NIL THEN handle.buttonProc[event: event, x: x, y: y]; }; DrawInPathViewer: PUBLIC PROCEDURE [pathData: PathData, drawProc: DrawProc] = { <> handle: Handle _ NARROW[pathData]; DrawInViewer[handle.pathViewer, drawProc]; }; Redraw: PUBLIC PROCEDURE [pathData: PathData, erase: BOOLEAN _ FALSE] = { <> handle: Handle _ NARROW[pathData]; RequestRefresh[handle.refreshData, erase]; }; FlushImage: PUBLIC PROCEDURE [pathData: PathData] = { <> handle: Handle _ NARROW[pathData]; DoDrawTrajectory: TrajectoryProc = { DoDrawSegment: SegmentProc = { DrawSegment[pathViewer: handle.pathViewer, segment: s.first, undo: TRUE]; IF t = handle.activeTrajectory THEN DrawVertices[pathViewer: handle.pathViewer, segment: s.first, undo: TRUE]; }; ForAllSegments[t.first.segments, DoDrawSegment]; }; ForAllTrajectories[handle.trajectoryList, DoDrawTrajectory]; FreeTrajectoryList[handle.trajectoryList]; handle.trajectoryList _ NIL; NewTrajectory[pathData]; InvalidateHitCache[handle]; }; <> <<>> NewTrajectory: PUBLIC PROCEDURE [pathData: PathData] = { <> handle: Handle _ NARROW[pathData]; handle.newTrajectory _ TRUE; DoSetActiveTrajectory[handle, NIL]; }; SetActiveTrajectory: PUBLIC PROCEDURE [pathData: PathData, where: Point] RETURNS [activeTrajectorySet: BOOLEAN, hitPoint: Point] = { <> handle: Handle _ NARROW[pathData]; hitTrajectory: TrajectoryNode; [hitTrajectory, hitPoint] _ TrajectoryHitTest[handle.trajectoryList, where]; DoSetActiveTrajectory[handle, hitTrajectory]; activeTrajectorySet _ hitTrajectory # NIL; }; DoSetActiveTrajectory: PROCEDURE [handle: Handle, activeTrajectory: TrajectoryNode] = { <> DrawAsBackground: SegmentProc = { DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: s.first, undo: TRUE]; DrawSegment[pathViewer: handle.pathViewer, segment: s.first, background: TRUE]; }; DrawAsForeground: SegmentProc = { DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: s.first, background: FALSE]; }; DoSetActiveVertex[handle, NIL, NIL]; IF handle.activeTrajectory # activeTrajectory THEN { NewRefreshData[handle.refreshData, handle.trajectoryList, activeTrajectory]; IF handle.activeTrajectory # NIL THEN ForAllSegments[handle.activeTrajectory.first.segments, DrawAsBackground]; handle.activeTrajectory _ activeTrajectory; IF handle.activeTrajectory # NIL THEN ForAllSegments[handle.activeTrajectory.first.segments, DrawAsForeground]; RequestRefresh[handle.refreshData]; }; }; TrajectoryIsEmpty: PUBLIC PROCEDURE [pathData: PathData] RETURNS [empty: BOOLEAN] = { <> handle: Handle _ NARROW[pathData]; RETURN [handle.activeTrajectory = NIL OR handle.activeTrajectory.first.segments = NIL]; }; DeleteActiveTrajectory: PUBLIC PROCEDURE [pathData: PathData] = { <> handle: Handle _ NARROW[pathData]; UnDraw: SegmentProc = { DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: s.first, undo: TRUE]; }; IF handle.activeTrajectory = NIL THEN RETURN; DisableTrajectoryRefresh[handle.activeTrajectory.first]; ForAllSegments[handle.activeTrajectory.first.segments, UnDraw]; [handle.trajectoryList,] _ RemoveTrajectory[handle.trajectoryList, handle.activeTrajectory]; DoSetActiveTrajectory[handle, NIL]; }; GetTrajectoryFile: PUBLIC PROCEDURE [pathData: PathData, fileName: Rope.ROPE] = { <> handle: Handle _ NARROW[pathData]; trajectories: TrajectoryList _ ReadTrajectoryFile[fileName]; t: TrajectoryNode _ LastTrajectoryNode[handle.trajectoryList]; IF t = NIL THEN handle.trajectoryList _ trajectories ELSE { t.rest _ trajectories; IF trajectories # NIL THEN trajectories.preceding _ t; }; DoSetActiveTrajectory[handle, NIL]; DoRequestRefresh[handle]; }; StoreTrajectoryFile: PUBLIC PROCEDURE [pathData: PathData, fileName: Rope.ROPE] = { <> handle: Handle _ NARROW[pathData]; WriteTrajectoryFile[fileName, handle.trajectoryList]; }; EnumerateTrajectories: PUBLIC PROCEDURE [pathData: PathData, moveToProc: MoveToProc, lineToProc: LineToProc, curveToProc: CurveToProc] = { <> handle: Handle _ NARROW[pathData]; EnumerateTrajectory: TrajectoryProc = { handle.activeTrajectory _ t; EnumerateActiveTrajectory[pathData, moveToProc, lineToProc, curveToProc]; }; saveActiveTrajectory: TrajectoryNode _ handle.activeTrajectory; ForAllTrajectories[handle.trajectoryList, EnumerateTrajectory]; handle.activeTrajectory _ saveActiveTrajectory; }; <> <<>> AddKnotToFront: PUBLIC PROCEDURE [pathData: PathData, position: Point] = { <> handle: Handle _ NARROW[pathData]; knot: Vertex _ NEW[VertexRec _ [point: position]]; newSegment: Segment _ NEW[SegmentRec _ [type: moveTo]]; newTrajectory: Trajectory; IF handle.activeTrajectory = NIL AND handle.newTrajectory THEN { newTrajectory _ NEW[TrajectoryRec]; [handle.trajectoryList, handle.activeTrajectory] _ InsertTrajectory[handle.trajectoryList, newTrajectory, NIL]; handle.newTrajectory _ FALSE; }; IF handle.activeTrajectory = NIL THEN RETURN; IF handle.activeTrajectory.first.segments # NIL THEN { DisableSegmentRefresh[handle.activeTrajectory.first.segments.first]; handle.activeTrajectory.first.segments.first.type _ curveTo; }; [newSegment.vertices,] _ InsertVertex[newSegment.vertices, knot, NIL]; [handle.activeTrajectory.first.segments,] _ InsertSegment[handle.activeTrajectory.first.segments, newSegment, NIL]; Constrain[pathViewer: handle.pathViewer, vertex: handle.activeTrajectory.first.segments.first.vertices, segment: handle.activeTrajectory.first.segments, newPosition: position]; DoSetActiveVertex[handle, handle.activeTrajectory.first.segments.first.vertices, handle.activeTrajectory.first.segments]; InvalidateHitCache[handle]; }; AddCubicToFront: PUBLIC PROCEDURE [pathData: PathData, cp1, cp2, knot: Point] = { <> handle: Handle _ NARROW[pathData]; knotVertex: Vertex _ NEW[VertexRec _ [point: knot]]; cp1Vertex: Vertex _ NEW[VertexRec _ [point: cp1]]; cp2Vertex: Vertex _ NEW[VertexRec _ [point: cp2]]; newSegment: Segment _ NEW[SegmentRec _ [type: moveTo]]; IF handle.activeTrajectory = NIL THEN RETURN; IF handle.activeTrajectory.first.segments # NIL THEN { DisableSegmentRefresh[handle.activeTrajectory.first.segments.first]; handle.activeTrajectory.first.segments.first.type _ curveTo; [handle.activeTrajectory.first.segments.first.vertices,] _ InsertVertex[handle.activeTrajectory.first.segments.first.vertices, cp1Vertex, NIL]; [handle.activeTrajectory.first.segments.first.vertices,] _ InsertVertex[handle.activeTrajectory.first.segments.first.vertices, cp2Vertex, NIL]; DrawVertices[handle.pathViewer, handle.activeTrajectory.first.segments.first]; [newSegment.vertices,] _ InsertVertex[newSegment.vertices, knotVertex, NIL]; [handle.activeTrajectory.first.segments,] _ InsertSegment[handle.activeTrajectory.first.segments, newSegment, NIL]; Constrain[pathViewer: handle.pathViewer, vertex: handle.activeTrajectory.first.segments.rest.first.vertices.rest, segment: handle.activeTrajectory.first.segments.rest, newPosition: cp1]; EnableSegmentRefresh[handle.activeTrajectory.first.segments.rest.first]; }; DoSetActiveVertex[handle, NIL, NIL]; InvalidateHitCache[handle]; }; AddKnotToRear: PUBLIC PROCEDURE [pathData: PathData, position: Point] = { <> handle: Handle _ NARROW[pathData]; knot: Vertex _ NEW[VertexRec _ [point: position]]; newSegment: Segment _ NEW[SegmentRec]; newSegmentNode: SegmentNode; IF handle.activeTrajectory = NIL OR handle.activeTrajectory.first.segments = NIL THEN AddKnotToFront[pathData, position] ELSE { [newSegment.vertices,] _ InsertVertex[newSegment.vertices, knot, NIL]; [handle.activeTrajectory.first.segments, newSegmentNode] _ InsertSegment[handle.activeTrajectory.first.segments, newSegment, LastSegmentNode[handle.activeTrajectory.first.segments]]; Constrain[pathViewer: handle.pathViewer, vertex: newSegment.vertices, segment: newSegmentNode, newPosition: position]; DoSetActiveVertex[handle, newSegment.vertices, newSegmentNode]; InvalidateHitCache[handle]; }; }; AddCubicToRear: PUBLIC PROCEDURE [pathData: PathData, cp1, cp2, knot: Point] = { <> handle: Handle _ NARROW[pathData]; knotVertex: Vertex _ NEW[VertexRec _ [point: knot]]; cp1Vertex: Vertex _ NEW[VertexRec _ [point: cp1]]; cp2Vertex: Vertex _ NEW[VertexRec _ [point: cp2]]; newSegment: Segment _ NEW[SegmentRec _ [type: moveTo]]; newSegmentNode: SegmentNode; IF handle.activeTrajectory = NIL THEN RETURN; IF handle.activeTrajectory.first.segments # NIL THEN { [newSegment.vertices,] _ InsertVertex[newSegment.vertices, knotVertex, NIL]; [newSegment.vertices,] _ InsertVertex[newSegment.vertices, cp2Vertex, NIL]; [newSegment.vertices,] _ InsertVertex[newSegment.vertices, cp1Vertex, NIL]; DrawVertices[handle.pathViewer, newSegment]; [handle.activeTrajectory.first.segments, newSegmentNode] _ InsertSegment[handle.activeTrajectory.first.segments, newSegment, LastSegmentNode[handle.activeTrajectory.first.segments]]; Constrain[pathViewer: handle.pathViewer, vertex: newSegment.vertices, segment: newSegmentNode, newPosition: cp1]; }; DoSetActiveVertex[handle, NIL, NIL]; InvalidateHitCache[handle]; }; SplitSegment: PUBLIC PROCEDURE [pathData: PathData, where: Point] = { <> handle: Handle _ NARROW[pathData]; segment: SegmentNode; point: Point; t: REAL; originalBezier: Bezier; b1, b2: Bezier; IF handle.activeTrajectory = NIL THEN RETURN; [segment, point, t] _ SegmentHitTest[handle.activeTrajectory.first.segments, where]; originalBezier _ SegmentToBezier[segment.first]; [b1, b2] _ SplitBezier[originalBezier, t]; [handle.splitReplacements,] _ InsertSegment[NIL, BezierToSegment[b1], NIL]; handle.splitReplacements.first.fp _ segment.first.fp; [handle.splitReplacements,] _ InsertSegment[handle.splitReplacements, BezierToSegment[b2], handle.splitReplacements]; handle.splitReplacements.rest.first.fp _ VertexListNthElement[handle.splitReplacements.first.vertices, -1]; DisableSegmentRefresh[handle.splitReplacements.first]; DisableSegmentRefresh[handle.splitReplacements.rest.first]; [handle.activeTrajectory.first.segments, handle.splitSegment] _ SwapSegments[handle.activeTrajectory.first.segments, handle.splitReplacements, segment]; DrawControlPoints[pathViewer: handle.pathViewer, segment: segment.first, undo: TRUE]; DrawVertices[pathViewer: handle.pathViewer, segment: handle.splitReplacements.first]; DrawControlPoints[pathViewer: handle.pathViewer, segment: handle.splitReplacements.rest.first]; DoSetActiveVertex[handle, NIL, NIL]; InvalidateHitCache[handle]; }; AdjustSegmentSplit: PUBLIC PROCEDURE [pathData: PathData, where: Point] = { <> handle: Handle _ NARROW[pathData]; hitSegment: SegmentNode; hitPoint: Point; hitT: REAL; IF handle.activeTrajectory = NIL THEN RETURN; IF handle.splitSegment # NIL THEN { [handle.activeTrajectory.first.segments, handle.splitReplacements] _ SwapSegments[handle.activeTrajectory.first.segments, handle.splitSegment, handle.splitReplacements, 2]; DrawVertices[pathViewer: handle.pathViewer, segment: handle.splitReplacements.first, undo: TRUE]; DrawControlPoints[pathViewer: handle.pathViewer, segment: handle.splitReplacements.rest.first, undo: TRUE]; DrawSegment[pathViewer: handle.pathViewer, segment: handle.splitSegment.first]; }; [hitSegment, hitPoint, hitT] _ SegmentHitTest[handle.activeTrajectory.first.segments, where]; IF handle.splitSegment # NIL THEN { IF hitSegment # handle.splitSegment THEN { DrawVertices[pathViewer: handle.pathViewer, segment: handle.splitReplacements.first, undo: TRUE]; DrawControlPoints[pathViewer: handle.pathViewer, segment: handle.splitReplacements.rest.first, undo: TRUE]; DrawControlPoints[pathViewer: handle.pathViewer, segment: handle.splitSegment.first]; EnableSegmentRefresh[segment: handle.splitSegment.first]; }; FreeSegmentList[handle.splitReplacements]; handle.splitReplacements _ NIL; }; IF hitSegment = NIL THEN handle.splitSegment _ NIL ELSE SplitSegment[pathData: pathData, where: where]; DoRequestRefresh[handle]; DoSetActiveVertex[handle, NIL, NIL]; InvalidateHitCache[handle]; }; ConfirmSegmentSplit: PUBLIC PROCEDURE[pathData: PathData] = { <> handle: Handle _ NARROW[pathData]; IF handle.activeTrajectory = NIL THEN RETURN; IF handle.splitSegment # NIL THEN { DrawSegment[pathViewer: handle.pathViewer, segment: handle.splitSegment.first, undo: TRUE]; FreeSegmentList[handle.splitSegment]; handle.splitSegment _ NIL; DrawSegment[pathViewer: handle.pathViewer, segment: handle.splitReplacements.first]; DrawSegment[pathViewer: handle.pathViewer, segment: handle.splitReplacements.rest.first]; EnableSegmentRefresh[handle.splitReplacements.first]; EnableSegmentRefresh[handle.splitReplacements.rest.first]; }; InvalidateHitCache[handle]; }; AddControlPoint: PUBLIC PROCEDURE [pathData: PathData, where, position: Point] = { <> handle: Handle _ NARROW[pathData]; vertex: Vertex _ NEW[VertexRec _ [point: position]]; newVertexNode: VertexNode _ NIL; hitSegment: SegmentNode; hitPoint: Point; hitT: REAL; numberVertices, maxNumberVertices: CARDINAL; segmentFull: BOOLEAN; precedingVertex: VertexNode; d0, d1: REAL; IF handle.activeTrajectory = NIL THEN RETURN; [hitSegment, hitPoint, hitT] _ SegmentHitTest[handle.activeTrajectory.first.segments, where]; IF hitSegment # NIL THEN { numberVertices _ VertexListLength[hitSegment.first.vertices]; maxNumberVertices _ SELECT hitSegment.first.type FROM moveTo => 1, curveTo => 3, ENDCASE => 0; segmentFull _ numberVertices >= maxNumberVertices; IF ~segmentFull THEN { DrawSegment[pathViewer: handle.pathViewer, segment: hitSegment.first, undo: TRUE]; IF numberVertices < 2 THEN precedingVertex _ NIL ELSE { d0 _ DistanceSquared[where, ClosestPointOnLineSegment[point: where, p0: hitSegment.first.fp.point, p1: hitSegment.first.vertices.first.point]]; d1 _ DistanceSquared[where, ClosestPointOnLineSegment[point: where, p0: hitSegment.first.vertices.first.point, p1: hitSegment.first.vertices.rest.first.point]]; precedingVertex _ IF d0 < d1 THEN NIL ELSE hitSegment.first.vertices; }; [hitSegment.first.vertices, newVertexNode] _ InsertVertex[hitSegment.first.vertices, vertex, precedingVertex]; Constrain[pathViewer: handle.pathViewer, vertex: newVertexNode, segment: hitSegment, newPosition: position]; InvalidateHitCache[handle]; }; }; DoSetActiveVertex[handle, newVertexNode, hitSegment]; }; <<>> StoreBetas: PUBLIC PROCEDURE [pathData: PathData, bias: REAL, tension: REAL] = { <> activeSegment: PETypes.SegmentNode; -- current segment as segment list is walked first: BOOLEAN _ TRUE; -- indicates if segment is first seg. to be processed firstSegInList: PETypes.Segment; -- ptr to first segment in active trajectory handle: Handle _ NARROW[pathData]; handleSegList: PETypes.SegmentList; -- ptr into active trajectory segment list newSegmentList: PETypes.SegmentList; -- list of the spline segments affected by change splineTraj: PETypes.Trajectory _ NIL; -- trajectory node containing new spline tempSegList: PETypes.SegmentList _ NIL; -- temporary segment list of new spline trajectory as it is being built up IF handle.activeTrajectory = NIL THEN RETURN; IF handle.activeTrajectory.first.segments.global = TRUE THEN{ <<-- Recompute the entire spline curve.>> activeSegment _ handle.activeTrajectory.first.segments; firstSegInList _ activeSegment.first; WHILE first OR activeSegment.first # firstSegInList DO activeSegment.first.vertices.first.bias _ bias; activeSegment.first.vertices.first.tension _ tension; activeSegment _ activeSegment.rest; IF first THEN first _ FALSE; ENDLOOP; newSegmentList _ handle.activeTrajectory.first.segments; handleSegList _ handle.activeTrajectory.first.segments; } ELSE { <<-- Change only the stored beta values for the active vertex.>> IF handle.activeVertex = NIL THEN RETURN; handle.activeVertex.first.bias _ bias; handle.activeVertex.first.tension _ tension; [handleSegList, newSegmentList] _ BuildBetaSplineSegment[handle, FALSE]; }; <<-- Compute the new curve segment(s)>> tempSegList _ PEBetaSpline.BetaSplinePoints[newSegmentList, handleSegList, FALSE]; splineTraj _ PEConstructSpline.BuildBetaSplineTrajectory[tempSegList]; InsertBetaSplineSegment[handle, splineTraj, handle.activeTrajectory.first.segments.global]; newSegmentList _ NIL; tempSegList _ NIL; DoRequestRefresh[handle]; }; SetGlobal: PUBLIC PROCEDURE [pathData: PathData] = { <> handle: Handle _ NARROW[pathData]; IF handle.activeTrajectory = NIL THEN RETURN; handle.activeTrajectory.first.segments.global _ TRUE; }; SetLocal: PUBLIC PROCEDURE [pathData: PathData] = { <> handle: Handle _ NARROW[pathData]; IF handle.activeTrajectory = NIL THEN RETURN; handle.activeTrajectory.first.segments.global _ FALSE; }; SetSimplify: PUBLIC PROCEDURE [pathData: PathData, delta: REAL] = { <> collinearSegNode: PETypes.SegmentNode; filtered: BOOLEAN; firstSegment: PETypes.SegmentNode; handle: Handle _ NARROW[pathData]; newControlPolygon: PETypes.Trajectory; newSpline: PETypes.Trajectory; newSplineSegmentList: PETypes.SegmentList; newTrajectoryList: PETypes.TrajectoryList; oldSegList: PETypes.SegmentList; segment: PETypes.SegmentNode; IF handle.activeTrajectory = NIL THEN RETURN; [filtered, collinearSegNode, newControlPolygon, newSpline, oldSegList] _ PESimplify.WeightedMidpoint[handle.activeTrajectory, delta]; IF filtered THEN{ -- Control polygon was filtered [newTrajectoryList, ] _ PETrajectoryOps.InsertTrajectory[handle.trajectoryList, newControlPolygon, NIL]; handle.trajectoryList _ newTrajectoryList; IF newSpline # NIL THEN{ [handle.trajectoryList, ] _ PETrajectoryOps.InsertTrajectory[newTrajectoryList, newSpline, NIL]; handle.trajectoryList.rest.spline _ handle.trajectoryList.first; }; } ELSE IF collinearSegNode # NIL THEN { -- Collinear points were found and simplified [handle.trajectoryList] _ DeleteVertex[pathData: pathData, vertex: collinearSegNode.first.vertices.first.point, collinear: TRUE, closestVertex: collinearSegNode.first.vertices, closestSegment: collinearSegNode]; } ELSE IF oldSegList # NIL THEN{ -- Circular arcs were found and simplified <<-- Obsolete control polygon must be erased>> firstSegment _ oldSegList; DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: firstSegment.first, undo: TRUE]; segment _ firstSegment.rest; WHILE segment # firstSegment DO DrawSegmentAndVertices[handle.pathViewer, segment.first, TRUE]; segment _ segment.rest; ENDLOOP; <<-- Obsolete spline curve must be erased and new one calculated>> PERefresh.DisableTrajectoryRefresh[handle.activeTrajectory.spline]; firstSegment _ handle.activeTrajectory.spline.segments; EraseOldSpline[handle, firstSegment]; segment _ firstSegment.rest; WHILE segment # firstSegment DO EraseOldSpline[handle, segment]; segment _ segment.rest; ENDLOOP; [newSplineSegmentList] _ PEBetaSpline.BetaSplinePoints[handle.activeTrajectory.first.segments, handle.activeTrajectory.first.segments, FALSE]; IF newSplineSegmentList # NIL THEN{ newSplineSegmentList.first.type _ moveTo; newSplineSegmentList.first.fp _ NIL; handle.activeTrajectory.spline.segments _ newSplineSegmentList; }; PERefresh.EnableTrajectoryRefresh[handle.activeTrajectory.spline]; }; DoRequestRefresh[handle]; }; CloseActiveTrajectory: PUBLIC PROCEDURE [pathData: PathData] = { <> fp: VertexNode; handle: Handle _ NARROW[pathData]; handleSegList: PETypes.SegmentList; -- pointer into old segment list to first affected pt. newSegmentList: PETypes.SegmentList; -- list of ctrl pts associated w/new spline segment segment: PETypes.SegmentNodeRec; -- holds current segment as segment list is walked splineTraj: PETypes.Trajectory; -- trajectory node pointing to new spline curve splineTrajectory: PETypes.TrajectoryList; -- pointer to old spline curve tempSegList: PETypes.SegmentList _ NIL; -- temporary segment list of new spline as it is being built up IF handle.activeTrajectory = NIL THEN RETURN; IF handle.activeTrajectory.first.segments.first.type = moveTo THEN { handle.activeTrajectory.first.segments.first.type _ curveTo; [fp,] _ PrecedingVertex[handle.activeTrajectory.first.segments.first.vertices, handle.activeTrajectory.first.segments]; handle.activeTrajectory.first.segments.first.fp _ IF fp # NIL THEN fp.first ELSE NIL; DrawSegment[pathViewer: handle.pathViewer, segment: handle.activeTrajectory.first.segments.first]; handle.activeTrajectory.first.segments _ handle.activeTrajectory.first.segments.preceding; handle.activeSegment _ handle.activeTrajectory.first.segments; handle.activeVertex _ handle.activeSegment.first.vertices; -- Create the new spline segment to tack onto the head of the spline trajectory newSegmentList _ NEW[PETypes.SegmentNodeRec]; newSegmentList _ NIL; handleSegList _ handle.activeTrajectory.first.segments.rest.rest.rest; segment _ handleSegList^; FOR i: CARDINAL _ 0, i+1 UNTIL i = 4 DO -- use the 4 ctrl pts at head for new spline seg newSegmentList _ PEConstructSpline.LinkBetaSplineSegments[tempSegList, segment]; tempSegList _ newSegmentList; segment _ segment.preceding^; handleSegList _ handleSegList.preceding; ENDLOOP; newSegmentList.first.type _ moveTo; newSegmentList.first.fp _ NIL; tempSegList _ PEBetaSpline.BetaSplinePoints[newSegmentList, handleSegList, FALSE]; splineTraj _ PEConstructSpline.BuildBetaSplineTrajectory[tempSegList]; newSegmentList _ NIL; tempSegList _ NIL; <<-- Find the trajectory that is the spline associated with the active trajectory>> splineTrajectory _ handle.trajectoryList; WHILE splineTrajectory.first # handle.activeTrajectory.spline DO splineTrajectory _ splineTrajectory.rest ENDLOOP; PERefresh.DisableTrajectoryRefresh[handle.activeTrajectory.spline]; PERefresh.DisableTrajectoryRefresh[splineTrajectory.first]; <<-- Tack the new spline segment to the head of the old spline>> splineTrajectory.first.segments.first.type _ curveTo; [newSegmentList] _ PETrajectoryOps.LinkSegments[splineTrajectory.first.segments, NIL, splineTraj.segments]; splineTrajectory.first.segments _ newSegmentList; handle.activeTrajectory.spline _ splineTrajectory.first; <<-- Set spline properties for proper refreshing>> IF handle.activeTrajectory.spline # NIL THEN{ handle.activeTrajectory.spline.segments.first.type _ moveTo; handle.activeTrajectory.spline.segments.first.fp _ NIL; PERefresh.EnableTrajectoryRefresh[handle.activeTrajectory.spline]; }; DoRequestRefresh[handle]; }; DoSetActiveVertex[handle, NIL, NIL]; }; OpenActiveTrajectory: PUBLIC PROCEDURE [pathData: PathData] = { <> handle: Handle _ NARROW[pathData]; lastVertex: VertexNode; newSegmentList: PETypes.SegmentList; -- new spline segments, after leading seg is deleted splineTrajectory: PETypes.TrajectoryList; -- pointer to old spline segments DoDeleteVertex: VertexProc = { IF v # lastVertex THEN { DrawVertex[pathViewer: handle.pathViewer, vertex: v.first, undo: TRUE]; [handle.activeTrajectory.first.segments.first.vertices,] _ RemoveVertex[handle.activeTrajectory.first.segments.first.vertices, v]; }; }; IF handle.activeTrajectory = NIL THEN RETURN; IF handle.activeTrajectory.first.segments.first.type # moveTo THEN { <<-- Find the spline trajectory associated with the active trajectory>> handle.activeVertex _ handle.activeTrajectory.first.segments.first.vertices; splineTrajectory _ handle.trajectoryList; WHILE splineTrajectory.first # handle.activeTrajectory.spline DO splineTrajectory _ splineTrajectory.rest ENDLOOP; PERefresh.DisableTrajectoryRefresh[handle.activeTrajectory.spline]; PERefresh.DisableTrajectoryRefresh[splineTrajectory.first]; <<-- Delete the first segment of the spline trajectory associated with the active trajectory>> newSegmentList _ DeleteSplineSegmentHead[handle, splineTrajectory]; splineTrajectory.first.segments _ newSegmentList; handle.activeTrajectory.spline _ splineTrajectory.first; IF handle.activeTrajectory.spline # NIL THEN{ handle.activeTrajectory.spline.segments.first.type _ moveTo; handle.activeTrajectory.spline.segments.first.fp _ NIL; PERefresh.EnableTrajectoryRefresh[handle.activeTrajectory.spline]; }; handle.activeTrajectory.first.segments _ handle.activeTrajectory.first.segments.rest; DrawSegment[pathViewer: handle.pathViewer, segment: handle.activeTrajectory.first.segments.first, undo: TRUE]; lastVertex _ LastVertexNode[handle.activeTrajectory.first.segments.first.vertices]; ForAllVertices[handle.activeTrajectory.first.segments.first.vertices, DoDeleteVertex]; handle.activeTrajectory.first.segments.first.type _ moveTo; handle.activeTrajectory.first.segments.first.fp _ NIL; }; DoSetActiveVertex[handle, NIL, NIL]; }; DeleteVertex: PUBLIC PROCEDURE [pathData: PathData, vertex: Point, collinear: BOOLEAN, closestVertex: VertexNode, closestSegment: SegmentNode] RETURNS [newTrajectoryList: PETypes.TrajectoryList]= { <> closed: BOOLEAN _ FALSE; -- indicates if active trajectory is closed pVertex, fVertex: VertexNode; pSegment, fSegment: SegmentNode; handle: Handle _ NARROW[pathData] ; handleSegList: PETypes.SegmentList; -- pointer into old seg list to first affected point newSegmentList: PETypes.SegmentList; -- list of ctrl pts associated w/new spline segment splineTraj: PETypes.Trajectory; -- trajectory node pointing to new spline curve tempSegList: PETypes.SegmentList _ NIL; --temporary segment list of new spline as it is being built up IF handle.activeTrajectory = NIL THEN RETURN; IF ~collinear THEN -- The point to delete is the closest one next to the mouse cursor [closestVertex, closestSegment] _ VertexHitTest[handle.activeTrajectory.first.segments, vertex]; [pVertex, pSegment] _ PrecedingVertex[closestVertex, closestSegment]; [fVertex, fSegment] _ FollowingVertex[closestVertex, closestSegment]; IF closestVertex # NIL THEN { <<-- Re-compute the part of the spline that is affected by the deletion of the control point>> handle.activeSegment _ closestSegment; handle.activeVertex _ closestVertex; [handleSegList, newSegmentList] _ BuildBetaSplineSegment[handle, TRUE]; tempSegList _ PEBetaSpline.BetaSplinePoints[newSegmentList, handleSegList, FALSE]; splineTraj _ PEConstructSpline.BuildBetaSplineTrajectory[tempSegList]; InsertBetaSplineSegment[handle, splineTraj, FALSE]; newSegmentList _ NIL; tempSegList _ NIL; IF IsAControlPoint[closestVertex] THEN { DisableSegmentRefresh[segment: closestSegment.first]; DrawVertex[pathViewer: handle.pathViewer, vertex: closestVertex.first, undo: TRUE]; DrawSegment[pathViewer: handle.pathViewer, segment: closestSegment.first, undo: TRUE]; [closestSegment.first.vertices,] _ RemoveVertex[closestSegment.first.vertices, closestVertex]; DrawSegment[pathViewer: handle.pathViewer, segment: closestSegment.first]; EnableSegmentRefresh[segment: closestSegment.first]; } ELSE { SELECT TRUE FROM closestSegment.rest = closestSegment => { DisableSegmentRefresh[segment: closestSegment.first]; DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: closestSegment.first, undo: TRUE]; [handle.activeTrajectory.first.segments,] _ RemoveSegment[handle.activeTrajectory.first.segments, closestSegment] }; IsFirstSegment[closestSegment] => { DisableSegmentRefresh[segment: closestSegment.first]; DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: closestSegment.first, undo: TRUE]; IF IsLastSegment[closestSegment] THEN [handle.activeTrajectory.first.segments,] _ RemoveSegment[handle.activeTrajectory.first.segments, closestSegment] ELSE { DisableSegmentRefresh[segment: closestSegment.rest.first]; DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: closestSegment.rest.first, undo: TRUE]; closestSegment.first.vertices.first^ _ LastVertexNode[closestSegment.rest.first.vertices].first^; [handle.activeTrajectory.first.segments,] _ RemoveSegment[handle.activeTrajectory.first.segments, closestSegment.rest]; DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: closestSegment.first]; EnableSegmentRefresh[segment: closestSegment.first]; }; }; IsLastSegment[closestSegment] => { DisableSegmentRefresh[segment: closestSegment.first]; DrawSegmentAndVertices[pathViewer: handle.pathViewer, segment: closestSegment.first, undo: TRUE]; [handle.activeTrajectory.first.segments,] _ RemoveSegment[handle.activeTrajectory.first.segments, closestSegment]; }; ENDCASE => MergeSegments[handle: handle, segmentNode: closestSegment]; }; IF TrajectoryIsEmpty[pathData] THEN { DeleteActiveTrajectory[pathData]; NewTrajectory[pathData]; } -- For some reason, pointers get changed when a vertex is deleted, so perform a god-awful hack to correct for that occurrence, ie. re-compute the entire spline curve all over again. ELSE { IF handle.activeTrajectory.first.segments.first.type = curveTo THEN closed _ TRUE; tempSegList _ PEBetaSpline.BetaSplinePoints[handle.activeTrajectory.first.segments, handle.activeTrajectory.first.segments, closed]; IF tempSegList # NIL THEN{ tempSegList.first.type _ moveTo; tempSegList.first.fp _ NIL; }; IF handle.activeTrajectory.spline # NIL THEN { PERefresh.DisableTrajectoryRefresh[handle.activeTrajectory.spline]; handle.activeTrajectory.spline.segments _ tempSegList; PERefresh.EnableTrajectoryRefresh[handle.activeTrajectory.spline]; }; tempSegList _ NIL; }; newTrajectoryList _ handle.trajectoryList; DoRequestRefresh[handle]; }; DoSetActiveVertex[handle, NIL, NIL]; InvalidateHitCache[handle]; }; MergeSegments: PROCEDURE [handle: Handle, segmentNode: SegmentNode] = { <> DeleteVertex: VertexProc = { DrawVertex[pathViewer: handle.pathViewer, vertex: v.first, undo: TRUE]; [segmentNode.first.vertices,] _ RemoveVertex[segmentNode.first.vertices, v]; }; vertexNode: VertexNode; vertexList: VertexList; IF IsLastSegment[segmentNode] THEN RETURN; DisableSegmentRefresh[segment: segmentNode.first]; DisableSegmentRefresh[segment: segmentNode.rest.first]; DrawSegment[pathViewer: handle.pathViewer, segment: segmentNode.first, undo: TRUE]; DrawSegment[pathViewer: handle.pathViewer, segment: segmentNode.rest.first, undo: TRUE]; vertexNode _ segmentNode.first.vertices; IF vertexNode.rest # NIL THEN vertexNode _ vertexNode.rest; ForAllVertices[vertexNode, DeleteVertex]; vertexList _ segmentNode.rest.first.vertices; segmentNode.rest.first.vertices _ NIL; IF VertexListLength[vertexList] > 2 THEN { DrawVertex[pathViewer: handle.pathViewer, vertex: vertexList.first, undo: TRUE]; [vertexList,] _ RemoveVertex[vertexList, vertexList]; }; segmentNode.first.vertices _ VertexListAppend[segmentNode.first.vertices, vertexList]; [handle.activeTrajectory.first.segments,] _ RemoveSegment[handle.activeTrajectory.first.segments, segmentNode.rest]; DrawSegment[pathViewer: handle.pathViewer, segment: segmentNode.first]; EnableSegmentRefresh[segment: segmentNode.first]; }; SetActiveVertex: PUBLIC PROCEDURE [pathData: PathData, where: Point] RETURNS [activeVertexSet: BOOLEAN, vertex: Point] = { <> handle: Handle _ NARROW[pathData]; activeVertex: VertexNode; activeSegment: SegmentNode; IF handle.activeTrajectory = NIL THEN activeVertex _ NIL ELSE [activeVertex, activeSegment] _ VertexHitTest[handle.activeTrajectory.first.segments, where]; DoSetActiveVertex[handle, activeVertex, activeSegment]; activeVertexSet _ activeVertex # NIL; IF activeVertexSet THEN vertex _ activeVertex.first.point; }; DoSetActiveVertex: PROCEDURE [handle: Handle, activeVertex: VertexNode, activeSegment: SegmentNode] = { <> IF activeVertex = NIL THEN { handle.activeVertex _ NIL; handle.activeSegment _ NIL; } ELSE { handle.activeVertex _ activeVertex; handle.activeSegment _ activeSegment; ViewerTools.SetContents[handle.biasViewer, Convert.RopeFromReal[handle.activeVertex.first.bias]]; ViewerTools.SetContents[handle.tensionViewer, Convert.RopeFromReal[handle.activeVertex.first.tension]]; }; }; GetActiveVertex: PUBLIC PROCEDURE [pathData: PathData] RETURNS [activeVertexSet: BOOLEAN, vertex: Point] = { <> handle: Handle _ NARROW[pathData]; activeVertexSet _ handle.activeVertex # NIL; IF activeVertexSet THEN vertex _ handle.activeVertex.first.point; }; MoveActiveVertex: PUBLIC PROCEDURE [pathData: PathData, newPosition: Point] = { <> handle: Handle _ NARROW[pathData]; handleSegList: PETypes.SegmentList; -- pointer into old segment list to first affected point newSegmentList: PETypes.SegmentList; -- list of ctrl pts associated w/new spline segment splineTraj: PETypes.Trajectory _ NIL; -- trajectory node pointing to new spline curve tempSegList: PETypes.SegmentList; -- temporary segment list of new spline as it is being built up IF handle.activeVertex # NIL THEN { Constrain[pathViewer: handle.pathViewer, vertex: handle.activeVertex, segment: handle.activeSegment, newPosition: newPosition]; <<-- Re-compute the spline segments that are changed by the movement of the control point>> [handleSegList, newSegmentList] _ BuildBetaSplineSegment[handle, FALSE]; tempSegList _ PEBetaSpline.BetaSplinePoints[newSegmentList, handleSegList, FALSE]; splineTraj _ PEConstructSpline.BuildBetaSplineTrajectory[tempSegList]; InsertBetaSplineSegment[handle, splineTraj, FALSE]; newSegmentList _ NIL; tempSegList _ NIL; DoRequestRefresh[handle]; }; InvalidateHitCache[handle]; }; SetContinuity: PUBLIC PROCEDURE [pathData: PathData, knot: Point, continuity: BooleanSpecification] = { <> handle: Handle _ NARROW[pathData]; hitVertex: VertexNode; hitSegment: SegmentNode; IF handle.activeTrajectory = NIL THEN RETURN; [hitVertex, hitSegment] _ VertexHitTest[handle.activeTrajectory.first.segments, knot]; IF IsAKnot[hitVertex] THEN { DisableSegmentRefresh[hitSegment.first]; DrawVertex[pathViewer: handle.pathViewer, vertex: hitVertex.first, undo: TRUE]; DrawVertex[pathViewer: handle.pathViewer, vertex: hitVertex.first]; EnableSegmentRefresh[hitSegment.first]; }; }; EnumerateActiveTrajectory: PUBLIC PROCEDURE [pathData: PathData, moveToProc: MoveToProc, lineToProc: LineToProc, curveToProc: CurveToProc] = { <> handle: Handle _ NARROW[pathData]; EnumerateSegment: SegmentProc = { numberVertices: CARDINAL; numberVertices _ VertexListLength[s.first.vertices]; SELECT TRUE FROM s.first.type = moveTo => moveToProc[p1: s.first.vertices.first.point]; numberVertices = 1 => lineToProc[p1: s.first.vertices.first.point]; numberVertices = 2 => curveToProc[p1: s.first.vertices.first.point, p2: s.first.vertices.first.point, p3: s.first.vertices.rest.first.point]; numberVertices = 3 => curveToProc[p1: s.first.vertices.first.point, p2: s.first.vertices.rest.first.point, p3: s.first.vertices.rest.rest.first.point]; ENDCASE; }; IF handle.activeTrajectory = NIL THEN RETURN; ForAllSegments[handle.activeTrajectory.first.segments, EnumerateSegment]; }; HitTestVertices: PUBLIC PROCEDURE [pathData: PathData, where: Point] RETURNS [closeToVertex: BOOLEAN, vertexType: VertexType, vertex: Point] = { <> handle: Handle _ NARROW[pathData]; hitVertex: VertexNode; hitSegment: SegmentNode; IF handle.vertexHit.hitCached AND where = handle.vertexHit.where THEN { closeToVertex _ handle.vertexHit.closeToVertex; IF closeToVertex THEN { vertexType _ handle.vertexHit.vertexType; vertex _ handle.vertexHit.vertex; }; } ELSE { IF handle.activeTrajectory = NIL THEN hitVertex _ NIL ELSE [hitVertex, hitSegment] _ VertexHitTest[handle.activeTrajectory.first.segments, where]; closeToVertex _ hitVertex # NIL; IF closeToVertex THEN { vertex _ hitVertex.first.point; vertexType _ SELECT TRUE FROM IsAKnot[hitVertex] AND IsFirstSegment[hitSegment] => frontKnot, IsAKnot[hitVertex] AND IsLastSegment[hitSegment] => rearKnot, IsAKnot[hitVertex] => intermediateKnot, ENDCASE => controlPoint; }; handle.vertexHit.hitCached _ TRUE; handle.vertexHit.closeToVertex _ closeToVertex; IF closeToVertex THEN { handle.vertexHit.vertexType _ vertexType; handle.vertexHit.vertex _ vertex; }; }; }; HitTestCurves: PUBLIC PROCEDURE [pathData: PathData, where: Point] RETURNS [closeToCurve: BOOLEAN, curveType: CurveType, p0, p1, p2, p3: Point, hitPoint: Point] = { <> handle: Handle _ NARROW[pathData]; hitT: REAL; hitSegment: SegmentNode; numberVertices: CARDINAL; bezier: Bezier; IF handle.curveHit.hitCached AND where = handle.curveHit.where THEN { closeToCurve _ handle.curveHit.closeToCurve; IF closeToCurve THEN { curveType _ handle.curveHit.curveType; p0 _ handle.curveHit.p0; p1 _ handle.curveHit.p1; p2 _ handle.curveHit.p2; p3 _ handle.curveHit.p3; hitPoint _ handle.curveHit.hitPoint; }; } ELSE { IF handle.activeTrajectory = NIL THEN hitSegment _ NIL ELSE [hitSegment, hitPoint, hitT] _ SegmentHitTest[handle.activeTrajectory.first.segments, where]; closeToCurve _ hitSegment # NIL; IF closeToCurve THEN { numberVertices _ VertexListLength[hitSegment.first.vertices]; bezier _ SegmentToBezier[hitSegment.first]; p0 _ bezier.b0; p1 _ bezier.b1; p2 _ bezier.b2; p3 _ bezier.b3; curveType _ SELECT numberVertices FROM 0 => point, 1 => line, ENDCASE => bezier; IF curveType = line THEN p1 _ bezier.b3; }; handle.curveHit.hitCached _ TRUE; handle.curveHit.closeToCurve _ closeToCurve; IF closeToCurve THEN { handle.curveHit.curveType _ curveType; handle.curveHit.p0 _ p0; handle.curveHit.p1 _ p1; handle.curveHit.p2 _ p2; handle.curveHit.p3 _ p3; handle.curveHit.hitPoint _ hitPoint; }; }; }; BuildBetaSplineSegment: PRIVATE PROCEDURE [handle: Handle, delete: BOOLEAN] RETURNS [handleSegList: PETypes.SegmentList, newSegmentList: PETypes.SegmentList] = { <> closed: BOOLEAN _ FALSE; -- indicates if active trajectory is closed count: INT; -- counter to ensure max # of ctrl. pts. is not exceeded first: BOOLEAN; -- indicates if a point is first one to be processed firstSegInList: PETypes.Segment; -- pointer to first segment in active trajectory influencingCPts: CARDINAL _ 3; -- max of 3 ctrl. pts. to one side of active Vertex can influence changing shape of the active spline curve numOfInfluencingCPts: CARDINAL _ 6; -- 0 to 6 => 7 ctrl pts max are affected here segment: PETypes.SegmentNodeRec; -- holds ptr to current segment as seg list is walked tempSegList: PETypes.SegmentList; -- temporarily holds the new segment list as it is being built <<-- Build new segment list containing affected control point. Also keep tabs on the corresponding positions within the active segment list.>> segment _ handle.activeSegment^; handleSegList _ handle.activeSegment; firstSegInList _ handle.activeTrajectory.first.segments.first; IF firstSegInList.type = curveTo THEN closed _ TRUE; first _ TRUE; count _ influencingCPts; WHILE (segment.first # firstSegInList AND count > 0) OR first DO <<-- Walk backward from active vertex to find first affected control point>> segment _ segment.rest^; handleSegList _ handleSegList.rest; count _ count-1; IF first THEN first _ FALSE; ENDLOOP; IF segment.first = firstSegInList AND ~closed THEN { <<-- Exclude the last segment found. We have wrapped around to the head of the active trajectory >> segment _ segment.preceding^; handleSegList _ handleSegList.preceding; count _ count+1; }; tempSegList _ NEW[PETypes.SegmentNodeRec]; tempSegList _ NIL; newSegmentList _ NEW[PETypes.SegmentNodeRec]; first _ TRUE; <<-- At this point, we have backed up to the first affected control point. Now walk forward to find the, at most, six other affected control points.>> WHILE (segment.first # firstSegInList AND count < numOfInfluencingCPts) OR first DO <<-- If the active vertex is to be deleted, then we do not want to include it in the new seg. list>> IF ~(delete AND segment.first.vertices = handle.activeVertex) THEN{ newSegmentList _ PEConstructSpline.LinkBetaSplineSegments[tempSegList, segment]; tempSegList _ newSegmentList; }; segment _ segment.preceding^; handleSegList _ handleSegList.preceding; count _ count+1; IF first THEN first _ FALSE; ENDLOOP; IF count <= numOfInfluencingCPts AND ~(delete AND segment.first.vertices = handle.activeVertex)THEN { <<-- Include the last seg. found if it is the head of the active traj. & it is not being deleted>> newSegmentList _ PEConstructSpline.LinkBetaSplineSegments[tempSegList, segment]; }; newSegmentList.first.type _ moveTo; newSegmentList.first.fp _ NIL; <<>> }; EraseOldSpline: PRIVATE PROCEDURE [handle: Handle, segment: PETypes.SegmentNode] = { <> DrawSegment[pathViewer: handle.pathViewer, segment: segment.first, undo: TRUE]; }; InsertBetaSplineSegment: PRIVATE PROCEDURE [handle: Handle, splineTraj: PETypes.Trajectory, changeAll: BOOLEAN] = { <> count: INT; first: BOOLEAN; -- TRUE iff a pt or seg is first one to be processed found: BOOLEAN _ TRUE; -- TRUE iff an old segment needs to be replaced insertSeg: PETypes.SegmentNode; -- place in seg list where new seg is to be placed newSegmentList: PETypes.SegmentList _ NIL; -- list of ctrl pts associated w/new spline seg. newTrajectoryList: PETypes.TrajectoryList; -- if no spline yet exists, a new one is created replacedFirstSeg: BOOLEAN _ FALSE; -- TRUE iff the first seg in the old list is replaced saveSeg: PETypes.SegmentNode; -- temp ptr to segments, usu. the first one in a list splineTrajectory: PETypes.TrajectoryList; -- pointer to old spline curve tempSegList: PETypes.SegmentList; -- temporary segment list of new spline as it is being built up IF handle.activeTrajectory.spline # NIL AND splineTraj # NIL THEN { -- replace old spline with new <<-- Find the 1st point on the spline associated with the active trajectory>> splineTrajectory _ handle.trajectoryList; WHILE splineTrajectory.first # handle.activeTrajectory.spline DO splineTrajectory _ splineTrajectory.rest ENDLOOP; PERefresh.DisableTrajectoryRefresh[handle.activeTrajectory.spline]; PERefresh.DisableTrajectoryRefresh[splineTrajectory.first]; <<-- The movement or addition of a control point will change the shape of the spline associated with the active trajectory or control polygon. We must retrieve those spline segments (a maximum of 4) that will be affected. This retrieval can be done because each spline segment has pointers to the 4 control points that determine its shape. If a control point is being added, then the boolean, found, is set to FALSE to indicate that no old segment is needs to be replaced.>> insertSeg _ splineTrajectory.first.segments; saveSeg _ insertSeg; first _ TRUE; IF changeAll THEN found _ TRUE <<-- Finding the first affected spline segment>> ELSE WHILE insertSeg.first.controlPtN # handle.activeVertex AND insertSeg.first.controlPtC # handle.activeVertex AND insertSeg.first.controlPtP # handle.activeVertex AND insertSeg.first.controlPtPP # handle.activeVertex AND (insertSeg # saveSeg OR first) DO insertSeg _ insertSeg.rest; IF insertSeg = saveSeg THEN found _ FALSE; IF first THEN first _ FALSE; ENDLOOP; IF found THEN { -- splice the new segments somewhere into the old spline traj. saveSeg _ insertSeg; first _ TRUE; count _ 0; IF changeAll THEN{ -- erase the whole old spline WHILE insertSeg # saveSeg OR first DO first _ FALSE; EraseOldSpline[handle, insertSeg]; insertSeg _ insertSeg.rest; ENDLOOP; } ELSE { -- erase a part of the old spline WHILE ((insertSeg.first.controlPtN = handle.activeVertex OR insertSeg.first.controlPtC = handle.activeVertex OR insertSeg.first.controlPtP = handle.activeVertex OR insertSeg.first.controlPtPP = handle.activeVertex) AND insertSeg # saveSeg) OR first DO first _ FALSE; count _ count+1; EraseOldSpline[handle, insertSeg]; insertSeg _ insertSeg.rest; ENDLOOP; IF saveSeg = splineTrajectory.first.segments THEN replacedFirstSeg _ TRUE; [newSegmentList,] _ PETrajectoryOps.UnlinkSegments[splineTrajectory.first.segments, saveSeg, count]; }; <<-- Replace the whole old spline with the new one>> IF newSegmentList = NIL OR changeAll THEN splineTrajectory.first _ splineTraj <<-- Or replace just a part of the old spline with the new spline piece>> ELSE [splineTrajectory.first.segments] _ PETrajectoryOps.LinkSegments[newSegmentList, insertSeg.preceding, splineTraj.segments]; <<-- If the first segment of the old spline has been replaced, then all pointers to the spline must be updated to point to the head of the new spline segment>> IF replacedFirstSeg THEN splineTrajectory.first.segments _ splineTraj.segments; } ELSE{ -- tack the new curve segments onto the head or tail of the spline trajectory IF handle.activeSegment.first.type = moveTo THEN { -- tack it on the head splineTrajectory.first.segments.first.type _ curveTo; [newSegmentList] _ PETrajectoryOps.LinkSegments[splineTrajectory.first.segments, NIL, splineTraj.segments]; splineTrajectory.first.segments _ newSegmentList; } ELSE { -- tack it on the tail [newSegmentList] _ PETrajectoryOps.LinkSegments[splineTrajectory.first.segments, splineTrajectory.first.segments.preceding, splineTraj.segments]; splineTrajectory.first.segments _ newSegmentList; }; }; handle.activeTrajectory.spline _ splineTrajectory.first; } ELSE IF handle.activeTrajectory.spline = NIL AND splineTraj # NIL THEN { <<-- Start a new spline trajectory>> [newTrajectoryList, ] _ PETrajectoryOps.InsertTrajectory[handle.trajectoryList, splineTraj, NIL]; PERefresh.DisableTrajectoryRefresh[newTrajectoryList.first]; handle.trajectoryList _ newTrajectoryList; handle.activeTrajectory.spline _ handle.trajectoryList.first; } ELSE IF handle.activeTrajectory.spline # NIL AND splineTraj = NIL THEN { <<-- Delete either the head or tail of the spline trajectory>> <<-- First, find the spline associated with the active trajectory>> splineTrajectory _ handle.trajectoryList; WHILE splineTrajectory.first # handle.activeTrajectory.spline DO splineTrajectory _ splineTrajectory.rest ENDLOOP; PERefresh.DisableTrajectoryRefresh[handle.activeTrajectory.spline]; PERefresh.DisableTrajectoryRefresh[splineTrajectory.first]; IF handle.activeSegment.first.type = moveTo THEN { -- Delete spline curve segment head newSegmentList _ DeleteSplineSegmentHead[handle, splineTrajectory]; } ELSE{ -- Delete spline curve segment tail insertSeg _ splineTrajectory.first.segments.preceding; newSegmentList _ splineTrajectory.first.segments; WHILE insertSeg.first.controlPtPP = handle.activeVertex DO EraseOldSpline[handle, insertSeg]; [tempSegList, ] _ PETrajectoryOps.RemoveSegment[newSegmentList, insertSeg]; newSegmentList _ tempSegList; insertSeg _ newSegmentList.preceding; ENDLOOP; }; splineTrajectory.first.segments _ newSegmentList; handle.activeTrajectory.spline _ splineTrajectory.first; }; IF handle.activeTrajectory.spline # NIL AND handle.activeTrajectory.spline.segments # NIL THEN{ -- Reset the spline parameters for proper refreshing handle.activeTrajectory.spline.segments.first.type _ moveTo; handle.activeTrajectory.spline.segments.first.fp _ NIL; PERefresh.EnableTrajectoryRefresh[handle.activeTrajectory.spline]; } ELSE IF handle.activeTrajectory.spline # NIL AND handle.activeTrajectory.spline.segments = NIL THEN{ -- The spline trajectory is empty, so remove the spline from the trajectory list handle.activeTrajectory.spline _ NIL; [handle.trajectoryList, ] _ PETrajectoryOps.RemoveTrajectory[handle.trajectoryList, splineTrajectory]; }; }; DeleteSplineSegmentHead: PRIVATE PROCEDURE [handle: Handle, splineTrajectory: PETypes.TrajectoryList] RETURNS [newSegmentList: PETypes.SegmentList] = { <> first: BOOLEAN; -- TRUE iff a point or segment is first one to be processed insertSeg: PETypes.SegmentNode; -- place in segment list where new segment is to be placed tempSegList: PETypes.SegmentList; -- temporary segment list of new spline as it is being built up <<-- Delete spline curve segment head>> newSegmentList _ NIL; IF splineTrajectory.first.segments = NIL THEN RETURN; insertSeg _ splineTrajectory.first.segments; newSegmentList _ splineTrajectory.first.segments; first _ TRUE; WHILE insertSeg.first.controlPtN = handle.activeVertex AND (first OR insertSeg # splineTrajectory.first.segments) DO IF first THEN first _ FALSE; EraseOldSpline[handle, insertSeg]; insertSeg _ insertSeg.rest; ENDLOOP; <<-- The following seven lines of code is not my idea of efficiency, but for reasons I cannot fathom, it will not work if I place the segment removal code in the while loop directly above>> insertSeg _ splineTrajectory.first.segments; first _ TRUE; WHILE insertSeg # NIL AND insertSeg.first.controlPtN = handle.activeVertex AND (first OR insertSeg # splineTrajectory.first.segments) DO IF first THEN first _ FALSE; [tempSegList, ] _ PETrajectoryOps.RemoveSegment[newSegmentList, insertSeg]; newSegmentList _ tempSegList; insertSeg _ newSegmentList; ENDLOOP; IF insertSeg = NIL THEN newSegmentList _ NIL; }; InvalidateHitCache: PROCEDURE [handle: Handle] = { <> handle.vertexHit.hitCached _ FALSE; handle.curveHit.hitCached _ FALSE; }; DoRequestRefresh: PROCEDURE [handle: Handle] = { <> NewRefreshData[handle.refreshData, handle.trajectoryList, handle.activeTrajectory]; RequestRefresh[handle.refreshData]; }; END. <<>>