DirectGeometry.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. from panda3d.core import (
  2. CSDefault,
  3. GeomNode,
  4. LineSegs,
  5. Mat4,
  6. NodePath,
  7. Point3,
  8. Quat,
  9. VBase3,
  10. VBase4,
  11. Vec3,
  12. composeMatrix,
  13. decomposeMatrix,
  14. deg2Rad,
  15. rad2Deg,
  16. )
  17. from .DirectGlobals import Q_EPSILON, UNIT_VEC, ZERO_VEC
  18. from .DirectUtil import CLAMP
  19. import math
  20. class LineNodePath(NodePath):
  21. def __init__(self, parent = None, name = None,
  22. thickness = 1.0, colorVec = VBase4(1)):
  23. # Initialize the superclass
  24. NodePath.__init__(self)
  25. if parent is None:
  26. parent = hidden
  27. # Attach a geomNode to the parent and set self to be
  28. # the resulting node path
  29. self.lineNode = GeomNode("lineNode")
  30. self.assign(parent.attachNewNode(self.lineNode))
  31. if name:
  32. self.setName(name)
  33. # Create a lineSegs object to hold the line
  34. ls = self.lineSegs = LineSegs()
  35. # Initialize the lineSegs parameters
  36. ls.setThickness(thickness)
  37. ls.setColor(colorVec)
  38. def moveTo(self, *_args):
  39. self.lineSegs.moveTo(*_args)
  40. def drawTo(self, *_args):
  41. self.lineSegs.drawTo(*_args)
  42. def create(self, frameAccurate = 0):
  43. self.lineSegs.create(self.lineNode, frameAccurate)
  44. def reset(self):
  45. self.lineSegs.reset()
  46. self.lineNode.removeAllGeoms()
  47. def isEmpty(self):
  48. return self.lineSegs.isEmpty()
  49. def setThickness(self, thickness):
  50. self.lineSegs.setThickness(thickness)
  51. def setColor(self, *_args):
  52. self.lineSegs.setColor(*_args)
  53. def setVertex(self, *_args):
  54. self.lineSegs.setVertex(*_args)
  55. def setVertexColor(self, vertex, *_args):
  56. self.lineSegs.setVertexColor(*(vertex,) + _args)
  57. def getCurrentPosition(self):
  58. return self.lineSegs.getCurrentPosition()
  59. def getNumVertices(self):
  60. return self.lineSegs.getNumVertices()
  61. def getVertex(self, index):
  62. return self.lineSegs.getVertex(index)
  63. def getVertexColor(self):
  64. return self.lineSegs.getVertexColor()
  65. def drawArrow(self, sv, ev, arrowAngle, arrowLength):
  66. """
  67. Do the work of moving the cursor around to draw an arrow from
  68. sv to ev. Hack: the arrows take the z value of the end point
  69. """
  70. self.moveTo(sv)
  71. self.drawTo(ev)
  72. v = sv - ev
  73. # Find the angle of the line
  74. angle = math.atan2(v[1], v[0])
  75. # Get the arrow angles
  76. a1 = angle + deg2Rad(arrowAngle)
  77. a2 = angle - deg2Rad(arrowAngle)
  78. # Get the arrow points
  79. a1x = arrowLength * math.cos(a1)
  80. a1y = arrowLength * math.sin(a1)
  81. a2x = arrowLength * math.cos(a2)
  82. a2y = arrowLength * math.sin(a2)
  83. z = ev[2]
  84. self.moveTo(ev)
  85. self.drawTo(Point3(ev + Point3(a1x, a1y, z)))
  86. self.moveTo(ev)
  87. self.drawTo(Point3(ev + Point3(a2x, a2y, z)))
  88. def drawArrow2d(self, sv, ev, arrowAngle, arrowLength):
  89. """
  90. Do the work of moving the cursor around to draw an arrow from
  91. sv to ev. Hack: the arrows take the z value of the end point
  92. """
  93. self.moveTo(sv)
  94. self.drawTo(ev)
  95. v = sv - ev
  96. # Find the angle of the line
  97. angle = math.atan2(v[2], v[0])
  98. # Get the arrow angles
  99. a1 = angle + deg2Rad(arrowAngle)
  100. a2 = angle - deg2Rad(arrowAngle)
  101. # Get the arrow points
  102. a1x = arrowLength * math.cos(a1)
  103. a1y = arrowLength * math.sin(a1)
  104. a2x = arrowLength * math.cos(a2)
  105. a2y = arrowLength * math.sin(a2)
  106. self.moveTo(ev)
  107. self.drawTo(Point3(ev + Point3(a1x, 0.0, a1y)))
  108. self.moveTo(ev)
  109. self.drawTo(Point3(ev + Point3(a2x, 0.0, a2y)))
  110. def drawLines(self, lineList):
  111. """
  112. Given a list of lists of points, draw a separate line for each list
  113. """
  114. for pointList in lineList:
  115. self.moveTo(*pointList[0])
  116. for point in pointList[1:]:
  117. self.drawTo(*point)
  118. ##
  119. ## Given a point in space, and a direction, find the point of intersection
  120. ## of that ray with a plane at the specified origin, with the specified normal
  121. def planeIntersect(lineOrigin, lineDir, planeOrigin, normal):
  122. t = 0
  123. offset = planeOrigin - lineOrigin
  124. t = offset.dot(normal) / lineDir.dot(normal)
  125. hitPt = lineDir * t
  126. return hitPt + lineOrigin
  127. def getNearProjectionPoint(nodePath):
  128. # Find the position of the projection of the specified node path
  129. # on the near plane
  130. origin = nodePath.getPos(base.direct.camera)
  131. # project this onto near plane
  132. if origin[1] != 0.0:
  133. return origin * (base.direct.dr.near / origin[1])
  134. else:
  135. # Object is coplaner with camera, just return something reasonable
  136. return Point3(0, base.direct.dr.near, 0)
  137. def getScreenXY(nodePath):
  138. # Where does the node path's projection fall on the near plane
  139. nearVec = getNearProjectionPoint(nodePath)
  140. # Clamp these coordinates to visible screen
  141. nearX = CLAMP(nearVec[0], base.direct.dr.left, base.direct.dr.right)
  142. nearY = CLAMP(nearVec[2], base.direct.dr.bottom, base.direct.dr.top)
  143. # What percentage of the distance across the screen is this?
  144. percentX = (nearX - base.direct.dr.left)/base.direct.dr.nearWidth
  145. percentY = (nearY - base.direct.dr.bottom)/base.direct.dr.nearHeight
  146. # Map this percentage to the same -1 to 1 space as the mouse
  147. screenXY = Vec3((2 * percentX) - 1.0, nearVec[1], (2 * percentY) - 1.0)
  148. # Return the resulting value
  149. return screenXY
  150. def getCrankAngle(center):
  151. # Used to compute current angle of mouse (relative to the coa's
  152. # origin) in screen space
  153. x = base.direct.dr.mouseX - center[0]
  154. y = base.direct.dr.mouseY - center[2]
  155. return 180 + rad2Deg(math.atan2(y, x))
  156. def relHpr(nodePath, base, h, p, r):
  157. # Compute nodePath2newNodePath relative to base coordinate system
  158. # nodePath2base
  159. mNodePath2Base = nodePath.getMat(base)
  160. # delta scale, orientation, and position matrix
  161. mBase2NewBase = Mat4(Mat4.identMat()) # [gjeon] fixed to give required argument
  162. composeMatrix(mBase2NewBase, UNIT_VEC, VBase3(h, p, r), ZERO_VEC,
  163. CSDefault)
  164. # base2nodePath
  165. mBase2NodePath = base.getMat(nodePath)
  166. # nodePath2 Parent
  167. mNodePath2Parent = nodePath.getMat()
  168. # Compose the result
  169. resultMat = mNodePath2Base * mBase2NewBase
  170. resultMat = resultMat * mBase2NodePath
  171. resultMat = resultMat * mNodePath2Parent
  172. # Extract and apply the hpr
  173. hpr = Vec3(0)
  174. decomposeMatrix(resultMat, VBase3(), hpr, VBase3(),
  175. CSDefault)
  176. nodePath.setHpr(hpr)
  177. # Quaternion interpolation
  178. def qSlerp(startQuat, endQuat, t):
  179. startQ = Quat(startQuat)
  180. destQuat = Quat(Quat.identQuat())
  181. # Calc dot product
  182. cosOmega = (startQ.getI() * endQuat.getI() +
  183. startQ.getJ() * endQuat.getJ() +
  184. startQ.getK() * endQuat.getK() +
  185. startQ.getR() * endQuat.getR())
  186. # If the above dot product is negative, it would be better to
  187. # go between the negative of the initial and the final, so that
  188. # we take the shorter path.
  189. if cosOmega < 0.0:
  190. cosOmega *= -1
  191. startQ.setI(-1 * startQ.getI())
  192. startQ.setJ(-1 * startQ.getJ())
  193. startQ.setK(-1 * startQ.getK())
  194. startQ.setR(-1 * startQ.getR())
  195. if (1.0 + cosOmega) > Q_EPSILON:
  196. # usual case
  197. if (1.0 - cosOmega) > Q_EPSILON:
  198. # usual case
  199. omega = math.acos(cosOmega)
  200. sinOmega = math.sin(omega)
  201. startScale = math.sin((1.0 - t) * omega)/sinOmega
  202. endScale = math.sin(t * omega)/sinOmega
  203. else:
  204. # ends very close
  205. startScale = 1.0 - t
  206. endScale = t
  207. destQuat.setI(startScale * startQ.getI() +
  208. endScale * endQuat.getI())
  209. destQuat.setJ(startScale * startQ.getJ() +
  210. endScale * endQuat.getJ())
  211. destQuat.setK(startScale * startQ.getK() +
  212. endScale * endQuat.getK())
  213. destQuat.setR(startScale * startQ.getR() +
  214. endScale * endQuat.getR())
  215. else:
  216. # ends nearly opposite
  217. destQuat.setI(-startQ.getJ())
  218. destQuat.setJ(startQ.getI())
  219. destQuat.setK(-startQ.getR())
  220. destQuat.setR(startQ.getK())
  221. startScale = math.sin((0.5 - t) * math.pi)
  222. endScale = math.sin(t * math.pi)
  223. destQuat.setI(startScale * startQ.getI() +
  224. endScale * endQuat.getI())
  225. destQuat.setJ(startScale * startQ.getJ() +
  226. endScale * endQuat.getJ())
  227. destQuat.setK(startScale * startQ.getK() +
  228. endScale * endQuat.getK())
  229. return destQuat