Hiya folks,

I spent a little time over the week reading about Rhodonea (Rose) curves, and generating some beautiful plots from what I learned. The following image created by Jason Davies illustrates curves plotted from a number of different curve parameters.

beautiful roses

As you can see, some parameters produce beautiful graphs some even resembling flowers. Even the more complex plots can contain inset flowers. The graphs with more complex patterns piqued my curiosity and as I read more on the topic I happened upon a spectacular example called the Maurer rose. This rose curve was introduced by Peter M. Maurer in 1987.

The Maurer rose is built upon a rose curve, embellishing the curve with a series of lines that connect points along the curve. This creates a polygonal curve and it gives the plot a distinctive solid but wireframe appearance that is really interesting. Sooooo, lets build one.

Step 1. Building A Rose Curve

For polar coordinates, a rose curve is defined by the following parametric equation:

$$ r = sin(k*\theta) $$

So lets convert this to some concept code, we use 361 points because this is required later when we make the Maurer rose.

        float n = 1f; // 1 is a typical sine

        // in 1 degree increments step 361 degrees
        for(int theta = 0; theta <= 360; theta++)
        {
            // convert the degree (theta) value to radians
            float k = theta * (Mathf.PI / 180);

            // this is the rose formula above, scaled up
            // using a scaling constant
            float r = roseScale * Mathf.Sin(n * k);

            // convert polar to cartesian coordinates
            float x = r * Mathf.Cos(k) + roseWidth/2;
            float y = r * Mathf.Sin(k) + roseHeight/2;

            // add the point on the rose to a list of points
            Vector3 point = new Vector3(x,y,0);
            points.Add(point);
        }

The parameter n is provided outside of the function, and the values affect the rose that is generated. The following illustration is a great animation that provides a more concrete example of how these parameters work.

image info

n is the radius of the light grey cog, d is the radius of the darker gray cog, and k is the length of the blue line. If all of the parameters are 1, we plot a circle.

Interesting things begin to happen when we play with these parameters. For example, if we change n to 3, leave d as 1 and change k to 3 we generate a rose curve with petals!

image info

Now lets plot these parameters using our code above. This produces the expected rose curve, wheee!

image info

Step 2. Connect The Points (Build A Maurer Rose)

As mentioned before, the Maurer rose builds upon the rose curve. So we can extend the example curve above and embellish it with lines. Hopefully that will provide a nice illustration for how this works. First lets start with a rose curve thats just a circle (remember, all rose params are 1, maurer ‘step’ param is 29 degrees).

image info

A Maurer rose of the rose r = sin(nθ) consists of 360 lines that connect the 361 points in the curve. It is a closed path, so the lines eventually connect back to the beginning.

Now to build on our rose above, with the parameters n to 3, leave d as 29 :

image info

And that, my friends, is pretty much it :). The d parameter ‘step’ is far easier to see on the simple circular curve. If you play with this parameter you can clearly see the stepped points on the curve, and how it relates to the ’d’ angle parameter. Here is some sketch-quality code if you want to play with these beautiful curves in your own time:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class DrawRose : MonoBehaviour
{
    public TMP_Text stepText;
    public GameObject PointPixel;

    public float roseWidth = 100f;
    public float roseHeight = 100f;
    public float roseScale = 200f;

    // parameters
    // n = 6, d = 71 is my fav :)
    public float n = 3f;  // 1 is a typical sine
    public float d = 29f; // the degree 'step' for each loop step

    float rosePointDelay = 0.001f;
    float roseLineDelay = 0.5f;

    LineDraw lineDraw;

    List<Vector3> rosePoints = new List<Vector3>();

    List<GameObject> rosePointMeshes = new List<GameObject>();
    List<GameObject> maurerPointMeshes = new List<GameObject>();

    void Start()
    {
        lineDraw = GetComponent<LineDraw>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            StartCoroutine(PlotRosePoints());
        }
    }

    IEnumerator PlotRosePoints()
    {
        // in 1 degree increments step 361 degrees
        for (int theta = 0; theta <= 360; theta++)
        {
            // convert the degree (theta) value to radians
            float k = theta * (Mathf.PI / 180);

            // take sin of n and k calculated above
            float r = roseScale * Mathf.Sin(n * k);

            // convert polar to cartesian coordinates
            float x = r * Mathf.Cos(k) + roseWidth / 2;
            float y = r * Mathf.Sin(k) + roseHeight / 2;

            Vector3 point = new Vector3(x, y, 0);
            rosePoints.Add(point);

            stepText.text = String.Format("Building Rose Curve\n");
            stepText.text += String.Format("--------------------------------------\n");
            stepText.text += String.Format("Theta: {0}\tRadians: {1}\n",theta, k);
            stepText.text += String.Format("(sin({0} * {1}) => {2}\n", n, k, Mathf.Sin(n * k));
            stepText.text += String.Format("\nx: {0}, y: {1}",  x, y);

            GameObject pixel = Instantiate(PointPixel, point, Quaternion.identity);
            rosePointMeshes.Add(pixel);

            yield return new WaitForSeconds(rosePointDelay);
        }
        StartCoroutine(PlotRoseLinePoints());
    }

    IEnumerator PlotRoseLinePoints()
    {
        Color pixelColor = Color.white;

        // in 1 degree increments step 361 degrees
        // for each degree, step d degrees
        for (int theta = 0; theta <= 360; theta++)
        {
            // convert the degree (theta) value to radians
            float k = (theta * d) * (Mathf.PI / 180); // d is the step angle

            // take sin of n and k calculated above
            float r = roseScale * Mathf.Sin(n * k);

            // convert polar to cartesian coordinates
            float x = r * Mathf.Cos(k) + roseWidth / 2;
            float y = r * Mathf.Sin(k) + roseHeight / 2;

            Vector3 point = new Vector3(x, y, 0);

            stepText.text = String.Format("Connecting Maurer Lines\n");
            stepText.text += String.Format("--------------------------------------\n");
            stepText.text += String.Format("Theta: {0} Step(d): {1} (theta*d): {2} Radians: {3}\n", theta, d, (theta * d), k);
            stepText.text += String.Format("(sin({0} * {1}) => {2}\n", n, k, Mathf.Sin(n * k));
            stepText.text += String.Format("\nx: {0}, y: {1}",  x, y);
            lineDraw.AddVertex(point);

            if (theta == 0)
            {
                pixelColor = Color.white;
            }
            else
            {
                pixelColor = Color.red;
            }

            // illustrate that d is the step, 1 is just connecting one to the next
            GameObject pixel = Instantiate(PointPixel, point, Quaternion.identity);
            pixel.GetComponent<Renderer>().material.color = pixelColor;
            pixel.GetComponent<Renderer>().material.EnableKeyword("_EMISSION");
            pixel.GetComponent<Renderer>().material.SetColor("_EmissionColor", pixelColor);
            pixel.transform.localScale *= 2;

            maurerPointMeshes.Add(pixel);

            yield return new WaitForSeconds(roseLineDelay);
        }
    }
}

And I’ll end here with a rose generated from my favourite parameters n=6 d=71

image info

Until next time friends! Be safe, have fun!