Equation Solver

Equation Solver

The context provides a constraint-based system for positioning points. Define relationships between points, and the solver computes the coordinates.

Creating a Context

local h = require("hobby")
local ctx = h.context()

Point Variables

known(x, y)

Creates a point with known coordinates.

local z0 = ctx:known(0, 0)
local z1 = ctx:known(100, 50)

unknown()

Creates a point with unknown coordinates (to be solved).

local mid = ctx:unknown()

point() / points(n)

Aliases for creating unknown points.

local p = ctx:point()         -- single unknown
local pts = ctx:points(5)     -- array of 5 unknowns

Constraints

Equality Constraints

ctx:eq(v, h.point(50, 50))   -- v = (50, 50)
ctx:eqx(v, 50)               -- v.x = 50
ctx:eqy(v, 30)               -- v.y = 30
ctx:eqvar(a, b)              -- a = b
ctx:eqvarx(a, b)             -- a.x = b.x
ctx:eqvary(a, b)             -- a.y = b.y

Midpoint

ctx:midpoint(m, a, b)        -- m = midpoint of a and b
local m = ctx:midpointof(a, b)  -- convenience: returns new point

Interpolation (Between)

ctx:between(p, a, b, 0.25)   -- p = 0.25[a,b] (quarter way)
local p = ctx:betweenat(a, b, 0.75)  -- convenience: returns new point

Collinear

ctx:collinear(p, a, b)       -- p lies on line through a and b

Intersection

ctx:intersection(p, a1, a2, b1, b2)  -- p = intersection of lines
local p = ctx:intersectionof(a1, a2, b1, b2)  -- convenience version

Vector Arithmetic

ctx:sum(r, a, b)             -- r = a + b
ctx:diff(r, a, b)            -- r = a - b
ctx:scaled(r, v, 2)          -- r = 2 * v

Solving

After defining constraints, call solve():

ctx:solve()

Accessing Results

After solving:

print(v.x, v.y)              -- direct access
local x, y = v:xy()          -- as tuple
local p = v:point()          -- as hobby point

Building Paths with Variables

The context can create path builders that reference variables:

local path = ctx:path()
    :movetovar(z0)
    :curvetovar(z1)
    :linetovar(z2)
    :build()

Complete Example

local h = require("hobby")

local ctx = h.context()

-- Define rectangle corners
local z0 = ctx:known(0, 0)      -- bottom-left
local z1 = ctx:known(100, 0)    -- bottom-right
local z2 = ctx:known(100, 60)   -- top-right
local z3 = ctx:known(0, 60)     -- top-left

-- Derived points
local mid_bottom = ctx:midpointof(z0, z1)
local mid_top = ctx:midpointof(z3, z2)
local center = ctx:midpointof(mid_bottom, mid_top)
local third = ctx:betweenat(z0, z1, 1/3)

-- Intersection of diagonals
local diag_center = ctx:intersectionof(z0, z2, z1, z3)

-- Solve
ctx:solve()

-- Print results
print(string.format("Center: (%.1f, %.1f)", center.x, center.y))
print(string.format("1/3 point: (%.1f, %.1f)", third.x, third.y))

-- Draw
local rect = h.path()
    :moveto(z0:point())
    :lineto(z1:point())
    :lineto(z2:point())
    :lineto(z3:point())
    :cycle()
    :stroke("black")
    :build()

local diag1 = h.path()
    :moveto(z0:point())
    :lineto(z2:point())
    :evenly()
    :stroke("gray")
    :build()

local diag2 = h.path()
    :moveto(z1:point())
    :lineto(z3:point())
    :evenly()
    :stroke("gray")
    :build()

local centerMark = h.fullcircle()
    :scaled(6)
    :shifted(center.x, center.y)
    :fill("red")

h.svg()
    :padding(10)
    :add(rect)
    :add(diag1)
    :add(diag2)
    :add(centerMark)
    :write("context.svg")

Context example

Triangle Centroid Example

local h = require("hobby")

local ctx = h.context()

-- Triangle vertices
local a = ctx:known(0, 0)
local b = ctx:known(80, 0)
local c = ctx:known(40, 70)

-- Midpoints of sides (for medians)
local mAB = ctx:known(40, 0)
local mBC = ctx:known(60, 35)
local mCA = ctx:known(20, 35)

-- Centroid: intersection of medians
local centroid = ctx:intersectionof(a, mBC, b, mCA)

ctx:solve()

print(string.format("Centroid: (%.1f, %.1f)", centroid.x, centroid.y))
-- Expected: (40.0, 23.3) = average of vertices