/* NetRexx */
import javax.swing.event -- to cover hyperlinkListener
class BezierCurves public extends JFrame
properties private
isTrue = 1
isFalse = 0
-- A modeless JDialog with this (JFrame) as parent
myDialog = JDialog JDialog(this, "Exploring Bezier curves with NetRexx", isFalse)
-- An on this (JFrame) dependent class for drawing the Bezier curves
myCanvas = DrawCanvas
-- The wanted dimensions for the JPanel(i.e. Canvas) and JDialog
canvas_width = 300
canvas_height = 300
dialog_width = 600
dialog_height = 770
/**
* The constructor for the BezierCurves class instances, call to super is automatically
* provided/inserted by NetRexx
*/
method BezierCurves
/**
* Create the HELP information in a un-editable JEditorPane
*/
myPane = JEditorPane JEditorPane()
myPane.setEditable(isFalse)
myPane.setContentType("text/html")
-- The help info in html
myPane.setText("
Exploring Beziercurves with:
" -
|| "" -
|| "NetRexx
(http://netrexx.org)" -
|| "The code is based on the information provided on " -
|| "this website
(https://pomac.github.io/bezierinfo/).
" -
|| "You can use this code in the following way:
" -
|| "" -
|| "- Execution starts with a predefined set of startpoint, endpoint and 3 controlpoints.
" -
|| "- With the left mousebutton all points can be dragged.
" -
|| "- Clicking the left mousebutton on one of the points will add an additional controlpoint " -
|| "between the one clicked and the next point.
" -
|| "- Clicking the right mousebutton on one of the points will remove that controlpoint.
" -
|| "- Double clicking the left mousebutton allows you the change the number of flattening steps.
" -
|| "
" -
|| "Go ahead, have some fun, I hope!
Ruurd Idenburg
")
-- The HyperlinkListener enables browsing of the referred websites
hll = HyperlinkListener myHyperlinkListener()
myPane.addHyperlinkListener(hll)
-- Add the JEditorPane to the JDialog
myDialog.getContentPane().add(myPane, BorderLayout.CENTER)
-- and set the necessary options for the JDialog
myDialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
myDialog.pack()
myDialog.setSize(dialog_width,dialog_height)
myDialog.setVisible(isTrue)
-- Create the canvas for drawing the curves
myCanvas = DrawCanvas(25)
myCanvas.setPreferredSize(Dimension(canvas_width, canvas_height))
-- Add the drawing canvas to the JFrame
this.getContentPane().add(myCanvas)
-- and set the necessary options for the JFrame
this.setTitle("Exploring Beziercurves.....")
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
this.pack()
this.setSize(canvas_width,canvas_height)
this.setLocationRelativeTo(null)
this.setVisible(isTrue)
/**
* Start the application in the most thread-safe way.
*/
method main(args=String[] ) static
SwingUtilities.invokeLater(myRunnable())
/**
* Define inner class DrawCanvas, which is a JPanel used for custom drawing.
*/
class BezierCurves.DrawCanvas dependent private extends JPanel -
adapter implements MouseListener, MouseMotionListener
properties public
-- first point is start, last point is end, in between are control
points=ArrayList()
-- curvePoint is updated every flattening step
curvePoint
-- size of a curvepoint
curvePointRadius = 5
-- distance between X and also Y axes
skeletonAxesIncrement = 25
-- The number of flattening steps to calculate the Bezier curve
flatteningSteps = 10
method DrawCanvas(steps)
-- Create the initial start-, end- and control-points
points.add(110 150)
points.add(25 190)
points.add(210 250)
points.add(230 110)
points.add(210 30)
-- Set the number of flattening steps
flatteningSteps = steps
-- Set the mouse listeners we need and implement
addMouseListener(this)
addMouseMotionListener(this)
method mouseDragged(event=MouseEvent)
moveCurvePoint(event)
method mouseReleased(event=MouseEvent)
moveCurvePoint(event)
method moveCurvePoint(event = MouseEvent)
ex = event.getX
ey = event.getY
r = curvePointRadius
loop label redraw i=0 while i=px-r & px<=px+r & ey>=py-r & ey<=py+r) then do
points.set(i, ex ey)
repaint()
leave redraw
end
end
/**
* Handle single click and double click and left or right mouse action
* Double click allows altering the number of flattenening steps
* Single click with left mouse button allows adding a control point
* Single click with right mouse button allows removing a control point
*/
method mouseClicked(event=MouseEvent)
if event.getClickCount>1 & \event.isConsumed() then do
event.consume()
input = JOptionPane.showInputDialog("Enter the number of curve flattening steps\nShould be in the range 0-100",flatteningSteps)
if (input\=null &\input.isEmpty()) then do
value = Rexx input
if (value.dataType('W') & value>=0 & value<=100) then do
flatteningSteps = value
repaint()
end
else do
JOptionPane.showMessageDialog(null, "Number of flattening steps not in range 0-100!")
end
end
end
if event.getClickCount=1 then do
ex = event.getX
ey = event.getY
r = curvePointRadius
loop label redraw i=0 while i=px-r & px<=px+r & ey>=py-r & ey<=py+r) then do
mouseButton = event.getButton
if mouseButton==MouseEvent.BUTTON1 then addControlPoint(i)
if mouseButton==MouseEvent.BUTTON3 then removeControlPoint(i)
leave redraw
end
end
end
/**
* A new control point is added halfway between the clicked one and the next control point.
* Adding a control point beyond the end point is not allowed
*/
method addControlPoint(i)
if i0 & ii then points.add(oldpts.get(j))
end
repaint()
end
else do
JOptionPane.showMessageDialog(null, "Removing start or end point is not allowed!")
end
/**
* The code to (re)paint the drawing canvas
*/
method paintComponent( g=Graphics )
super.paintComponent(g)
-- draw the skeleton
incr = skeletonAxesIncrement
height = getHeight()
width = getWidth()
g.setColor(Color.lightgray)
loop i=0 by incr while i<=(Rexx height).max(width)
g.drawLine(i,0,i,height)
g.drawLIne(0,i,width,i)
end
-- show the number of flattening steps
g.setColor(Color.BLACK)
g.setFont(Font("Monospaced",Font.BOLD,10))
xPos = 5
yPos = height-5
g.drawString("Flattening steps:" flatteningSteps, xPos, yPos)
-- draw the lines between the points
if points.size>1 then do
px = 0
py = 0
loop i=0 while i0 then g.drawLine(px,py,x,y)
px = x
py = y
end
end
-- draw the points and information text
if points.size>0 then do
loop i=0 while i1 then do
t = 0.0
prevptx = 0
prevpty = 0
loop while t<=1
g.setColor(Color.blue)
stepCurvePoint(points, t)
ptx = curvePoint.word(1)
pty = curvePoint.word(2)
g.drawOval(int ptx-1,int pty-1, 2, 2)
if t<>0 then do
g.drawLine(int prevptx,int prevpty,int ptx,int pty)
end
prevptx = ptx
prevpty = pty
t = t+(100/flatteningSteps/100)
end
end
/**
* If we want to draw Bézier curves, we can run through all values of t from 0 to 1 and
* then compute the weighted basis function at each value, getting the x/y values we need
* to plot. Unfortunately, the more complex the curve gets, the more expensive this
* computation becomes. Instead, we can use de Casteljau's algorithm to draw curves.
* This is a geometric approach to curve drawing, and it's really easy to implement.
* So easy, in fact, you can do it by hand with a pencil and ruler.
*
* - Treat t as a ratio (which it is). t=0 is 0% along a line, t=1 is 100% along a line.
*
- Take all lines between the curve's defining points. For an order n curve, that's n lines.
*
- 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.
*
- Now form lines between those points. This gives n-1 lines.
*
- Place markers along each of these line at distance t.
*
- Form lines between those points. This'll be n-2 lines.
*
- Place markers, form lines, place markers, etc.
*
- Repeat this until you have only one line left. The point t on that line coincides with the original curve point at t.
*
*/
method stepCurvePoint(_points=ArrayList, t)
if _points.size>1 then do
newPoints= ArrayList()
--say newPoints.length
loop i=0 while i<_points.size-1
x = (1-t)*(Rexx _points.get(i)).word(1)+t*(Rexx _points.get(i+1)).word(1)
y = (1-t)*(Rexx _points.get(i)).word(2)+t*(Rexx _points.get(i+1)).word(2)
newPoints.add(x y)
end
stepCurvePoint(newPoints, t)
end
else do
x = (Rexx _points.get(0)).word(1).format(null,0)
y = (Rexx _points.get(0)).word(2).format(null,0)
curvePoint = x y
end
/**
* The HyperlinkListener is triggered by the hyperlinkUpdate event
* The event type can be ENTERED, EXITED or ACTIVATED. If the hyperlink is activated
* the (default) desktop browser is invoked to try to browse the activated link.
*/
class myHyperlinkListener implements HyperlinkListener
method hyperlinkUpdate(event = HyperlinkEvent)
if (event.getEventType == HyperlinkEvent.EventType.ACTIVATED) then do
theURL = URL event.getURL()
if (theURL\=null) then do
dt = Desktop Desktop.getDesktop()
dt.browse(theURL.toURI)
catch ex=Exception
ex.printStackTrace
end
end
/**
* Start the application in a thread-safe way
*/
class myRunnable private implements Runnable
method run
BezierCurves()