intersection.py 12.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# -*- coding: utf-8 -*-
"""
$Id$

Copyright 2008-2010 Lode Leroy

This file is part of PyCAM.

PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PyCAM.  If not, see <http://www.gnu.org/licenses/>.
"""

lode_leroy's avatar
lode_leroy committed
23

sumpfralle's avatar
sumpfralle committed
24 25
#import pycam.Geometry
from pycam.Utils.polynomials import poly4_roots
26
from pycam.Geometry.utils import INFINITE, sqrt, epsilon
lode_leroy's avatar
lode_leroy committed
27 28
from pycam.Geometry.Plane import Plane
from pycam.Geometry.Line import Line
29
from pycam.Geometry.PointUtils import *
lode_leroy's avatar
lode_leroy committed
30

lode_leroy's avatar
lode_leroy committed
31
def isNear(a, b):
32
    return abs(a - b) < epsilon
lode_leroy's avatar
lode_leroy committed
33 34

def isZero(a):
sumpfralle's avatar
sumpfralle committed
35
    return isNear(a, 0)
lode_leroy's avatar
lode_leroy committed
36

sumpfralle's avatar
sumpfralle committed
37
def intersect_lines(xl, zl, nxl, nzl, xm, zm, nxm, nzm):
lode_leroy's avatar
lode_leroy committed
38 39
    X = None
    Z = None
40 41
    try:
        if isZero(nzl) and isZero(nzm):
lode_leroy's avatar
lode_leroy committed
42
            pass
43 44
        elif isZero(nzl) or isZero(nxl):
            X = xl
sumpfralle's avatar
sumpfralle committed
45 46
            Z = zm + (xm - xl) * nxm / nzm
            return (X, Z)
47 48
        elif isZero(nzm) or isZero(nxm):
            X = xm
sumpfralle's avatar
sumpfralle committed
49 50
            Z = zl - (xm - xl) * nxl / nzl
            return (X, Z)
51
        else:
sumpfralle's avatar
sumpfralle committed
52 53
            X = (zl - zm +(xm * nxm / nzm - xl * nxl / nzl)) \
                    / (nxm / nzm - nxl / nzl)
54
            if X and xl < X and X < xm:
sumpfralle's avatar
sumpfralle committed
55 56 57
                Z = zl + (X -xl) * nxl / nzl
                return (X, Z)
    except ZeroDivisionError:
58
        pass
sumpfralle's avatar
sumpfralle committed
59
    return (None, None)
lode_leroy's avatar
lode_leroy committed
60

lode_leroy's avatar
lode_leroy committed
61 62
def intersect_cylinder_point(center, axis, radius, radiussq, direction, point):
    # take a plane along direction and axis
63
    n = pnormalized(pcross(direction, axis))
lode_leroy's avatar
lode_leroy committed
64
    # distance of the point to this plane
65
    d = pdot(n, point) - pdot(n, center)
66
    if abs(d) > radius - epsilon:
sumpfralle's avatar
sumpfralle committed
67
        return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
68 69
    # ccl is on cylinder
    d2 = sqrt(radiussq-d*d)
70
    ccl = padd( padd(center, pmul(n, d)), pmul(direction, d2))
lode_leroy's avatar
lode_leroy committed
71
    # take plane through ccl and axis
72
    plane = Plane(ccl, direction)
lode_leroy's avatar
lode_leroy committed
73
    # intersect point with plane
74
    (ccp, l) = plane.intersect_point(direction, point)
sumpfralle's avatar
sumpfralle committed
75
    return (ccp, point, -l)
lode_leroy's avatar
lode_leroy committed
76 77

def intersect_cylinder_line(center, axis, radius, radiussq, direction, edge):
78
    d = edge.dir
lode_leroy's avatar
lode_leroy committed
79
    # take a plane throught the line and along the cylinder axis (1)
80 81
    n = pcross(d, axis)
    if pnorm(n) == 0:
sumpfralle's avatar
sumpfralle committed
82 83 84
        # no contact point, but should check here if cylinder *always*
        # intersects line...
        return (None, None, INFINITE)
85
    n = pnormalized(n)
lode_leroy's avatar
lode_leroy committed
86 87 88
    # the contact line between the cylinder and this plane (1)
    # is where the surface normal is perpendicular to the plane
    # so line := ccl + \lambda * axis
89 90
    if pdot(n, direction) < 0:
        ccl = psub(center, pmul(n, radius))
lode_leroy's avatar
lode_leroy committed
91
    else:
92
        ccl = padd(center, pmul(n, radius))
lode_leroy's avatar
lode_leroy committed
93
    # now extrude the contact line along the direction, this is a plane (2)
94 95
    n2 = pcross(direction, axis)
    if pnorm(n2) == 0:
sumpfralle's avatar
sumpfralle committed
96 97 98
        # no contact point, but should check here if cylinder *always*
        # intersects line...
        return (None, None, INFINITE)
99
    n2 = pnormalized(n2)
100
    plane1 = Plane(ccl, n2)
lode_leroy's avatar
lode_leroy committed
101
    # intersect this plane with the line, this gives us the contact point
102
    (cp, l) = plane1.intersect_point(d, edge.p1)
lode_leroy's avatar
lode_leroy committed
103
    if not cp:
sumpfralle's avatar
sumpfralle committed
104 105 106
        return (None, None, INFINITE)
    # now take a plane through the contact line and perpendicular to the
    # direction (3)
107
    plane2 = Plane(ccl, direction)
sumpfralle's avatar
sumpfralle committed
108
    # the intersection of this plane (3) with the line through the contact point
lode_leroy's avatar
lode_leroy committed
109
    # gives us the cutter contact point
110
    (ccp, l) = plane2.intersect_point(direction, cp)
111
    cp = padd(ccp, pmul(direction, -l))
sumpfralle's avatar
sumpfralle committed
112
    return (ccp, cp, -l)
lode_leroy's avatar
lode_leroy committed
113 114 115

def intersect_circle_plane(center, radius, direction, triangle):
    # let n be the normal to the plane
116
    n = triangle.normal
117
    if pdot(n,direction) == 0:
sumpfralle's avatar
sumpfralle committed
118
        return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
119
    # project onto z=0
120 121
    n2 = (n[0], n[1], 0)
    if pnorm(n2) == 0:
122
        (cp, d) = triangle.plane.intersect_point(direction, center)
123
        ccp = psub(cp, pmul(direction, d))
sumpfralle's avatar
sumpfralle committed
124
        return (ccp, cp, d)
125
    n2 = pnormalized(n2)
lode_leroy's avatar
lode_leroy committed
126
    # the cutter contact point is on the circle, where the surface normal is n
127
    ccp = padd(center, pmul(n2, -radius))
lode_leroy's avatar
lode_leroy committed
128
    # intersect the plane with a line through the contact point
129
    (cp, d) = triangle.plane.intersect_point(direction, ccp)
sumpfralle's avatar
sumpfralle committed
130
    return (ccp, cp, d)
lode_leroy's avatar
lode_leroy committed
131 132 133

def intersect_circle_point(center, axis, radius, radiussq, direction, point):
    # take a plane through the base
134
    plane = Plane(center, axis)
lode_leroy's avatar
lode_leroy committed
135
    # intersect with line gives ccp
136
    (ccp, l) = plane.intersect_point(direction, point)
lode_leroy's avatar
lode_leroy committed
137
    # check if inside circle
138
    if ccp and (pnormsq(psub(center, ccp)) < radiussq - epsilon):
sumpfralle's avatar
sumpfralle committed
139 140
        return (ccp, point, -l)
    return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
141 142 143

def intersect_circle_line(center, axis, radius, radiussq, direction, edge):
    # make a plane by sliding the line along the direction (1)
144
    d = edge.dir
145 146
    if pdot(d, axis) == 0:
        if pdot(direction, axis) == 0:
sumpfralle's avatar
sumpfralle committed
147
            return (None, None, INFINITE)
148 149 150
        plane = Plane(center, axis)
        (p1, l) = plane.intersect_point(direction, edge.p1)
        (p2, l) = plane.intersect_point(direction, edge.p2)
sumpfralle's avatar
sumpfralle committed
151
        pc = Line(p1, p2).closest_point(center)
152
        d_sq = pnormsq(psub(pc, center))
153
        if d_sq >= radiussq:
sumpfralle's avatar
sumpfralle committed
154 155
            return (None, None, INFINITE)
        a = sqrt(radiussq - d_sq)
156 157
        d1 = pdot(psub(p1, pc), d)
        d2 = pdot(psub(p2, pc), d)
lode_leroy's avatar
lode_leroy committed
158 159
        ccp = None
        cp = None
160
        if abs(d1) < a - epsilon:
lode_leroy's avatar
lode_leroy committed
161
            ccp = p1
162
            cp = psub(p1, pmul(direction, l))
163
        elif abs(d2) < a - epsilon:
lode_leroy's avatar
lode_leroy committed
164
            ccp = p2
165
            cp = psub(p2, pmul(direction, l))
166 167
        elif ((d1 < -a + epsilon) and (d2 > a - epsilon)) \
                or ((d2 < -a + epsilon) and (d1 > a - epsilon)):
lode_leroy's avatar
lode_leroy committed
168
            ccp = pc
169
            cp = psub(pc, pmul(direction, l))
sumpfralle's avatar
sumpfralle committed
170
        return (ccp, cp, -l)
171 172
    n = pcross(d, direction)
    if pnorm(n)== 0:
sumpfralle's avatar
sumpfralle committed
173 174 175
        # no contact point, but should check here if circle *always* intersects
        # line...
        return (None, None, INFINITE)
176
    n = pnormalized(n)
lode_leroy's avatar
lode_leroy committed
177
    # take a plane through the base
178
    plane = Plane(center, axis)
lode_leroy's avatar
lode_leroy committed
179
    # intersect base with line
180
    (lp, l) = plane.intersect_point(d, edge.p1)
lode_leroy's avatar
lode_leroy committed
181
    if not lp:
sumpfralle's avatar
sumpfralle committed
182
        return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
183
    # intersection of 2 planes: lp + \lambda v
184 185
    v = pcross(axis, n)
    if pnorm(v) == 0:
sumpfralle's avatar
sumpfralle committed
186
        return (None, None, INFINITE)
187
    v = pnormalized(v)
lode_leroy's avatar
lode_leroy committed
188
    # take plane through intersection line and parallel to axis
189 190
    n2 = pcross(v, axis)
    if pnorm(n2) == 0:
sumpfralle's avatar
sumpfralle committed
191
        return (None, None, INFINITE)
192
    n2 = pnormalized(n2)
lode_leroy's avatar
lode_leroy committed
193
    # distance from center to this plane
194
    dist = pdot(n2, center) - pdot(n2, lp)
sumpfralle's avatar
sumpfralle committed
195
    distsq = dist * dist
196
    if distsq > radiussq - epsilon:
sumpfralle's avatar
sumpfralle committed
197
        return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
198
    # must be on circle
sumpfralle's avatar
sumpfralle committed
199
    dist2 = sqrt(radiussq - distsq)
200
    if pdot(d, axis) < 0:
lode_leroy's avatar
lode_leroy committed
201
        dist2 = -dist2
202 203
    ccp = psub(center, psub(pmul(n2, dist), pmul(v, dist2)))
    plane = Plane(edge.p1, pcross(pcross(d, direction), d))
204
    (cp, l) = plane.intersect_point(direction, ccp)
sumpfralle's avatar
sumpfralle committed
205
    return (ccp, cp, l)
lode_leroy's avatar
lode_leroy committed
206 207 208

def intersect_sphere_plane(center, radius, direction, triangle):
    # let n be the normal to the plane
209
    n = triangle.normal
210
    if pdot(n, direction) == 0:
sumpfralle's avatar
sumpfralle committed
211
        return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
212
    # the cutter contact point is on the sphere, where the surface normal is n
213 214
    if pdot(n, direction) < 0:
        ccp = psub(center, pmul(n, radius))
lode_leroy's avatar
lode_leroy committed
215
    else:
216
        ccp = padd(center, pmul(n, radius))
lode_leroy's avatar
lode_leroy committed
217
    # intersect the plane with a line through the contact point
218
    (cp, d) = triangle.plane.intersect_point(direction, ccp)
sumpfralle's avatar
sumpfralle committed
219
    return (ccp, cp, d)
lode_leroy's avatar
lode_leroy committed
220 221 222 223 224 225 226

def intersect_sphere_point(center, radius, radiussq, direction, point):
    # line equation
    # (1) x = p_0 + \lambda * d
    # sphere equation
    # (2) (x-x_0)^2 = R^2
    # (1) in (2) gives a quadratic in \lambda
227 228 229 230
    p0_x0 = psub(center, point)
    a = pnormsq(direction)
    b = 2 * pdot(p0_x0, direction)
    c = pnormsq(p0_x0) - radiussq
sumpfralle's avatar
sumpfralle committed
231 232 233 234 235
    d = b * b - 4 * a * c
    if d < 0:
        return (None, None, INFINITE)
    if a < 0:
        l = (-b + sqrt(d)) / (2 * a)
lode_leroy's avatar
lode_leroy committed
236
    else:
sumpfralle's avatar
sumpfralle committed
237
        l = (-b - sqrt(d)) / (2 * a)
lode_leroy's avatar
lode_leroy committed
238
    # cutter contact point
239
    ccp = padd(point, pmul(direction, -l))
sumpfralle's avatar
sumpfralle committed
240
    return (ccp, point, l)
lode_leroy's avatar
lode_leroy committed
241 242 243

def intersect_sphere_line(center, radius, radiussq, direction, edge):
    # make a plane by sliding the line along the direction (1)
244
    d = edge.dir
245 246
    n = pcross(n, direction)
    if pnorm(n) == 0:
sumpfralle's avatar
sumpfralle committed
247 248 249
        # no contact point, but should check here if sphere *always* intersects
        # line...
        return (None, None, INFINITE)
250
    n = pnormalized(n)
lode_leroy's avatar
lode_leroy committed
251 252

    # calculate the distance from the sphere center to the plane
253
    dist = - pdot(center, n) + pdot(edge.p1, n)
254
    if abs(dist) > radius - epsilon:
sumpfralle's avatar
sumpfralle committed
255
        return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
256 257 258 259 260 261
    # this gives us the intersection circle on the sphere

    # now take a plane through the edge and perpendicular to the direction (2)
    # find the center on the circle closest to this plane

    # which means the other component is perpendicular to this plane (2)
262
    n2 = pnormalized(pcross(n, d))
lode_leroy's avatar
lode_leroy committed
263 264

    # the contact point is on a big circle through the sphere...
sumpfralle's avatar
sumpfralle committed
265
    dist2 = sqrt(radiussq - dist * dist)
lode_leroy's avatar
lode_leroy committed
266 267

    # ... and it's on the plane (1)
268
    ccp = padd(center, padd(pmul(n, dist), pmul(n2, dist2)))
lode_leroy's avatar
lode_leroy committed
269 270

    # now intersect a line through this point with the plane (2)
271 272
    plane = Plane(edge.p1, n2)
    (cp, l) = plane.intersect_point(direction, ccp)
sumpfralle's avatar
sumpfralle committed
273
    return (ccp, cp, l)
lode_leroy's avatar
lode_leroy committed
274

sumpfralle's avatar
sumpfralle committed
275 276
def intersect_torus_plane(center, axis, majorradius, minorradius, direction,
        triangle):
lode_leroy's avatar
lode_leroy committed
277
    # take normal to the plane
278
    n = triangle.normal
279
    if pdot(n, direction) == 0:
sumpfralle's avatar
sumpfralle committed
280
        return (None, None, INFINITE)
281
    if pdot(n, axis) == 1:
sumpfralle's avatar
sumpfralle committed
282
        return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
283
    # find place on torus where surface normal is n
284
    b = pmul(n, -1)
lode_leroy's avatar
lode_leroy committed
285
    z = axis
286 287
    a = psub(b, pmul(z,pdot(z, b)))
    a_sq = pnormsq(a)
sumpfralle's avatar
sumpfralle committed
288 289
    if a_sq <= 0:
        return (None, None, INFINITE)
290 291
    a = pdiv(a, sqrt(a_sq))
    ccp = padd(padd(center, pmul(a, majorradius)), pmul(b, minorradius))
lode_leroy's avatar
lode_leroy committed
292
    # find intersection with plane
293
    (cp, l) = triangle.plane.intersect_point(direction, ccp)
sumpfralle's avatar
sumpfralle committed
294
    return (ccp, cp, l)
lode_leroy's avatar
lode_leroy committed
295

sumpfralle's avatar
sumpfralle committed
296 297
def intersect_torus_point(center, axis, majorradius, minorradius, majorradiussq,
        minorradiussq, direction, point):
lode_leroy's avatar
lode_leroy committed
298
    dist = 0
299
    if (direction[0] == 0) and (direction[1] == 0):
sumpfralle's avatar
sumpfralle committed
300 301 302
        # drop
        minlsq = (majorradius - minorradius) ** 2
        maxlsq = (majorradius + minorradius) ** 2
303
        l_sq = (point[0]-center[0]) ** 2 + (point[1] - center[1]) ** 2
304
        if (l_sq < minlsq + epsilon) or (l_sq > maxlsq - epsilon):
sumpfralle's avatar
sumpfralle committed
305
            return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
306
        l = sqrt(l_sq)
sumpfralle's avatar
sumpfralle committed
307
        z_sq = minorradiussq - (majorradius - l) ** 2
lode_leroy's avatar
lode_leroy committed
308
        if z_sq < 0:
sumpfralle's avatar
sumpfralle committed
309
            return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
310
        z = sqrt(z_sq)
311 312 313
        ccp = (point[0], point[1], center[2] - z)
        dist = ccp[2] - point[2]
    elif direction[2] == 0:
sumpfralle's avatar
sumpfralle committed
314
        # push
315
        z = point[2] - center[2]
316
        if abs(z) > minorradius - epsilon:
sumpfralle's avatar
sumpfralle committed
317 318
            return (None, None, INFINITE)
        l = majorradius + sqrt(minorradiussq - z * z)
319 320
        n = pcross(axis, direction)
        d = pdot(n, point) - pdot(n, center)
321
        if abs(d) > l - epsilon:
sumpfralle's avatar
sumpfralle committed
322 323
            return (None, None, INFINITE)
        a = sqrt(l * l - d * d)
324 325 326
        ccp = padd(padd(center, pmul(n, d)), pmul(direction, a))
        ccp = (ccp[0], ccp[1], point[2])
        dist = pdot(psub(point, ccp), direction)
sumpfralle's avatar
sumpfralle committed
327 328
    else:
        # general case
329 330 331 332 333 334 335 336 337
        x = psub(point, center)
        v = pmul(direction, -1)
        x_x = pdot(x, x)
        x_v = pdot(x, v)
        x1 = (x[0], x[1], 0)
        v1 = (v[0], v[1], 0)
        x1_x1 = pdot(x1, x1)
        x1_v1 = pdot(x1, v1)
        v1_v1 = pdot(v1, v1)
lode_leroy's avatar
lode_leroy committed
338 339 340
        R2 = majorradiussq
        r2 = minorradiussq
        a = 1.0
sumpfralle's avatar
sumpfralle committed
341 342 343 344 345
        b = 4 * x_v
        c = 2 * (x_x + 2 * x_v ** 2 + (R2 - r2) - 2 * R2 * v1_v1)
        d = 4 * (x_x * x_v + x_v * (R2 - r2) - 2 * R2 * x1_v1)
        e = (x_x) ** 2 + 2 * x_x * (R2 - r2) + (R2 - r2) ** 2 - 4 * R2 * x1_x1
        r = poly4_roots(a, b, c, d, e)
lode_leroy's avatar
lode_leroy committed
346
        if not r:
sumpfralle's avatar
sumpfralle committed
347
            return (None, None, INFINITE)
lode_leroy's avatar
lode_leroy committed
348
        else:
sumpfralle's avatar
sumpfralle committed
349
            l = min(r)
350
        ccp = padd(point, pmul(direction, -l))
lode_leroy's avatar
lode_leroy committed
351
        dist = l
sumpfralle's avatar
sumpfralle committed
352
    return (ccp, point, dist)
lode_leroy's avatar
lode_leroy committed
353