patternMinor
Calculating a circle with bezier curves
Viewed 0 times
circlewithcalculatingbeziercurves
Problem
I am new to Haskell, and have been reading Learn You a Haskell for Great Good!.
I've rewritten a problem that I recently solved in JavaScript in Haskell to practice what I have been reading.
A little bit of a back story with out rambling on too much; I wanted to create a circle using bezier curve drawing commands. Similar to SVG but for an Android vector drawable. The reason I needed the circle in a path command is for animation. I was using AnimatedVectorDrawable to animate a circle into a square. If your drawable has the same path with the same number of commands in two different files it can animate fluidly between them. Making a square with curve commands is easy enough, but the circle needed some math.
What I did to quickly generate my circle path was create a script in Javascript to do it. Being a math problem I felt this was a good candidate for Haskell exercise.
Here is my Haskell script:
```
-- Usage:
-- beziercircle [circumfrance] [offest x] [offset y]
--
-- Example:
-- beziercircle 500
--
--
-- Calculates a circle using bezier paths for using in android
-- vector drawables
--
import System.Environment
import Text.Printf
bz = 0.552284749831
zero = read "0" :: Float
main = do
args = i + 1 then read (args !! i) :: Float else zero)
ip d = d - (d * bz)
op d = d + (d * bz)
controls a b c d x y = map (\(a, b, c, d) -> (ox a,oy b,ox c,oy d)) [c1, c2, c3, c4]
where ox = (+x)
oy = (+y)
c1 = (a, b, b, a)
c2 = (c, a, d, b)
c3 = (d, c, c, d)
c4 = (b, d, a, c)
points d x y = map (\(x,y) -> (ox x, oy y)) [p2, p3, p4, p1]
where ox = (+x)
oy = (+y)
p1 = (zero, d)
p2 = rotate90 p1
p3 = offset d p2
p4 = offset d p1
offset o (x, y) = (x + o, y + o)
rotate90 (x, y) = (y, x * (-1))
showMove (x, y) = printf "M %f %f \n" x y
showCurve (x, y) (cx1, cy1, cx2, cy2) = do
printf "C %f %f %f %f %f %f \n" cx1 cy1 cx2 cy2 x y
showA
I've rewritten a problem that I recently solved in JavaScript in Haskell to practice what I have been reading.
A little bit of a back story with out rambling on too much; I wanted to create a circle using bezier curve drawing commands. Similar to SVG but for an Android vector drawable. The reason I needed the circle in a path command is for animation. I was using AnimatedVectorDrawable to animate a circle into a square. If your drawable has the same path with the same number of commands in two different files it can animate fluidly between them. Making a square with curve commands is easy enough, but the circle needed some math.
What I did to quickly generate my circle path was create a script in Javascript to do it. Being a math problem I felt this was a good candidate for Haskell exercise.
Here is my Haskell script:
```
-- Usage:
-- beziercircle [circumfrance] [offest x] [offset y]
--
-- Example:
-- beziercircle 500
--
--
-- Calculates a circle using bezier paths for using in android
-- vector drawables
--
import System.Environment
import Text.Printf
bz = 0.552284749831
zero = read "0" :: Float
main = do
args = i + 1 then read (args !! i) :: Float else zero)
ip d = d - (d * bz)
op d = d + (d * bz)
controls a b c d x y = map (\(a, b, c, d) -> (ox a,oy b,ox c,oy d)) [c1, c2, c3, c4]
where ox = (+x)
oy = (+y)
c1 = (a, b, b, a)
c2 = (c, a, d, b)
c3 = (d, c, c, d)
c4 = (b, d, a, c)
points d x y = map (\(x,y) -> (ox x, oy y)) [p2, p3, p4, p1]
where ox = (+x)
oy = (+y)
p1 = (zero, d)
p2 = rotate90 p1
p3 = offset d p2
p4 = offset d p1
offset o (x, y) = (x + o, y + o)
rotate90 (x, y) = (y, x * (-1))
showMove (x, y) = printf "M %f %f \n" x y
showCurve (x, y) (cx1, cy1, cx2, cy2) = do
printf "C %f %f %f %f %f %f \n" cx1 cy1 cx2 cy2 x y
showA
Solution
Let's start with the back story:
I wanted to create a circle using bezier curve drawing commands. Similar to SVG but for an Android vector drawable. The reason I needed the circle in a path command is for animation.
The documentation for Android's
The variable names are not as helpful as they could be. What is
Edit: having compiled and tested the code, it's definitely the diameter rather than the circumference, so the comment is buggy.
SVG paths have relative and absolute versions of the movement instructions. If you were to use the relative
Edit: to justify my claim that using relative movement rather than absolute simplifies the code, here's the full program using relative movement, minus the header comment:
I'm sure
You say that you're looking for techniques to shrink the code. The simplest one is to not overcomplicate. Consider
vs
I find the second easier to read as well as shorter.
I wanted to create a circle using bezier curve drawing commands. Similar to SVG but for an Android vector drawable. The reason I needed the circle in a path command is for animation.
The documentation for Android's
VectorDrawable says that it uses exactly the same path syntax as SVG. That means that it supports A and a for circular arcs. Have you actually tested that they don't work?The variable names are not as helpful as they could be. What is
bz? Given that one of the inputs is documented as circumfrance (correct spelling is circumference), I can't understand why I don't see pi anywhere: it is actually the diameter instead?Edit: having compiled and tested the code, it's definitely the diameter rather than the circumference, so the comment is buggy.
SVG paths have relative and absolute versions of the movement instructions. If you were to use the relative
c instead of the absolute C you could eliminate the offsets, simplifying the code.Edit: to justify my claim that using relative movement rather than absolute simplifies the code, here's the full program using relative movement, minus the header comment:
import System.Environment
import Text.Printf
bz = 0.552284749831
main = do
args = i + 1 then read (args !! i) :: Float else 0.0)
quadrant r = [(0.0, -bz*r), ((1.0-bz)*r, -r), (r, -r)]
quadrants r = [q1, q2, q3, q4]
where rotate90AC (x, y) = (-y, x)
q1 = quadrant r
q2 = map rotate90AC q1
q3 = map rotate90AC q2
q4 = map rotate90AC q3
showMove (x, y) = printf "M %f %f \n" x y
showQuadrant [(cx1, cy1), (cx2, cy2), (x, y)] = do
printf "c %f %f %f %f %f %f \n" cx1 cy1 cx2 cy2 x y
showAllQuadrants r = concat $ (map showQuadrant (quadrants r))I'm sure
quadrants can be simplified further by someone who knows the Haskell standard library better than me.You say that you're looking for techniques to shrink the code. The simplest one is to not overcomplicate. Consider
controls a b c d x y = map (\(a, b, c, d) -> (ox a,oy b,ox c,oy d)) [c1, c2, c3, c4]
where ox = (+x)
oy = (+y)vs
controls a b c d x y = map (\(a, b, c, d) -> (a+x, b+y, c+x, d+y)) [c1, c2, c3, c4]I find the second easier to read as well as shorter.
Code Snippets
import System.Environment
import Text.Printf
bz = 0.552284749831
main = do
args <- getArgs
let d = parseArg 0 args
let r = d / 2
let ox = parseArg 1 args
let oy = parseArg 2 args
putStrLn $ (showMove (ox, r + oy)) ++ (showAllQuadrants r) ++ "Z"
where
parseArg i args = (if length args >= i + 1 then read (args !! i) :: Float else 0.0)
quadrant r = [(0.0, -bz*r), ((1.0-bz)*r, -r), (r, -r)]
quadrants r = [q1, q2, q3, q4]
where rotate90AC (x, y) = (-y, x)
q1 = quadrant r
q2 = map rotate90AC q1
q3 = map rotate90AC q2
q4 = map rotate90AC q3
showMove (x, y) = printf "M %f %f \n" x y
showQuadrant [(cx1, cy1), (cx2, cy2), (x, y)] = do
printf "c %f %f %f %f %f %f \n" cx1 cy1 cx2 cy2 x y
showAllQuadrants r = concat $ (map showQuadrant (quadrants r))controls a b c d x y = map (\(a, b, c, d) -> (ox a,oy b,ox c,oy d)) [c1, c2, c3, c4]
where ox = (+x)
oy = (+y)controls a b c d x y = map (\(a, b, c, d) -> (a+x, b+y, c+x, d+y)) [c1, c2, c3, c4]Context
StackExchange Code Review Q#141491, answer score: 2
Revisions (0)
No revisions yet.