import math import pymel.core as pm import pymel.core.datatypes as dt def sigmoid(x): return 1 / (1 + math.exp(-x)) def distance(a, b): return (b - a).length() def lerp_vec(a, b, t): return (a * (1 - t)) + (b * t) def dist_to_plane(pt, n, d): return n.dot(pt) - (d / n.dot(n)) def dist_to_line(a, b, p): ap = p - a ab = b - a result = a + ((ap.dot(ab) / ab.dot(ab)) * ab) return distance(result, p) def is_same_side_of_plane(pt, test_pt, n, d): d1 = math.copysign(1, dist_to_plane(pt, n, d)) d2 = math.copysign(1, dist_to_plane(test_pt, n, d)) # print(pt, test_pt, d1, d2) return d1 * d2 == 1.0 def proj_pt_to_plane(pt, n, d): t = n.dot(pt) - d return (pt - (n * t)) def pt_in_sphere(pt, c, r): return (pt - c).length() <= r def pt_in_cylinder(pt, p, q, r): n = (q - p).normal() d = n.dot(p) if not is_same_side_of_plane(pt, (p + q) / 2.0, n, d): return False n = (q - p).normal() d = n.dot(q) if not is_same_side_of_plane(pt, (p + q) / 2.0, n, d): return False proj_pt = proj_pt_to_plane(pt, n, d) # logging("proj_pt", proj_pt) # logging("q", q) # logging("distance(proj_pt, q)", distance(proj_pt, q)) return distance(proj_pt, q) <= r def segment_sphere_isect(sa, sb, c, r): NotFound = (False, None) p = sa d = (sb - sa).normal() m = p - c b = m.dot(d) c = m.dot(m) - r * r if c > 0.0 and b > 0.0: return NotFound discr = b * b - c if discr < 0.0: return NotFound t = -b - math.sqrt(discr) if t < 0.0: return NotFound dist = distance(sa, sb) q = p + d * t return ((t >= 0 and t <= dist), q) def segment_cylinder_isect(sa, sb, p, q, r): SM_EPSILON = 1e-6 d = q - p m = sa - p n = sb - sa md = m.dot(d) nd = n.dot(d) dd = d.dot(d) NotFound = (False, None) if md < 0 and md + nd < 0: return NotFound if md > dd and md + nd > dd: return NotFound nn = n.dot(n) mn = m.dot(n) a = dd * nn - nd * nd k = m.dot(m) - r * r c = dd * k - md * md if abs(a) < SM_EPSILON: if c > 0: return NotFound if md < 0: t = -mn / nn elif md > dd: t = (nd - mn) / nn else: t = 0 return (True, lerp_vec(sa, sb, t)) b = dd * mn - nd * md discr = b * b - a * c if discr < 0: return NotFound t = (-b - math.sqrt(discr)) / a if t < 0.0 or t > 1.0: return NotFound if (md + t * nd < 0.0): if nd <= 0.0: return NotFound t = -md / nd return (k + 2 * t * (mn + t * nn) <= 0.0, lerp_vec(sa, sb, t)) elif md + t * nd > dd: if nd >= 0.0: return NotFound t = (dd - md) / nd return (k + dd - 2 * md + t * (2 * (mn - nd) + t * nn) <= 0.0, lerp_vec(sa, sb, t)) return (True, lerp_vec(sa, sb, t)) def pt_in_capsule(pt, p, q, r): return pt_in_cylinder(pt, p, q, r) or pt_in_sphere(pt, p, r) or pt_in_sphere(pt, q, r) def segment_capsule_isect(sa, sb, p, q, r): # sa = dt.Vector() # ray start point pos vector # sb = dt.Vector() # ray end point pos vector # p = dt.Vector() # capsle one sphere tip pos # q = dt.Vector() # capsle another sphere tip pos # r = float # radio of capsle sphere if pt_in_capsule(sa, p, q, r): if pt_in_capsule(sb, p, q, r): # both inside. extend sb to get intersection newb = sa + (sb - sa).normal() * 200.0 sa, sb = newb, sa else: sb, sa = sa, sb # d = (sb - sa).normal() i1 = segment_sphere_isect(sa, sb, p, r) i2 = segment_sphere_isect(sa, sb, q, r) i3 = segment_cylinder_isect(sa, sb, p, q, r) dist = float('inf') closest_pt = None hit = False hitCylinder = False for i in [i1, i2, i3]: if i[0]: hit = True pt = i[1] if distance(sa, pt) < dist: closest_pt = pt dist = min(dist, distance(sa, pt)) # draw_locator(i1[2], 'i1') return (hit, closest_pt, hitCylinder) def checkCollision(cur_pos, pre_pos, capsuleLst, isRevert): # calculate collision with all the capsule in scene if isRevert: sa = cur_pos sb = pre_pos else: sb = cur_pos sa = pre_pos isHited = False closest_pt_dict = {} for obj in capsuleLst: objChildren = pm.listRelatives(obj, children=1, type='transform') p = objChildren[0].getTranslation(space='world') q = objChildren[1].getTranslation(space='world') r = obj.getAttr('scaleZ') * 1 hit, closest_pt, hitCylinder = segment_capsule_isect(sa, sb, p, q, r) if hit: isHited = True closest_pt_dict[obj.name()] = [obj, closest_pt] # drawDebug_box(closest_pt) if isHited: pt_length = 9999 closest_pt = None col_obj = None for pt in closest_pt_dict.keys(): lLength = (closest_pt_dict[pt][1] - pre_pos).length() if lLength < pt_length: pt_length = lLength closest_pt = closest_pt_dict[pt][1] col_obj = closest_pt_dict[pt][0] # return col pt and col_body speed return closest_pt, col_obj, hitCylinder else: return None, None, None def ckeckPointInTri(pos, pa, pb, pc): ra = math.acos(((pa - pos).normal()).dot((pb - pos).normal())) ra = dt.degrees(ra) rb = math.acos(((pb - pos).normal()).dot((pc - pos).normal())) rb = dt.degrees(rb) rc = math.acos(((pc - pos).normal()).dot((pa - pos).normal())) rc = dt.degrees(rc) return (abs(ra + rb + rc) > 359) def getVertexPositions(obj): vertex_positions_list = [] for vertex in obj.vtx: vertex_positions_list.append(vertex.getPosition(space='world')) return vertex_positions_list def checkPlaneCollision(objPos, childPos, colPlane): v_coords = getVertexPositions(colPlane) collision_plane_matrix = pm.xform(colPlane, worldSpace=1, matrix=1, q=1) n = dt.Vector(collision_plane_matrix[4:7]) # Y axis direction of plane q = v_coords[1] d = n.dot(q) # get obj distance to plane toPlaneDistance = dist_to_plane(objPos, n, d) toPlaneDistance_child = dist_to_plane(childPos, n, d) # child projection position on plane projectPos_child = proj_pt_to_plane(childPos, n, d) inPlane = False if ckeckPointInTri(projectPos_child, v_coords[0], v_coords[1], v_coords[2]): inPlane = True elif ckeckPointInTri(projectPos_child, v_coords[3], v_coords[1], v_coords[2]): inPlane = True # bone above plane and bone child under plane and child project point on plane # means has collision with plane if (toPlaneDistance > 0) and (toPlaneDistance_child < 0) and inPlane: return projectPos_child else: return None