PathBuilder.mesa
Written by Darlene Plebon on August 31, 1983 11:36 am
This program is the driver for the Path Tool.
Last Edited by: Beach, September 26, 1983 1:56 pm
Last Edited by: Spreitzer, May 29, 1985 12:12:32 pm PDT
DIRECTORY
Graphics USING [black, Box, DrawTo, GetBounds, SetColor, SetCP, SetFat, white],
PathEditor USING [AddControlPoint, AddKnotToFront, AddKnotToRear, AdjustSegmentSplit, BooleanSpecification, ButtonHitProc, CloseActiveTrajectory, ConfirmSegmentSplit, CreatePathViewer, CurveType, DeleteActiveTrajectory, DeleteVertex, DrawInPathViewer, DrawProc, FixControlPoint, FlushImage, GetTrajectoryFile, HitTestCurves, HitTestVertices, MoveActiveVertex, NewTrajectory, OpenActiveTrajectory, PathData, Point, Redraw, SetActiveTrajectory, SetActiveVertex, SetContinuity, SetTrajectoryContinuity, SplitSegment, StoreTrajectoryFile, TrajectoryIsEmpty, VertexType],
Real USING [RoundI],
Rope USING [ROPE],
ViewerTools USING [GetSelectionContents];
PathBuilder:
CEDAR
PROGRAM
IMPORTS Graphics, PathEditor, Real, ViewerTools =
BEGIN OPEN PathEditor;
handle: Handle ← NEW[SplineToolRec];
Handle: TYPE = REF SplineToolRec;
SplineToolRec:
TYPE =
RECORD [
pathData: PathData,
gridConstrain: BOOLEAN ← FALSE,
redHeldProc: ButtonProc ← NIL,
redUpProc: ButtonProc ← NIL
];
ButtonProc: TYPE = PROCEDURE [point: Point];
ButtonHit: ButtonHitProc = {
IF handle.gridConstrain THEN [x, y] ← GridConstrain[x, y];
SELECT event
FROM
$NewImage => FlushImage[pathData: handle.pathData];
$Redraw => RedrawViewer;
$GridOn => SetGridConstraints[TRUE];
$GridOff => SetGridConstraints[FALSE];
$Get => GetTrajectories[];
$Store => StoreTrajectories[];
$NewTraj => NewTrajectory[pathData: handle.pathData];
$CloseTraj => CloseActiveTrajectory[pathData: handle.pathData];
$OpenTraj => OpenActiveTrajectory[pathData: handle.pathData];
$ConstrainTraj => SetTrajectoryContinuity[handle.pathData, toggle];
$SetActiveVertex => [] ← SetActiveVertex[pathData: handle.pathData, where: [x, y]];
$MoveActiveVertex => MoveActiveVertex[pathData: handle.pathData, newPosition: [x, y]];
$SetConstraints => SetConstraints[point: [x, y]];
$SetActiveTrajectory => [] ← SetActiveTrajectory[pathData: handle.pathData, where: [x, y]];
$SetActiveTrajectory => [] ← SetActiveTrajectory[pathData: handle.pathData, where: [x, y]];
$AddKnot => AddKnot[point: [x, y]];
$MoveKnot => IF handle.redHeldProc # NIL THEN handle.redHeldProc[point: [x, y]];
$ConfirmMove => IF handle.redUpProc # NIL THEN handle.redUpProc[point: [x, y]];
$AddControlPoint => AddControlPoint[pathData: handle.pathData, where: [x, y], position: [x, y]];
$MoveActiveVertex => MoveActiveVertex[pathData: handle.pathData, newPosition: [x, y]];
$AddTrajectory => AddTrajectory[point: [x, y]];
$MoveActiveVertex => MoveActiveVertex[pathData: handle.pathData, newPosition: [x, y]];
$DeleteVertex => DeleteVertex[pathData: handle.pathData, vertex: [x, y]];
$DeleteTrajectory => DeleteTrajectory[point: [x, y]];
ENDCASE;
};
RedrawViewer:
PROCEDURE [] = {
This routine redraws the grid if it should currently be visible and the image being manipulated.
DrawInPathViewer[handle.pathData, DrawGrid];
Redraw[pathData: handle.pathData];
};
gridOffsetX: REAL = 0.0;
gridOffsetY: REAL = 0.0;
gridResolutionX: REAL = 16.0;
gridResolutionY: REAL = 16.0;
GridConstrain:
PROCEDURE [x, y:
REAL]
RETURNS [newX, newY:
REAL] = {
This routine constrains the point (x,y) to lie at the nearest grid point.
newX ← gridOffsetX + Real.RoundI[(x - gridOffsetX) / gridResolutionX] * gridResolutionX;
newY ← gridOffsetY + Real.RoundI[(y - gridOffsetY) / gridResolutionY] * gridResolutionY;
};
DrawGrid: DrawProc = {
bounds: Graphics.Box ← Graphics.GetBounds[context];
startX, startY, x, y: REAL;
[startX, startY] ← GridConstrain[bounds.xmin, bounds.ymin];
IF startX < bounds.xmin THEN startX ← startX + gridResolutionX;
IF startY < bounds.ymin THEN startY ← startY + gridResolutionY;
IF handle.gridConstrain THEN Graphics.SetColor[context, Graphics.black]
ELSE Graphics.SetColor[context, Graphics.white];
[] ← Graphics.SetFat[context, TRUE];
FOR y ← startY, y + gridResolutionY
UNTIL y > bounds.ymax
DO
FOR x ← startX, x + gridResolutionX
UNTIL x > bounds.xmax
DO
Graphics.SetCP[context, x, y];
Graphics.DrawTo[context, x, y];
ENDLOOP;
ENDLOOP;
};
SetGridConstraints:
PROCEDURE [on:
BOOLEAN] = {
This routine turns the grid constraints option on/off and set the grid visible/invisible accordingly.
handle.gridConstrain ← on;
DrawInPathViewer[handle.pathData, DrawGrid];
};
GetTrajectories:
PROCEDURE [] = {
This routine gets a path from a file.
fileName: Rope.ROPE ← ViewerTools.GetSelectionContents[];
GetTrajectoryFile[pathData: handle.pathData, fileName: fileName];
};
StoreTrajectories:
PROCEDURE [] = {
This routine stores a path on a file.
fileName: Rope.ROPE ← ViewerTools.GetSelectionContents[];
StoreTrajectoryFile[pathData: handle.pathData, fileName: fileName];
};
AddKnot: ButtonProc = {
This routine adds a knot to the path. Adding knots results in the creation of new segments.
closeToVertex, closeToCurve: BOOLEAN;
vertexType: VertexType;
curveType: CurveType;
[closeToVertex, vertexType,] ← HitTestVertices[pathData: handle.pathData, where: point];
SELECT
TRUE
FROM
TrajectoryIsEmpty[handle.pathData]
OR (closeToVertex
AND vertexType = frontKnot) => {
handle.redHeldProc ← MoveVertex;
handle.redUpProc ← NIL;
AddKnotToFront[pathData: handle.pathData, position: point];
};
closeToVertex
AND vertexType = rearKnot => {
handle.redHeldProc ← MoveVertex;
handle.redUpProc ← NIL;
AddKnotToRear[pathData: handle.pathData, position: point];
};
ENDCASE => {
[closeToCurve, curveType,] ← HitTestCurves[pathData: handle.pathData, where: point];
IF ~closeToCurve
THEN {
handle.redHeldProc ← NIL;
handle.redUpProc ← NIL;
}
ELSE {
handle.redHeldProc ← MoveSplit;
handle.redUpProc ← ConfirmSplit;
SplitSegment[pathData: handle.pathData, where: point];
};
};
};
AddTrajectory: ButtonProc = {
This routine starts a new trajectory.
NewTrajectory[pathData: handle.pathData];
AddKnot[point: point];
};
MoveVertex: ButtonProc = {
This routine moves a vertex.
MoveActiveVertex[pathData: handle.pathData, newPosition: point];
};
MoveSplit: ButtonProc = {
This routine adjusts a segment split.
AdjustSegmentSplit[pathData: handle.pathData, where: point];
};
ConfirmSplit: ButtonProc = {
This routine confirms the splitting of a segment. That is it makes it permanent.
ConfirmSegmentSplit[pathData: handle.pathData];
handle.redHeldProc ← NIL;
handle.redUpProc ← NIL;
};
SetConstraints: ButtonProc = {
This routine sets the continuity constraints at a vertex (for both knots and control points).
closeToVertex: BOOLEAN;
vertexType: VertexType;
[closeToVertex, vertexType,] ← HitTestVertices[pathData: handle.pathData, where: point];
SELECT
TRUE
FROM
closeToVertex
AND vertexType = controlPoint => {
FixControlPoint[pathData: handle.pathData, controlPoint: point, fixed: toggle];
};
closeToVertex => {
SetContinuity[pathData: handle.pathData, knot: point, continuity: toggle];
};
ENDCASE;
};
DeleteTrajectory: ButtonProc = {
This routine deletes the trajectory being pointed at.
[] ← SetActiveTrajectory[pathData: handle.pathData, where: point];
DeleteActiveTrajectory[pathData: handle.pathData];
};
[handle.pathData] ← CreatePathViewer[
name: "Path Tool",
menuLabels:
LIST[
[$NewImage, TRUE],
[$Redraw, FALSE],
[$GridOn, FALSE],
[$GridOff, FALSE],
[$Get, TRUE],
[$Store, TRUE],
[$NewTraj, FALSE],
[$CloseTraj, FALSE],
[$OpenTraj, FALSE],
[$ConstrainTraj, FALSE]
],
buttonProc: ButtonHit,
redrawProc: DrawGrid
];