Skip to content

Draw some heart #19

@ghost

Description

Thanks for the cool lua lib.

play with keyboard 1/2/3.

image

image

image

Lua Code
local fenster = require('fenster')

local width = 500
local height = 500
local fps = 30

local window = fenster.open(width, height, 'Hello fenster!', 1, fps)

-- Heart shape function
local function heart(x, y)
    -- https://mathworld.wolfram.com/HeartCurve.html
    -- Convert window coordinates to mathematical coordinates, using window center as origin
    local xp = (x - width / 2) / (width / 3)  -- Scale factor for x
    local yp = (height / 2 - y) / (height / 3)  -- Scale and invert y because y increases downwards

    -- Heart equation: (x^2 + y^2 - 1)^3 = x^2 * y^3
    return (xp^2 + yp^2 - 1)^3 - xp^2 * yp^3 <= 0
end

-- Define the heart shape function with a pattern using level sets
local function heart_with_pattern(x, y)
    -- https://en.wikipedia.org/wiki/Level_set
    -- Convert window coordinates to mathematical coordinates, using window center as origin
    local xp = (x - width / 2) / (width / 3)  -- Scale factor for x
    local yp = (height / 2 - y) / (height / 3)  -- Scale and invert y

    -- Heart level set function
    local z = xp * xp + yp * yp - 1
    local f = z * z * z - xp * xp * yp * yp * yp

    -- Mapping result to color intensity (like characters in the original code)
    if f <= 0.0 then
        -- Map 'f' value to grayscale intensity based on the same idea as character mapping
        local intensity = math.floor((f * -8.0) % 8)  -- Same concept as indexing into characters
        local shades = {
            0x000000, -- Black (represents '@')
            0x222222, -- Dark gray (represents '%')
            0x444444, -- Medium dark gray (represents '#')
            0x666666, -- Gray (represents '*')
            0x888888, -- Light gray (represents '+')
            0xaaaaaa, -- Lighter gray (represents '=')
            0xcccccc, -- Very light gray (represents ':')
            0xffffff  -- White (represents '.')
        }
        local shades = {
            0x330000, -- Dark red (represents '@')
            0x660000, -- Darker red (represents '%')
            0x990000, -- Medium dark red (represents '#')
            0xcc0000, -- Medium red (represents '*')
            0xff0000, -- Bright red (represents '+')
            0xff3333, -- Light red (represents '=')
            0xff6666, -- Lighter red (represents ':')
            0xffffff  -- White (represents '.')
        }
        return shades[intensity + 1]  -- Return color based on intensity
    else
        return 0xffffff  -- Background white
    end
end

-- Define the heart shape function f(x, y, z)
local function f(x, y, z)
    local a = x * x + (9.0 / 4.0) * y * y + z * z - 1
    return a * a * a - x * x * z * z * z - (9.0 / 80.0) * y * y * z * z * z
end

-- Define h(x, z) function to find the surface at a given x and z
local function h(x, z)
    for y = 1.0, 0.0, -0.001 do
        if f(x, y, z) <= 0.0 then
            return y
        end
    end
    return 0.0
end


local function draw_heart_with_pattern()
    for x = 0, width - 1 do
        for y = 0, height - 1 do
            local color = heart_with_pattern(x, y)
            window:set(x, y, color)  -- Set pixel color based on the level set function using hex
        end
    end
end

local function draw_heart()
    for x = 0, width - 1 do
        for y = 0, height - 1 do
            if heart(x, y) then
                window:set(x, y, 0xff0000) -- Set red color for the heart shape
            end
        end
    end
end

local function draw_heart3d()
    for sy = 0, height - 1 do -- same
        local z = 1.5 - sy * 3.0 / height  -- Map screen coordinates to z
        for sx = 0, width - 1 do
            local x = sx * 3.0 / width - 1.5  -- Map screen coordinates to x
            local v = f(x, 0.0, z)  -- Evaluate the heart equation
            local r = 0  -- Red channel value

            if v <= 0.0 then
                local y0 = h(x, z)  -- Get the y value at (x, z)
                local ny = 0.0011  -- Small offset for normal calculation
                local nx = h(x + ny, z) - y0  -- Calculate the normal x component
                local nz = h(x, z + ny) - y0  -- Calculate the normal z component
                local nd = 1.0 / math.sqrt(nx * nx + ny * ny + nz * nz)  -- Normalize
                local d = (nx + ny - nz) * nd * 0.5 + 0.5  -- Calculate brightness
                r = math.floor(d * 255.0)  -- Map to 0-255 range
            end
            
            -- Ensure red channel is clamped between 0 and 255
            r = math.max(0, math.min(255, r))
            
            -- Set the pixel color (R, G, B)
            window:set(sx, sy, (r * 2^16) + (0 * 2^8) + 0)  -- Only red color
        end
    end
end

-- Draw the heart shape with color
local function draw_heart3d_animate(t)
    -- Heart scaling based on time (to create the pulsing effect)
    local s = math.sin(t)
    local a = s * s * s * s * 0.2  -- Scaling factor based on sine wave

    for sy = 0, height - 1 do
        local z = 1.5 - sy * 3.0 / height  -- Map screen coordinates to z
        z = z * (1.2 - a) -- animate
        for sx = 0, width - 1 do
            local x = sx * 3.0 / width - 1.5  -- Map screen coordinates to x
            x = x * (1.2 + a) -- animate
            local v = f(x, 0.0, z)  -- Evaluate the heart equation
            local r = 0  -- Red channel value

            if v <= 0.0 then
                local y0 = h(x, z)  -- Get the y value at (x, z)
                local ny = 0.0011  -- Small offset for normal calculation
                local nx = h(x + ny, z) - y0  -- Calculate the normal x component
                local nz = h(x, z + ny) - y0  -- Calculate the normal z component
                local nd = 1.0 / math.sqrt(nx * nx + ny * ny + nz * nz)  -- Normalize
                local d = (nx + ny - nz) * nd * 0.5 + 0.5  -- Calculate brightness
                r = math.floor(d * 255.0)  -- Map to 0-255 range
            end
            
            -- Ensure red channel is clamped between 0 and 255
            r = math.max(0, math.min(255, r))
            
            -- Set the pixel color (R, G, B)
            local color = fenster.rgb(r, 0, 0)
            window:set(sx, sy, color)  -- Only red color
        end
    end
end


local draw_menu = {
    [1] = draw_heart,
    [2] = draw_heart_with_pattern,
    [3] = draw_heart3d,
    [4] = draw_heart3d_animate,
}
local draw_type = 1

local t = 0
while window:loop() and not window.keys[27] do
    window:clear()

    if window.keys[string.byte('1')] then
        draw_type = 1
    elseif window.keys[string.byte('2')] then
        draw_type = 2
    elseif window.keys[string.byte('3')] then
        draw_type = 3
    elseif window.keys[string.byte('4')] then
        t = 0
        draw_type = 4
    end

    draw_menu[draw_type](t)
    t = t + 0.1  -- Update time
end

windows playground.
playground.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions