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: || "
" -
39: || "- Execution starts with a predefined set of startpoint, endpoint and 3 controlpoints.
" -
40: || "- With the left mousebutton all points can be dragged.
" -
41: || "- Clicking the left mousebutton on one of the points will add an additional controlpoint " -
42: || "between the one clicked and the next point.
" -
43: || "- Clicking the right mousebutton on one of the points will remove that controlpoint.
" -
44: || "- Double clicking the left mousebutton allows you the change the number of flattening steps.
" -
45: || "
" -
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: * - Treat t as a ratio (which it is). t=0 is 0% along a line, t=1 is 100% along a line.
273: *
- Take all lines between the curve's defining points. For an order n curve, that's n lines.
274: *
- 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: *
- Now form lines between those points. This gives n-1 lines.
276: *
- Place markers along each of these line at distance t.
277: *
- Form lines between those points. This'll be n-2 lines.
278: *
- Place markers, form lines, place markers, etc.
279: *
- 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: