File: winding.rs

package info (click to toggle)
rust-genmesh 0.6.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 220 kB
  • sloc: makefile: 4
file content (158 lines) | stat: -rw-r--r-- 4,851 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
extern crate cgmath;
extern crate genmesh;

use std::collections::HashSet;

use cgmath::InnerSpace;
use genmesh::{generators, EmitLines, Line, Lines, MapToVertices, Vertex};

#[derive(Debug)]
struct Edge {
    dir: cgmath::Vector3<f32>,
    mid: cgmath::Vector3<f32>,
    nor: cgmath::Vector3<f32>,
}

impl Edge {
    fn new(line: Line<Vertex>) -> Self {
        let Line {
            x: Vertex { pos: x, normal: nx },
            y: Vertex { pos: y, normal: ny },
        } = line;

        Edge {
            dir: cgmath::vec3(y.x - x.x, y.y - x.y, y.z - x.z),
            mid: cgmath::vec3(y.x + x.x, y.y + x.y, y.z + x.z) * 0.5,
            nor: cgmath::vec3(nx.x + ny.x, nx.y + ny.y, nx.z + ny.z),
        }
    }

    /// Check that the corner `(self, e)` has outward winding order
    /// (thus, it's normal is in the same hemisphere as it's offset).
    fn check_to(&self, e: &Edge) {
        let normal = self.dir.cross(e.dir);
        let mid = (self.mid + e.mid) * 0.5;
        assert!(normal.dot(mid) > 0.0 && e.nor.dot(mid) > 0.0);
    }
}

/// Make sure that all the polygons in the `poly_iter` have the outward
/// winding order relative to the origin of the coordinate system.
/// This is a simplified (and incomplete) convex shape test.
fn test_outward<P, I>(poly_iter: I)
where
    P: EmitLines<Vertex = Vertex> + ::std::fmt::Debug,
    I: Iterator<Item = P>,
{
    let mut edges = Vec::new();
    for poly in poly_iter {
        edges.clear();
        poly.emit_lines(|l| edges.push(Edge::new(l)));
        // check last-first corner first, since it wraps
        edges.last().unwrap().check_to(&edges[0]);
        // check all the non-wrapping corners
        for (a, b) in edges.iter().zip(edges[1..].iter()) {
            a.check_to(b);
        }
    }
}

// this will check that a primitive is closed, and that every edge of
// every polygon making up the primitive has a paired neighbor. This
// does not test that a polygon has the correcting winding direction
// just that the winding directions are consistent for the polygon.
//
// This is based on ftp://ftp.sgi.com/opengl/contrib/blythe/advanced99/notes/node16.html
//
fn test_closed<P, I>(poly_iter: I)
where
    P: EmitLines<Vertex = Vertex> + ::std::fmt::Debug,
    I: Iterator<Item = P>,
{
    // convert the vertex to something that we can use to find approximate
    // polygons. This is mostly to get past the fact that f32 is a cursed
    // type in rust and can not be used as a key.
    fn to_checkable(vertex: Vertex) -> [i32; 3] {
        [
            (vertex.pos.x * 1000000.) as i32,
            (vertex.pos.y * 1000000.) as i32,
            (vertex.pos.z * 1000000.) as i32,
        ]
    }

    let mut lines = HashSet::new();
    for line in poly_iter.lines().vertex(to_checkable) {
        // if the line was in the set we found the matching pair
        // which is one less pair that we are looking for
        if !lines.remove(&line) {
            // if didn't find the pair, we flip the line around and put it into
            // the search table.
            lines.insert(Line {
                x: line.y,
                y: line.x,
            });
        }
    }

    // if we found all the pairs, we should be valid and a closed geometry
    // this means that there is no polygon who's neighbor switches winding
    // direction, but it does not mean that the polygon is correct. They
    // all could be backwards. So this still requires a secondary inspection.
    assert!(lines.len() == 0);
}

#[test]
fn wind_plane() {
    // the plane is not closed, so no point in testing for a closed
    // shape.

    test_outward(generators::Plane::new().vertex(|mut v| {
        v.pos.z = 1.;
        v
    }));
    test_outward(generators::Plane::subdivide(3, 4).vertex(|mut v| {
        v.pos.z = 1.;
        v
    }));
}

#[test]
fn gen_cube() {
    test_outward(generators::Cube::new());
    test_closed(generators::Cube::new());
}

#[test]
fn gen_cylinder() {
    test_outward(generators::Cylinder::new(5));
    test_closed(generators::Cylinder::new(5));
    test_outward(generators::Cylinder::subdivide(3, 4));
    test_closed(generators::Cylinder::subdivide(3, 4));
}

#[test]
fn gen_sphere_uv() {
    test_outward(generators::SphereUv::new(4, 3));
    test_closed(generators::SphereUv::new(4, 3));
}

#[test]
fn gen_ico_sphere() {
    test_outward(generators::IcoSphere::new());
    test_closed(generators::IcoSphere::new());
    test_outward(generators::IcoSphere::subdivide(3));
    test_closed(generators::IcoSphere::subdivide(3));
}

#[test]
fn gen_cone() {
    test_outward(generators::Cone::new(8));
    test_closed(generators::Cone::new(8));
}

#[test]
fn gen_torus() {
    // we don't do an outward test because the primitive is not
    // convex and will fail this test.
    test_closed(generators::Torus::new(10.0, 5.0, 8, 8));
}