├── voxlib ├── common │ ├── __init__.py │ └── progressbar.py ├── voxelintersect │ ├── __init__.py │ ├── triangleCube_win32.so │ ├── triangleCube_linux64.so │ ├── LICENSE.md │ ├── triangleCube.c │ └── triangle.py ├── __init__.py ├── mesh.py └── voxelize.py ├── requirements.txt ├── README.md ├── setup.py └── LICENSE /voxlib/common/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Peter Hofmann' 2 | -------------------------------------------------------------------------------- /voxlib/voxelintersect/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Peter Hofmann' 2 | -------------------------------------------------------------------------------- /voxlib/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Peter Hofmann' 2 | __version__ = '0.0.5' 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.12.1 2 | git+https://github.com/p-hofmann/MeshReader.git#egg=meshlib 3 | meshlib>=0.0.2 4 | -------------------------------------------------------------------------------- /voxlib/voxelintersect/triangleCube_win32.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-hofmann/PyVoxelizer/HEAD/voxlib/voxelintersect/triangleCube_win32.so -------------------------------------------------------------------------------- /voxlib/voxelintersect/triangleCube_linux64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-hofmann/PyVoxelizer/HEAD/voxlib/voxelintersect/triangleCube_linux64.so -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyVoxelizer 2 | A python converter of 3D model surface into voxels. 3 | File format '.stl' '.obj' and '.mtl' are supported. 4 | 5 | # Acknowledgment 6 | This voxelizer uses code originaly from [GraphicsGems](https://github.com/erich666/GraphicsGems/blob/master/gemsiii/triangleCube.c) for trinagle cube intersection detection. 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | from voxlib import __version__ as version, __author__ as author 5 | 6 | setup( 7 | name='voxlib', 8 | version=version, 9 | description='A python converter of 3D model into voxels', 10 | author=author, 11 | author_email='', 12 | url='', 13 | packages=find_packages(exclude=('unittest', '__pycache__')), 14 | ) 15 | -------------------------------------------------------------------------------- /voxlib/voxelintersect/LICENSE.md: -------------------------------------------------------------------------------- 1 | LICENSE 2 | 3 | This code repository predates the concept of Open Source, and predates most licenses along such lines. As such, the official license truly is: 4 | 5 | EULA: The Graphics Gems code is copyright-protected. In other words, you cannot claim the text of the code as your own and resell it. Using the code is permitted in any program, product, or library, non-commercial or commercial. Giving credit is not required, though is a nice gesture. The code comes as-is, and if there are any flaws or problems with any Gems code, nobody involved with Gems - authors, editors, publishers, or webmasters - are to be held responsible. Basically, don't be a jerk, and remember that anything free comes with no guarantee. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Peter Hofmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /voxlib/common/progressbar.py: -------------------------------------------------------------------------------- 1 | # Print iterations progress 2 | # https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console 3 | import sys 4 | 5 | 6 | def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=20, fill='='): 7 | """ 8 | Call in a loop to create terminal progress bar 9 | @params: 10 | iteration - Required : current iteration (Int) 11 | total - Required : total iterations (Int) 12 | prefix - Optional : prefix string (Str) 13 | suffix - Optional : suffix string (Str) 14 | decimals - Optional : positive number of decimals in percent complete (Int) 15 | length - Optional : character length of bar (Int) 16 | fill - Optional : bar fill character (Str) 17 | """ 18 | percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total))) 19 | filled_length = int(length * iteration // total) 20 | bar = fill * filled_length + '-' * (length - filled_length) 21 | sys.stderr.write('\r%s |%s| %s%% %s' % (prefix, bar, percent, suffix)) 22 | # Print New Line on Complete 23 | if iteration == total: 24 | sys.stderr.write('\n') 25 | -------------------------------------------------------------------------------- /voxlib/mesh.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # functions are loosly based 4 | 5 | 6 | def get_scale_and_shift(mesh, resolution): 7 | """ 8 | 9 | @type mesh: list[((float, float, float), (float, float, float), (float, float, float))] 10 | @type resolution: int 11 | @rtype: (float, list[float], int) 12 | """ 13 | triangle_count = 0 14 | mins = list(mesh[0][0]) 15 | maxs = list(mesh[0][0]) 16 | for triangle in mesh: 17 | triangle_count += 1 18 | for index, point in enumerate(triangle): 19 | if point[index] < mins[index]: 20 | mins[index] = point[index] 21 | if point[index] > maxs[index]: 22 | maxs[index] = point[index] 23 | shift = [-minimum for minimum in mins] 24 | scale = float(resolution - 1) / (max(maxs[0] - mins[0], maxs[1] - mins[1], maxs[2] - mins[2])) 25 | return scale, shift, triangle_count 26 | 27 | 28 | def scale_and_shift_triangle(triangle, scale, shift): 29 | """ 30 | 31 | @type triangle: ((float, float, float), (float, float, float), (float, float, float)) 32 | @type scale: float 33 | @type shift: list[float 34 | 35 | @rtype: list[(float, float, float)] | None 36 | """ 37 | shifted_triangle = [] 38 | for point in triangle: 39 | new_point = np.array([.0, .0, .0]) 40 | for i in range(3): 41 | new_point[i] = (point[i] + shift[i]) * scale 42 | shifted_triangle.append(new_point) 43 | del triangle 44 | return shifted_triangle 45 | -------------------------------------------------------------------------------- /voxlib/voxelize.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import math 4 | import numpy as np 5 | 6 | from .common.progressbar import print_progress_bar 7 | from meshlib.meshreader import MeshReader 8 | from .voxelintersect.triangle import Triangle, t_c_intersection, INSIDE, vertexes_to_c_triangle, triangle_lib 9 | from .mesh import get_scale_and_shift, scale_and_shift_triangle 10 | 11 | 12 | class BoundaryBox(object): 13 | """ 14 | @type minimum: list[int] 15 | @type maximum: list[int] 16 | """ 17 | 18 | minimum = None 19 | maximum = None 20 | 21 | def get_center(self): 22 | assert self.minimum, "BoundaryBox not initialized" 23 | return [ 24 | int((self.maximum[0] + self.minimum[0])/2.0), 25 | int((self.maximum[1] + self.minimum[1])/2.0), 26 | int((self.maximum[2] + self.minimum[2])/2.0) 27 | ] 28 | 29 | def from_triangle(self, triangle): 30 | """ 31 | @type triangle: Triangle 32 | """ 33 | self.minimum[0] = math.floor(triangle.min(0)) 34 | self.minimum[1] = math.floor(triangle.min(1)) 35 | self.minimum[2] = math.floor(triangle.min(2)) 36 | 37 | self.maximum[0] = math.ceil(triangle.max(0)) 38 | self.maximum[1] = math.ceil(triangle.max(1)) 39 | self.maximum[2] = math.ceil(triangle.max(2)) 40 | 41 | def from_vertexes(self, vertex_1, vertex_2, vertex_3): 42 | """ 43 | @type vertex_1: (float, float, float) 44 | @type vertex_2: (float, float, float) 45 | @type vertex_3: (float, float, float) 46 | """ 47 | if self.minimum is None: 48 | self.minimum = [0, 0, 0] 49 | self.maximum = [0, 0, 0] 50 | 51 | self.minimum[0] = math.floor(min([vertex_1[0], vertex_2[0], vertex_3[0]])) 52 | self.minimum[1] = math.floor(min([vertex_1[1], vertex_2[1], vertex_3[1]])) 53 | self.minimum[2] = math.floor(min([vertex_1[2], vertex_2[2], vertex_3[2]])) 54 | 55 | self.maximum[0] = math.ceil(max([vertex_1[0], vertex_2[0], vertex_3[0]])) 56 | self.maximum[1] = math.ceil(max([vertex_1[1], vertex_2[1], vertex_3[1]])) 57 | self.maximum[2] = math.ceil(max([vertex_1[2], vertex_2[2], vertex_3[2]])) 58 | else: 59 | self.minimum[0] = math.floor(min([vertex_1[0], vertex_2[0], vertex_3[0], self.minimum[0]])) 60 | self.minimum[1] = math.floor(min([vertex_1[1], vertex_2[1], vertex_3[1], self.minimum[1]])) 61 | self.minimum[2] = math.floor(min([vertex_1[2], vertex_2[2], vertex_3[2], self.minimum[2]])) 62 | 63 | self.maximum[0] = math.ceil(max([vertex_1[0], vertex_2[0], vertex_3[0], self.maximum[0]])) 64 | self.maximum[1] = math.ceil(max([vertex_1[1], vertex_2[1], vertex_3[1], self.maximum[1]])) 65 | self.maximum[2] = math.ceil(max([vertex_1[2], vertex_2[2], vertex_3[2], self.maximum[2]])) 66 | 67 | 68 | n_range = {-1, 0, 1} 69 | 70 | 71 | def get_intersecting_voxels_depth_first(vertex_1, vertex_2, vertex_3): 72 | """ 73 | 74 | @type vertex_1: numpy.ndarray 75 | @type vertex_2: numpy.ndarray 76 | @type vertex_3: numpy.ndarray 77 | 78 | @rtype: list[(int, int, int)] 79 | """ 80 | c_lib = triangle_lib 81 | result_positions = [] 82 | tmp_triangle = None 83 | searched = set() 84 | stack = set() 85 | 86 | seed = (int(vertex_1[0]), int(vertex_1[1]), int(vertex_1[2])) 87 | for x in n_range: 88 | for y in n_range: 89 | for z in n_range: 90 | neighbour = (seed[0] + x, seed[1] + y, seed[2] + z) 91 | if neighbour not in searched: 92 | stack.add(neighbour) 93 | 94 | tmp = np.array([0.0, 0.0, 0.0]) 95 | tmp_vertex_1 = np.array([0.0, 0.0, 0.0]) 96 | tmp_vertex_2 = np.array([0.0, 0.0, 0.0]) 97 | tmp_vertex_3 = np.array([0.0, 0.0, 0.0]) 98 | if not c_lib: 99 | tmp_triangle = Triangle() 100 | tmp_triangle.set(tmp_vertex_1, tmp_vertex_2, tmp_vertex_3) 101 | while len(stack) > 0: 102 | position = stack.pop() 103 | searched.add(position) 104 | tmp[0] = 0.5 + position[0] 105 | tmp[1] = 0.5 + position[1] 106 | tmp[2] = 0.5 + position[2] 107 | 108 | # move raster to origin, test assumed triangle in relation to origin 109 | np.subtract(vertex_1, tmp, tmp_vertex_1) 110 | np.subtract(vertex_2, tmp, tmp_vertex_2) 111 | np.subtract(vertex_3, tmp, tmp_vertex_3) 112 | 113 | try: 114 | if c_lib: 115 | is_inside = c_lib.t_c_intersection( 116 | vertexes_to_c_triangle(tmp_vertex_1, tmp_vertex_2, tmp_vertex_3)) == INSIDE 117 | else: 118 | is_inside = t_c_intersection(tmp_triangle) == INSIDE 119 | except Exception: 120 | c_lib = None 121 | tmp_triangle = Triangle() 122 | tmp_triangle.set(tmp_vertex_1, tmp_vertex_2, tmp_vertex_3) 123 | is_inside = t_c_intersection(tmp_triangle) == INSIDE 124 | 125 | if is_inside: 126 | result_positions.append(position) 127 | 128 | neighbours = set() 129 | if tmp_vertex_2[0] < 0: 130 | neighbours.add((position[0] - 1, position[1], position[2])) 131 | if tmp_vertex_3[0] > 0: 132 | neighbours.add((position[0] + 1, position[1], position[2])) 133 | else: 134 | neighbours.add((position[0] + 1, position[1], position[2])) 135 | if tmp_vertex_3[0] < 0: 136 | neighbours.add((position[0] - 1, position[1], position[2])) 137 | 138 | if tmp_vertex_2[1] < 0: 139 | neighbours.add((position[0], position[1] - 1, position[2])) 140 | if tmp_vertex_3[1] > 0: 141 | neighbours.add((position[0], position[1] + 1, position[2])) 142 | else: 143 | neighbours.add((position[0], position[1] + 1, position[2])) 144 | if tmp_vertex_3[1] < 0: 145 | neighbours.add((position[0], position[1] - 1, position[2])) 146 | 147 | if tmp_vertex_2[2] < 0: 148 | neighbours.add((position[0], position[1], position[2] - 1)) 149 | if tmp_vertex_3[2] > 0: 150 | neighbours.add((position[0], position[1], position[2] + 1)) 151 | else: 152 | neighbours.add((position[0], position[1], position[2] + 1)) 153 | if tmp_vertex_3[2] < 0: 154 | neighbours.add((position[0], position[1], position[2] - 1)) 155 | 156 | for neighbour in neighbours: 157 | if neighbour not in searched: 158 | stack.add(neighbour) 159 | del searched, stack 160 | return result_positions 161 | 162 | 163 | def voxelize(file_path, resolution, progress_bar=None): 164 | """ 165 | 166 | @type file_path: str 167 | @type resolution: int 168 | @type progress_bar: any 169 | """ 170 | if not progress_bar: 171 | progress_bar = print_progress_bar 172 | mesh_reader = MeshReader() 173 | if file_path.endswith('.zip'): 174 | mesh_reader.read_archive(file_path) 175 | else: 176 | mesh_reader.read(file_path) 177 | if not mesh_reader.has_triangular_facets(): 178 | raise NotImplementedError("Unsupported polygonal face elements. Only triangular facets supported.") 179 | 180 | list_of_triangles = list(mesh_reader.get_facets()) 181 | scale, shift, triangle_count = get_scale_and_shift(list_of_triangles, resolution) 182 | progress_counter = 0 183 | voxels = set() 184 | bounding_box = BoundaryBox() 185 | for triangle in list_of_triangles: 186 | progress_counter += 1 187 | progress_bar(progress_counter, triangle_count, prefix="Voxelize: ") 188 | 189 | (vertex_1, vertex_2, vertex_3) = scale_and_shift_triangle(triangle, scale, shift) 190 | bounding_box.from_vertexes(vertex_1, vertex_2, vertex_3) 191 | voxels.update(get_intersecting_voxels_depth_first(vertex_1, vertex_2, vertex_3)) 192 | center = bounding_box.get_center() 193 | while len(voxels) > 0: 194 | (x, y, z) = voxels.pop() 195 | yield x-center[0], y-center[1], z-center[2] 196 | 197 | 198 | if __name__ == '__main__': 199 | # parse cli args 200 | parser = argparse.ArgumentParser(description='stl/obj file to voxels converter') 201 | parser.add_argument('input') 202 | parser.add_argument('resolution', type=int) 203 | args = parser.parse_args() 204 | for pos_x, pos_y, pos_z in voxelize(args.input, args.resolution): 205 | sys.stdout.write("{}\t{}\t{}\n".format(pos_x, pos_y, pos_z)) 206 | -------------------------------------------------------------------------------- /voxlib/voxelintersect/triangleCube.c: -------------------------------------------------------------------------------- 1 | /* Original file: 2 | * https://github.com/erich666/GraphicsGems/blob/master/gemsiii/triangleCube.c 3 | * Some optimisations for use in voxelisation have been made 4 | */ 5 | 6 | #include 7 | 8 | /* this version of SIGN3 shows some numerical instability, and is improved 9 | * by using the uncommented macro that follows, and a different test with it */ 10 | #ifdef OLD_TEST 11 | #define SIGN3( A ) (((A).x<0)?4:0 | ((A).y<0)?2:0 | ((A).z<0)?1:0) 12 | #else 13 | #define EPS 1e-5 14 | #define SIGN3( A ) \ 15 | (((A).x < EPS) ? 4 : 0 | ((A).x > -EPS) ? 32 : 0 | \ 16 | ((A).y < EPS) ? 2 : 0 | ((A).y > -EPS) ? 16 : 0 | \ 17 | ((A).z < EPS) ? 1 : 0 | ((A).z > -EPS) ? 8 : 0) 18 | #endif 19 | 20 | #define CROSS( A, B, C ) { \ 21 | (C).x = (A).y * (B).z - (A).z * (B).y; \ 22 | (C).y = -(A).x * (B).z + (A).z * (B).x; \ 23 | (C).z = (A).x * (B).y - (A).y * (B).x; \ 24 | } 25 | #define SUB( A, B, C ) { \ 26 | (C).x = (A).x - (B).x; \ 27 | (C).y = (A).y - (B).y; \ 28 | (C).z = (A).z - (B).z; \ 29 | } 30 | #define LERP( A, B, C) ((B)+(A)*((C)-(B))) 31 | #define MIN3(a,b,c) ((((a)<(b))&&((a)<(c))) ? (a) : (((b)<(c)) ? (b) : (c))) 32 | #define MAX3(a,b,c) ((((a)>(b))&&((a)>(c))) ? (a) : (((b)>(c)) ? (b) : (c))) 33 | #define INSIDE 0 34 | #define OUTSIDE 1 35 | 36 | typedef struct { 37 | float x; 38 | float y; 39 | float z; 40 | } Point3; 41 | 42 | typedef struct{ 43 | Point3 v1; /* Vertex1 */ 44 | Point3 v2; /* Vertex2 */ 45 | Point3 v3; /* Vertex3 */ 46 | } Triangle3; 47 | 48 | /*___________________________________________________________________________*/ 49 | 50 | /* Which of the six face-plane(s) is point P outside of? */ 51 | 52 | long face_plane(Point3 p) 53 | { 54 | long outcode; 55 | 56 | outcode = 0; 57 | if (p.x >= .5) outcode |= 0x01; // > .5 58 | if (p.x < -.5) outcode |= 0x02; 59 | if (p.y >= .5) outcode |= 0x04; // > .5 60 | if (p.y < -.5) outcode |= 0x08; 61 | if (p.z >= .5) outcode |= 0x10; // > .5 62 | if (p.z < -.5) outcode |= 0x20; 63 | return(outcode); 64 | } 65 | 66 | /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */ 67 | 68 | /* Which of the twelve edge plane(s) is point P outside of? */ 69 | 70 | long bevel_2d(Point3 p) 71 | { 72 | long outcode; 73 | 74 | outcode = 0; 75 | if ( p.x + p.y >= 1.0) outcode |= 0x001; // > 1.0 76 | if ( p.x - p.y >= 1.0) outcode |= 0x002; // > 1.0 77 | if (-p.x + p.y > 1.0) outcode |= 0x004; 78 | if (-p.x - p.y > 1.0) outcode |= 0x008; 79 | if ( p.x + p.z >= 1.0) outcode |= 0x010; // > 1.0 80 | if ( p.x - p.z >= 1.0) outcode |= 0x020; // > 1.0 81 | if (-p.x + p.z > 1.0) outcode |= 0x040; 82 | if (-p.x - p.z > 1.0) outcode |= 0x080; 83 | if ( p.y + p.z >= 1.0) outcode |= 0x100; // > 1.0 84 | if ( p.y - p.z >= 1.0) outcode |= 0x200; // > 1.0 85 | if (-p.y + p.z > 1.0) outcode |= 0x400; 86 | if (-p.y - p.z > 1.0) outcode |= 0x800; 87 | return(outcode); 88 | } 89 | 90 | /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */ 91 | 92 | /* Which of the eight corner plane(s) is point P outside of? */ 93 | 94 | long bevel_3d(Point3 p) 95 | { 96 | long outcode; 97 | 98 | outcode = 0; 99 | if (( p.x + p.y + p.z) >= 1.5) outcode |= 0x01; // > 1.5 100 | if (( p.x + p.y - p.z) >= 1.5) outcode |= 0x02; // > 1.5 101 | if (( p.x - p.y + p.z) >= 1.5) outcode |= 0x04; // > 1.5 102 | if (( p.x - p.y - p.z) >= 1.5) outcode |= 0x08; // > 1.5 103 | if ((-p.x + p.y + p.z) > 1.5) outcode |= 0x10; 104 | if ((-p.x + p.y - p.z) > 1.5) outcode |= 0x20; 105 | if ((-p.x - p.y + p.z) > 1.5) outcode |= 0x40; 106 | if ((-p.x - p.y - p.z) > 1.5) outcode |= 0x80; 107 | return(outcode); 108 | } 109 | 110 | /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */ 111 | 112 | /* Test the point "alpha" of the way from P1 to P2 */ 113 | /* See if it is on a face of the cube */ 114 | /* Consider only faces in "mask" */ 115 | 116 | long check_point(Point3 p1, Point3 p2, float alpha, long mask) 117 | { 118 | Point3 plane_point; 119 | 120 | plane_point.x = LERP(alpha, p1.x, p2.x); 121 | plane_point.y = LERP(alpha, p1.y, p2.y); 122 | plane_point.z = LERP(alpha, p1.z, p2.z); 123 | return(face_plane(plane_point) & mask); 124 | } 125 | 126 | /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */ 127 | 128 | /* Compute intersection of P1 --> P2 line segment with face planes */ 129 | /* Then test intersection point to see if it is on cube face */ 130 | /* Consider only face planes in "outcode_diff" */ 131 | /* Note: Zero bits in "outcode_diff" means face line is outside of */ 132 | 133 | long check_line(Point3 p1, Point3 p2, long outcode_diff) 134 | { 135 | 136 | if ((0x01 & outcode_diff) != 0) 137 | if (check_point(p1,p2,( 0.5f-p1.x)/(p2.x-p1.x),0x3e) == INSIDE) return(INSIDE); 138 | if ((0x02 & outcode_diff) != 0) 139 | if (check_point(p1,p2,(-0.5f-p1.x)/(p2.x-p1.x),0x3d) == INSIDE) return(INSIDE); 140 | if ((0x04 & outcode_diff) != 0) 141 | if (check_point(p1,p2,( 0.5f-p1.y)/(p2.y-p1.y),0x3b) == INSIDE) return(INSIDE); 142 | if ((0x08 & outcode_diff) != 0) 143 | if (check_point(p1,p2,(-0.5f-p1.y)/(p2.y-p1.y),0x37) == INSIDE) return(INSIDE); 144 | if ((0x10 & outcode_diff) != 0) 145 | if (check_point(p1,p2,( 0.5f-p1.z)/(p2.z-p1.z),0x2f) == INSIDE) return(INSIDE); 146 | if ((0x20 & outcode_diff) != 0) 147 | if (check_point(p1,p2,(-0.5f-p1.z)/(p2.z-p1.z),0x1f) == INSIDE) return(INSIDE); 148 | return(OUTSIDE); 149 | } 150 | 151 | /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */ 152 | 153 | /* Test if 3D point is inside 3D triangle */ 154 | 155 | long point_triangle_intersection(Point3 p, Triangle3 t) 156 | { 157 | long sign12,sign23,sign31; 158 | Point3 vect12,vect23,vect31,vect1h,vect2h,vect3h; 159 | Point3 cross12_1p,cross23_2p,cross31_3p; 160 | 161 | /* First, a quick bounding-box test: */ 162 | /* If P is outside triangle bbox, there cannot be an intersection. */ 163 | 164 | if (p.x > MAX3(t.v1.x, t.v2.x, t.v3.x) + EPS) return(OUTSIDE); 165 | if (p.y > MAX3(t.v1.y, t.v2.y, t.v3.y) + EPS) return(OUTSIDE); 166 | if (p.z > MAX3(t.v1.z, t.v2.z, t.v3.z) + EPS) return(OUTSIDE); 167 | if (p.x < MIN3(t.v1.x, t.v2.x, t.v3.x) - EPS) return(OUTSIDE); 168 | if (p.y < MIN3(t.v1.y, t.v2.y, t.v3.y) - EPS) return(OUTSIDE); 169 | if (p.z < MIN3(t.v1.z, t.v2.z, t.v3.z) - EPS) return(OUTSIDE); 170 | 171 | /* For each triangle side, make a vector out of it by subtracting vertexes; */ 172 | /* make another vector from one vertex to point P. */ 173 | /* The crossproduct of these two vectors is orthogonal to both and the */ 174 | /* signs of its X,Y,Z components indicate whether P was to the inside or */ 175 | /* to the outside of this triangle side. */ 176 | 177 | SUB(t.v1, t.v2, vect12) 178 | SUB(t.v1, p, vect1h); 179 | CROSS(vect12, vect1h, cross12_1p) 180 | sign12 = SIGN3(cross12_1p); /* Extract X,Y,Z signs as 0..7 or 0...63 integer */ 181 | 182 | SUB(t.v2, t.v3, vect23) 183 | SUB(t.v2, p, vect2h); 184 | CROSS(vect23, vect2h, cross23_2p) 185 | sign23 = SIGN3(cross23_2p); 186 | 187 | SUB(t.v3, t.v1, vect31) 188 | SUB(t.v3, p, vect3h); 189 | CROSS(vect31, vect3h, cross31_3p) 190 | sign31 = SIGN3(cross31_3p); 191 | 192 | /* If all three crossproduct vectors agree in their component signs, */ 193 | /* then the point must be inside all three. */ 194 | /* P cannot be OUTSIDE all three sides simultaneously. */ 195 | 196 | /* this is the old test; with the revised SIGN3() macro, the test 197 | * needs to be revised. */ 198 | #ifdef OLD_TEST 199 | if ((sign12 == sign23) && (sign23 == sign31)) 200 | return(INSIDE); 201 | else 202 | return(OUTSIDE); 203 | #else 204 | return ((sign12 & sign23 & sign31) == 0) ? OUTSIDE : INSIDE; 205 | #endif 206 | } 207 | 208 | /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */ 209 | 210 | /**********************************************/ 211 | /* This is the main algorithm procedure. */ 212 | /* Triangle t is compared with a unit cube, */ 213 | /* centered on the origin. */ 214 | /* It returns INSIDE (0) or OUTSIDE(1) if t */ 215 | /* intersects or does not intersect the cube. */ 216 | /**********************************************/ 217 | 218 | long t_c_intersection(Triangle3 t) 219 | { 220 | long v1_test,v2_test,v3_test; 221 | float d,denom; 222 | Point3 vect12,vect13,norm; 223 | Point3 hitpp,hitpn,hitnp,hitnn; 224 | 225 | /* First compare all three vertexes with all six face-planes */ 226 | /* If any vertex is inside the cube, return immediately! */ 227 | 228 | if ((v1_test = face_plane(t.v1)) == INSIDE) return(INSIDE); 229 | if ((v2_test = face_plane(t.v2)) == INSIDE) return(INSIDE); 230 | if ((v3_test = face_plane(t.v3)) == INSIDE) return(INSIDE); 231 | 232 | /* If all three vertexes were outside of one or more face-planes, */ 233 | /* return immediately with a trivial rejection! */ 234 | 235 | if ((v1_test & v2_test & v3_test) != 0) return(OUTSIDE); 236 | 237 | /* Now do the same trivial rejection test for the 12 edge planes */ 238 | 239 | v1_test |= bevel_2d(t.v1) << 8; 240 | v2_test |= bevel_2d(t.v2) << 8; 241 | v3_test |= bevel_2d(t.v3) << 8; 242 | if ((v1_test & v2_test & v3_test) != 0) return(OUTSIDE); 243 | 244 | /* Now do the same trivial rejection test for the 8 corner planes */ 245 | 246 | v1_test |= bevel_3d(t.v1) << 24; 247 | v2_test |= bevel_3d(t.v2) << 24; 248 | v3_test |= bevel_3d(t.v3) << 24; 249 | if ((v1_test & v2_test & v3_test) != 0) return(OUTSIDE); 250 | 251 | /* If vertex 1 and 2, as a pair, cannot be trivially rejected */ 252 | /* by the above tests, then see if the v1-->v2 triangle edge */ 253 | /* intersects the cube. Do the same for v1-->v3 and v2-->v3. */ 254 | /* Pass to the intersection algorithm the "OR" of the outcode */ 255 | /* bits, so that only those cube faces which are spanned by */ 256 | /* each triangle edge need be tested. */ 257 | 258 | if ((v1_test & v2_test) == 0) 259 | if (check_line(t.v1,t.v2,v1_test|v2_test) == INSIDE) return(INSIDE); 260 | if ((v1_test & v3_test) == 0) 261 | if (check_line(t.v1,t.v3,v1_test|v3_test) == INSIDE) return(INSIDE); 262 | if ((v2_test & v3_test) == 0) 263 | if (check_line(t.v2,t.v3,v2_test|v3_test) == INSIDE) return(INSIDE); 264 | 265 | /* By now, we know that the triangle is not off to any side, */ 266 | /* and that its sides do not penetrate the cube. We must now */ 267 | /* test for the cube intersecting the interior of the triangle. */ 268 | /* We do this by looking for intersections between the cube */ 269 | /* diagonals and the triangle...first finding the intersection */ 270 | /* of the four diagonals with the plane of the triangle, and */ 271 | /* then if that intersection is inside the cube, pursuing */ 272 | /* whether the intersection point is inside the triangle itself. */ 273 | 274 | /* To find plane of the triangle, first perform crossproduct on */ 275 | /* two triangle side vectors to compute the normal vector. */ 276 | 277 | SUB(t.v1,t.v2,vect12); 278 | SUB(t.v1,t.v3,vect13); 279 | CROSS(vect12,vect13,norm) 280 | 281 | /* The normal vector "norm" X,Y,Z components are the coefficients */ 282 | /* of the triangles AX + BY + CZ + D = 0 plane equation. If we */ 283 | /* solve the plane equation for X=Y=Z (a diagonal), we get */ 284 | /* -D/(A+B+C) as a metric of the distance from cube center to the */ 285 | /* diagonal/plane intersection. If this is between -0.5 and 0.5, */ 286 | /* the intersection is inside the cube. If so, we continue by */ 287 | /* doing a point/triangle intersection. */ 288 | /* Do this for all four diagonals. */ 289 | 290 | d = norm.x * t.v1.x + norm.y * t.v1.y + norm.z * t.v1.z; 291 | 292 | /* if one of the diagonals is parallel to the plane, the other will intersect the plane */ 293 | if(fabs(denom=(norm.x + norm.y + norm.z))>EPS) 294 | /* skip parallel diagonals to the plane; division by 0 can occur */ 295 | { 296 | hitpp.x = hitpp.y = hitpp.z = d / denom; 297 | if (fabs(hitpp.x) <= 0.5) 298 | if (point_triangle_intersection(hitpp,t) == INSIDE) return(INSIDE); 299 | } 300 | if(fabs(denom=(norm.x + norm.y - norm.z))>EPS) 301 | { 302 | hitpn.z = -(hitpn.x = hitpn.y = d / denom); 303 | if (fabs(hitpn.x) <= 0.5) 304 | if (point_triangle_intersection(hitpn,t) == INSIDE) return(INSIDE); 305 | } 306 | if(fabs(denom=(norm.x - norm.y + norm.z))>EPS) 307 | { 308 | hitnp.y = -(hitnp.x = hitnp.z = d / denom); 309 | if (fabs(hitnp.x) <= 0.5) 310 | if (point_triangle_intersection(hitnp,t) == INSIDE) return(INSIDE); 311 | } 312 | if(fabs(denom=(norm.x - norm.y - norm.z))>EPS) 313 | { 314 | hitnn.y = hitnn.z = -(hitnn.x = d / denom); 315 | if (fabs(hitnn.x) <= 0.5) 316 | if (point_triangle_intersection(hitnn,t) == INSIDE) return(INSIDE); 317 | } 318 | 319 | /* No edge touched the cube; no cube diagonal touched the triangle. */ 320 | /* We're done...there was no intersection. */ 321 | 322 | return(OUTSIDE); 323 | 324 | } 325 | -------------------------------------------------------------------------------- /voxlib/voxelintersect/triangle.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | from ctypes import cdll, Structure, c_float 5 | 6 | 7 | class Point3(Structure): 8 | _fields_ = [ 9 | ("x", c_float), 10 | ("y", c_float), 11 | ("z", c_float) 12 | ] 13 | 14 | 15 | class Triangle3(Structure): 16 | _fields_ = [ 17 | ("v1", Point3), 18 | ("v2", Point3), 19 | ("v3", Point3) 20 | ] 21 | 22 | 23 | triangle_lib = None 24 | script_dir = os.path.dirname(os.path.realpath(__file__)) 25 | try: 26 | if sys.platform.startswith('linux') and sys.maxsize == 9223372036854775807: 27 | file_path_library = os.path.join(script_dir, 'triangleCube_linux64.so') 28 | if os.path.exists(file_path_library): 29 | triangle_lib = cdll.LoadLibrary(file_path_library) 30 | elif sys.platform.startswith("win") and sys.maxsize == 2147483647: 31 | file_path_library = os.path.join(script_dir, 'triangleCube_win32.so') 32 | if os.path.exists(file_path_library): 33 | triangle_lib = cdll.LoadLibrary(file_path_library) 34 | except OSError: 35 | triangle_lib = None 36 | 37 | 38 | """ 39 | Code conversion into python from: 40 | 'https://github.com/erich666/GraphicsGems/blob/master/gemsiii/triangleCube.c' 41 | """ 42 | 43 | INSIDE = 0 44 | OUTSIDE = 1 45 | EPS = 1e-5 46 | # EPS = 0.0 47 | # print(EPS) 48 | 49 | 50 | def cross_product(a, b): 51 | return ( 52 | a[1] * b[2] - a[2] * b[1], 53 | -a[0] * b[2] + a[2] * b[0], 54 | a[0] * b[1] - a[1] * b[0]) 55 | 56 | 57 | def sign3(point): 58 | sign_code = 0 59 | 60 | if point[0] < EPS: 61 | sign_code |= 4 62 | if point[0] > -EPS: 63 | sign_code |= 32 64 | 65 | if point[1] < EPS: 66 | sign_code |= 2 67 | if point[1] > -EPS: 68 | sign_code |= 16 69 | 70 | if point[2] < EPS: 71 | sign_code |= 1 72 | if point[2] > -EPS: 73 | sign_code |= 8 74 | 75 | return sign_code 76 | 77 | 78 | def lerp(alpha, a, b): 79 | return a + alpha * (b - a) 80 | 81 | 82 | class Triangle(object): 83 | """ 84 | 85 | @type v1: numpy.ndarray 86 | @type v2: numpy.ndarray 87 | @type v3: numpy.ndarray 88 | """ 89 | 90 | def __init__(self): 91 | """ 92 | 93 | """ 94 | self.v1 = 0 95 | self.v2 = 0 96 | self.v3 = 0 97 | 98 | def set(self, vertex_1, vertex_2, vertex_3): 99 | """ 100 | 101 | @type vertex_1: numpy.ndarray 102 | @type vertex_2: numpy.ndarray 103 | @type vertex_3: numpy.ndarray 104 | """ 105 | self.v1 = vertex_1 106 | self.v2 = vertex_2 107 | self.v3 = vertex_3 108 | 109 | def min(self, index): 110 | if self.v1[index] < self.v2[index] and self.v1[index] < self.v3[index]: 111 | return self.v1[index] 112 | elif self.v2[index] < self.v3[index]: 113 | return self.v2[index] 114 | else: 115 | return self.v3[index] 116 | 117 | def max(self, index): 118 | if self.v1[index] > self.v2[index] and self.v1[index] > self.v3[index]: 119 | return self.v1[index] 120 | elif self.v2[index] > self.v3[index]: 121 | return self.v2[index] 122 | else: 123 | return self.v3[index] 124 | 125 | 126 | def vertexes_to_c_triangle(vertex_1, vertex_2, vertex_3): 127 | return Triangle3( 128 | Point3(vertex_1[0], vertex_1[1], vertex_1[2]), 129 | Point3(vertex_2[0], vertex_2[1], vertex_2[2]), 130 | Point3(vertex_3[0], vertex_3[1], vertex_3[2]) 131 | ) 132 | 133 | 134 | def face_plane(point): 135 | """ 136 | Which of the six face-plane(s) is point P outside of? 137 | 138 | @type point: numpy.ndarray | (float, float, float) 139 | """ 140 | face_plane_code = 0 141 | if point[0] >= .5: 142 | face_plane_code |= 0x01 143 | if point[0] < -.5: 144 | face_plane_code |= 0x02 145 | if point[1] >= .5: 146 | face_plane_code |= 0x04 147 | if point[1] < -.5: 148 | face_plane_code |= 0x08 149 | if point[2] >= .5: 150 | face_plane_code |= 0x10 151 | if point[2] < -.5: 152 | face_plane_code |= 0x20 153 | return face_plane_code 154 | 155 | 156 | def bevel_2d(point): 157 | """ 158 | 159 | Which of the twelve edge plane(s) is point P outside of? 160 | """ 161 | edge_plane_code = 0 162 | if point[0] + point[1] >= 1.0: 163 | edge_plane_code |= 0x001 164 | if point[0] - point[1] >= 1.0: 165 | edge_plane_code |= 0x002 166 | if -point[0] + point[1] > 1.0: 167 | edge_plane_code |= 0x004 168 | if -point[0] - point[1] > 1.0: 169 | edge_plane_code |= 0x008 170 | 171 | if point[0] + point[2] >= 1.0: 172 | edge_plane_code |= 0x010 173 | if point[0] - point[2] >= 1.0: 174 | edge_plane_code |= 0x020 175 | if -point[0] + point[2] > 1.0: 176 | edge_plane_code |= 0x040 177 | if -point[0] - point[2] > 1.0: 178 | edge_plane_code |= 0x080 179 | 180 | if point[1] + point[2] >= 1.0: 181 | edge_plane_code |= 0x100 182 | if point[1] - point[2] >= 1.0: 183 | edge_plane_code |= 0x200 184 | if -point[1] + point[2] > 1.0: 185 | edge_plane_code |= 0x400 186 | if -point[1] - point[2] > 1.0: 187 | edge_plane_code |= 0x800 188 | return edge_plane_code 189 | 190 | 191 | def bevel_3d(point): 192 | """ 193 | Which of the eight corner plane(s) is point P outside of? 194 | """ 195 | corner_plane_code = 0 196 | if (point[0] + point[1] + point[2]) >= 1.5: 197 | corner_plane_code |= 0x01 198 | if (point[0] + point[1] - point[2]) >= 1.5: 199 | corner_plane_code |= 0x02 200 | if (point[0] - point[1] + point[2]) >= 1.5: 201 | corner_plane_code |= 0x04 202 | if (point[0] - point[1] - point[2]) >= 1.5: 203 | corner_plane_code |= 0x08 204 | if (-point[0] + point[1] + point[2]) > 1.5: 205 | corner_plane_code |= 0x10 206 | if (-point[0] + point[1] - point[2]) > 1.5: 207 | corner_plane_code |= 0x20 208 | if (-point[0] - point[1] + point[2]) > 1.5: 209 | corner_plane_code |= 0x40 210 | if (-point[0] - point[1] - point[2]) > 1.5: 211 | corner_plane_code |= 0x80 212 | return corner_plane_code 213 | 214 | 215 | def check_point(point_a, point_b, alpha, mask): 216 | """ 217 | Test the point "alpha" of the way from P1 to P2 218 | See if it is on a face of the cube 219 | Consider only faces in "mask" 220 | """ 221 | plane_point_x = lerp(alpha, point_a[0], point_b[0]) 222 | plane_point_y = lerp(alpha, point_a[1], point_b[1]) 223 | plane_point_z = lerp(alpha, point_a[2], point_b[2]) 224 | 225 | plane_point = (plane_point_x, plane_point_y, plane_point_z) 226 | return face_plane(plane_point) & mask 227 | 228 | 229 | def check_line(point_a, point_b, outcode_diff): 230 | """ 231 | /* Compute intersection of P1 --> P2 line segment with face planes */ 232 | /* Then test intersection point to see if it is on cube face */ 233 | /* Consider only face planes in "outcode_diff" */ 234 | /* Note: Zero bits in "outcode_diff" means face line is outside of */ 235 | """ 236 | if (0x01 & outcode_diff) != 0: 237 | if check_point(point_a, point_b, (0.5 - point_a[0])/(point_b[0] - point_a[0]), 0x3e) == INSIDE: 238 | return INSIDE 239 | if (0x02 & outcode_diff) != 0: 240 | if check_point(point_a, point_b, (-0.5 - point_a[0])/(point_b[0] - point_a[0]), 0x3d) == INSIDE: 241 | return INSIDE 242 | if (0x04 & outcode_diff) != 0: 243 | if check_point(point_a, point_b, (0.5 - point_a[1])/(point_b[1] - point_a[1]), 0x3b) == INSIDE: 244 | return INSIDE 245 | if (0x08 & outcode_diff) != 0: 246 | if check_point(point_a, point_b, (-0.5 - point_a[1])/(point_b[1] - point_a[1]), 0x37) == INSIDE: 247 | return INSIDE 248 | if (0x10 & outcode_diff) != 0: 249 | if check_point(point_a, point_b, (0.5 - point_a[2])/(point_b[2] - point_a[2]), 0x2f) == INSIDE: 250 | return INSIDE 251 | if (0x20 & outcode_diff) != 0: 252 | if check_point(point_a, point_b, (-0.5 - point_a[2])/(point_b[2] - point_a[2]), 0x1f) == INSIDE: 253 | return INSIDE 254 | return OUTSIDE 255 | 256 | 257 | def point_triangle_intersection(p, t): 258 | """ 259 | Test if 3D point is inside 3D triangle 260 | 261 | @type p: list[float] 262 | @type t: Triangle 263 | """ 264 | # /* First, a quick bounding-box test: */ 265 | # /* If P is outside triangle bbox, there cannot be an intersection. */ 266 | 267 | # add/sub EPS as buffer to avoid an floating point issue 268 | 269 | if p[0] > t.max(0) + EPS: 270 | return OUTSIDE 271 | if p[1] > t.max(1) + EPS: 272 | return OUTSIDE 273 | if p[2] > t.max(2) + EPS: 274 | return OUTSIDE 275 | if p[0] < t.min(0) - EPS: 276 | return OUTSIDE 277 | if p[1] < t.min(1) - EPS: 278 | return OUTSIDE 279 | if p[2] < t.min(2) - EPS: 280 | return OUTSIDE 281 | 282 | # /* For each triangle side, make a vector out of it by subtracting vertexes; */ 283 | # /* make another vector from one vertex to point P. */ 284 | # /* The crossproduct of these two vectors is orthogonal to both and the */ 285 | # /* signs of its X,Y,Z components indicate whether P was to the inside or */ 286 | # /* to the outside of this triangle side. */ 287 | 288 | vect12 = np.subtract(t.v1, t.v2) 289 | vect1h = np.subtract(t.v1, p) 290 | cross12_1p = cross_product(vect12, vect1h) 291 | sign12 = sign3(cross12_1p) # /* Extract X,Y,Z signs as 0..7 or 0...63 integer */ 292 | 293 | vect23 = np.subtract(t.v2, t.v3) 294 | vect2h = np.subtract(t.v2, p) 295 | cross23_2p = cross_product(vect23, vect2h) 296 | sign23 = sign3(cross23_2p) 297 | 298 | vect31 = np.subtract(t.v3, t.v1) 299 | vect3h = np.subtract(t.v3, p) 300 | cross31_3p = cross_product(vect31, vect3h) 301 | sign31 = sign3(cross31_3p) 302 | 303 | # /* If all three cross product vectors agree in their component signs, */ 304 | # /* then the point must be inside all three. */ 305 | # /* P cannot be OUTSIDE all three sides simultaneously. */ 306 | 307 | if (sign12 & sign23 & sign31) == 0: 308 | return OUTSIDE 309 | return INSIDE 310 | 311 | 312 | def t_c_intersection(triangle): 313 | """ 314 | /**********************************************/ 315 | /* This is the main algorithm procedure. */ 316 | /* Triangle t is compared with a unit cube, */ 317 | /* centered on the origin. */ 318 | /* It returns INSIDE (0) or OUTSIDE(1) if t */ 319 | /* intersects or does not intersect the cube. */ 320 | /**********************************************/ 321 | 322 | @type triangle: Triangle 323 | """ 324 | 325 | # long v1_test,v2_test,v3_test; 326 | # float d,denom; 327 | # Point3 vect12,vect13,norm; 328 | # Point3 hitpp,hitpn,hitnp,hitnn; 329 | 330 | # /* First compare all three vertexes with all six face-planes */ 331 | # /* If any vertex is inside the cube, return immediately! */ 332 | 333 | v1_test = face_plane(triangle.v1) 334 | v2_test = face_plane(triangle.v2) 335 | v3_test = face_plane(triangle.v3) 336 | if v1_test == INSIDE: 337 | return INSIDE 338 | if v2_test == INSIDE: 339 | return INSIDE 340 | if v3_test == INSIDE: 341 | return INSIDE 342 | 343 | # /* If all three vertexes were outside of one or more face-planes, */ 344 | # /* return immediately with a trivial rejection! */ 345 | 346 | if (v1_test & v2_test & v3_test) != INSIDE: 347 | return OUTSIDE 348 | 349 | # /* Now do the same trivial rejection test for the 12 edge planes */ 350 | 351 | v1_test |= bevel_2d(triangle.v1) << 8 352 | v2_test |= bevel_2d(triangle.v2) << 8 353 | v3_test |= bevel_2d(triangle.v3) << 8 354 | if (v1_test & v2_test & v3_test) != INSIDE: 355 | return OUTSIDE 356 | 357 | # /* Now do the same trivial rejection test for the 8 corner planes */ 358 | 359 | v1_test |= bevel_3d(triangle.v1) << 24 360 | v2_test |= bevel_3d(triangle.v2) << 24 361 | v3_test |= bevel_3d(triangle.v3) << 24 362 | if (v1_test & v2_test & v3_test) != INSIDE: 363 | return OUTSIDE 364 | 365 | # /* If vertex 1 and 2, as a pair, cannot be trivially rejected */ 366 | # /* by the above tests, then see if the v1-->v2 triangle edge */ 367 | # /* intersects the cube. Do the same for v1-->v3 and v2-->v3. */ 368 | # /* Pass to the intersection algorithm the "OR" of the outcode */ 369 | # /* bits, so that only those cube faces which are spanned by */ 370 | # /* each triangle edge need be tested. */ 371 | 372 | if (v1_test & v2_test) == 0: 373 | if check_line(triangle.v1, triangle.v2, v1_test | v2_test) == INSIDE: 374 | return INSIDE 375 | if (v1_test & v3_test) == 0: 376 | if check_line(triangle.v1, triangle.v3, v1_test | v3_test) == INSIDE: 377 | return INSIDE 378 | if (v2_test & v3_test) == 0: 379 | if check_line(triangle.v2, triangle.v3, v2_test | v3_test) == INSIDE: 380 | return INSIDE 381 | 382 | # /* By now, we know that the triangle is not off to any side, */ 383 | # /* and that its sides do not penetrate the cube. We must now */ 384 | # /* test for the cube intersecting the interior of the triangle. */ 385 | # /* We do this by looking for intersections between the cube */ 386 | # /* diagonals and the triangle...first finding the intersection */ 387 | # /* of the four diagonals with the plane of the triangle, and */ 388 | # /* then if that intersection is inside the cube, pursuing */ 389 | # /* whether the intersection point is inside the triangle itself. */ 390 | 391 | # /* To find plane of the triangle, first perform crossproduct on */ 392 | # /* two triangle side vectors to compute the normal vector. */ 393 | 394 | vect12 = np.subtract(triangle.v1, triangle.v2) 395 | vect13 = np.subtract(triangle.v1, triangle.v3) 396 | norm = cross_product(vect12, vect13) 397 | 398 | # /* The normal vector "norm" X,Y,Z components are the coefficients */ 399 | # /* of the triangles AX + BY + CZ + D = 0 plane equation. If we */ 400 | # /* solve the plane equation for X=Y=Z (a diagonal), we get */ 401 | # /* -D/(A+B+C) as a metric of the distance from cube center to the */ 402 | # /* diagonal/plane intersection. If this is between -0.5 and 0.5, */ 403 | # /* the intersection is inside the cube. If so, we continue by */ 404 | # /* doing a point/triangle intersection. */ 405 | # /* Do this for all four diagonals. */ 406 | 407 | d = norm[0] * triangle.v1[0] + norm[1] * triangle.v1[1] + norm[2] * triangle.v1[2] 408 | 409 | # /* if one of the diagonals is parallel to the plane, the other will intersect the plane */ 410 | denom = norm[0] + norm[1] + norm[2] 411 | hitpp = [0.0, 0.0, 0.0] 412 | if abs(denom) > EPS: 413 | # /* skip parallel diagonals to the plane; division by 0 can occur */ 414 | hitpp[0] = hitpp[1] = hitpp[2] = d / denom 415 | if abs(hitpp[0]) <= 0.5: 416 | if point_triangle_intersection(hitpp, triangle) == INSIDE: 417 | return INSIDE 418 | 419 | denom = norm[0] + norm[1] - norm[2] 420 | hitpn = [0.0, 0.0, 0.0] 421 | if abs(denom) > EPS: 422 | hitpn[0] = hitpn[1] = d / denom 423 | hitpn[2] = -hitpn[0] 424 | if abs(hitpn[0]) <= 0.5: 425 | if point_triangle_intersection(hitpn, triangle) == INSIDE: 426 | return INSIDE 427 | 428 | denom = norm[0] - norm[1] + norm[2] 429 | hitnp = [0.0, 0.0, 0.0] 430 | if abs(denom) > EPS: 431 | hitnp[0] = hitnp[2] = d / denom 432 | hitnp[1] = -hitnp[0] 433 | if abs(hitnp[0]) <= 0.5: 434 | if point_triangle_intersection(hitnp, triangle) == INSIDE: 435 | return INSIDE 436 | 437 | denom = norm[0] - norm[1] - norm[2] 438 | hitnn = [0.0, 0.0, 0.0] 439 | if abs(denom) > EPS: 440 | hitnn[0] = d / denom 441 | hitnn[1] = hitnn[2] = -hitnn[0] 442 | if abs(hitnn[0]) <= 0.5: 443 | if point_triangle_intersection(hitnn, triangle) == INSIDE: 444 | return INSIDE 445 | 446 | # /* No edge touched the cube; no cube diagonal touched the triangle. */ 447 | # /* We're done...there was no intersection. */ 448 | 449 | return OUTSIDE 450 | --------------------------------------------------------------------------------