G2dContourCmdsImpl.mesa
Copyright Ó 1985, 1992 by Xerox Corporation. All rights reserved.
Bloomenthal, July 20, 1992 3:37 pm PDT
DIRECTORY Commander, Controls, ControlsPrivate, FS, G2dBasic, G2dContour, G2dTool, Imager, IO, Rope, TIPUser, ViewerClasses, ViewerOps;
G2dContourCmdsImpl: CEDAR PROGRAM
IMPORTS Controls, ControlsPrivate, FS, G2dContour, G2dTool, Imager, IO, TIPUser, ViewerOps
~ BEGIN
Types
Control:     TYPE ~ Controls.Control;
IntegerPair:    TYPE ~ G2dBasic.IntegerPair;
IntegerPairSequence: TYPE ~ G2dBasic.IntegerPairSequence;
Pair:      TYPE ~ G2dBasic.Pair;
Contour:     TYPE ~ G2dContour.Contour;
ROPE:      TYPE ~ Rope.ROPE;
ProgramData:    TYPE ~ REF ProgramDataRec;
ProgramDataRec:   TYPE ~ RECORD [
outerData:      Controls.OuterData ¬ NIL,  -- some good stuff here
alpha:        Control ¬ NIL,     -- contour interpolator
scaler:        Control ¬ NIL,     -- contour scaler
contour:       Control ¬ NIL,     -- the contour control
contour0:       Control ¬ NIL,     -- key contour one
contour1:       Control ¬ NIL,     -- key contour two
save:        IntegerPairSequence ¬ NIL, -- permit undo
center:       Pair ¬ [0, 0],      -- of contour
normals:       BOOL ¬ FALSE     -- show contour normals?
];
General Testing Program
ContoursTest: Commander.CommandProc ~ {
p: ProgramData ¬ NEW[ProgramDataRec];
p.contour ¬ Controls.NewControl[
name: "Contour and Normals", type: contour, clientData: p, w: 180, flavor: $Normals];
p.scaler ¬ Controls.NewControl["Scale", vSlider, p, 0.0, 5.0, 1.0, Scale,,,,,,, 138];
p.outerData ¬ Controls.OuterViewer[
name: "Contours Test",
column: left,
controls: LIST[p.contour, p.scaler],
buttons: LIST[
Controls.ClickButton["Normals-Off", ToggleNormals, p, 0],
Controls.ClickButton["Read", Read, p, 0],
Controls.ClickButton["Write", Write, p, 0],
Controls.ClickButton["Undo", Undo, p, 1],
Controls.ClickButton["Center", Center, p, 1],
Controls.ClickButton["Thin", Thin, p, 1],
Controls.ClickButton["Smooth", Smooth, p, 1]],
typescriptHeight: 18,
clientData: p];
};
Scale: Controls.ControlProc ~ {
p: ProgramData ¬ NARROW[control.clientData];
IF control.mouse.state = down THEN {
ips: IntegerPairSequence ¬ Controls.GetContour[p.contour];
IF ips # NIL AND (p.save = NIL OR p.save.length # ips.length) THEN {
p.save ¬ Controls.GetContour[p.contour];
p.center ¬ G2dContour.Centroid[G2dContour.FromIntegerPairs[p.save, TRUE]];
};
};
IF p.save # NIL THEN {
c: Contour ¬ G2dContour.FromIntegerPairs[p.save, TRUE];
c ¬ G2dContour.Offset[c, [-p.center.x, -p.center.y]];
c ¬ G2dContour.Scale[c, [p.scaler.value, p.scaler.value]];
c ¬ G2dContour.Offset[c, p.center];
ToControl[p.contour, c];
};
};
Center: Controls.ClickProc ~ {
p: ProgramData ¬ NARROW[clientData];
p.center ¬ [p.contour.w/2, p.contour.h/2];
ToControl[p.contour,G2dContour.Offset[G2dContour.Center[FromControl[p.contour]],p.center]];
p.save ¬ Controls.GetContour[p.contour];
};
Thin: Controls.ClickProc ~ {
p: ProgramData ¬ NARROW[clientData];
p.save ¬ Controls.GetContour[p.contour];
ToControl[p.contour, G2dContour.Thin[FromControl[p.contour]]];
};
Smooth: Controls.ClickProc ~ {
p: ProgramData ¬ NARROW[clientData];
p.save ¬ Controls.GetContour[p.contour];
ToControl[p.contour, G2dContour.Smooth[FromControl[p.contour]]];
};
Undo: Controls.ClickProc ~ {
p: ProgramData ¬ NARROW[clientData];
Controls.SetContour[p.contour, p.save, TRUE];
p.center ¬ G2dContour.Centroid[FromControl[p.contour]];
};
ToggleNormals: Controls.ClickProc ~ {
p: ProgramData ¬ NARROW[clientData];
Controls.ButtonToggle[p.outerData, p.normals ¬ NOT p.normals, "Normals-On", "Normals-Off"];
ViewerOps.PaintViewer[p.contour.viewer, client, FALSE, NIL];
};
PaintNormals: ViewerClasses.PaintProc ~ {
control: Control ¬ NARROW[self.data];
p: ProgramData ¬ NARROW[control.clientData];
IF p.normals
THEN {
contour: Contour ¬ FromControl[p.contour];
IF contour = NIL THEN RETURN;
contour.normals ¬ G2dContour.Normals[contour];
G2dContour.Paint[contour, context, TRUE];
}
ELSE [] ¬ ControlsPrivate.PaintContour[self, context, whatChanged, clear];
};
Read: Controls.ClickProc ~ {
p: ProgramData ¬ NARROW[clientData];
ts: Controls.Typescript ¬ p.outerData.typescript;
name: ROPE ¬ Controls.TypescriptReadFileName[ts];
IF name # NIL THEN {
stream: IO.STREAM ¬ FS.StreamOpen[name ! FS.Error => GOTO Bad];
ToControl[p.contour, G2dContour.Read[stream]];
EXITS Bad => Controls.TypescriptWrite[ts, IO.PutFR1["Can't open %g\n", IO.rope[name]]];
};
};
Write: Controls.ClickProc ~ {
p: ProgramData ¬ NARROW[clientData];
r: ROPE ¬ Controls.TypescriptReadFileName[p.outerData.typescript];
IF r # NIL THEN G2dContour.Write[FS.StreamOpen[r, $create], FromControl[p.contour]];
};
FromControl: PROC [control: Control, t: REAL ¬ 0.0] RETURNS [contour: Contour] ~ {
ips: IntegerPairSequence ¬ Controls.GetContour[control];
IF ips # NIL THEN contour ¬ G2dContour.FromIntegerPairs[ips, TRUE, t];
};
ToControl: PROC [control: Control, contour: Contour, repaint: BOOL ¬ TRUE] ~ {
IF contour = NIL
THEN Controls.Reset[control]
ELSE Controls.SetContour[
control, G2dContour.IntegerPairsFromPairs[contour.pairs], TRUE, repaint];
};
Contour Interpolation Command
ContoursInterpolate: Commander.CommandProc ~ {
p: ProgramData ¬ NEW[ProgramDataRec];
p.alpha ¬ Controls.NewControl["Alpha", vSlider, p, 0.0, 1.0, 0.5, Interpolate,,,,,,, 120];
p.contour0 ¬ Controls.NewControl[type: contour, w: 160, proc: Interpolate, clientData: p];
p.contour1 ¬ Controls.NewControl[type: contour, w: 160, proc: Interpolate, clientData: p];
p.contour ¬ Controls.NewControl[type: contour, w: 160, clientData: p];
p.outerData ¬ Controls.OuterViewer[
name: "Contours Interpolate",
controls: LIST[p.contour0, p.contour1, p.contour, p.alpha],
clientData: p
];
};
Interpolate: Controls.ControlProc ~ {
IF control.mouse.state # up THEN {
p: ProgramData ¬ NARROW[control.clientData];
contour0: Contour ¬ FromControl[p.contour0];
contour1: Contour ¬ FromControl[p.contour1];
ToControl[p.contour, G2dContour.Interpolate[contour0, contour1, p.alpha.value]];
};
};
Contour Similarity Command
SimilarData:  TYPE ~ REF SimilarDataRec;
SimilarDataRec: TYPE ~ RECORD [
outerData:    Controls.OuterData ← NIL,   -- some good stuff here
alpha:      Control ← NIL,   -- interpolation value
box0:      Control ← NIL,   -- control for contour0
box1:      Control ← NIL,   -- control for contour1
boxI0:      Control ← NIL,   -- control for misc. display
boxI1:      Control ← NIL,   -- control for misc. display
contour:     Contour ← NIL,   -- interpolated contour
contour0:     Contour ← NIL,   -- contour0
contour1:     Contour ← NIL,   -- contour1
save0:      Contour ← NIL,   -- to undo contour0
save1:      Contour ← NIL   -- to undo contour1
];
ContoursSimilar: Commander.CommandProc ~ {
NewControl: PROC [name: ROPE, row: NAT, proc: Controls.ControlProc, flavor: ATOM]
RETURNS [Control] ~ {
RETURN[Controls.NewControl[
name: name, type: contour, w: 150, proc: proc, clientData: p, row: row, flavor: flavor]];
};
p: SimilarData ← NEW[SimilarDataRec];
p.alpha ← Controls.NewControl["Alpha", vSlider, p, 0.0, 1.0, 0.5, Alpha, , , , , , , 120];
p.boxI0 ← NewControl["Interp Vs. Contour0", 0, NIL, $Similar];
p.boxI1 ← NewControl["Interp Vs. Contour1", 0, NIL, $Similar];
p.box0 ← NewControl["Contour0", 1, Contour0Changed, $Nil];
p.box1 ← NewControl["Contour1", 1, Contour1Changed, $Nil];
p.outerData ← Controls.OuterViewer[
name: "Contours Similar",
controls: LIST[p.boxI0, p.boxI1, p.box0, p.box1, p.alpha],
buttons: LIST[
Controls.ClickButton["Undo", SimilarUndo],
Controls.ClickButton["Circles", SimilarCircles],
Controls.ClickButton["Smooth", SimilarSmooth],
Controls.ClickButton["Thin", SimilarThin]],
clientData: p];
};
PrepContour: PROC [contour: Contour] RETURNS [Contour] ~ {
IF contour = NIL THEN RETURN[NIL];
contour ← G2dContour.Orient[contour];
contour.normals ← G2dContour.Normals[contour];
contour.percents ← G2dContour.Percents[contour];
RETURN[contour];
};
Contour0Changed: Controls.ControlProc ~ {
p: SimilarData ← NARROW[control.clientData];
p.contour0 ← PrepContour[FromControl[p.box0]];
Similar[p];
};
Contour1Changed: Controls.ControlProc ~ {
p: SimilarData ← NARROW[control.clientData];
p.contour1 ← PrepContour[FromControl[p.box1]];
Similar[p];
};
Alpha: Controls.ControlProc ~ {
IF control.mouse.state # up THEN Similar[NARROW[control.clientData, SimilarData]];
};
Similar: PROC [p: SimilarData] ~ {
IF G2dContour.ContourOK[p.contour0] AND G2dContour.ContourOK[p.contour1] THEN {
p.contour ← G2dContour.Interpolate[p.contour0, p.contour1, p.alpha.value];
p.contour.normals ← G2dContour.Normals[p.contour];
}
ELSE p.contour ← NIL;
ViewerOps.PaintViewer[p.boxI0.viewer, client, FALSE, NIL];
ViewerOps.PaintViewer[p.boxI1.viewer, client, FALSE, NIL];
};
PaintSimilar: ViewerClasses.PaintProc ~ {
ShowNormalsRange: PROC [c, cc: Contour] ~ {
Label: PROC [y: NAT, rope: ROPE] ~ {Draw2d.Label[context, [5, y], rope]};
Xform: PROC [p: Pair] RETURNS [Pair] ~ {RETURN[[wScale*(p.x+1), hScale*(p.y+1)]]};
DrawNormal: PROC [base, normal: Pair, scale: REAL] ~ {
Draw2d.Arrow[context, base, Vector2.Add[base, Vector2.Mul[normal, scale]]];
};
IF G2dContour.ContourOK[c] AND G2dContour.ContourOK[cc] THEN {
min: REAL ← 10000.0;
p0: Pair ← Xform[c.pairs[c.pairs.length-1]];
FOR i: NAT IN [0..c.pairs.length) DO
p1: Pair ← Xform[c.pairs[i]];
n: Pair ← c.normals[i];
nn: Pair ← G2dContour.PercentNormal[cc, c.percents[i]];
min ← MIN[min, Vector2.Dot[n, nn]];
DrawNormal[p1, n, 20.0];
DrawNormal[p1, nn, 30.0];
Draw2d.Line[context, p0, p1, dotted];
p0 ← p1;
ENDLOOP;
Label[135, IO.PutFR["min = %6.3f", IO.real[min]]];
Label[125, IO.PutFR["%g vs %g points", IO.int[c.pairs.length], IO.int[cc.pairs.length]]];
};
};
p: SimilarData ← NARROW[NARROW[self.data, Control].clientData];
bounds: Imager.Rectangle ← ImagerBackdoor.GetBounds[context];
wScale: REAL ← 0.5*(bounds.w-1.0);
hScale: REAL ← 0.5*(bounds.h-1.0);
Draw2d.Clear[context];
SELECT self FROM
p.boxI0.viewer => ShowNormalsRange[p.contour, p.contour0];
p.boxI1.viewer => ShowNormalsRange[p.contour, p.contour1];
ENDCASE;
};
ActionProc: TYPE ~ PROC [contour: Contour] RETURNS [Contour];
DoToContours: PROC [p: SimilarData, action: ActionProc] ~ {
p.save0 ← G2dContour.Copy[p.contour0];
p.save1 ← G2dContour.Copy[p.contour1];
p.contour0 ← PrepContour[action[p.contour0]];
p.contour1 ← PrepContour[action[p.contour1]];
ToControl[p.box0, p.contour0];
ToControl[p.box1, p.contour1];
Similar[p];
};
SimilarUndo: Controls.ClickProc ~ {
p: SimilarData ← NARROW[NARROW[clientData, Controls.OuterData].clientData];
ToControl[p.box0, p.contour0 ← p.save0];
ToControl[p.box1, p.contour1 ← p.save1];
Similar[p];
};
SimilarClear: Controls.ClickProc ~ {
Action: ActionProc ~ {RETURN[NIL]};
DoToContours[NARROW[clientData], Action];
};
SimilarCircles: Controls.ClickProc ~ {
action: ActionProc ~ {
return: Contour ← G2dContour.Scale[G2dContour.Circle[4], [0.5, 0.5]];
return.circle ← FALSE;
RETURN[return];
};
DoToContours[NARROW[NARROW[clientData, Controls.OuterData].clientData], action];
};
SimilarThin: Controls.ClickProc ~ {
Action: ActionProc ~ {RETURN[G2dContour.Thin[contour]]};
DoToContours[NARROW[clientData], Action];
};
SimilarSmooth: Controls.ClickProc ~ {
Action: ActionProc ~ {RETURN[G2dContour.Smooth[contour]]};
DoToContours[NARROW[clientData], Action];
};
testUsage: ROPE ¬ "\nDraw a contour.";
interpolateUsage: ROPE ¬ "\nDraw two contours and interpolate between them.";
ViewerOps.RegisterViewerClass[$Normals, NEW[ViewerClasses.ViewerClassRec ¬ [
notify: Controls.NotifyControl,
paint: PaintNormals,
tipTable: TIPUser.InstantiateNewTIPTable["../Controls/Controls.tip"]]]];
ViewerOps.RegisterViewerClass[$Similar, NEW[ViewerClasses.ViewerClassRec ← [
notify: Controls.NotifyControl,
paint: PaintSimilar,
tipTable: TIPUser.InstantiateNewTIPTable["Controls.tip"]]]];
G2dTool.Register["ContoursTest", ContoursTest, testUsage];
G2dTool.Register["ContoursInterpolate", ContoursInterpolate, interpolateUsage];
G2dTool.Register["ContoursSimilar", ContoursSimilar, "Test Similar[]\n"];
END.
..
Fun and Hue
Outline: Controls.ClickProc ~ {
p: ProgramData ¬ NARROW[clientData];
IF ColorOk[p] THEN G2dContour.Outline[p.cd, FromControl[p.contour], p.color];
};
ColorOk: PROC [p: ProgramData] RETURNS [ok: BOOL ¬ TRUE] ~ {
ok ← CtBasic.InsureColorDisplayOn[8];
vt: Terminal.Virtual ← InterminalBackdoor.terminal;
IF p.cd = NIL THEN {
IF ColorDisplayManager.NextState[].level = off
THEN {
MessageWindow.Append["Turn on 8bpp color display"];
MessageWindow.Blink[];
RETURN[FALSE];
}
ELSE p.cd ← IF ColorDisplayManager.NextState[].type = $Dither8
THEN ImagerDitherContext.FromTerminal[vt, TRUE]
ELSE ImagerFullColorContext.FromTerminal[vt, TRUE];
};
};
Fun: Controls.ControlProc ~ {
p: ProgramData ← NARROW[control.clientData];
hue: REAL ← p.hue.value+0.02;
IF control.mouse.state = up OR NOT ColorOk[p] THEN RETURN;
IF hue > p.hue.max THEN hue ← p.hue.min;
Controls.SetSliderDialValue[p.hue, hue];
Controls.SetSliderDialValue[p.scaler, p.fun.value];
ScaleContour[NARROW[control.clientData, ProgramData]];
p.color ← ImagerColor.ColorFromRGB[ImagerColorFns.RGBFromHSL[[p.hue.value, 1.0, 0.5]]];
FillContour[p];
};
FillContour: PROC [p: ProgramData] ~ {
IF ColorOk[p] THEN G2dContour.Fill[p.cd, FromControl[p.contour], p.color];
};
Fill: Controls.ClickProc ~ {FillContour[NARROW[clientData]]};
FillMap: Controls.ClickProc ~ {
p: ProgramData ← NARROW[clientData];
IF ColorOk[p] THEN {
map: SampleMap ← ImagerSample.MapFromFrameBuffer[
Terminal.GetColorFrameBufferA[InterminalBackdoor.terminal]];
G2dContour.FillMap[map, FromControl[p.contour], 100];
};
};
Hue: Controls.ControlProc ~ {
p: ProgramData ← NARROW[control.clientData];
IF control.mouse.state = up THEN RETURN;
p.color ← ImagerColor.ColorFromRGB[ImagerColorFns.RGBFromHSL[[p.hue.value, 1.0, 0.5]]];
FillContour[p];
};
Compose Comand
SplineData:   TYPE ~ SplineStructure.SplineData;
ComposeData:  TYPE ~ REF ComposeDataRec;
ComposeDataRec:  TYPE ~ RECORD [
out:     IO.STREAMNIL,
outer, graphics:  Controls.Viewer ← NIL,
entries:    LIST OF Controls.Entry ← NIL,
cam:     Controls3d.Camera,    -- camera
c:      Coeffs ← NIL,
composed:   BOOLFALSE,
graphicsData:  Controls.GraphicsData ← NIL,
view:     Matrix3d.Matrix ← NIL
];
ContoursCompose: Commander.CommandProc ~ {
d: ComposeData ← NEW[ComposeDataRec];
d.out ← cmd.err;
d.view ← NEW[Matrix3d.MatrixRep];
d.cam ← Controls3d.InitCamera[scale: 3.0, proc: Controller, data: d];
d.c ← Spline3d.CoeffsFromHermite[[[-0.5,0.,0.], [1.,1.,0.], [0.5,0.,0.], [1.,-1.,0.]]];
Controls.ControlRow[d.cam.pan, 2];
d.outer ← Controls.OuterViewer[
name: "Cotour Composer",
column: left,
entries: LIST[["Compose", Compose], ["Reset", Reset]],
controls: LIST[
d.cam.xRot, d.cam.yRot, d.cam.zRot, d.cam.scale, d.cam.x, d.cam.y, d.cam.z, d.cam.fov],
graphicsHeight: 300,
graphicsShow: Display,
data: d];
d.entries ← NARROW[d.outer.data, Controls.OuterData].entries;
d.graphics ← NARROW[d.outer.data, Controls.OuterData].graphics;
d.graphicsData ← NARROW[d.graphics.data];
};
Controller: PUBLIC Controls.ControlProc ~ {
d: ComposeData ← NARROW[control.data];
SELECT control FROM
d.cam.pan, d.cam.tilt, d.cam.roll, d.cam.zoom, d.cam.dx, d.cam.dy, d.cam.dz, d.cam.fov => Controls3d.SetCamera[d.cam];
ENDCASE => NULL;
ViewerOps.PaintViewer[control.graphics, client, FALSE, control];
};
Reset: Controls.ClickProc ~ {
d: ComposeData ← NARROW[NARROW[clientData, Controls.OuterData].data];
d.c ← Spline3d.CoeffsFromHermite[[[-0.5,0.,0.], [1.,1.,0.], [0.5,0.,0.], [1.,-1.,0.]]];
ViewerOps.PaintViewer[d.graphics, client, FALSE, d];
};
Compose: Controls.ClickProc ~ {
d: ComposeData ← NARROW[NARROW[clientData, Controls.OuterData].data];
d.composed ← TRUE;
ViewerOps.PaintViewer[d.graphics, client, FALSE, d];
d.composed ← FALSE;
};
Display: Controls.GraphicsShow ~ {
d: ComposeData ← NARROW[data];
Action: PROC ~ {
d.view ← Controls3d.InitPix[context, w, h];
IF NOT d.composed
THEN FOR s: SplineData ← SplineStructure.contour, s.next WHILE s # NIL DO
Draw3d.DrawBezierPolygon[Spline3d.BezierFromCoeffs[s.c], context, d.view, dot];
Draw3d.DrawCurve[s.c, context, d.view];
ENDLOOP
ELSE Draw3d.DrawCurve[d.c, context, d.view];
};
Draw2d.DoWithBuffer[context, Action, w, h]
};
Miscellany
NegateSegment: PROC [pm: PixelMap, s0, s1: Spot] ~ {
x: REAL ← s0.x;
xMax: INTEGER ← pm.fMin+pm.fSize;
dx: REALIF s0.y # s1.y THEN (s1.x-s0.x)/(s1.y-s0.y) ELSE 1.0;
color: CARDINALIF dx < 0 THEN 100 ELSE p.color;
FOR y: INTEGER IN [s0.y..s1.y) DO
xx: INTEGER ← Real.RoundI[x];
dest: PixelMap ← ImagerPixelMap.SetWindow[p.pm, [y, xx, 1, xMax-xx]];
ImagerPixelMap.Transfer[dest, p.pm, [null, complement]];
x ← x+dx;
ENDLOOP;
};
Fill: Controls.ControlProc ~ {
Presumes contour is clockwise and its coordinates are  [-1..1].
p: ProgramData ← NARROW[control.data];
contour: Contour ← Contours.FromControl[p.contour];
SpotsStart: PROC [spots: SpotSequence] RETURNS [INTEGER] ~ {
start, ymin: INTEGER ← 10000;
FOR n: NAT IN [0..spots.length) DO
IF spots[n].y < ymin THEN {ymin ← spots[n].y; start ← n};
ENDLOOP;
RETURN[start];
};
FloodSegment: PROC [pm: PixelMap, s0, s1: Spot] ~ {
x: REAL ← s0.x;
xMax: INTEGER ← pm.fMin+pm.fSize;
dx: REALIF s0.y # s1.y THEN (s1.x-s0.x)/(s1.y-s0.y) ELSE 1.0;
color: CARDINALIF dx < 0 THEN 100 ELSE p.color;
FOR y: INTEGER IN [s0.y..s1.y] DO
xx: INTEGER ← Real.RoundI[x];
ImagerPixelMap.Fill[pm, [y, xx, 1, xMax-xx], color];
x ← x+dx;
ENDLOOP;
};
IF contour.closed THEN {
spots: SpotSequence ← PmSpotsFromContour[contour, p.pm];
n, start: INTEGER ← SpotsStart[spots];
s0, s1: Spot ← spots[start];
ImagerPixelMap.Fill[p.pm, [p.pm.sMin, p.pm.fMin, p.pm.sSize, p.pm.fSize], 60];
DO
s0 ← s1;
s1 ← spots[n ← (n+1) MOD spots.length];
FloodSegment[p.pm, s0, s1];
IF n = start THEN EXIT;
ENDLOOP;
};
};
PmSpotsFromContour: PROC [contour: Contour, pm: PixelMap, invert: BOOLTRUE]
RETURNS [SpotSequence] ~ {
spots: SpotSequence ← NEW[SpotSequenceRec[contour.pairs.length]];
scale: REAL(MIN[pm.sSize, pm.fSize]-1)/2.0;
xOffset: REAL ← pm.fMin+scale-1.0;
yOffset: REAL ← pm.sMin+scale-1.0;
xMin: INTEGER ← 10000;
spots.length ← spots.maxLength;
FOR n: NAT IN [0..spots.length) DO
spots[n] ← [
Real.RoundI[xOffset+scale*contour.pairs[n].x],
Real.RoundI[yOffset+scale*contour.pairs[n].y]];
ENDLOOP;
IF invert THEN FOR n: NAT IN [0..spots.length) DO
spots[n].y ← pm.sMin+pm.sSize-spots[n].y;
ENDLOOP;
RETURN[spots];
};
Negate: Controls.ControlProc ~ {
Presumes contour is clockwise and its coordinates are  [-1..1].
p: ProgramData ← NARROW[control.data];
yLast: INTEGER ← -1;
xMax: INTEGER ← p.pm.fMin+p.pm.fSize;
contour: Contour ← Contours.FromControl[p.contour];
NegateSegment: PROC [pm: PixelMap, s0, s1: Spot] ~ {
NegateToEnd: PROC [x, y: INTEGER] ~ {
IF y # yLast THEN {
dest: PixelMap ← ImagerPixelMap.SetWindow[pm, [y, x, 1, xMax-x]];
ImagerPixelMap.Transfer[dest, pm, [null, complement]];
yLast ← y;
};
};
IF s0.y = s1.y
THEN NegateToEnd[s0.x, s0.y]
ELSE {
Dot: Draw2d.PixelProc ~ {NegateToEnd[x, y]};
Draw2d.DoWithLine[s0.x, s0.y, s1.x, s1.y, Dot];
};
};
IF contour.closed THEN {
spots: SpotSequence ← PmSpotsFromContour[contour, p.pm];
s0, s1: Spot ← spots[spots.length-1];
CtBasic.FillPm[p.pm, 100];
FOR n: NAT IN [0..spots.length) DO
s0 ← s1;
s1 ← spots[n];
CtBasic.PutLine[p.pm, s0.x, s0.y, s1.x, s1.y, 200];
NegateSegment[p.pm, s0, s1];
ENDLOOP;
};
};
Negate: Controls.ControlProc ~ {
Presumes contour is clockwise and its coordinates are  [-1..1].
p: ProgramData ← NARROW[control.data];
yLast: INTEGER ← -1;
xMax: INTEGER ← p.pm.fMin+p.pm.fSize;
contour: Contour ← Contours.FromControl[p.contour];
NegateSegment: PROC [pm: PixelMap, s0, s1: Spot] ~ {
NegateToEnd: PROC [x, y: INTEGER] ~ {
IF y # yLast THEN {
dest: PixelMap ← ImagerPixelMap.SetWindow[pm, [y, x, 1, xMax-x]];
ImagerPixelMap.Transfer[dest, pm, [null, complement]];
yLast ← y;
};
};
IF s0.y = s1.y
THEN NegateToEnd[s0.x, s0.y]
ELSE {
Dot: Draw2d.PixelProc ~ {NegateToEnd[x, y]};
Draw2d.DoWithLine[s0.x, s0.y, s1.x, s1.y, Dot];
};
};
IF contour.closed THEN {
spots: SpotSequence ← PmSpotsFromContour[contour, p.pm];
s0, s1: Spot ← spots[spots.length-1];
CtBasic.FillPm[p.pm, 100];
FOR n: NAT IN [0..spots.length) DO
s0 ← s1;
s1 ← spots[n];
CtBasic.PutLine[p.pm, s0.x, s0.y, s1.x, s1.y, 200];
NegateSegment[p.pm, s0, s1];
ENDLOOP;
};
};
Random Contours
gaussArray:   Spline3d.RealSequence ← InitGaussSequence[1024];
Triple:    TYPE ~ Vector3d.Triple;
Coeffs:    TYPE ~ Spline3d.Coeffs;
Bezier:    TYPE ~ Spline3d.Bezier;
Box:     TYPE ~ RECORD [a, b, c, d: Triple];
BoxSequence:   TYPE ~ REF BoxSequenceRec;
BoxSequenceRec:  TYPE ~ RECORD [element: SEQUENCE maxLength: NAT OF Box];
SplineData:   TYPE ~ SplineStructure.SplineData;
SplineDataRec:  TYPE ~ SplineStructure.SplineDataRec;
ProgramData:   TYPE ~ REF ProgramDataRec;
ProgramDataRec:  TYPE ~ RECORD [
out:     IO.STREAMNIL,
outer, graphics:  Controls.Viewer ← NIL,
entries:    LIST OF Controls.Entry ← NIL,
mouse:    SplineStructure.Mouse ← [0, 0, none, left],
cam:     Controls3d.Camera,    -- camera
hold:     Controls3d.Hold,     -- handle on a point and its tangent
nSpl:     Controls.ControlData ← NIL, -- number of new splines from old one
nRec:     Controls.ControlData ← NIL, -- number of recursive levels
graphicsData:  Controls.GraphicsData ← NIL,
view:     Matrix3d.Matrix ← NIL,
recurse:  BOOLFALSE,
noPick:    BOOLTRUE,
s:      SplineData ← NIL,    -- all the contour splines
pd:     SplineStructure.PickData ← NIL
];
Contour: Commander.CommandProc ~ {
d: ProgramData ← NEW[ProgramDataRec];
d.out ← cmd.err;
d.pd ← NEW[SplineStructure.PickDataRec];
d.view ← NEW[Matrix3d.MatrixRep];
d.s ← NEW[SplineDataRec];
d.cam ← Controls3d.InitCamera[dz: 0.5, zoom: 3., fov: 0., proc: Controller, data: d];
d.hold ← Controls3d.InitHold[Controller, d];
d.pd.s ← SplineStructure.contour ← d.s;
d.s.c ← Spline3d.CoeffsFromHermite[[[-0.5, 0., 0.], [0.5, 0., 0.], [1., 1., 0.], [1., -1., 0.]]];
d.nSpl ← Controls.NewControl["nSpl", vert, , 2.0, 15.0, 4.0, TRUE, , , Controller, d];
d.nRec ← Controls.NewControl["nRec", vert, , 1.0, 15.0, 1.0, TRUE, , , Controller, d];
SplineStructure.PointPick[d.pd];
Controls3d.FocusHold[d.pd.tan, d.hold];
Controls.ControlRow[d.cam.pan, 2];
d.outer ← Controls.OuterViewer[
name: "Contour",
column: left,
entries: LIST[
["Recurse", Recurse], ["Randomize", Randomize],
["PickToggle", PickToggle], ["Reset", Reset], ["PrintC", PrintC]],
controls: LIST[
d.hold.x, d.hold.y, d.hold.z, d.hold.lng, d.hold.lat, d.hold.mag, d.nSpl, d.nRec,
d.cam.xRot, d.cam.yRot, d.cam.zRot, d.cam.scale, d.cam.x, d.cam.y, d.cam.z, d.cam.fov],
graphicsHeight: 300,
graphicsProc: ScreenPick,
graphicsShow: Display,
data: d];
d.entries ← NARROW[d.outer.data, Controls.OuterData].entries;
d.graphics ← NARROW[d.outer.data, Controls.OuterData].graphics;
d.graphicsData ← NARROW[d.graphics.data];
};
Controller: PUBLIC Controls.ControlProc ~ {
d: ProgramData ← NARROW[control.data];
IF d.pd.dividePending THEN {
SplineStructure.DivideSpline[d.pd];
Controls.ControlVal[d.hold.mag, Vector3d.Mag[d.pd.sPt0.v1]];
};
SELECT control FROM
d.hold.x, d.hold.y, d.hold.z =>
SplineStructure.ChangePosition[d.s, d.pd, [d.hold.x.val, d.hold.y.val, d.hold.z.val]];
d.hold.lng, d.hold.lat, d.hold.mag =>
IF NOT d.noPick THEN SplineStructure.ChangeTangent[
d.s, d.pd, Vector3d.CartesianFromPolar[[d.hold.lng.val, d.hold.lat.val, d.hold.mag.val]]];
d.cam.xRot, d.cam.yRot, d.cam.zRot, d.cam.scale, d.cam.x, d.cam.y, d.cam.z, d.cam.fov => Controls3d.UpDateCamera[d.cam];
ENDCASE => NULL;
ViewerOps.PaintViewer[control.graphics, client, FALSE, control];
};
ScreenPick: Controls.GraphicsProc ~ {
d: ProgramData ← NARROW[graphics.data];
IF graphics.mouse.state = up THEN RETURN;
IF graphics.mouse.button = left THEN {
SplineStructure.ScreenPick[d.s, graphics.mouse, d.view, d.pd];
Controls3d.FocusHold[d.pd.tan, d.hold];
};
};
PickToggle: Controls.ClickProc ~ {
d: ProgramData ← NARROW[NARROW[clientData, Controls.OuterData].data];
d.noPick ← NOT d.noPick;
ViewerOps.PaintViewer[d.graphics, client, FALSE, d];
};
PrintC: Controls.ClickProc ~ {
d: ProgramData ← NARROW[NARROW[clientData, Controls.OuterData].data];
FOR s: SplineData ← d.s, s.next WHILE s # NIL DO Print3d.Matrix[d.out, s.c]; ENDLOOP;
};
Reset: Controls.ClickProc ~ {
d: ProgramData ← NARROW[NARROW[clientData, Controls.OuterData].data];
SplineStructure.contour ← d.s ← NEW[SplineDataRec];
(d.pd.s ← d.s).c ← Spline3d.CoeffsFromHermite[[[-0.5,0.,0.], [0.5,0.,0.], [1.,1.,0.], [1.,-1.,0.]]];
ViewerOps.PaintViewer[d.graphics, client, FALSE, d];
};
Recurse: Controls.ClickProc ~ {
d: ProgramData ← NARROW[NARROW[clientData, Controls.OuterData].data];
d.recurse ← TRUE;
ViewerOps.PaintViewer[d.graphics, client, FALSE, d];
d.recurse ← FALSE;
};
Randomize: Controls.ClickProc ~ {
d: ProgramData ← NARROW[NARROW[clientData, Controls.OuterData].data];
s: SplineData ← d.s;
WHILE s # NIL DO
r: SplineData ← RandomizeSpline[s.c, Real.RoundI[d.nSpl.val]];
r.prev ← s.prev;
IF s.prev # NIL THEN s.prev.next ← r ELSE SplineStructure.contour ← d.s ← r;
WHILE r.next # NIL DO r ← r.next; ENDLOOP;
IF s.next # NIL THEN s.next.prev ← r;
s ← r.next ← s.next;
ENDLOOP;
ViewerOps.PaintViewer[d.graphics, client, FALSE, d];
};
InsertSpline: PROC [insert, splines: SplineData] RETURNS [SplineData] ~ {
Insert new SplineData (insert) at end of splines:
s: SplineData ← splines;
IF splines = NIL THEN RETURN[insert];
WHILE s.next # NIL DO s ← s.next; ENDLOOP;
insert.prev ← s;
insert.next ← s.next;
s.next ← insert;
IF insert.next # NIL THEN insert.next.prev ← insert;
RETURN[splines];
};
RandomizeSpline: PROC [c: Coeffs, nSplines: INTEGER ← 2] RETURNS [SplineData] ~ {
splines: SplineData ← NIL;
coeffs: Spline3d.CoeffsSequence;
bez: Bezier ← Spline3d.BezierFromCoeffs[c];
boxes: BoxSequence ← BoxesFromBezier[bez, nSplines];
knots: Spline3d.KnotSequence ← NEW[Spline3d.KnotSequenceRep[nSplines+1]];
scale: REAL ← 3.0/REAL[MAX[1, nSplines]];
tan0: Triple ← Vector3d.Mul[Vector3d.Sub[bez.b1, bez.b0], scale];
tan1: Triple ← Vector3d.Mul[Vector3d.Sub[bez.b3, bez.b2], scale];
IF nSplines = 0 THEN RETURN[splines];
knots[0] ← bez.b0;
FOR n: NAT IN [1..nSplines) DO knots[n] ← RandomPtInBox[boxes[n-1]]; ENDLOOP;
knots[nSplines] ← bez.b3;
knots.length ← nSplines+1;
coeffs ← Spline3d.Interpolate[knots, tan0, tan1];
FOR n: NAT IN [0..nSplines) DO
insert: SplineData ← NEW[SplineDataRec];
insert.c ← coeffs[n];
splines ← InsertSpline[insert, splines];
ENDLOOP;
RETURN[splines];
};
BoxesFromBezier: PROC [bez: Bezier, nBoxes: NAT] RETURNS [BoxSequence] ~ {
boxes: BoxSequence ← NEW[BoxSequenceRec[nBoxes]];
p0: Triple ← bez.b0;
p1: Triple ← bez.b1;
dp0: Triple ← Vector3d.Div[Vector3d.Sub[bez.b3, bez.b0], REAL[nBoxes]];
dp1: Triple ← Vector3d.Div[Vector3d.Sub[bez.b2, bez.b1], REAL[nBoxes]];
FOR n: NAT IN [0..nBoxes) DO
pp0: Triple ← Vector3d.Add[p0, dp0];
pp1: Triple ← Vector3d.Add[p1, dp1];
boxes[n] ← [p0, p1, pp1, pp0];
p0 ← pp0;
p1 ← pp1;
ENDLOOP;
RETURN[boxes];
};
RanR: PROC RETURNS [REAL] ~ {RETURN[gaussArray[Random.ChooseInt[min: 0, max: 1023]]]};
RandomPtInSegment: PROC [p0, p1: Triple] RETURNS[Triple] ~ {
w: REAL ← RanR[];
RETURN[Vector3d.Add[Vector3d.Mul[p0, w], Vector3d.Mul[p1, 1.0-w]]];
};
RandomPtInBox: PROC [box: Box] RETURNS[t: Triple] ~ {
w0: REAL ← RanR[];
w1: REAL ← RanR[];
w2: REAL ← RanR[];
w3: REAL ← RanR[];
n: REAL ← 1.0/(w0+w1+w2+w3);
w0 ← w0*n; w1 ← w1*n; w2 ← w2*n; w3 ← w3*n;
t.x ← box.a.x*w0+box.b.x*w1+box.c.x*w2+box.d.x*w3;
t.y ← box.a.y*w0+box.b.y*w1+box.c.y*w2+box.d.y*w3;
t.z ← box.a.z*w0+box.b.z*w1+box.c.z*w2+box.d.z*w3;
};
Function: PROC [x: REAL] RETURNS [REAL] ~ {
Input and output range [0..1].
x ← 2.0*3.1415926535*(x-0.5);
RETURN[0.5*(1.0+RealFns.Sin[x]+RealFns.Cos[x])];
};
Display: Controls.GraphicsShow ~ {
d: ProgramData ← NARROW[data];
Action: PROC ~ {
d.view ← Controls3d.InitPix[context, w, h];
IF d.recurse THEN {
p, q: Triple ← [0.0, 0.0, 0.0];
FOR i: NAT IN [0..100) DO
q.x ← REAL[i-50]/100.0;
q.y ← 0.5+q.x;
FOR n: NAT IN[1..Real.RoundI[d.nRec.val]] DO
q.y ← Function[q.y];
ENDLOOP;
q.y ← 0.5*q.y;
IF i > 0 THEN Draw3d.PP[p, q, context, d.view];
p ← q;
ENDLOOP;
}
ELSE FOR s: SplineData ← d.s, s.next WHILE s # NIL DO
Draw3d.DrawBezierPolygon[Spline3d.BezierFromCoeffs[s.c], context, d.view, dot];
Draw3d.DrawCurve[s.c, context, d.view];
ENDLOOP;
IF NOT d.noPick THEN Draw3d.PV[d.pd.pos, d.pd.tan,, context, d.view, IF d.pd.t IN(0.0..1.0) THEN cross ELSE none];
};
Draw2d.DoWithBuffer[context, Action, w, h]
};
InitGaussSequence: PROC [nEntries: NAT] RETURNS [Spline3d.RealSequence] ~ {
x: REAL ← 0.0;
sigma: REAL ~ 0.16;
dx: REAL ← 1.0/REAL[nEntries];
ret: Spline3d.RealSequence ← NEW[Spline3d.RealSequenceRec[nEntries]];
FOR i: NAT IN [0..1024) DO
xx: REAL ← x-0.5;
ret[i] ← RealFns.Exp[-xx*xx/(2.0*sigma*sigma)];
x ← x+dx;
ENDLOOP;
RETURN[ret];
};
Commander.Register["Contour", Contour, "\nTest some contour ideas."];
RandomizeSpline: PROC [c: Coeffs, nSplines: INTEGER ← 2] RETURNS [SplineData] ~ {
splines: SplineData ← NIL;
nBoxes: NAT ← 2*nSplines-2;
bez: Bezier ← Spline3d.BezierFromCoeffs[c];
boxes: BoxSequence ← BoxesFromBezier[bez, nBoxes];
pts: TripleSequence ← NEW[TripleSequenceRec[nBoxes+2]];
ends: TripleSequence ← NEW[TripleSequenceRec[nSplines+1]];
pts[0]    ← RandomPtInSegment[bez.b0, bez.b1];
pts[nBoxes+1] ← RandomPtInSegment[bez.b2, bez.b3];
FOR n: NAT IN [0..nBoxes) DO pts[n+1] ← RandomPtInBox[boxes[n]]; ENDLOOP;
ends[0]   ← bez.b0;
ends[nSplines] ← bez.b3;
FOR n: NAT IN [1..nSplines) DO
ends[n] ← Ave[pts[n+n-1], pts[n+n]];
ENDLOOP;
FOR n: NAT IN [0..nSplines) DO
insert: SplineData ← NEW[SplineDataRec];
insert.c ← Spline3d.CoeffsFromBezier[[ends[n], pts[n+n], pts[n+n+1], ends[n+1]]];
splines ← InsertSpline[insert, splines];
ENDLOOP;
RETURN[splines];
};
MinMaxOfCurve: PROC [c: Coeffs, i: NAT] RETURNS [min, max: REAL] ~ {
CheckI: PROC [t: REAL] ~ {
t2: REAL ← t*t;
y: REAL ← t2*t*c[0][i]+t2*c[1][i]+t*c[2][i]+c[3][i];
min ← MIN[min, y];
max ← MAX[max, y];
};
roots: Quadratic.Roots ← Quadratic.RealRoots[3.0*c[0][i], 2.0*c[1][i], c[2][i]];
min ← max ← c[3][i];
CheckI[1.0];
IF roots.nRoots > 0 AND roots.r1 IN [0.0..1.0] THEN CheckI[roots.r1];
IF roots.nRoots > 1 AND roots.r2 IN [0.0..1.0] THEN CheckI[roots.r2];
};
Ave: PROC [a, b: Triple] RETURNS [Triple] ~ {
RETURN[[0.5*(a.x+b.x), 0.5*(a.y+b.y), 0.5*(a.z+b.z)]];
};