construct/
lib.rs

1#![deny(missing_docs)]
2
3//! A library for higher order functional programming with homotopy maps to construct 3D geometry.
4//!
5//! ### What is a homotopy map?
6//!
7//! A [homotopy](https://en.wikipedia.org/wiki/Homotopy) is a continuous
8//! deformation between two functions.
9//! Think about combining two functions `f` and `g` with a parameter in the range
10//! between 0 and 1 such that setting the parameter to 0 gives you `f` and
11//! setting it to 1 gives you `g`.
12//! With other words, it lets you interpolate smoothly between functions.
13//!
14//! This library uses a simplified homotopy version designed for constructing 3D geometry:
15//!
16//! ```rust
17//! /// A function of type `1d -> 3d`.
18//! pub type Fn1<T> = Arc<Fn(T) -> [T; 3] + Sync + Send>;
19//! /// A function of type `2d -> 3d`.
20//! pub type Fn2<T> = Arc<Fn([T; 2]) -> [T; 3] + Sync + Send>;
21//! /// A function of type `3d -> 3d`.
22//! pub type Fn3<T> = Arc<Fn([T; 3]) -> [T; 3] + Sync + Send>;
23//! ```
24//!
25//! In this library, these functions are called *homotopy maps* and usually
26//! satisfies these properties:
27//!
28//! - All inputs are assumed to be normalized, starting at 0 and ending at 1.
29//!   This means that `Fn1` forms a curved line, `Fn2` forms a curved quad,
30//!   and `Fn3` forms a curved cube.
31//! - The `Arc` smart pointer makes it possible to clone closures.
32//! - The `Sync` and `Send` constraints makes it easier to program with multiple threads.
33//! - Basic geometric shapes are continuous within the range from 0 to 1.
34//!
35//! A curved cube does not mean it need to look like a cube.
36//! Actually, you can create a variety of shapes that do not look like cubes at all,
37//! e.g. a sphere.
38//! What is meant by a "curved cube" is that there are 3 parameters between 0 and 1
39//! controlling the generation of points.
40//! If you used an identity map, you would get a cube shape.
41//! The transformation to other shapes is the reason it is called a "curved cube".
42//!
43//! ### Motivation
44//!
45//! Constructing 3D geometry is an iterative process where the final design/need
46//! can be quite different from the first draft.
47//! In game engines there are additional needs like generating multiple models of various
48//! detail or adjusting models depending on the capacity of the target platform.
49//! This makes it desirable to have some tools where one can work with an idea without
50//! getting slowed down by a lot of technical details.
51//!
52//! Homotopy maps have the property that the geometry can be constructed by need,
53//! without any additional instructions.
54//! This makes it a suitable candidate for combining them with higher order functional programming.
55//! Functions give an accurate representation while at the same time being lazy,
56//! such that one can e.g. intersect a curved cube to get a curved quad.
57//!
58//! This library is an experiment to see how homotopy maps and higher order functional programming
59//! can be used to iterate on design.
60//! Function names are very short to provide good ergonomics.
61
62extern crate vecmath;
63
64pub use vecmath::vec3_add as add3;
65pub use vecmath::vec2_add as add2;
66pub use vecmath::vec3_sub as sub3;
67pub use vecmath::vec2_sub as sub2;
68pub use vecmath::vec3_len as len3;
69pub use vecmath::vec2_len as len2;
70pub use vecmath::vec3_scale as scale3;
71pub use vecmath::vec2_scale as scale2;
72pub use vecmath::vec2_cast as cast2;
73pub use vecmath::vec3_cast as cast3;
74pub use vecmath::traits::*;
75
76use std::sync::Arc;
77
78/// A function of type `1d -> 3d`.
79pub type Fn1<T> = Arc<Fn(T) -> [T; 3] + Sync + Send>;
80/// A function of type `2d -> 3d`.
81pub type Fn2<T> = Arc<Fn([T; 2]) -> [T; 3] + Sync + Send>;
82/// A function of type `3d -> 3d`.
83pub type Fn3<T> = Arc<Fn([T; 3]) -> [T; 3] + Sync + Send>;
84
85/// Returns a linear function.
86pub fn lin<T: Float>(a: [T; 3], b: [T; 3]) -> Fn1<T> {
87    return Arc::new(move |t| add3(a, scale3(sub3(b, a), t)))
88}
89
90/// Creates a linear interpolation between two functions.
91pub fn lin2<T: Float>(a: Fn1<T>, b: Fn1<T>) -> Fn1<T> {
92    return Arc::new(move |t| {
93        add3(scale3(a(t), <T as One>::one() - t), scale3(b(t), t))
94    })
95}
96
97/// Quadratic bezier curve.
98pub fn qbez<T: Float>(a: [T; 3], b: [T; 3], c: [T; 3]) -> Fn1<T> {
99    lin2(lin(a, b), lin(b, c))
100}
101
102/// Cubic bezier curve.
103pub fn cbez<T: Float>(
104    a: [T; 3],
105    b: [T; 3],
106    c: [T; 3],
107    d: [T; 3],
108) -> Fn1<T> {
109    lin2(lin(a, b), lin(c, d))
110}
111
112/// Constructs a curved quad by smoothing between boundary functions.
113pub fn cquad<T: Float>(
114    smooth: T,
115    ab: Fn1<T>,
116    cd: Fn1<T>,
117    ac: Fn1<T>,
118    bd: Fn1<T>
119) -> Fn2<T>
120    where f64: Cast<T>
121{
122    let _1: T = One::one();
123    let _0: T = Zero::zero();
124    let _05: T = 0.5.cast();
125    let _4: T = 4.0.cast();
126    return Arc::new(move |t| {
127        let abx = ab(t[1]);
128        let cdx = cd(t[1]);
129        let acx = ac(t[0]);
130        let bdx = bd(t[0]);
131
132        let w0 = _4 * (t[0] - _05) * (t[0] - _05) + smooth;
133        let w1 = _4 * (t[1] - _05) * (t[1] - _05) + smooth;
134        // Normalize weights.
135        let (w0, w1) = (w0 / (w0 + w1), w1 / (w0 + w1));
136
137        let a = add3(abx, scale3(sub3(cdx, abx), t[0]));
138        let b = add3(acx, scale3(sub3(bdx, acx), t[1]));
139        if w0 == _1 {a}
140        else if w1 == _1 {b}
141        else if (w0 + w1) == _0 {
142            scale3(add3(a, b), _05)
143        }
144        else {
145            add3(scale3(a, w0), scale3(b, w1))
146        }
147    })
148}
149
150/// Concatenates two `1d -> 3d` functions returning a new function.
151///
152/// The input to the new function is normalized.
153pub fn con<T: Float>(w: T, a: Fn1<T>, b: Fn1<T>) -> Fn1<T> {
154    return Arc::new(move |t| {
155        if t < w {a(t / w)}
156        else {b((t - w) / (<T as One>::one() - w))}
157    })
158}
159
160/// Concatenates two `2d -> 3d` functions at x-weight.
161pub fn conx2<T: Float>(wx: T, a: Fn2<T>, b: Fn2<T>) -> Fn2<T> {
162    return Arc::new(move |t| {
163        if t[0] < wx {a([t[0] / wx, t[1]])}
164        else {b(([(t[0] - wx) / (<T as One>::one() - wx), t[1]]))}
165    })
166}
167
168/// Concatenates two `2d -> 3d` functions at y-weight.
169pub fn cony2<T: Float>(wy: T, a: Fn2<T>, b: Fn2<T>) -> Fn2<T> {
170    return Arc::new(move |t| {
171        if t[1] < wy {a([t[0], t[1] / wy])}
172        else {b([t[0], (t[1] - wy) / (<T as One>::one() - wy)])}
173    })
174}
175
176/// Concatenates two `3d -> 3d` functions at x-weight.
177pub fn conx3<T: Float>(wx: T, a: Fn3<T>, b: Fn3<T>) -> Fn3<T> {
178    return Arc::new(move |t| {
179        if t[0] < wx {a([t[0] / wx, t[1], t[2]])}
180        else {b(([(t[0] - wx) / (<T as One>::one() - wx), t[1], t[2]]))}
181    })
182}
183
184/// Concates two `3d -> 3d` functions at y-weight.
185pub fn cony3<T: Float>(wy: T, a: Fn3<T>, b: Fn3<T>) -> Fn3<T> {
186    return Arc::new(move |t| {
187        if t[1] < wy {a([t[0], t[1] / wy, t[2]])}
188        else {b([t[0], (t[1] - wy) / (<T as One>::one() - wy), t[2]])}
189    })
190}
191
192/// Concates two `3d -> 3d` functions at z-weight.
193pub fn conz3<T: Float>(wz: T, a: Fn3<T>, b: Fn3<T>) -> Fn3<T> {
194    return Arc::new(move |t| {
195        if t[2] < wz {a([t[0], t[1], t[2] / wz])}
196        else {b([t[0], t[1], (t[2] - wz) / (<T as One>::one() - wz)])}
197    })
198}
199
200/// Mirror shape `1d -> 3d` around yz-plane at x coordinate.
201pub fn mx<T: 'static, U: Float>(
202    x: U,
203    a: Arc<Fn(T) -> [U; 3] + Sync + Send>
204) -> Arc<Fn(T) -> [U; 3] + Sync + Send>
205    where f64: Cast<U>
206{
207    return Arc::new(move |t| {
208        let pos = a(t);
209        [2.0.cast() * x - pos[0], pos[1], pos[2]]
210    })
211}
212
213/// Mirror shape `1d -> 3d` around xz-plane at y coordinate.
214pub fn my<T: 'static, U: Float>(
215    y: U,
216    a: Arc<Fn(T) -> [U; 3] + Sync + Send>
217) -> Arc<Fn(T) -> [U; 3] + Sync + Send>
218    where f64: Cast<U>
219{
220    return Arc::new(move |t| {
221        let pos = a(t);
222        [pos[0], 2.0.cast() * y - pos[1], pos[2]]
223    })
224}
225
226/// Mirror shape `1d -> 3d` around xy-plane at z coordinate.
227pub fn mz<T: 'static, U: Float>(
228    z: U,
229    a: Arc<Fn(T) -> [U; 3] + Sync + Send>
230) -> Arc<Fn(T) -> [U; 3] + Sync + Send>
231    where f64: Cast<U>
232{
233    return Arc::new(move |t| {
234        let pos = a(t);
235        [pos[0], pos[1], 2.0.cast() * z - pos[2]]
236    })
237}
238
239/// Bake mirror `2d -> 3d` around yz-plane at x coordinate.
240pub fn mirx2<T: Float>(x: T, a: Fn2<T>) -> Fn2<T>
241    where f64: Cast<T>
242{
243    conx2(0.5.cast(), a.clone(), mx(x, a))
244}
245
246/// Bake mirror `2d -> 3d` around xz-plane at y coordinate.
247pub fn miry2<T: Float>(y: T, a: Fn2<T>) -> Fn2<T>
248    where f64: Cast<T>
249{
250    cony2(0.5.cast(), a.clone(), my(y, a))
251}
252
253/// Bake mirror `3d -> 3d` around yz-plane at x coordinate.
254pub fn mirx3<T: Float>(x: T, a: Fn3<T>) -> Fn3<T>
255    where f64: Cast<T>
256{
257    conx3(0.5.cast(), a.clone(), mx(x, a))
258}
259
260/// Bake mirror `3d -> 3d` around xz-plane at y coordinate.
261pub fn miry3<T: Float>(y: T, a: Fn3<T>) -> Fn3<T>
262    where f64: Cast<T>
263{
264    cony3(0.5.cast(), a.clone(), my(y, a))
265}
266
267/// Bake mirror `3d -> 3d` around xy-plane at z coordinate.
268pub fn mirz3<T: Float>(z: T, a: Fn3<T>) -> Fn3<T>
269    where f64: Cast<T>
270{
271    conz3(0.5.cast(), a.clone(), mz(z, a))
272}
273
274/// Reverses input direction.
275pub fn rev<T: Float>(a: Fn1<T>) -> Fn1<T> {
276    seg1([One::one(), Zero::zero()], a)
277}
278
279/// Offsets `3d -> 3d` at position.
280pub fn off<T: 'static, U: Float>(
281    pos: [U; 3],
282    a: Arc<Fn(T) -> [U; 3] + Sync + Send>
283) -> Arc<Fn(T) -> [U; 3] + Sync + Send> {
284    return Arc::new(move |t| add3(a(t), pos))
285}
286
287/// Gets the contour line of a curved quad.
288///
289/// ```ignore
290/// 0.0-0.25: [0.0, 0.0] -> [1.0, 0.0]
291/// 0.25-0.5: [1.0, 0.0] -> [1.0, 1.0]
292/// 0.5-0.75: [1.0, 1.0] -> [0.0, 1.0]
293/// 0.75-1.0: [0.0, 1.0] -> [0.0, 0.0]
294/// ```
295pub fn contour<T: Float>(a: Fn2<T>) -> Fn1<T>
296    where f64: Cast<T>
297{
298    let _025: T = 0.25.cast();
299    let _4: T = 4.0.cast();
300    let _0: T = 0.0.cast();
301    let _05: T = 0.5.cast();
302    let _1: T = 1.0.cast();
303    let _075 = 0.75.cast();
304    return Arc::new(move |t| {
305        if t < _025 {a([_4 * t, _0])}
306        else if t < _05 {a([_1, _4 * (t - _025)])}
307        else if t < _075 {a([_1 - _4 * (t - _05), _1])}
308        else {a([_0, _1 - _4 * (t - _075)])}
309    })
310}
311
312/// Adds a margin to input of a `1d -> 3d` function.
313pub fn margin1<T: Float>(m: T, a: Fn1<T>) -> Fn1<T>
314    where f64: Cast<T>
315{
316    let _1 = 1.0.cast();
317    let _2 = 2.0.cast();
318    let s = _1 / (_1 + _2 * m);
319    return Arc::new(move |t| a((t + m) * s))
320}
321
322/// Adds a margin to input of a `2d -> 3d` function.
323pub fn margin2<T: Float>(m: T, a: Fn2<T>) -> Fn2<T>
324    where f64: Cast<T>
325{
326    let _1 = 1.0.cast();
327    let _2 = 2.0.cast();
328    let s = _1 / (_1 + _2 * m);
329    return Arc::new(move |t| a([(t[0] + m) * s, (t[1] + m) * s]))
330}
331
332/// Adds a margin to input of a `3d -> 3d` function.
333pub fn margin3<T: Float>(m: T, a: Fn3<T>) -> Fn3<T>
334    where f64: Cast<T>
335{
336    let _1 = 1.0.cast();
337    let _2 = 2.0.cast();
338    let s = _1 / (_1 + _2 * m);
339    return Arc::new(move |t| a([(t[0] + m) * s, (t[1] + m) * s, (t[2] + m) * s]))
340}
341
342/// Creates a circle located at a center and with a radius.
343///
344/// The first input argument is the angle starting at 0,
345/// rotating 360 degrees around the center endering at 1.
346/// The second input argument is the radius starting at 0 ending at 1.
347///
348/// The circle is flat along the z axis.
349pub fn circle<T: Float>(center: [T; 3], radius: T) -> Fn2<T>
350    where f64: Cast<T>
351{
352    let two_pi = 6.283185307179586.cast();
353    return Arc::new(move |t| {
354        let angle = t[0] * two_pi;
355        [
356            center[0] + radius * t[1] * angle.cos(),
357            center[1] + radius * t[1] * angle.sin(),
358            center[2]
359        ]
360    })
361}
362
363/// Creates a sphere located at a center and with a radius.
364///
365/// The two first arguments are angles, the third is radius.
366/// The first input argument controls rotation around the z axis.
367/// The second input argument starts at the top of the sphere
368/// and moves down to the bottom of the sphere.
369pub fn sphere<T: Float>(center: [T; 3], radius: T) -> Fn3<T>
370    where f64: Cast<T>
371{
372    let two_pi = 6.283185307179586.cast();
373    let _1 = 1.0.cast();
374    let _2 = 2.0.cast();
375    return Arc::new(move |t| {
376        let angle0 = t[0] * two_pi;
377        let tx = _2 * t[1] - _1;
378        let rad = radius * (_1 - tx * tx).sqrt();
379        [
380            center[0] + rad * t[2] * angle0.cos(),
381            center[1] + rad * t[2] * angle0.sin(),
382            center[2] - radius + _2 * radius * t[1],
383        ]
384    })
385}
386
387/// Intersects a curved quad at x-line.
388pub fn x2<T: Float>(x: T, a: Fn2<T>) -> Fn1<T>
389    where f64: Cast<T>
390{
391    return Arc::new(move |t| a([x, t]))
392}
393
394/// Intersects a curved quad at y-line.
395pub fn y2<T: Float>(y: T, a: Fn2<T>) -> Fn1<T> {
396    return Arc::new(move |t| a([t, y]))
397}
398
399/// Intersects a curved cube at x-plane.
400pub fn x3<T: Float>(x: T, a: Fn3<T>) -> Fn2<T> {
401    return Arc::new(move |t| a([x, t[0], t[1]]))
402}
403
404/// Intersects a curved cube at y-plane.
405pub fn y3<T: Float>(y: T, a: Fn3<T>) -> Fn2<T> {
406    return Arc::new(move |t| a([t[0], y, t[1]]))
407}
408
409/// Intersects a curved cube at z-plane.
410pub fn z3<T: Float>(z: T, a: Fn3<T>) -> Fn2<T> {
411    return Arc::new(move |t| a([t[0], t[1], z]))
412}
413
414/// Extends a 1d shape into 2d by adding a
415/// vector to the result generated by a 1d shape.
416pub fn ext1<T: Float>(a: Fn1<T>, b: Fn1<T>) -> Fn2<T> {
417    return Arc::new(move |t| add3(a(t[0]), b(t[1])))
418}
419
420/// Extends a 2d shape into 3d by adding
421/// a vector to the result generated by a 1d shape.
422pub fn ext2<T: Float>(a: Fn1<T>, b: Fn2<T>) -> Fn3<T> {
423    return Arc::new(move |t| add3(a(t[0]), b([t[1], t[2]])))
424}
425
426/// Uses a range to pick a segment of a curve.
427pub fn seg1<T: Float>(range: [T; 2], a: Fn1<T>) -> Fn1<T> {
428    return Arc::new(move |t| a(range[0] + (range[1] - range[0]) * t))
429}