<<>> <> <> <> <> DIRECTORY CedarProcess, Commander, Controls, Draw2d, FS, G2dBasic, G3dBasic, G3dPlot, G3dTool, G3dVector, Icons, ImagerBackdoor, IO, MessageWindow, Process, Prop, Real, RealFns, Rope, ViewerAbort, ViewerClasses, ViewerOps, ViewerTools; G3dPlotImpl: CEDAR PROGRAM IMPORTS CedarProcess, Controls, Draw2d, FS, G3dTool, Icons, ImagerBackdoor, MessageWindow, Process, Prop, Real, RealFns, Rope, ViewerAbort, ViewerOps, ViewerTools EXPORTS G3dPlot ~ BEGIN <> ROPE: TYPE ~ Rope.ROPE; Viewer: TYPE ~ Controls.Viewer; ClickProc: TYPE ~ Controls.ClickProc; OuterData: TYPE ~ Controls.OuterData; Control: TYPE ~ Controls.Control; Typescript: TYPE ~ Controls.Typescript; Context: TYPE ~ Draw2d.Context; DrawProc: TYPE ~ Draw2d.DrawProc; Pair: TYPE ~ G3dPlot.Pair; PairSequence: TYPE ~ G3dPlot.PairSequence; PairSequenceRep: TYPE ~ G3dPlot.PairSequenceRep; Triple: TYPE ~ G2dBasic.Triple; IntPair: TYPE ~ G2dBasic.IntPair; Line: TYPE ~ G3dPlot.Line; LineSequence: TYPE ~ G3dPlot.LineSequence; LineSequenceRep: TYPE ~ G3dPlot.LineSequenceRep; RealSequence: TYPE ~ G3dBasic.RealSequence; RealSequenceRep: TYPE ~ G3dBasic.RealSequenceRep; HeightField: TYPE ~ G3dPlot.HeightField; HeightFieldRep: TYPE ~ G3dPlot.HeightFieldRep; HeightProc: TYPE ~ G3dPlot.HeightProc; PrepareProc: TYPE ~ G3dPlot.PrepareProc; <> IntRange: TYPE ~ G3dPlot.IntRange; RealRange: TYPE ~ G3dPlot.RealRange; HorizonType: TYPE ~ G3dPlot.HorizonType; HorizonList: TYPE ~ G3dPlot.HorizonList; HorizonListRep: TYPE ~ G3dPlot.HorizonListRep; HorizonNode: TYPE ~ G3dPlot.HorizonNode; HorizonNodeRep: TYPE ~ G3dPlot.HorizonNodeRep; HeightTool: TYPE ~ G3dPlot.HeightTool; HeightToolRep: TYPE ~ G3dPlot.HeightToolRep; <> MakeHeightTool: PROC [ht: HeightTool, name: ROPE, controls: LIST OF Controls.Control] ~ { ht.outer ¬ Controls.OuterViewer[ name: name, buttons: LIST[ Controls.ClickButton["Interpress Out", IPOutButton, ht], Controls.ClickButton["Hide X ", DrawXButton, ht], Controls.ClickButton["Hide Y ", DrawYButton, ht] ], controls: CONS[Controls.NewControl["Z Scale",, ht, -5.0, 5.0, 1.0, Controller], controls], typescriptHeight: 18, graphicsHeight: 300, drawProc: Draw, clientData: ht, noOpen: TRUE].parent; ht.outer.label ¬ name; ht.outerData ¬ NARROW[ht.outer.data]; ht.graphics ¬ ht.outerData.graphics; ht.outer.icon ¬ icon; IF ht.heightProc # NIL THEN SetHeightsFromProc[ht]; ViewerOps.OpenIcon[ht.outer]; }; SetHeightsFromProc: PROC [ht: HeightTool] ~ { dx: REAL ¬ (ht.xRange.h-ht.xRange.l)/MAX[1.0, (ht.nXSamples-1.0)]; dy: REAL ¬ (ht.yRange.h-ht.yRange.l)/MAX[1.0, (ht.nYSamples-1.0)]; ht.nSamples ¬ (ht.nXSamples+1)*(ht.nYSamples+1); IF ht.heightField = NIL THEN ht.heightField ¬ NEW[HeightFieldRep[ht.nYSamples+1]]; IF ht.heightField.maxLength < ht.nYSamples+1 THEN ht.heightField ¬ NEW[HeightFieldRep[ht.nYSamples+1]]; ht.heightField.length ¬ ht.nYSamples+1; FOR n: NAT IN [0..ht.nYSamples] DO IF ht.heightField[n] = NIL OR ht.heightField[n].maxLength < ht.nXSamples+1 THEN ht.heightField[n] ¬ NEW[RealSequenceRep[ht.nXSamples+1]]; ht.heightField[n].length ¬ ht.nXSamples+1; ENDLOOP; IF ht.prepareProc # NIL THEN ht.prepareProc[ht.xRange.l, ht.xRange.h, ht.yRange.l, ht.yRange.h, ht.nXSamples, ht.nYSamples, ht.clientData]; FOR iy: NAT IN [0..ht.nYSamples) DO y: REAL ¬ ht.yRange.l+iy*dy; reals: RealSequence ¬ ht.heightField[iy]; FOR ix: NAT IN [0..ht.nXSamples) DO reals[ix] ¬ ht.heightProc[ht.xRange.l+ix*dx, y, ht.clientData]; ENDLOOP; ENDLOOP; }; Draw: Controls.DrawProc ~ TRUSTED { ht: HeightTool ¬ NARROW[clientData]; IF CedarProcess.GetStatus[ht.drawProcess] # busy THEN { FOR c: LIST OF Control ¬ ht.outerData.controls, c.rest WHILE c # NIL DO SELECT TRUE FROM Rope.Equal[c.first.name, "Height"] => ht.zScale ¬ c.first.value; Rope.Equal[c.first.name, "xMin"] => ht.xRange.l ¬ c.first.value; Rope.Equal[c.first.name, "xMax"] => ht.xRange.h ¬ c.first.value; Rope.Equal[c.first.name, "yMin"] => ht.yRange.l ¬ c.first.value; Rope.Equal[c.first.name, "yMax"] => ht.yRange.h ¬ c.first.value; Rope.Equal[c.first.name, "xSamp"] => ht.nXSamples ¬ Real.Round[c.first.value]; Rope.Equal[c.first.name, "ySamp"] => ht.nYSamples ¬ Real.Round[c.first.value]; Rope.Equal[c.first.name, "yOffset"] => ht.yOffset ¬ Real.Round[c.first.value]; ENDCASE; ENDLOOP; context.propList ¬ Prop.Put[context.propList, $HeightTool, ht]; [] ¬ CedarProcess.Join[ht.drawProcess ¬ CedarProcess.Fork[DoDraw, context]]; context.propList ¬ Prop.Rem[context.propList, $HeightTool]; }; }; DoDraw: CedarProcess.ForkableProc ~ { Action: PROC ~ { <<>> Lerp: PROC [lo, hi, a: REAL] RETURNS [REAL] ~ { RETURN[lo + (a * (hi-lo))]; }; LerpPair: PROC [lo, hi: Pair, a: REAL] RETURNS [Pair] ~ { RETURN[[Lerp[lo.x, hi.x, a], Lerp[lo.y, hi.y, a]]]; }; IntersectPairs: PROC [j0, j1, k0, k1: Pair] RETURNS [Pair] ~ { Intersect: PROC [a, b: Triple] RETURNS [Pair] ~ { det: REAL ¬ a.x*b.y - b.x*a.y; IF det # 0.0 THEN det ¬ 1.0/det ELSE det ¬ 1.0; RETURN[[det*(a.y*b.z - b.y*a.z), det*(b.x*a.z - a.x*b.z)]]; }; a: Triple ¬ [j0.y-j1.y, j1.x-j0.x, j0.x*j1.y - j1.x*j0.y]; b: Triple ¬ [k0.y-k1.y, k1.x-k0.x, k0.x*k1.y - k1.x*k0.y]; RETURN[Intersect[a, b]]; }; InsertHorizon: PROC [h: HorizonList, left, right: Pair] ~ { InsertABeforeB: PROC [a, b: HorizonNode] ~ { a.prev ¬ b.prev; a.next ¬ b; b.prev.next ¬ a; b.prev ¬ a; }; InsertAAfterB: PROC [a, b: HorizonNode] ~ { a.prev ¬ b; a.next ¬ b.next; b.next.prev ¬ a; b.next ¬ a; }; DeleteA: PROC [a: HorizonNode] ~ { a.prev.next ¬ a.next; a.next.prev ¬ a.prev; a.prev ¬ a.next ¬ NIL; }; ll, lr, rl, rr: HorizonNode; newright, newleft: HorizonNode; oldly, oldry, lm, lb, rm, rb: REAL; ll ¬ lr ¬ rl ¬ rr ¬ h.firstSaved; WHILE lr # NIL AND lr.x <= left.x DO lr ¬ lr.next; ENDLOOP; ll ¬ lr.prev; WHILE rr # NIL AND rr.x <= right.x DO rr ¬ rr.next; ENDLOOP; rl ¬ rr.prev; <> lm ¬ (lr.ly - ll.ry) / MAX[1.0, (lr.x - ll.x)]; lb ¬ ll.ry - (ll.x * lm); oldly ¬ lb + (lm * left.x); rm ¬ (rr.ly - rl.ry) / MAX[1.0, (rr.x - rl.x)]; rb ¬ rr.ry - (rr.x * rm); oldry ¬ rb + (rm * right.x); <> IF lr # rr THEN { -- just wipe the links, the gc will pick up the nodes lr.prev ¬ rl.next ¬ NIL; ll.next ¬ rr; rr.prev ¬ ll; }; <> newleft ¬ NEW[HorizonNodeRep]; newleft.x ¬ left.x; newleft.ly ¬ oldly; newleft.ry ¬ left.y; newright ¬ NEW[HorizonNodeRep]; newright.x ¬ right.x; newright.ly ¬ right.y; newright.ry ¬ oldry; <> InsertAAfterB[newleft, ll]; InsertABeforeB[newright, rr]; IF ABS[ll.x - newleft.x] < ht.epsilon THEN { newleft.ly ¬ ll.ly; IF rr # ll THEN DeleteA[ll]; }; IF ABS[rr.x - newright.x] < ht.epsilon THEN { newright.ry ¬ rr.ry; IF rr # ll THEN DeleteA[rr]; }; }; UpdateAction: TYPE ~ { draw, insert }; LineAction: PROC [h: HorizonList, left, right: Pair, action: UpdateAction] ~ { Flush: PROC ~ { IF qLeft.x > qRight.x THEN { tmp: Pair ¬ qLeft; qLeft ¬ qRight; qRight ¬ tmp; }; IF pending AND ABS[qRight.x - qLeft.x] > ht.epsilon THEN IF action = draw THEN Draw2d.Line[context, qLeft, qRight, solid, zip] ELSE InsertHorizon[h, qLeft, qRight]; pending ¬ FALSE; }; Extend: PROC [left, right: Pair] ~ { IF NOT pending THEN { qLeft ¬ left; pending ¬ TRUE; }; qRight ¬ right; }; NearBy: PROC [a, b: Pair] RETURNS [BOOL] ~ { RETURN[(ABS[a.x - b.x] < ht.epsilon) AND (ABS[a.y - b.y] < ht.epsilon)]; }; pending: BOOL ¬ FALSE; qLeft, qRight: Pair; n: HorizonNode; m: REAL ¬ (right.y - left.y) / MAX[1.0, (right.x - left.x)]; b: REAL ¬ right.y - (right.x * m); n ¬ h.first; WHILE n # NIL AND n.x < left.x DO n ¬ n.next; ENDLOOP; n ¬ n.prev; WHILE n.x < right.x DO xWindow: RealRange ¬ [MAX[n.x, left.x], MIN[n.next.x, right.x]]; bandm: REAL ¬ (n.next.ly - n.ry) / MAX[1.0, (n.next.x - n.x)]; bandb: REAL ¬ n.ry - (n.x * bandm); oldLeft: Pair ¬ [xWindow.l, bandb + (bandm * xWindow.l)]; oldRight: Pair ¬ [xWindow.h, bandb + (bandm * xWindow.h)]; newLeft: Pair ¬ [xWindow.l, b + (m * xWindow.l)]; newRight: Pair ¬ [xWindow.h, b + (m * xWindow.h)]; leftSign: BOOL ¬ IF h.type = upper THEN newLeft.y >= oldLeft.y ELSE newLeft.y <= oldLeft.y; rightSign: BOOL ¬ IF h.type = upper THEN newRight.y >= oldRight.y ELSE newRight.y <= oldRight.y; IF NearBy[left, [n.x, n.ry]] THEN { IF h.type = upper THEN IF m > bandm THEN leftSign ¬ TRUE ELSE leftSign ¬ FALSE ELSE IF m < bandm THEN leftSign ¬ TRUE ELSE leftSign ¬ FALSE; }; IF NearBy[right, [n.next.x, n.next.ly]] THEN { IF h.type = upper THEN IF m < bandm THEN leftSign ¬ TRUE ELSE leftSign ¬ FALSE ELSE IF m > bandm THEN leftSign ¬ TRUE ELSE leftSign ¬ FALSE; }; IF leftSign # rightSign THEN { hitPt: Pair ¬ IntersectPairs[oldLeft, oldRight, newLeft, newRight]; IF leftSign = TRUE THEN { Extend[newLeft, hitPt]; Flush[]; } ELSE { Flush[]; Extend[hitPt, newRight]; }; } ELSE IF leftSign = TRUE THEN Extend[newLeft, newRight]; IF n.next = NIL THEN n ¬ n; n ¬ n.next; ENDLOOP; Flush[]; }; CopyAction: TYPE ~ { backup, restore }; CopyHorizonList: PROC [h: HorizonList, action: CopyAction] ~ { src: HorizonNode ¬ IF action = backup THEN h.first ELSE h.firstSaved; dst: HorizonNode ¬ NIL; WHILE src # NIL DO n: HorizonNode ¬ NEW[HorizonNodeRep]; n.x ¬ src.x; n.ly ¬ src.ly; n.ry ¬ src.ry; IF dst = NIL THEN { IF action = backup THEN h.firstSaved ¬ n ELSE h.first ¬ n; } ELSE { n.prev ¬ dst; dst.next ¬ n; }; dst ¬ n; src ¬ src.next; ENDLOOP; }; UpdateLines: PROC [list: LineSequence] ~ { FOR k: INT IN [0 .. 1] DO h: HorizonList ¬ IF k = 0 THEN upper ELSE lower; FOR i: INT IN [0 .. list.length) DO -- first draw all the lines LineAction[h, list[i].left, list[i].right, draw]; ENDLOOP; CopyHorizonList[h, backup]; FOR i: INT IN [0 .. list.length) DO -- now update visibility lists LineAction[h, list[i].left, list[i].right, insert]; ENDLOOP; CopyHorizonList[h, restore]; ENDLOOP; }; InitLists: PROC ~ { ul: HorizonNode ¬ NEW[HorizonNodeRep]; ur: HorizonNode ¬ NEW[HorizonNodeRep]; ll: HorizonNode ¬ NEW[HorizonNodeRep]; lr: HorizonNode ¬ NEW[HorizonNodeRep]; ul.x ¬ -1.0; ul.ly ¬ ul.ry ¬ -1.0; ul.next ¬ ur; ur.x ¬ bounds.w+1.0; ur.ly ¬ ur.ry ¬ -1.0; ur.prev ¬ ul; ll.x ¬ -1.0; ll.ly ¬ ll.ry ¬ bounds.h+1; ll.next ¬ lr; lr.x ¬ bounds.w+1.0; lr.ly ¬ lr.ry ¬ bounds.h+1; lr.prev ¬ ll; upper.first ¬ ul; lower.first ¬ ll; upper.type ¬ upper; lower.type ¬ lower; }; zip: Draw2d.Zip ¬ Draw2d.GetZip[context]; intWidth: INT ¬ Real.Round[canvasWidth]; nearSlice: PairSequence ¬ NEW[PairSequenceRep[ht.nXSamples]]; upper: HorizonList ¬ NEW[HorizonListRep]; lower: HorizonList ¬ NEW[HorizonListRep]; farSlice: PairSequence ¬ NEW[PairSequenceRep[ht.nXSamples]]; xDistance: REAL ¬ ht.xRange.h - ht.xRange.l; yDistance: REAL ¬ ht.yRange.h - ht.yRange.l; invYAlpha: REAL ¬ 1.0 / MAX[1.0, (ht.nYSamples-1.0)]; invXAlpha: REAL ¬ 1.0 / MAX[1.0, (ht.nXSamples-1.0)]; maxLines: INT ¬ MAX[ht.nXSamples, ht.nYSamples]; lineList: LineSequence ¬ NEW[LineSequenceRep[maxLines]]; <> InitLists[]; lineList.length ¬ 0; nearSlice.length ¬ farSlice.length ¬ ht.nXSamples; FOR yIndex: INT IN [0 .. ht.nYSamples) DO yAlpha: REAL ¬ yIndex * invYAlpha; nextyAlpha: REAL ¬ (yIndex+1) * invYAlpha; yValue: REAL ¬ ht.yRange.l + (yAlpha * yDistance); nextyValue: REAL ¬ ht.yRange.l + (nextyAlpha * yDistance); bFL: Pair ¬ LerpPair[pFL, pBL, yAlpha]; bFR: Pair ¬ LerpPair[pFR, pBR, yAlpha]; bBL: Pair ¬ LerpPair[pFL, pBL, nextyAlpha]; bBR: Pair ¬ LerpPair[pFR, pBR, nextyAlpha]; GetVal: PROC [xIndex, yIndex: INT, xValue, yValue: REAL] RETURNS [REAL] ~ { v: REAL ¬ IF ht.heightProc = NIL THEN ht.heightField[yIndex][xIndex] ELSE ht.heightProc[xValue, yValue, ht.clientData]; RETURN[ht.zScale * v]; }; Process.CheckForAbort[]; FOR xIndex: INT IN [0 .. ht.nXSamples) DO xAlpha: REAL ¬ xIndex * invXAlpha; xValue: REAL ¬ ht.xRange.l + (xAlpha * xDistance); <> IF yIndex > 0 THEN { nearSlice[xIndex] ¬ farSlice[xIndex]; } ELSE { nearSlice[xIndex] ¬ LerpPair[bFL, bFR, xAlpha]; nearSlice[xIndex].y ¬ nearSlice[xIndex].y + GetVal[xIndex, yIndex, xValue, yValue]; }; <> farSlice[xIndex] ¬ LerpPair[bBL, bBR, xAlpha]; farSlice[xIndex].y ¬ farSlice[xIndex].y + GetVal[xIndex, yIndex+1, xValue, nextyValue]; ENDLOOP; <> <> <> <> <> <> <> <> <<};>> <<>> <> <> <> <> <> <> <> <> <> <<};>> <<};>> <<>> <<-- first the right-most near-far edge>> lineList.length ¬ 1; lineList[0] ¬ [nearSlice[ht.nXSamples-1], farSlice[ht.nXSamples-1]]; IF ht.drawY THEN UpdateLines[lineList]; <<-- now the h/v pairs>> FOR xIndex: INT DECREASING IN [0 .. ht.nXSamples-1) DO lineList.length ¬ 0; IF ht.drawX THEN { lineList[lineList.length] ¬ [nearSlice[xIndex], nearSlice[xIndex+1]]; lineList.length ¬ lineList.length+1; }; IF ht.drawY THEN { lineList[lineList.length] ¬ [nearSlice[xIndex], farSlice[xIndex]]; lineList.length ¬ lineList.length+1; }; UpdateLines[lineList]; ENDLOOP; ENDLOOP; <> Draw2d.Line[context, pFL, pFR, dotted, zip]; Draw2d.Line[context, pFR, pBR, dotted, zip]; Draw2d.Line[context, pBR, pBL, dotted, zip]; Draw2d.Line[context, pBL, pFL, dotted, zip]; }; AddPair: PROC [p1, p2: Pair] RETURNS [Pair] ~ { RETURN[[p1.x+p2.x, p1.y+p2.y]]; }; GetBounds: PROC RETURNS [bounds: Imager.Rectangle] ~ { bounds ¬ [0, 0, 100, 100]; bounds ¬ ImagerBackdoor.GetBounds[context ! Imager.Error => CONTINUE]; }; context: Context ¬ NARROW[data]; ht: HeightTool ¬ NARROW[Prop.Get[context.propList, $HeightTool]]; bounds: Imager.Rectangle ¬ GetBounds[]; pLL: Pair ¬ [0.05, 0.05]; -- these variables should be command-line or file pUR: Pair ¬ [0.95, 0.95]; pWidth: REAL ¬ 0.7; -- percent of width taken by one h line pRise: REAL ¬ 0.4; -- percent of height taken by plot baselines skew: REAL ¬ 0.05; -- amount by which baseline is tilted (skew*pWidth < pRise) screenWidth: INT ¬ Real.Round[bounds.w]; canvasWidth: REAL ¬ (pUR.x - pLL.x) * bounds.w; canvasHeight: REAL ¬ (pUR.y - pLL.y) * bounds.h; canvasLeft: REAL ¬ pLL.x * bounds.w; canvasBottom: REAL ¬ pLL.y * bounds.h; canvasRight: REAL ¬ canvasLeft + canvasWidth; canvasTop: REAL ¬ canvasBottom + canvasHeight; canvasOrigin: Pair ¬ [canvasLeft, canvasBottom]; pFL: Pair ¬ [0.0, skew*pWidth*canvasWidth]; -- front left pFR: Pair ¬ [(1.0-skew)*pWidth*canvasWidth, 0.0]; -- front right pBL: Pair ¬ [canvasWidth - pFR.x, pRise*canvasHeight]; -- back left pBR: Pair ¬ [canvasWidth, (pRise*canvasHeight)-pFL.y]; -- back right plotOffset: Pair ¬ [0.0, REAL[ht.yOffset]]; plotOrigin: Pair ¬ AddPair[plotOffset, canvasOrigin]; pFL ¬ AddPair[pFL, plotOrigin]; pFR ¬ AddPair[pFR, plotOrigin]; pBL ¬ AddPair[pBL, plotOrigin]; pBR ¬ AddPair[pBR, plotOrigin]; ViewerAbort.CallWithAbortEnabled[ht.graphics, Action]; }; IPOutButton: ViewerClasses.ClickProc ~ { fileName: Rope.ROPE ¬ ViewerTools.GetSelectionContents[]; IF Rope.IsEmpty[fileName] THEN Complain["\t\tPlease select a filename first."] ELSE Draw2d.IPOut[fileName, Draw, clientData ! FS.Error => {Complain[Rope.Concat["Bad name: ", fileName]]; CONTINUE}]; }; DrawXButton: ViewerClasses.ClickProc ~ { ht: HeightTool ¬ NARROW[clientData]; IF ht.drawX THEN { ht.drawX ¬ FALSE; Controls.ButtonRelabel[ht.outerData, "Hide X ", "Draw X "]; } ELSE { ht.drawX ¬ TRUE; Controls.ButtonRelabel[ht.outerData, "Draw X ", "Hide X "]; }; ViewerOps.PaintViewer[ht.outer, client]; }; DrawYButton: ViewerClasses.ClickProc ~ { ht: HeightTool ¬ NARROW[clientData]; IF ht.drawY THEN { ht.drawY ¬ FALSE; Controls.ButtonRelabel[ht.outerData, "Hide Y ", "Draw Y "]; } ELSE { ht.drawY ¬ TRUE; Controls.ButtonRelabel[ht.outerData, "Draw Y ", "Hide Y "]; }; ViewerOps.PaintViewer[ht.outer, client]; }; Complain: PROC [message: ROPE] ~ { MessageWindow.Append[message, TRUE]; MessageWindow.Blink[]; }; Controller: Controls.ControlProc ~ { IF control.mouse.state # down AND (control.mouse.button = right OR control.whatChanged = $TypedIn) THEN ViewerOps.PaintViewer[NARROW[control.clientData, HeightTool].graphics, client]; }; MakeHeightToolFromProc: PUBLIC PROC [ toolName: ROPE ¬ NIL, heightProc: HeightProc ¬ NIL, prepareProc: PrepareProc ¬ NIL, clientData: REF ANY ¬ NIL] ~ { ht: HeightTool ¬ NEW[HeightToolRep ¬ [ heightProc: heightProc, prepareProc: prepareProc, clientData: clientData]]; ht.epsilon ¬ .01; MakeHeightTool[ht, toolName, LIST[ Controls.NewControl["xMin",, ht, -100.0, 100.0, ht.xRange.l, Controller], Controls.NewControl["xMax",, ht, -100.0, 100.0, ht.xRange.h, Controller], Controls.NewControl["yMin",, ht, -100.0, 100.0, ht.yRange.l, Controller], Controls.NewControl["yMax",, ht, -100.0, 100.0, ht.yRange.h, Controller], Controls.NewControl["xSamp",, ht, 1, 100, ht.nXSamples, Controller,, 0], Controls.NewControl["ySamp",, ht, 1, 100, ht.nYSamples, Controller,, 0], Controls.NewControl["yOffset",, ht, -300, 300, ht.yOffset, Controller,, 0]]]; }; MakeHeightToolFromData: PUBLIC PROC [toolName: ROPE, heightField: HeightField] ~ { ht: HeightTool ¬ NEW[HeightToolRep ¬ [heightField: heightField]]; MakeHeightTool[ht, toolName, NIL]; }; <> SincData: TYPE ~ REF SincDataRep; SincDataRep: TYPE ~ RECORD [cycles, rScl, eScl: REAL]; SincPrep: PrepareProc ~ { d: SincData ¬ NARROW[clientData]; rMax: REAL ¬ RealFns.SqRt[(hx*hx)+(hy*hy)]; finalHeight: REAL ¬ 0.1; d.cycles ¬ 4.0; d.eScl ¬ RealFns.Ln[finalHeight]/rMax; d.rScl ¬ d.cycles * 2.0 * 3.1415926535 / rMax; }; Sinc: HeightProc ~ { d: SincData ¬ NARROW[clientData]; r2: REAL ¬ (x*x)+(y*y); r: REAL ¬ RealFns.SqRt[r2]; val: REAL ¬ RealFns.Exp[d.eScl * r] * RealFns.Cos[d.rScl * r]; <> RETURN[200.0 * val]; }; HeightPlotTest: Commander.CommandProc ~ { refSincData: SincData ¬ NEW[SincDataRep]; MakeHeightToolFromProc["HieghtPlotTest", Sinc, SincPrep, refSincData]; }; <> icon: Icons.IconFlavor ¬ Icons.NewIconFromFile["G3dUser.icons", 4]; G3dTool.Register["3dHeightPlotTest", HeightPlotTest, "Usage: 3dHeightPlotTest (test)"]; END.