Advertisement
Vaeb

Custom Touched Event

Oct 24th, 2016
2,091
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.04 KB | None | 0 0
  1. --[[
  2.  
  3. Documentation:
  4.  
  5. Methods:
  6.     touchMod:connect([BasePart] mainPart, [Function] func, [Boolean] runWhilstTouching (defaults to false))
  7.         > calls func whenever a BasePart touches mainPart
  8.         > if runWhilstTouching is true then it will repeatedly call func every RunService.Stepped (whilst it's touching)
  9.         > func can have two parameters (obj1, obj2, intersections) where obj1 is the part it touched, obj2 is mainPart and intersections is a table of intersection points
  10.         > func won't be called on parts already touching mainPart when first connected
  11.         > this supports the parent of mainPart changing
  12.    
  13.     touchMod:connect([Model] mainModel, [Function] func, [Boolean] runWhilstTouching (defaults to false))
  14.         > everything is same as above method except for the info below
  15.         > first parameter is a model so func is called whenever an object touches any descendant in mainModel
  16.         > func is ran per model_touching_part, not per descendant_touching_part (so even if a part is touching multiple descendants of mainModel, func will still only be called once for that touching part)
  17.         > the obj2 parameter in func will be the descendant in mainModel which first touched the touching part (obj1 parameter)
  18.         > func won't be called on parts in mainModel touching each other
  19.         > this supports descendants being added and removed
  20.  
  21.     touchMod:getTouching([BasePart] mainPart)
  22.         > returns dictionary of parts touching mainPart and intersection points
  23.         > key 'touching' represents the touching part
  24.         > key 'part' represents mainPart
  25.         > key 'intersections' represents the table of intersection points
  26.  
  27.     touchMod:getTouching([Model] mainModel)
  28.         > everything is same as above method except for the info below
  29.         > first parameter is a model so it returns data for all parts touching descendants of mainModel
  30.         > key 'part' represents the descendant in mainModel which first touched the touching part ('touching')
  31.         > it will not return data for any descendants of mainModel touching each other
  32.  
  33.  
  34. Example usage can be found at the bottom of the script
  35.  
  36. Credit goes to EgoMoose for the rotatable region3 module
  37.  
  38. ]]
  39.  
  40. local region = {} do
  41.     local insert = table.insert
  42.     local tsort = table.sort
  43.     local max = math.max
  44.     local min = math.min
  45.     local abs = math.abs
  46.     local cframe = CFrame.new
  47.     local vector3 = Vector3.new
  48.     local fromNormal = Vector3.FromNormalId
  49.     local nC = cframe()
  50.     local nV = vector3()
  51.     local cross = nV.Cross
  52.     local dot = nV.Dot
  53.     local enumItems = Enum.NormalId:GetEnumItems()
  54.     local vectorToWorldSpace = nC.vectorToWorldSpace
  55.     local origRegion3 = Region3.new
  56.     local findPartsInRegion3 = workspace.FindPartsInRegion3
  57.     local findPartsInRegion3WithIgnoreList = workspace.FindPartsInRegion3WithIgnoreList
  58.  
  59.     local function shallowcopy(t)
  60.         local nt = {}
  61.         for k, v in next, t do
  62.             nt[k] = v
  63.         end
  64.         return nt
  65.     end
  66.  
  67.     local function planeIntersect(point, vector, origin, normal)
  68.         local rpoint = point - origin
  69.         local t = -dot(rpoint, normal) / dot(vector, normal)
  70.         return point + t * vector, t
  71.     end
  72.  
  73.     local function getCorners(cf, size)
  74.         local size, corners = size / 2, {}
  75.         for x = -1, 1, 2 do
  76.             for y = -1, 1, 2 do
  77.                 for z = -1, 1, 2 do
  78.                     insert(corners, (cf * cframe(size * vector3(x, y, z))).p)
  79.                 end
  80.             end
  81.         end
  82.         return corners
  83.     end
  84.  
  85.     local function getAxis(c1, c2)
  86.         local axis = {}
  87.         axis[1] = (c1[2] - c1[1]).unit;
  88.         axis[2] = (c1[3] - c1[1]).unit;
  89.         axis[3] = (c1[5] - c1[1]).unit;
  90.         axis[4] = (c2[2] - c2[1]).unit;
  91.         axis[5] = (c2[3] - c2[1]).unit;
  92.         axis[6] = (c2[5] - c2[1]).unit;
  93.         axis[7] = cross(axis[1], axis[4]).unit;
  94.         axis[8] = cross(axis[1], axis[5]).unit;
  95.         axis[9] = cross(axis[1], axis[6]).unit;
  96.         axis[10] = cross(axis[2], axis[4]).unit;
  97.         axis[11] = cross(axis[2], axis[5]).unit;
  98.         axis[12] = cross(axis[2], axis[6]).unit;
  99.         axis[13] = cross(axis[3], axis[4]).unit;
  100.         axis[14] = cross(axis[3], axis[5]).unit;
  101.         axis[15] = cross(axis[3], axis[6]).unit;
  102.         return axis
  103.     end
  104.  
  105.     local function testAxis(corners1, corners2, axis, surface)
  106.         if axis.Magnitude == 0 or tostring(axis) == "NAN, NAN, NAN" then
  107.             return true
  108.         end
  109.         local adists, bdists = {}, {}
  110.         for i = 1, 8 do
  111.             insert(adists, dot(corners1[i], axis))
  112.             insert(bdists, dot(corners2[i], axis))
  113.         end
  114.         local amax, amin = max(unpack(adists)), min(unpack(adists))
  115.         local bmax, bmin = max(unpack(bdists)), min(unpack(bdists))
  116.         local longspan = max(amax, bmax) - min(amin, bmin)
  117.         local sumspan = amax - amin + bmax - bmin
  118.         local pass, mtv
  119.         if surface then
  120.             pass = longspan <= sumspan
  121.         else
  122.             pass = longspan < sumspan
  123.         end
  124.         if pass then
  125.             local overlap = amax > bmax and -(bmax - amin) or (amax - bmin)
  126.             mtv = axis * overlap
  127.         end
  128.         return pass, mtv
  129.     end
  130.  
  131.     function region.new(cf, size)
  132.         local self = setmetatable({}, {__index = region})
  133.         self.surfaceCountsAsCollision = true
  134.         self.cframe = cf
  135.         self.size = size
  136.         self.planes = {}
  137.         self.corners = getCorners(self.cframe, self.size)
  138.         for _, enum in next, enumItems do
  139.             local lnormal = fromNormal(enum)
  140.             local wnormal = vectorToWorldSpace(self.cframe, lnormal)
  141.             local distance = (lnormal * self.size/2).magnitude
  142.             local point = self.cframe.p + wnormal * distance
  143.             insert(self.planes, {
  144.                 normal = wnormal;
  145.                 point = point;
  146.             })
  147.         end
  148.         return self
  149.     end
  150.  
  151.     function region.fromPart(part)
  152.         return region.new(
  153.             part.CFrame,
  154.             part.Size
  155.         )
  156.     end
  157.  
  158.     function region:castPart(part)
  159.         local corners1 = self.corners
  160.         local corners2 = getCorners(part.CFrame, part.Size)
  161.         local axis, mtvs = getAxis(corners1, corners2), {}
  162.         for i = 1, #axis do
  163.             local intersect, mtv = testAxis(corners1, corners2, axis[i], self.surfaceCountsAsCollision)
  164.             if not intersect then return false, nV end
  165.             if mtv then insert(mtvs, mtv) end
  166.         end
  167.         tsort(mtvs, function(a, b) return a.magnitude < b.magnitude end)
  168.         return true, mtvs[1]
  169.     end
  170.  
  171.     function region:intersectionPoints(part)
  172.         local intersections = {};  
  173.        
  174.         local corners = getCorners(part.CFrame, part.Size);
  175.         local attach = {
  176.             [corners[1]] = {corners[3], corners[2], corners[5]};
  177.             [corners[4]] = {corners[3], corners[2], corners[8]};
  178.             [corners[6]] = {corners[5], corners[2], corners[8]};
  179.             [corners[7]] = {corners[3], corners[8], corners[5]};
  180.         };
  181.         for corner, set in next, attach do
  182.             for _, con in next, set do
  183.                 local v = con - corner;
  184.                 for i, plane in next, self.planes do
  185.                     local p, t = planeIntersect(corner, v, plane.point, plane.normal)
  186.                     if t >= 0 and t <= 1 then
  187.                         local pass = true;
  188.                         for i2, plane2 in next, self.planes do
  189.                             if i2 ~= i then
  190.                                 local relative = p - plane2.point;
  191.                                 if dot(relative, plane2.normal) >= 0 then
  192.                                     pass = false;
  193.                                 end;
  194.                             end;
  195.                         end;
  196.                         if pass then insert(intersections, p); end;
  197.                     end;
  198.                 end;
  199.             end;
  200.         end;
  201.        
  202.         local planes = {};
  203.         for _, enum in next, enumItems do
  204.             local lnormal = fromNormal(enum);
  205.             local wnormal = vectorToWorldSpace(part.CFrame, lnormal);
  206.             local distance = (lnormal * part.Size/2).magnitude;
  207.             local point = part.CFrame.p + wnormal * distance;
  208.             insert(planes, {
  209.                 normal = wnormal;
  210.                 point = point;
  211.             });
  212.         end;
  213.         local corners = self.corners;
  214.         local attach = {
  215.             [corners[1]] = {corners[3], corners[2], corners[5]};
  216.             [corners[4]] = {corners[3], corners[2], corners[8]};
  217.             [corners[6]] = {corners[5], corners[2], corners[8]};
  218.             [corners[7]] = {corners[3], corners[8], corners[5]};
  219.         };
  220.         for corner, set in next, attach do
  221.             for _, con in next, set do
  222.                 local v = con - corner;
  223.                 for i, plane in next, planes do
  224.                     local p, t = planeIntersect(corner, v, plane.point, plane.normal)
  225.                     if t >= 0 and t <= 1 then
  226.                         local pass = true;
  227.                         for i2, plane2 in next, planes do
  228.                             if i2 ~= i then
  229.                                 local relative = p - plane2.point;
  230.                                 if dot(relative, plane2.normal) >= 0 then
  231.                                     pass = false;
  232.                                 end;
  233.                             end;
  234.                         end;
  235.                         if pass then insert(intersections, p); end;
  236.                     end;
  237.                 end;
  238.             end;
  239.         end;
  240.        
  241.         return intersections;
  242.     end;
  243.  
  244.     function region:cast(ignore, maxParts)
  245.         local maxParts = maxParts or 20
  246.  
  247.         local rmin, rmax = {}, {}
  248.         local copy = shallowcopy(self.corners)
  249.         for _, enum in next, {Enum.NormalId.Right, Enum.NormalId.Top, Enum.NormalId.Back} do
  250.             local lnormal = fromNormal(enum)
  251.             tsort(copy, function(a, b) return dot(a, lnormal) > dot(b, lnormal) end)
  252.             insert(rmin, copy[#copy])
  253.             insert(rmax, copy[1])
  254.         end
  255.         rmin, rmax = vector3(rmin[1].x, rmin[2].y, rmin[3].z), vector3(rmax[1].x, rmax[2].y, rmax[3].z)
  256.        
  257.         local realRegion3 = origRegion3(rmin, rmax)
  258.         local parts = type(ignore) == "table" and findPartsInRegion3WithIgnoreList(workspace, realRegion3, ignore, maxParts) or findPartsInRegion3(workspace, realRegion3, ignore, maxParts)
  259.        
  260.         local inRegion = {}
  261.         for _, part in next, parts do
  262.             if self:castPart(part) then
  263.                 insert(inRegion, part)
  264.             end
  265.         end
  266.        
  267.         return inRegion
  268.     end
  269. end
  270.  
  271. local touchMod = {} do
  272.     local touchEventParts = {}
  273.  
  274.     game:GetService("RunService").Stepped:connect(function()
  275.         for i = 1, #touchEventParts do
  276.             local data = touchEventParts[i]
  277.             local parts = data[1]
  278.             local func = data[2]
  279.             local runWhilstTouching = data[3]
  280.             local ignoreParts = data[4]
  281.             local initialize = data[5]
  282.             local ignore = {}
  283.             if initialize then
  284.                 data[5] = false
  285.             end
  286.             local stillTouching = {}
  287.             for j = 1, #parts do
  288.                 ignore[#ignore+1] = parts[j]
  289.             end
  290.             for j = 1, #parts do
  291.                 local part = parts[j]
  292.                 local nowRegion = region.fromPart(part)
  293.                 local nowParts = nowRegion:cast(ignore, math.huge)
  294.                 if runWhilstTouching then
  295.                     for o = 1, #nowParts do
  296.                         local touchPart = nowParts[o]
  297.                         if not initialize then func(touchPart, part, nowRegion:intersectionPoints(touchPart)) end
  298.                         ignore[#ignore+1] = touchPart
  299.                     end
  300.                 else
  301.                     for o = 1, #nowParts do
  302.                         local touchPart = nowParts[o]
  303.                         stillTouching[touchPart] = true
  304.                         if not ignoreParts[touchPart] then
  305.                             ignoreParts[touchPart] = true
  306.                             if not initialize then func(touchPart, part, nowRegion:intersectionPoints(touchPart)) end
  307.                         end
  308.                         ignore[#ignore+1] = touchPart
  309.                     end
  310.                 end
  311.             end
  312.             if not runWhilstTouching then
  313.                 for oldPart in next, ignoreParts do
  314.                     if not stillTouching[oldPart] then
  315.                         ignoreParts[oldPart] = nil
  316.                     end
  317.                 end
  318.             end
  319.         end
  320.     end)
  321.  
  322.     function touchMod:getTouching(obj)
  323.         local connected = {}
  324.         local parts = {}
  325.         local ignore = {}
  326.         if obj:IsA("BasePart") then
  327.             parts[1] = obj
  328.         else
  329.             do
  330.                 local children = obj:GetChildren()
  331.                 local n = 0
  332.                 while n < #children do
  333.                     n = n+1
  334.                     local obj = children[n]
  335.                     local descendants = obj:GetChildren()
  336.                     for i = 1, #descendants do
  337.                         children[#children+1] = descendants[i]
  338.                     end
  339.                     if obj:IsA("BasePart") then
  340.                         parts[#parts+1] = obj
  341.                     end
  342.                 end
  343.             end
  344.         end
  345.         for i = 1, #parts do
  346.             ignore[#ignore+1] = parts[i]
  347.         end
  348.         for i = 1, #parts do
  349.             local part = parts[i]
  350.             local nowRegion = region.fromPart(parts[i])
  351.             local nowParts = nowRegion:cast(ignore, math.huge)
  352.             for j = 1, #nowParts do
  353.                 local touchPart = nowParts[j]
  354.                 connected[#connected+1] = {touching = touchPart, part = part, intersections = nowRegion:intersectionPoints(touchPart)}
  355.                 ignore[#ignore+1] = touchPart
  356.             end
  357.         end
  358.         return connected
  359.     end
  360.  
  361.     function touchMod:connect(obj, func, runWhilstTouching) --Add something for model touched so it can dteect all parts touching
  362.         local stored = {}
  363.         if obj:IsA("BasePart") then
  364.             stored[1] = obj
  365.         else
  366.             do
  367.                 local children = obj:GetChildren()
  368.                 local n = 0
  369.                 while n < #children do
  370.                     n = n+1
  371.                     local obj = children[n]
  372.                     local descendants = obj:GetChildren()
  373.                     for i = 1, #descendants do
  374.                         children[#children+1] = descendants[i]
  375.                     end
  376.                     if obj:IsA("BasePart") then
  377.                         stored[#stored+1] = obj
  378.                     end
  379.                 end
  380.             end
  381.             obj.DescendantAdded:connect(function(new)
  382.                 if not new:IsA("BasePart") then return end
  383.                 stored[#stored+1] = new
  384.             end)
  385.             obj.DescendantRemoving:connect(function(new)
  386.                 if not new:IsA("BasePart") then return end
  387.                 local numItems = #stored
  388.                 local n = 1
  389.                 while n <= numItems do
  390.                     if stored[n] == new then
  391.                         stored[n] = stored[numItems]
  392.                         stored[numItems] = nil
  393.                         break --Only one should exist
  394.                     else
  395.                         n = n + 1
  396.                     end
  397.                 end
  398.             end)
  399.         end
  400.         local nowData = {stored, func, runWhilstTouching, {}, true}
  401.         local exists = false
  402.         if obj:IsDescendantOf(workspace) then
  403.             exists = true
  404.             touchEventParts[#touchEventParts+1] = nowData
  405.         end
  406.         obj.Changed:connect(function(p)
  407.             if p ~= "Parent" then return end
  408.             local shouldExist = obj:IsDescendantOf(workspace)
  409.             if shouldExist and not exists then
  410.                 exists = true
  411.                 touchEventParts[#touchEventParts+1] = nowData
  412.             elseif not shouldExist and exists then
  413.                 exists = false
  414.                 local numItems = #touchEventParts
  415.                 local n = 1
  416.                 while n <= numItems do
  417.                     if touchEventParts[n] == nowData then
  418.                         touchEventParts[n] = touchEventParts[numItems]
  419.                         touchEventParts[numItems] = nil
  420.                         numItems = numItems - 1
  421.                     else
  422.                         n = n + 1
  423.                     end
  424.                 end
  425.                 nowData[4] = {}
  426.                 nowData[5] = true
  427.             end
  428.         end)
  429.     end
  430. end
  431.  
  432. --Example usage--
  433.  
  434. local RunService = game:GetService("RunService")
  435.  
  436. --Example 1 (Touched event connected to Part):
  437.  
  438. do
  439.     local rootPart = Instance.new("Part")
  440.     rootPart.Name = "rootPart"
  441.     rootPart.Size = Vector3.new(4, 4, 4)
  442.     rootPart.CFrame = CFrame.new(0, 4, -16)
  443.     rootPart.Anchored = true
  444.     rootPart.CanCollide = false
  445.     rootPart.TopSurface = "Smooth"
  446.     rootPart.BottomSurface = "Smooth"
  447.     rootPart.Parent = workspace
  448.  
  449.     local movePart = rootPart:Clone()
  450.     movePart.Name = "movePart"
  451.     movePart.Size = Vector3.new(3, 3, 3)
  452.     movePart.CFrame = CFrame.new(-12, 4, -16)
  453.     movePart.BrickColor = BrickColor.new("Bright red")
  454.     movePart.Anchored = true
  455.     movePart.CanCollide = false
  456.     movePart.Parent = workspace
  457.  
  458.     touchMod:connect(rootPart, function(obj, this, intersections)
  459.         local interStr = ""
  460.         for i = 1, #intersections do
  461.             if #interStr > 0 then interStr = interStr .. " & " end
  462.             interStr = interStr .. tostring(intersections[i])
  463.         end
  464.         print(tick(), obj.Name, "touched", this.Name, "at", interStr)
  465.     end, false)
  466.  
  467.     for i = -12, 8, .2 do
  468.         movePart.CFrame = movePart.CFrame * CFrame.new(.2, 0, 0)
  469.         RunService.Heartbeat:wait()
  470.     end
  471.  
  472.     for i = 8, -12, -.2 do
  473.         movePart.CFrame = movePart.CFrame * CFrame.new(-.2, 0, 0)
  474.         RunService.Heartbeat:wait()
  475.     end
  476.  
  477.     rootPart:Destroy()
  478.     movePart:Destroy()
  479. end
  480.  
  481. --Example 2 (Touched event connected to Model):
  482.  
  483. do
  484.     local model = Instance.new("Model", workspace)
  485.  
  486.     local rootPart = Instance.new("Part")
  487.     rootPart.Name = "rootPart"
  488.     rootPart.Size = Vector3.new(4, 4, 4)
  489.     rootPart.CFrame = CFrame.new(0, 4, -16)
  490.     rootPart.Anchored = true
  491.     rootPart.CanCollide = false
  492.     rootPart.TopSurface = "Smooth"
  493.     rootPart.BottomSurface = "Smooth"
  494.     rootPart.Parent = model
  495.  
  496.     local movePart = rootPart:Clone()
  497.     movePart.Name = "movePart"
  498.     movePart.Size = Vector3.new(3, 3, 3)
  499.     movePart.CFrame = CFrame.new(-12, 4, -16)
  500.     movePart.BrickColor = BrickColor.new("Bright red")
  501.     movePart.Anchored = true
  502.     movePart.CanCollide = false
  503.     movePart.Parent = workspace
  504.  
  505.     local rootPart2 = rootPart:Clone()
  506.     rootPart2.Name = "rootPart2"
  507.     rootPart2.CFrame = rootPart.CFrame * CFrame.new(1, 0, 0)
  508.     rootPart2.BrickColor = BrickColor.new("Bright blue")
  509.     rootPart2.Parent = model
  510.  
  511.     touchMod:connect(model, function(obj, modelPartTouched, intersections)
  512.         local interStr = ""
  513.         for i = 1, #intersections do
  514.             if #interStr > 0 then interStr = interStr .. " & " end
  515.             interStr = interStr .. tostring(intersections[i])
  516.         end
  517.         print(tick(), obj.Name, "touched", modelPartTouched, "in", model.Name, "at", interStr)
  518.     end, false)
  519.  
  520.     for i = -12, 8, .2 do
  521.         movePart.CFrame = movePart.CFrame * CFrame.new(.2, 0, 0)
  522.         RunService.Heartbeat:wait()
  523.     end
  524.  
  525.     for i = 8, -12, -.2 do
  526.         movePart.CFrame = movePart.CFrame * CFrame.new(-.2, 0, 0)
  527.         RunService.Heartbeat:wait()
  528.     end
  529.  
  530.     model:Destroy()
  531.     movePart:Destroy()
  532. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement