dumbmath/
quad.rs

1// Copyright 2015 Nicholas Bishop
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use vec2f::{Line2f, Vec2f};
16
17/// 2D Quadrilateral
18/// 
19/// ```text
20///          top
21///     p3________p2
22///      |        |
23///      |        |
24/// left |        | right
25///      |        |
26///      |________|
27///     p0        p1
28///        bottom
29/// ```
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct Quad2f {
32    pub points: (Vec2f, Vec2f, Vec2f, Vec2f)
33}
34
35/// Return type for Quad2f::iblerp
36#[derive(Clone, Copy, Debug, PartialEq)]
37pub enum IBLerpResult {
38    NoSolution,
39    /// One solutions (as parametric coordinate)
40    OneSolution(Vec2f),
41    /// Two solutions (as parametric coordinates)
42    TwoSolutions(Vec2f, Vec2f),
43    ManySolutions
44}
45
46/// Return type for Quad2f::inv_bilerp_u
47#[derive(Clone, Copy, Debug, PartialEq)]
48pub enum InvBilerpResult {
49    NoSolution,
50    /// One solutions (as parametric coordinate)
51    OneSolution(f32),
52    /// Two solutions (as parametric coordinates)
53    TwoSolutions(f32, f32),
54    ManySolutions
55}
56
57impl Quad2f {
58    pub fn new(a: Vec2f, b: Vec2f, c: Vec2f, d: Vec2f) -> Quad2f {
59        Quad2f {
60            points: (a, b, c, d)
61        }
62    }
63
64    // TODO(nicholasbishop): code dedup
65
66    /// Inverse bilinear interpolation
67    ///
68    /// Adapted from stackoverflow.com/questions/808441
69    pub fn iblerp(self, point: Vec2f) -> IBLerpResult {
70        let p0mp = self.points.0 - point;
71        let p1mp = self.points.1 - point;
72        let p0mp3 = self.points.0 - self.points.3;
73        let p1mp2 = self.points.1 - self.points.2;
74
75        let a = p0mp.cross(p0mp3);
76        let b0 = p0mp.cross(p1mp2);
77        let b1 = p1mp.cross(p0mp3);
78        let b = (b0 + b1) / 2.0;
79        let c = p1mp.cross(p1mp2);
80
81        let calc_st = |s| {
82            let den = (1.0 - s) * p0mp3.x + s * p1mp2.x;
83            let t = if den == 0.0 {
84                // TODO(nicholasbishop): perhaps there's a more
85                // efficient way to solve this, the SO post doesn't
86                // seem to cover this case
87                let rb = self.points.0.lerp(self.points.1, s);
88                let rt = self.points.3.lerp(self.points.2, s);
89                Line2f::new(rb, rt).closest_parametric_point(point)
90            }
91            else {
92                ((1.0 - s) * (p0mp.x) + s * p1mp.x) / den
93            };
94            Vec2f::new(s, t)
95        };
96
97        let den = a - (2.0 * b) + c;
98        if den == 0.0 {
99            let m = a - c;
100            if m == 0.0 {
101                if a == 0.0 {
102                    IBLerpResult::ManySolutions
103                }
104                else {
105                    IBLerpResult::NoSolution
106                }
107            }
108            else {
109                IBLerpResult::OneSolution(calc_st(a / m))
110            }
111        }
112        else {
113            let left = a - b;
114            let right = (b.powi(2) - a*c).sqrt();
115            let s0 = (left + right) / den;
116            let s1 = (left - right) / den;
117            IBLerpResult::TwoSolutions(calc_st(s0), calc_st(s1))
118        }
119    }
120
121    /// Calculate horizontal parametric coordinate from cartesian point.
122    pub fn inv_bilerp_u(self, point: Vec2f) -> InvBilerpResult {
123        let p0mp = self.points.0 - point;
124        let p1mp = self.points.1 - point;
125        let p0mp3 = self.points.0 - self.points.3;
126        let p1mp2 = self.points.1 - self.points.2;
127
128        let a = p0mp.cross(p0mp3);
129        let b0 = p0mp.cross(p1mp2);
130        let b1 = p1mp.cross(p0mp3);
131        let b = (b0 + b1) / 2.0;
132        let c = p1mp.cross(p1mp2);
133
134        let den = a - (2.0 * b) + c;
135        if den == 0.0 {
136            let m = a - c;
137            if m == 0.0 {
138                if a == 0.0 {
139                    InvBilerpResult::ManySolutions
140                }
141                else {
142                    InvBilerpResult::NoSolution
143                }
144            }
145            else {
146                InvBilerpResult::OneSolution(a / m)
147            }
148        }
149        else {
150            let left = a - b;
151            let right = (b.powi(2) - a*c).sqrt();
152            let s0 = (left + right) / den;
153            let s1 = (left - right) / den;
154            InvBilerpResult::TwoSolutions(s0, s1)
155        }
156    }
157
158    pub fn lerp_bottom(self, u: f32) -> Vec2f {
159        self.points.0.lerp(self.points.1, u)
160    }
161
162    pub fn lerp_top(self, u: f32) -> Vec2f {
163        self.points.3.lerp(self.points.2, u)
164    }
165
166    pub fn lerp_left(self, v: f32) -> Vec2f {
167        self.points.0.lerp(self.points.3, v)
168    }
169
170    pub fn lerp_right(self, v: f32) -> Vec2f {
171        self.points.1.lerp(self.points.2, v)
172    }
173
174    pub fn blerp(self, uv: Vec2f) -> Vec2f {
175        let rb = self.points.0.lerp(self.points.1, uv.x);
176        let rt = self.points.3.lerp(self.points.2, uv.x);
177        rb.lerp(rt, uv.y)
178    }
179}
180
181#[cfg(test)]
182mod test {
183    use super::*;
184    use vec2f::vec2f;
185
186    #[test]
187    fn test_inv_bilerp_u() {
188        let q = Quad2f::new(vec2f(0, 0),
189                            vec2f(4, 0),
190                            vec2f(4, 4),
191                            vec2f(0, 4));
192        assert_eq!(q.inv_bilerp_u(vec2f(2, 2)),
193                   InvBilerpResult::OneSolution(0.5));
194        assert_eq!(q.inv_bilerp_u(vec2f(1, 2)),
195                   InvBilerpResult::OneSolution(0.25));
196        assert_eq!(q.inv_bilerp_u(vec2f(1, 3)),
197                   InvBilerpResult::OneSolution(0.25));
198    }
199
200    #[test]
201    fn test_iblerp() {
202        let q = Quad2f::new(vec2f(0, 0),
203                            vec2f(4, 0),
204                            vec2f(4, 4),
205                            vec2f(0, 4));
206        assert_eq!(q.iblerp(vec2f(2, 2)),
207                   IBLerpResult::OneSolution(vec2f(0.5, 0.5)));
208    }
209}