okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Ayman BagabasMerge Layout with Cassowary constraints (#88)2c67e98

upstream
3.5 KiB172 linesraw
1// Package casso implements a Cassowary constraint solver.
2//
3// Ported from github.com/lithdew/casso with unused API removed.
4package casso
5
6import "sync/atomic"
7
8type symbolKind uint8
9
10const (
11	external symbolKind = iota
12	slack
13	errorSym
14	dummy
15)
16
17func (s symbolKind) restricted() bool { return s == slack || s == errorSym }
18
19// Symbol is an opaque identifier for a variable in the solver.
20type Symbol uint64
21
22var (
23	count uint64
24	zero  Symbol
25)
26
27// New creates a new external symbol.
28func New() Symbol { return next(external) }
29
30func next(typ symbolKind) Symbol {
31	return Symbol((atomic.AddUint64(&count, 1) & 0x3fffffffffffffff) | (uint64(typ) << 62))
32}
33
34func (sym Symbol) kind() symbolKind { return symbolKind(sym >> 62) }
35func (sym Symbol) isZero() bool     { return sym == zero }
36func (sym Symbol) restricted() bool { return !sym.isZero() && sym.kind().restricted() }
37func (sym Symbol) external() bool   { return !sym.isZero() && sym.kind() == external }
38func (sym Symbol) isDummy() bool    { return !sym.isZero() && sym.kind() == dummy }
39
40// T creates a Term with the given coefficient.
41func (sym Symbol) T(coeff float64) Term { return Term{coeff: coeff, id: sym} }
42
43// Priority represents the strength of a constraint.
44type Priority float64
45
46// Op represents a constraint operator.
47type Op uint8
48
49const (
50	EQ Op = iota
51	GTE
52	LTE
53)
54
55// Constraint represents a linear constraint.
56type Constraint struct {
57	op   Op
58	expr expr
59}
60
61// NewConstraint creates a new constraint.
62func NewConstraint(op Op, constant float64, terms ...Term) Constraint {
63	return Constraint{op: op, expr: newExpr(constant, terms...)}
64}
65
66func (c Constraint) clone() Constraint {
67	return Constraint{op: c.op, expr: c.expr.clone()}
68}
69
70// Term represents a variable with a coefficient.
71type Term struct {
72	coeff float64
73	id    Symbol
74}
75
76type expr struct {
77	constant float64
78	terms    []Term
79}
80
81func newExpr(constant float64, terms ...Term) expr {
82	return expr{constant: constant, terms: terms}
83}
84
85func (c expr) clone() expr {
86	res := expr{constant: c.constant, terms: make([]Term, len(c.terms))}
87	copy(res.terms, c.terms)
88	return res
89}
90
91func (c expr) find(id Symbol) int {
92	for i := 0; i < len(c.terms); i++ {
93		if c.terms[i].id == id {
94			return i
95		}
96	}
97	return -1
98}
99
100func (c *expr) delete(idx int) {
101	copy(c.terms[idx:], c.terms[idx+1:])
102	c.terms = c.terms[:len(c.terms)-1]
103}
104
105func (c *expr) addSymbol(coeff float64, id Symbol) {
106	idx := c.find(id)
107	if idx == -1 {
108		if !eqz(coeff) {
109			c.terms = append(c.terms, Term{coeff: coeff, id: id})
110		}
111		return
112	}
113	c.terms[idx].coeff += coeff
114	if eqz(c.terms[idx].coeff) {
115		c.delete(idx)
116	}
117}
118
119func (c *expr) addExpr(coeff float64, other expr) {
120	c.constant += coeff * other.constant
121	for i := 0; i < len(other.terms); i++ {
122		c.addSymbol(coeff*other.terms[i].coeff, other.terms[i].id)
123	}
124}
125
126func (c *expr) negate() {
127	c.constant = -c.constant
128	for i := 0; i < len(c.terms); i++ {
129		c.terms[i].coeff = -c.terms[i].coeff
130	}
131}
132
133func (c *expr) solveFor(id Symbol) {
134	idx := c.find(id)
135	if idx == -1 {
136		return
137	}
138
139	coeff := -1.0 / c.terms[idx].coeff
140	c.delete(idx)
141
142	if coeff == 1.0 {
143		return
144	}
145
146	c.constant *= coeff
147	for i := 0; i < len(c.terms); i++ {
148		c.terms[i].coeff *= coeff
149	}
150}
151
152func (c *expr) solveForSymbols(lhs, rhs Symbol) {
153	c.addSymbol(-1.0, lhs)
154	c.solveFor(rhs)
155}
156
157func (c *expr) substitute(id Symbol, other expr) {
158	idx := c.find(id)
159	if idx == -1 {
160		return
161	}
162	coeff := c.terms[idx].coeff
163	c.delete(idx)
164	c.addExpr(coeff, other)
165}
166
167func eqz(val float64) bool {
168	if val < 0 {
169		return -val < 1.0e-8
170	}
171	return val < 1.0e-8
172}