Whitemambaa

Closest Point

Jun 23rd, 2014
296
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.32 KB | None | 0 0
  1. --By xXxMoNkEyMaNxXx
  2. --[[
  3. Usage:
  4. local ClosestPointOnSurface,DistanceToSurface,SurfaceNormal=ClosestPointOnPart(Part,Point)
  5. DistanceToSurface will be negative when the point is in the part!
  6. --]]
  7. local sqrt=math.sqrt
  8.  
  9. local Terrain=workspace.Terrain
  10. local GetCell=Terrain.GetCell
  11. local CellCenterToWorld=Terrain.CellCenterToWorld
  12. local WorldToCellPreferSolid=Terrain.WorldToCellPreferSolid
  13.  
  14. local vec3=Vector3.new
  15. local IdentityVector=vec3()
  16. local dot=IdentityVector.Dot
  17. local cross=IdentityVector.Cross
  18.  
  19. local mat3=CFrame.new
  20. local IdentityCFrame=mat3()
  21. local ptos=IdentityCFrame.pointToObjectSpace
  22. local vtws=IdentityCFrame.vectorToWorldSpace
  23.  
  24. --Part geometry data
  25. local UnitaryConvexPlaneMeshes={--I realized that I could make each component of the normal vector dependent on every component of the size using matrices (genius!)
  26.     WedgePart={{vec3(0,-1,0),vec3(0,-0.5,0)},{vec3(0,0,1),vec3(0,0,0.5)},{mat3(0,0,0, 0,0,0, 0,0,1, 0,-1,0),vec3(0,0,0)},{vec3(1,0,0),vec3(0.5,0,0)},{vec3(-1,0,0),vec3(-0.5,0,0)}},
  27.     CornerWedgePart={{vec3(0,-1,0),vec3(0,-0.5,0)},{vec3(1,0,0),vec3(0.5,0,0)},{vec3(0,0,-1),vec3(0,0,-0.5)},{mat3(0,0,0, 0,-1,0, 1,0,0, 0,0,0),vec3(0,0,0)},{mat3(0,0,0, 0,0,0, 0,0,1, 0,1,0),vec3(0,0,0)}},
  28.     Part={{vec3(1,0,0),vec3(0.5,0,0)},{vec3(0,1,0),vec3(0,0.5,0)},{vec3(0,0,1),vec3(0,0,0.5)},{vec3(-1,0,0),vec3(-0.5,0,0)},{vec3(0,-1,0),vec3(0,-0.5,0)},{vec3(0,0,-1),vec3(0,0,-0.5)}}
  29. }
  30.  
  31. --Terrain geometry data
  32. local TerrainCellSize=vec3(4,4,4)--Support arbitrary stuff BECAUSE I CAN
  33. local TerrainCellOrientations={
  34.     [0]=mat3(0,0,0, 1,0,0, 0,1,0, 0,0,1),
  35.     mat3(0,0,0, 0,0,1, 0,1,0, -1,0,0),
  36.     mat3(0,0,0, -1,0,0, 0,1,0, 0,0,-1),
  37.     mat3(0,0,0, 0,0,-1, 0,1,0, 1,0,0)
  38. }
  39. local TerrainCellBlockUnitaryConvexPlaneMeshes={
  40.     [0]={{vec3(1,0,0),vec3(0.5,0,0)},{vec3(0,1,0),vec3(0,0.5,0)},{vec3(0,0,1),vec3(0,0,0.5)},{vec3(-1,0,0),vec3(-0.5,0,0)},{vec3(0,-1,0),vec3(0,-0.5,0)},{vec3(0,0,-1),vec3(0,0,-0.5)}},
  41.     {{vec3(1,0,0),vec3(0.5,0,0)},{vec3(-1,0,0),vec3(-0.5,0,0)},{vec3(0,-1,0),vec3(0,-0.5,0)},{vec3(0,0,-1),vec3(0,0,-0.5)},{mat3(0,0,0, 0,0,0, 0,0,1, 0,1,0),vec3(0,0,0)}},
  42.     {{vec3(1,0,0),vec3(0.5,0,0)},{vec3(0,-1,0),vec3(0,-0.5,0)},{vec3(0,0,-1),vec3(0,0,-0.5)},{mat3(0,0,0, 0,-1,-1, 1,0,1, 1,1,0),vec3(0.5,-0.5,-0.5)/3}},
  43.     {{vec3(1,0,0),vec3(0.5,0,0)},{vec3(0,1,0),vec3(0,0.5,0)},{vec3(0,0,1),vec3(0,0,0.5)},{vec3(-1,0,0),vec3(-0.5,0,0)},{vec3(0,-1,0),vec3(0,-0.5,0)},{vec3(0,0,-1),vec3(0,0,-0.5)},{mat3(0,0,0, 0,-1,-1, 1,0,1, 1,1,0),vec3(-0.5,0.5,0.5)/3}},
  44.     {{vec3(1,0,0),vec3(0.5,0,0)},{vec3(0,1,0),vec3(0,0.5,0)},{vec3(0,-1,0),vec3(0,-0.5,0)},{vec3(0,0,-1),vec3(0,0,-0.5)},{mat3(0,0,0, 0,0,-1, 0,0,0, 1,0,0),vec3(0,0,0)}}
  45. }
  46.  
  47. --Returns:
  48. --Index of closest plane to p
  49. local function ClosestNormalVector(p,planes)
  50.     local best_d=-math.huge
  51.     local best_i
  52.     for i=1,#planes do
  53.         local plane=planes[i]
  54.         local d=dot(plane[1],p-plane[2])
  55.         if d>best_d then
  56.             best_i,best_d=i,d
  57.         end
  58.     end
  59.     return best_i
  60. end
  61.  
  62. --Returns:
  63. --ConvexPlaneMesh in local coordinates
  64. --CFrame that can be used to convert to world coordinates
  65. --Scale that was used (not sure how it could be useful to return it)
  66. local function ConvexPlaneMesh(part,point)
  67.     local UCPM
  68.     local partCFrame,partSize=part.CFrame,part.Size
  69.     if part.ClassName=="Terrain" then
  70.         local CellGridLocation=WorldToCellPreferSolid(part,vec3(point.x,point.y-1e-5,point.z))--Ugly floating point fix.  Alternatively, one could check the distance to the surrounding cells' CPM, and use the closest one, but I don't feel like it.
  71.         local CellMaterial,CellBlock,CellOrientation=GetCell(part,CellGridLocation.x,CellGridLocation.y,CellGridLocation.z)
  72.         UCPM=TerrainCellBlockUnitaryConvexPlaneMeshes[CellBlock.Value]
  73.         partCFrame=TerrainCellOrientations[CellOrientation.Value]+CellCenterToWorld(part,CellGridLocation.x,CellGridLocation.y,CellGridLocation.z)
  74.         partSize=TerrainCellSize
  75.     else
  76.         UCPM=UnitaryConvexPlaneMeshes[part.ClassName] or UnitaryConvexPlaneMeshes.Part--Trusses, SpawnLocations, etc.
  77.     end
  78.     local CPM={}
  79.     for i=1,#UCPM do
  80.         local plane=UCPM[i]
  81.         CPM[i]={(plane[1]*partSize).unit,plane[2]*partSize}
  82.     end
  83.     return CPM,partCFrame,partSize
  84. end
  85.  
  86.  
  87. local function NormalVector(part,point)
  88.     if part.ClassName=="Part" and (part.Shape==Enum.PartType.Ball or part.Shape==Enum.PartType.Cylinder) then
  89.         return vtws(part.CFrame,ptos(part.CFrame,point).unit)--A bit simpler than the other ones.  Just a bit.
  90.     --[[
  91.     elseif part.ClassName=="Part" and part.Shape==Enum.PartType.Cylinder then
  92.         local Point=ptos(part.CFrame,point)
  93.         if Point.x*Point.x>Point.y*Point.y+Point.z*Point.z then
  94.             return vtws(part.CFrame,Vector3.new(Point.x<0 and -1 or 1,0,0))
  95.         else
  96.             return vtws(part.CFrame,(Point*Vector3.new(0,1,1)).unit)
  97.         end
  98.     --]]
  99.     else
  100.         local CPM,partCFrame=ConvexPlaneMesh(part,point)
  101.         local PlaneIndex=ClosestNormalVector(ptos(partCFrame,point),CPM)
  102.         if PlaneIndex then
  103.             return vtws(partCFrame,CPM[PlaneIndex][1])
  104.         else
  105.             return IdentityVector--Dead code unless the tables are tampered with
  106.         end
  107.     end
  108. end
  109.  
  110. --Returns:
  111. --Closest point on planes to p
  112. --Distance to planes from p (p is inside if this is negative)
  113. --Index of closest plane to p
  114. local function ClosestPointOnCPM(p,planes)
  115.     local best_d=-math.huge
  116.     local best_i
  117.     local AboveCount=0
  118.     local AbovePlanes={}
  119.     local PlaneIndices={}
  120.     local DistanceAbove={}
  121.     local ConstrainedPoints={}
  122.     for i=1,#planes do
  123.         local plane=planes[i]
  124.         local n=plane[1]
  125.         local d=dot(n,p-plane[2])
  126.         if d>best_d then
  127.             best_i,best_d=i,d
  128.         end
  129.         if d>0 then
  130.             AboveCount=AboveCount+1
  131.             AbovePlanes[AboveCount]=plane
  132.             PlaneIndices[AboveCount]=i
  133.             DistanceAbove[AboveCount]=d
  134.             ConstrainedPoints[AboveCount]=p-n*d
  135.         end
  136.     end
  137.     if #AbovePlanes>0 then
  138.         local SortedData={}--Plane index and DistanceAbove sorted by distance above from greatest to least
  139.         for i1=1,AboveCount do
  140.             local ConstrainedPoint=ConstrainedPoints[i1]
  141.             local IsOnPlane=true
  142.             for i2=1,AboveCount do
  143.                 if i1~=i2 then
  144.                     local plane=AbovePlanes[i2]
  145.                     if dot(plane[1],ConstrainedPoint-plane[2])>0 then
  146.                         IsOnPlane=false
  147.                         break
  148.                     end
  149.                 end
  150.             end
  151.             local d=DistanceAbove[i1]
  152.             if IsOnPlane then
  153.                 return ConstrainedPoint,d,PlaneIndices[i1]
  154.             else
  155.                 local Unsorted=true
  156.                 for i=#SortedData,1,-1 do
  157.                     local Data=SortedData[i]
  158.                     if d>Data[2] then
  159.                         if i<3 then --Only keep the top 3
  160.                             SortedData[i+1]=Data
  161.                         end
  162.                     else
  163.                         Unsorted=false
  164.                         SortedData[i+1]={PlaneIndices[i1],d}
  165.                         break
  166.                     end
  167.                 end
  168.                 if Unsorted then
  169.                     SortedData[1]={PlaneIndices[i1],d}
  170.                 end
  171.             end
  172.         end
  173.         if #SortedData==2 then
  174.             local Data1,Data2=SortedData[1],SortedData[2]
  175.             local plane1,plane2=planes[Data1[1]],planes[Data2[1]]
  176.             local n1,n2=plane1[1],plane2[1]
  177.             local d1,d2=Data1[2],Data2[2]
  178.             --Closest point to p on the intersection of the 2 planes
  179.             local w=dot(n1,n2)
  180.             local n3=cross(n1,n2)--Appears to not need to be unit length
  181.             --local ClosestPoint=(dot(n1,plane1[2])*cross(n2,n3)+dot(n2,plane2[2])*cross(n3,n1)+dot(n3,p)*n3)/(n1.x*n2.y*n3.z-n1.x*n3.y*n2.z-n2.x*n1.y*n3.z+n2.x*n3.y*n1.z+n3.x*n1.y*n2.z-n3.x*n2.y*n1.z)
  182.             --return ClosestPoint,(p-ClosestPoint).magnitude,Data1[1]
  183.             --Using geometry and triple product simplifications...
  184.             return (dot(n1,plane1[2])*(n1-n2*w)+dot(n2,plane2[2])*(n2-n1*w)+dot(n3,p)*n3)/dot(n3,n3),sqrt((d1*d1+d2*d2-2*w*d1*d2)/(1-w*w)),Data1[1]
  185.         elseif #SortedData==3 then
  186.             local Data1,Data2,Data3=SortedData[1],SortedData[2],SortedData[3]
  187.             local plane1,plane2,plane3=planes[Data1[1]],planes[Data2[1]],planes[Data3[1]]
  188.             local n1,n2,n3=plane1[1],plane2[1],plane3[1]
  189.             --The intersection of the 3 planes is the closest point
  190.             local ClosestPoint=(dot(n1,plane1[2])*cross(n2,n3)+dot(n2,plane2[2])*cross(n3,n1)+dot(n3,plane3[2])*cross(n1,n2))/(n1.x*n2.y*n3.z-n1.x*n3.y*n2.z-n2.x*n1.y*n3.z+n2.x*n3.y*n1.z+n3.x*n1.y*n2.z-n3.x*n2.y*n1.z) --determinant pls. I didn't want to choose between dot(n1,cross(n2,n3)) and the other cyclic equivalents...
  191.             return ClosestPoint,(p-ClosestPoint).magnitude,Data1[1]
  192.         else
  193.             print'This should never run'
  194.             return p-planes[SortedData[1][1]]*SortedData[1][2],SortedData[1][2],SortedData[1][1]
  195.         end
  196.     else
  197.         return p-planes[best_i][1]*best_d,best_d,best_i
  198.     end
  199. end
  200.  
  201. --Returns:
  202. --Closest point on part to point
  203. --Distance to that point
  204. --Normal at that point (should give the same result as NormalVector(part,point))
  205. local function ClosestPointOnPart(part,point) --Will not work correctly with terrain in most cases due to its complexity, feel free to make it slower.
  206.     if part.ClassName=="Part" and (part.Shape==Enum.PartType.Ball or part.Shape==Enum.PartType.Cylinder) then
  207.         --I was going to find the closest point on an ellipsoid just for the hell of it, but it turns out you need to find the roots of a degree 6 polynomial... not worth it for the impossible case of being an ellipsoid.
  208.         local Point=ptos(part.CFrame,point)
  209.         local Normal=Point.unit --Why not
  210.         local RelClosestPoint=Normal*part.Size
  211.         local ClosestPoint=part.CFrame*RelClosestPoint --lolhack umad
  212.         return ClosestPoint,(Point.magnitude<RelClosestPoint.magnitude and -1 or 1)*(point-ClosestPoint).magnitude,vtws(part.CFrame,Normal)
  213.     else
  214.         local CPM,partCFrame=ConvexPlaneMesh(part,point)
  215.         local ClosestPoint,DistanceToPoint,PlaneIndex=ClosestPointOnCPM(ptos(partCFrame,point),CPM)
  216.         return partCFrame*ClosestPoint,DistanceToPoint,vtws(partCFrame,CPM[PlaneIndex][1])
  217.     end
  218. end
Advertisement
Add Comment
Please, Sign In to add comment