NetRexx logo
   1: /* NetRexx */
   2: 
   3: import javax.swing.event -- to cover hyperlinkListener
   4: 
   5: class BezierCurves public extends JFrame 
   6: 
   7:     properties private 
   8: 
   9:         isTrue = 1
  10:         isFalse = 0
  11: -- A modeless JDialog with this (JFrame) as parent
  12:         myDialog = JDialog JDialog(this, "Exploring Bezier curves with NetRexx", isFalse)
  13: -- An on this (JFrame) dependent class for drawing the Bezier curves
  14:         myCanvas = DrawCanvas
  15: -- The wanted dimensions for  the JPanel(i.e. Canvas) and JDialog
  16:         canvas_width = 300
  17:         canvas_height = 300
  18:         dialog_width = 600
  19:         dialog_height = 770
  20: /**
  21:  * The constructor for the BezierCurves class instances, call to super is automatically
  22:  * provided/inserted by NetRexx
  23:  */     
  24:     method BezierCurves 
  25: /**
  26:  * Create the HELP information in a un-editable JEditorPane
  27:  */
  28:         myPane = JEditorPane JEditorPane()
  29:         myPane.setEditable(isFalse)
  30:         myPane.setContentType("text/html")
  31: -- The help info in html
  32:         myPane.setText("

Exploring Beziercurves with:

" - 33: || "" - 34: || "

NetRexx

(http://netrexx.org)" - 35: || "

The code is based on the information provided on " - 36: || "this website
(https://pomac.github.io/bezierinfo/).

" - 37: || "

You can use this code in the following way:

" - 38: || "" - 46: || "

Go ahead, have some fun, I hope!

Ruurd Idenburg

") 47: -- The HyperlinkListener enables browsing of the referred websites 48: hll = HyperlinkListener myHyperlinkListener() 49: myPane.addHyperlinkListener(hll) 50: -- Add the JEditorPane to the JDialog 51: myDialog.getContentPane().add(myPane, BorderLayout.CENTER) 52: -- and set the necessary options for the JDialog 53: myDialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE) 54: myDialog.pack() 55: myDialog.setSize(dialog_width,dialog_height) 56: myDialog.setVisible(isTrue) 57: -- Create the canvas for drawing the curves 58: myCanvas = DrawCanvas(25) 59: myCanvas.setPreferredSize(Dimension(canvas_width, canvas_height)) 60: -- Add the drawing canvas to the JFrame 61: this.getContentPane().add(myCanvas) 62: -- and set the necessary options for the JFrame 63: this.setTitle("Exploring Beziercurves.....") 64: this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) 65: this.pack() 66: this.setSize(canvas_width,canvas_height) 67: this.setLocationRelativeTo(null) 68: this.setVisible(isTrue) 69: /** 70: * Start the application in the most thread-safe way. 71: */ 72: method main(args=String[] ) static 73: SwingUtilities.invokeLater(myRunnable()) 74: 75: /** 76: * Define inner class DrawCanvas, which is a JPanel used for custom drawing. 77: */ 78: class BezierCurves.DrawCanvas dependent private extends JPanel - 79: adapter implements MouseListener, MouseMotionListener 80: 81: properties public 82: -- first point is start, last point is end, in between are control 83: points=ArrayList() 84: -- curvePoint is updated every flattening step 85: curvePoint 86: -- size of a curvepoint 87: curvePointRadius = 5 88: -- distance between X and also Y axes 89: skeletonAxesIncrement = 25 90: -- The number of flattening steps to calculate the Bezier curve 91: flatteningSteps = 10 92: 93: method DrawCanvas(steps) 94: -- Create the initial start-, end- and control-points 95: points.add(110 150) 96: points.add(25 190) 97: points.add(210 250) 98: points.add(230 110) 99: points.add(210 30) 100: -- Set the number of flattening steps 101: flatteningSteps = steps 102: -- Set the mouse listeners we need and implement 103: addMouseListener(this) 104: addMouseMotionListener(this) 105: 106: method mouseDragged(event=MouseEvent) 107: moveCurvePoint(event) 108: 109: method mouseReleased(event=MouseEvent) 110: moveCurvePoint(event) 111: 112: method moveCurvePoint(event = MouseEvent) 113: ex = event.getX 114: ey = event.getY 115: r = curvePointRadius 116: loop label redraw i=0 while i=px-r & px<=px+r & ey>=py-r & ey<=py+r) then do 120: points.set(i, ex ey) 121: repaint() 122: leave redraw 123: end 124: end 125: /** 126: * Handle single click and double click and left or right mouse action 127: * Double click allows altering the number of flattenening steps 128: * Single click with left mouse button allows adding a control point 129: * Single click with right mouse button allows removing a control point 130: */ 131: method mouseClicked(event=MouseEvent) 132: if event.getClickCount>1 & \event.isConsumed() then do 133: event.consume() 134: input = JOptionPane.showInputDialog("Enter the number of curve flattening steps\nShould be in the range 0-100",flatteningSteps) 135: if (input\=null &\input.isEmpty()) then do 136: value = Rexx input 137: if (value.dataType('W') & value>=0 & value<=100) then do 138: flatteningSteps = value 139: repaint() 140: end 141: else do 142: JOptionPane.showMessageDialog(null, "Number of flattening steps not in range 0-100!") 143: end 144: 145: end 146: end 147: if event.getClickCount=1 then do 148: ex = event.getX 149: ey = event.getY 150: r = curvePointRadius 151: loop label redraw i=0 while i=px-r & px<=px+r & ey>=py-r & ey<=py+r) then do 155: mouseButton = event.getButton 156: if mouseButton==MouseEvent.BUTTON1 then addControlPoint(i) 157: if mouseButton==MouseEvent.BUTTON3 then removeControlPoint(i) 158: leave redraw 159: end 160: end 161: end 162: 163: /** 164: * A new control point is added halfway between the clicked one and the next control point. 165: * Adding a control point beyond the end point is not allowed 166: */ 167: method addControlPoint(i) 168: if i0 & ii then points.add(oldpts.get(j)) 193: end 194: repaint() 195: end 196: else do 197: JOptionPane.showMessageDialog(null, "Removing start or end point is not allowed!") 198: end 199: 200: /** 201: * The code to (re)paint the drawing canvas 202: */ 203: method paintComponent( g=Graphics ) 204: super.paintComponent(g) 205: -- draw the skeleton 206: incr = skeletonAxesIncrement 207: height = getHeight() 208: width = getWidth() 209: g.setColor(Color.lightgray) 210: loop i=0 by incr while i<=(Rexx height).max(width) 211: g.drawLine(i,0,i,height) 212: g.drawLIne(0,i,width,i) 213: end 214: -- show the number of flattening steps 215: g.setColor(Color.BLACK) 216: g.setFont(Font("Monospaced",Font.BOLD,10)) 217: xPos = 5 218: yPos = height-5 219: g.drawString("Flattening steps:" flatteningSteps, xPos, yPos) 220: -- draw the lines between the points 221: if points.size>1 then do 222: px = 0 223: py = 0 224: loop i=0 while i0 then g.drawLine(px,py,x,y) 228: px = x 229: py = y 230: end 231: end 232: -- draw the points and information text 233: if points.size>0 then do 234: loop i=0 while i1 then do 246: t = 0.0 247: prevptx = 0 248: prevpty = 0 249: loop while t<=1 250: g.setColor(Color.blue) 251: stepCurvePoint(points, t) 252: ptx = curvePoint.word(1) 253: pty = curvePoint.word(2) 254: g.drawOval(int ptx-1,int pty-1, 2, 2) 255: if t<>0 then do 256: g.drawLine(int prevptx,int prevpty,int ptx,int pty) 257: end 258: prevptx = ptx 259: prevpty = pty 260: t = t+(100/flatteningSteps/100) 261: end 262: end 263: 264: /** 265: * If we want to draw Bézier curves, we can run through all values of t from 0 to 1 and 266: * then compute the weighted basis function at each value, getting the x/y values we need 267: * to plot. Unfortunately, the more complex the curve gets, the more expensive this 268: * computation becomes. Instead, we can use de Casteljau's algorithm to draw curves. 269: * This is a geometric approach to curve drawing, and it's really easy to implement. 270: * So easy, in fact, you can do it by hand with a pencil and ruler. 271: *
    272: *
  1. Treat t as a ratio (which it is). t=0 is 0% along a line, t=1 is 100% along a line. 273: *
  2. Take all lines between the curve's defining points. For an order n curve, that's n lines. 274: *
  3. Place markers along each of these line, at distance t. So if t is 0.2, place the mark at 20% from the start, 80% from the end. 275: *
  4. Now form lines between those points. This gives n-1 lines. 276: *
  5. Place markers along each of these line at distance t. 277: *
  6. Form lines between those points. This'll be n-2 lines. 278: *
  7. Place markers, form lines, place markers, etc. 279: *
  8. Repeat this until you have only one line left. The point t on that line coincides with the original curve point at t. 280: *
281: */ 282: method stepCurvePoint(_points=ArrayList, t) 283: if _points.size>1 then do 284: newPoints= ArrayList() 285: --say newPoints.length 286: loop i=0 while i<_points.size-1 287: x = (1-t)*(Rexx _points.get(i)).word(1)+t*(Rexx _points.get(i+1)).word(1) 288: y = (1-t)*(Rexx _points.get(i)).word(2)+t*(Rexx _points.get(i+1)).word(2) 289: newPoints.add(x y) 290: end 291: stepCurvePoint(newPoints, t) 292: end 293: else do 294: x = (Rexx _points.get(0)).word(1).format(null,0) 295: y = (Rexx _points.get(0)).word(2).format(null,0) 296: curvePoint = x y 297: end 298: 299: /** 300: * The HyperlinkListener is triggered by the hyperlinkUpdate event 301: * The event type can be ENTERED, EXITED or ACTIVATED. If the hyperlink is activated 302: * the (default) desktop browser is invoked to try to browse the activated link. 303: */ 304: class myHyperlinkListener implements HyperlinkListener 305: 306: method hyperlinkUpdate(event = HyperlinkEvent) 307: if (event.getEventType == HyperlinkEvent.EventType.ACTIVATED) then do 308: theURL = URL event.getURL() 309: if (theURL\=null) then do 310: dt = Desktop Desktop.getDesktop() 311: dt.browse(theURL.toURI) 312: catch ex=Exception 313: ex.printStackTrace 314: end 315: end 316: 317: /** 318: * Start the application in a thread-safe way 319: */ 320: class myRunnable private implements Runnable 321: method run 322: BezierCurves() 323:
If you feel inclined to make corrections, suggestions etc., please mail me any.
All content © Ruurd Idenburg, 2007–, except where marked otherwise. All rights reserved. This page is primarily for non-commercial use only. The Idenburg website records no personal information and sets no ‘cookies’. This site is hosted on a VPS(Virtual Private System) rented from Transip.nl, a Dutch company, falling under Dutch (privacy) laws (I think).

This page updated on by Ruurd Idenburg.