Sine heart

A simple sine heart graph with multiple frequencies.

Log in to post a comment.

// Little sine wave heart generator

// Originality disclosure:
// Sine wave heart equation was found on the internet a while ago (not sure what the original source is)
// Was being lazy and used an LLM to change the sine wave heart function, but cleaned
// it up afterwards, so it's fairly decent now (but the interpolation "idea" came from the LLM, 
// comments and current set up are mine).

// TODO: Make offsets and scalars constants. Some DRY refinement.

Canvas.setpenopacity(1);

// Detail level
const xStep = 0.001; // min = 0.001 max = 10 step=0.001

// // Original sine wave heart function
// function sine_heart_old(x, a) {
//     return Math.abs(x)**(2/3) + Math.sqrt(3.3 - x**2) *  Math.sin(a * Math.PI * x);
// }


function sine_heart(x, a) {
    // (semi-)Circular envelope
    const circle_envelope = Math.sqrt(3.3 - x**2);
    // Sine wave in circular envelope
    const wave = circle_envelope * Math.sin(a * Math.PI * x);
    // Rounded heart envelope (like a ⋎, rounded v)
    const heart_envelope = Math.abs(x)**(2/3)
    // Blending constant. Only affects <1 "a"s
    // that were having a C^1 discontinuity at x=0
    const blend = Math.min(1, Math.abs(a))**4;
    // Average height of the heart term. Calculated via:
    // (\int_{xMin}^{xMax} sine_heart(x, 16) dx)/(xMax-xMin)
    // This may be the wrong way to calculate it
    // TODO: fix this calculation to properly align low "a" value hearts
    const baseline = 0.893283331772;

    // Interpolate between a constant and the heart envelope to smooth
    // the x=0 kink caused by the heart envelope & noticable in low "a"s
    const offset = baseline + blend * (heart_envelope - baseline);
    
    // Final heart: circular sine + interpolated heart swoop
    return wave + offset;
}


// Main code
const turtle = new Turtle();
// Boundary of our heart. Decided by sine_heart()
const xMin = -Math.sqrt(3.3);
const xMax = Math.sqrt(3.3);
// Arbitrary scale by to fill the space nicely
const scale_by = 5;
// frequency values for the row of hearts
var a_vals = [0.5, 1, 2, 4, 8, 16];
// Distance between the start of each heart
var xshift=5*scale_by;

// Loop over frequency values
for (let i=0; i<a_vals.length; i++) {
    var a = a_vals[i];
    var x=xMin;
    var y=sine_heart(x,a);
    turtle.penup();
    turtle.goto(x*scale_by+(i*xshift-65),-y*scale_by);
    
    turtle.pendown();
    // Step through heart function
    for (let j=0; x<xMax; j++) {
        x = xMin + (j*xStep);
        if(x>xMax) { 
            break;
        }
        y = sine_heart(x,a);
        turtle.goto(x*scale_by+(i*xshift-65),-y*scale_by);
    }
    turtle.penup();
}