Check for intersecting vector3 volumes

I’d like to know how to find if two vector3 volumes are intersecting. These would be oriented around parts in the workspace, but I cant use getpartsinpart, as this is using imaginary volumes.

I’d also like to know if it’s possible to do the same with vector3 volumes and baseparts of any shape.

1 Like

For between two cubes get all the corner points of one and check if each point overlaps with the other cube. This should work for rotated ones as well. There’s plenty of examples on the forum of getting the corners of a cube. I’ll guess and say you’re trying to use this for your voxel module. This is kinda what I did for mine with using imaginary parts and then greedy meshing and finally creating parts. It’s pretty performant.

Here’s some example code for checking if a point is in a cube:


function isPointInCube(position, brickCFrame, brickSize)
	local v3 = brickCFrame:PointToObjectSpace(position)
	return (math.abs(v3.X) <= brickSize.X / 2)
		and (math.abs(v3.Y) <= brickSize.Y / 2)
		and (math.abs(v3.Z) <= brickSize.Z / 2)
end

2 Likes

Thanks, this works. But I’m also wondering if you know of a way to greedy mesh a table of part information. I’m using these types:

type VoxelInfo = {
	AlreadyExistsInWorkspace:boolean,
	Size:Vector3,
	CFrame:CFrame,
	CanCollide:boolean,
	Parent:Instance,
	Color:Color3,
	Material:Enum.Material,
	Anchored:boolean,
	Transparency:number,
	AlreadyDivided:boolean
}
type VoxelInfoTable = {VoxelInfo}

And essentially I’m trying to loop through the infoTable to greedymesh everything within the table before actually constructing the parts. I’ve looked into greedymeshing resources and they seem to be based around the parts already being instanced within the game. This is kind of unrelated to the original question but I noticed you have experience with greedymeshing so I figured I’d ask anyways.

With “Vector3 volume”, do you mean the region inside an imaginary part?

When both parts are convex polyhedra, the separating axis theorem can be used. I wrote a simpler version that only works for box-box pairs and a more general and less efficient version that also works for wedges and corner wedges. Here’s a Stackexchange question with an answer that helped me understand the logic behind using this theorem to check whether convex polyhedra intersect.

Collisions between a sphere and another shape can be tested by calculating the point on the other shape that is closest to the sphere center and comparing the distance between the sphere center and that point to the sphere radius. We can also compare the square of the distance to the square of the radius because if a >= 0 and b >= 0 and a <= b then a^2 <= b^2.

I’m not sure whether my code is entirely correct but based on my testing so far it seems to work correctly. If my closest point calculation code for the sphere collision checks doesn’t work correctly in some cases, you can also try the closest point code in this tutorial.

My code is made for the primitive roblox shapes but the code for convex polyhedron primitives could be modified to work for arbitrary convex polyhedra. If you have concave polyhedra (none of Roblox’s primitive shapes (Enum.PartType) are concave, though), then I think you could check whether any edge of either of them intersects any face of the other (you’d have to do the same check for both permutations of the shape pair i.e. check whether any edge of shape 1 intersects any face of shape 2 or any edge of shape 2 intersects any face of shape 1). If no intersects are found this way, you’d also need to check whether either of them is completely inside the other by just choosing one vertex of both and checking whether that vertex is in the other polyhedron.

Unfortunately, I don’t know how to check for collisions between a pair of cylinders or a cylinder and a polyhedron.

Here’s my code.

BoxBoxIntersectionQuery ModuleScript
--!strict
local BoxBoxIntersectionQuery = {}

local function areProjectionsOverlapping(cf1: CFrame, size1: Vector3, cf2: CFrame, size2: Vector3, projectionVector: Vector3): boolean
	local posCoord1: number = cf1.Position:Dot(projectionVector)
	local halfLength1: number = .5 * Vector3.new(cf1.XVector:Dot(projectionVector), cf1.YVector:Dot(projectionVector), cf1.ZVector:Dot(projectionVector)):Abs():Dot(size1)
	local minCoord1: number, maxCoord1: number = posCoord1 - halfLength1, posCoord1 + halfLength1

	local posCoord2: number = cf2.Position:Dot(projectionVector)
	local halfLength2: number = .5 * Vector3.new(cf2.XVector:Dot(projectionVector), cf2.YVector:Dot(projectionVector), cf2.ZVector:Dot(projectionVector)):Abs():Dot(size2)
	local minCoord2: number, maxCoord2: number = posCoord2 - halfLength2, posCoord2 + halfLength2

	return maxCoord1 > minCoord2 and minCoord1 < maxCoord2
end

-- In the case of boxes, for every edge, there's a normal that is parallel to it.
-- So although the second step of using SAT (separating axis theorem) for checking for collision between
-- convex polyhedra is doing projection overlap tests with projection axes that
-- are perpendicular to edge pairs, when the polyhedra are boxes, we can use the normal vectors as edge vectors.
function BoxBoxIntersectionQuery.doBoxesIntersect(cf1: CFrame, size1: Vector3, cf2: CFrame, size2: Vector3): boolean
	local axes: {Enum.Axis} = Enum.Axis:GetEnumItems()
	local rot1: CFrame, rot2: CFrame = cf1.Rotation, cf2.Rotation
	for _, axis: Enum.Axis in axes do
		local axisVector: Vector3 = Vector3.FromAxis(axis)

		local box1Normal: Vector3 = rot1 * axisVector
		if not areProjectionsOverlapping(cf1, size1, cf2, size2, box1Normal) then
			return false
		end

		local box2Normal: Vector3 = rot2 * axisVector
		if not areProjectionsOverlapping(cf1, size1, cf2, size2, box2Normal) then
			return false
		end

		for _, otherAxis: Enum.Axis in axes do
			local otherAxisVector: Vector3 = Vector3.FromAxis(otherAxis)

			local box2OtherNormal: Vector3 = rot2 * otherAxisVector
			local crossProduct: Vector3 = box1Normal:Cross(box2OtherNormal)
			if math.abs(crossProduct:Dot(crossProduct)) < 1e-4 then
				continue
			end
			if not areProjectionsOverlapping(cf1, size1, cf2, size2, crossProduct) then
				return false
			end
		end
	end
	return true
end

return BoxBoxIntersectionQuery
ConvexPolyhedronPrimitivesIntersectionQuery ModuleScript
--!strict
local ConvexPolyhedronPrimitivesIntersectionQuery = {}

local faceNormalsForShapes: {[Enum.PartType]: {{Vector3}}} = {
	[Enum.PartType.Block] = {
		{Vector3.xAxis, Vector3.zero, Vector3.zero},
		{Vector3.zero, Vector3.yAxis, Vector3.zero},
		{Vector3.zero, Vector3.zero, Vector3.zAxis}
	},
	[Enum.PartType.Wedge] = {
		{Vector3.xAxis, Vector3.zero, Vector3.zero},
		{Vector3.zero, Vector3.yAxis, Vector3.zero},
		{Vector3.zero, Vector3.zero, Vector3.zAxis},
		{Vector3.zero, Vector3.zAxis, -Vector3.yAxis}
	},
	[Enum.PartType.CornerWedge] = {
		{Vector3.xAxis, Vector3.zero, Vector3.zero},
		{Vector3.zero, Vector3.yAxis, Vector3.zero},
		{Vector3.zero, Vector3.zero, Vector3.zAxis},
		{Vector3.zero, Vector3.zAxis, Vector3.yAxis},
		{-Vector3.yAxis, Vector3.xAxis, Vector3.zero}
	}
}

local edgeVectorsForShapes: {[Enum.PartType]: {Vector3}} = {
	[Enum.PartType.Block] = {
		Vector3.xAxis,
		Vector3.yAxis,
		Vector3.zAxis
	},
	[Enum.PartType.Wedge] = {
		Vector3.xAxis,
		Vector3.yAxis,
		Vector3.zAxis,
		Vector3.new(0, 1, 1)
	},
	[Enum.PartType.CornerWedge] = {
		Vector3.xAxis,
		Vector3.yAxis,
		Vector3.zAxis,
		Vector3.new(0, 1, -1),
		Vector3.new(1, 1, -1),
		Vector3.new(1, 1, 0)
	}
}

local verticesForShapes: {[Enum.PartType]: {Vector3}} = {
	[Enum.PartType.Block] = {
		Vector3.new(-1, -1, -1),
		Vector3.new(-1, -1, 1),
		Vector3.new(-1, 1, -1),
		Vector3.new(-1, 1, 1),
		Vector3.new(1, -1, -1),
		Vector3.new(1, -1, 1),
		Vector3.new(1, 1, -1),
		Vector3.new(1, 1, 1)
	},
	[Enum.PartType.Wedge] = {
		Vector3.new(-1, -1, -1),
		Vector3.new(-1, -1, 1),
		Vector3.new(-1, 1, 1),
		Vector3.new(1, -1, -1),
		Vector3.new(1, -1, 1),
		Vector3.new(1, 1, 1)
	},
	[Enum.PartType.CornerWedge] = {
		Vector3.new(-1, -1, -1),
		Vector3.new(-1, -1, 1),
		Vector3.new(1, -1, -1),
		Vector3.new(1, -1, 1),
		Vector3.new(1, 1, -1)
	}
}

local function doProjectionsOverlap(shape1: Enum.PartType, cf1: CFrame, size1: Vector3, shape2: Enum.PartType, cf2: CFrame, size2: Vector3, projectionVector: Vector3): boolean
	local shape1MinCoord: number, shape1MaxCoord: number = math.huge, -math.huge
	for _, shape1GeneralizedVertex: Vector3 in verticesForShapes[shape1] do
		local vertex: Vector3 = cf1 * (.5 * shape1GeneralizedVertex * size1)
		local coord: number = vertex:Dot(projectionVector)
		if coord < shape1MinCoord then
			shape1MinCoord = coord
		end
		if coord > shape1MaxCoord then
			shape1MaxCoord = coord
		end
	end
	local shape2MinCoord: number, shape2MaxCoord: number = math.huge, -math.huge
	for _, shape2GeneralizedVertex: Vector3 in verticesForShapes[shape2] do
		local vertex: Vector3 = cf2 * (.5 * shape2GeneralizedVertex * size2)
		local coord: number = vertex:Dot(projectionVector)
		if coord < shape2MinCoord then
			shape2MinCoord = coord
		end
		if coord > shape2MaxCoord then
			shape2MaxCoord = coord
		end
	end
	
	return shape1MaxCoord > shape2MinCoord and shape1MinCoord < shape2MaxCoord
end

function ConvexPolyhedronPrimitivesIntersectionQuery.doConvexPolyhedronPrimitivesIntersect(shape1: Enum.PartType, cf1: CFrame, size1: Vector3, shape2: Enum.PartType, cf2: CFrame, size2: Vector3): boolean
	local rot1: CFrame, rot2: CFrame = cf1.Rotation, cf2.Rotation
	for _, shape1GeneralizedNormal: {Vector3} in faceNormalsForShapes[shape1] do
		local normal: Vector3 = rot1 * Vector3.new(size1:Dot(shape1GeneralizedNormal[1]), size1:Dot(shape1GeneralizedNormal[2]), size1:Dot(shape1GeneralizedNormal[3]))
		if not doProjectionsOverlap(shape1, cf1, size1, shape2, cf2, size2, normal) then
			return false
		end
	end
	for _, shape2GeneralizedNormal: {Vector3} in faceNormalsForShapes[shape2] do
		local normal: Vector3 = rot2 * Vector3.new(size2:Dot(shape2GeneralizedNormal[1]), size2:Dot(shape2GeneralizedNormal[2]), size2:Dot(shape2GeneralizedNormal[3]))
		if not doProjectionsOverlap(shape1, cf1, size1, shape2, cf2, size2, normal) then
			return false
		end
	end
	for _, shape1GeneralizedEdgeVector: Vector3 in edgeVectorsForShapes[shape1] do
		local shape1EdgeVector: Vector3 = rot1 * (shape1GeneralizedEdgeVector * size1)
		for _, shape2GeneralizedEdgeVector: Vector3 in edgeVectorsForShapes[shape2] do
			local shape2EdgeVector: Vector3 = rot2 * (shape2GeneralizedEdgeVector * size2)
			local crossProduct: Vector3 = shape1EdgeVector:Cross(shape2EdgeVector)
			if math.abs(crossProduct:Dot(crossProduct)) < 1e-4 then
				continue
			end
			if not doProjectionsOverlap(shape1, cf1, size1, shape2, cf2, size2, crossProduct) then
				return false
			end
		end
	end
	return true
end

return ConvexPolyhedronPrimitivesIntersectionQuery
SphereIntersectionQueries ModuleScript
--!strict
local SphereIntersectionQueries = {}

function SphereIntersectionQueries.doSpheresIntersect(cf1: CFrame, size1: Vector3, cf2: CFrame, size2: Vector3): boolean
	local radius1: number, radius2: number = .5 * math.min(size1.X, size1.Y, size1.Z), .5 * math.min(size2.X, size2.Y, size2.Z)
	local vectorBetweenCenters: Vector3 = cf2.Position - cf1.Position
	-- Checking whether squared distance between centers is greater than squared sum of radii.
	return vectorBetweenCenters:Dot(vectorBetweenCenters) <= (radius1 + radius2) * (radius1 + radius2)
end

-- This function and the functions below calculate the point in the other shape closest to the sphere center
-- and compare the squared distance between that point and the sphere center to the square radius of the sphere.
function SphereIntersectionQueries.doSphereAndBoxIntersect(sphereCf: CFrame, sphereSize: Vector3, boxCf: CFrame, boxSize: Vector3): boolean
	local sphereRadius: number = .5 * math.min(sphereSize.X, sphereSize.Y, sphereSize.Z)
	local sphereCenterInBoxLocalSpace: Vector3 = boxCf:PointToObjectSpace(sphereCf.Position)
	local halfBoxSize: Vector3 = .5 * boxSize
	local localSpacePointInBoxClosestToSphereCenter: Vector3 = Vector3.new(
		math.clamp(sphereCenterInBoxLocalSpace.X, -halfBoxSize.X, halfBoxSize.X),
		math.clamp(sphereCenterInBoxLocalSpace.Y, -halfBoxSize.Y, halfBoxSize.Y),
		math.clamp(sphereCenterInBoxLocalSpace.Z, -halfBoxSize.Z, halfBoxSize.Z)
	)
	local vectorFromClosestPointToSphereCenter: Vector3 = sphereCenterInBoxLocalSpace - localSpacePointInBoxClosestToSphereCenter
	return vectorFromClosestPointToSphereCenter:Dot(vectorFromClosestPointToSphereCenter) <= sphereRadius * sphereRadius
end

function SphereIntersectionQueries.doSphereAndCylinderIntersect(sphereCf: CFrame, sphereSize: Vector3, cylinderCf: CFrame, cylinderSize: Vector3): boolean
	if sphereCf.Position:FuzzyEq(cylinderCf.Position) then
		-- This is done to prevent a NAN vector in the calculations.
		return true
	end
	local sphereRadius: number = .5 * math.min(sphereSize.X, sphereSize.Y, sphereSize.Z)
	local cylinderRadius: number = .5 * math.min(cylinderSize.Y, cylinderSize.Z)
	local sphereCenterInCylinderLocalSpace: Vector3 = cylinderCf:PointToObjectSpace(sphereCf.Position)
	local sphereYZCenterInCylinderLocalSpace: Vector3 = sphereCenterInCylinderLocalSpace * Vector3.new(0, 1, 1)
	local closestPointYZ: Vector3 = sphereYZCenterInCylinderLocalSpace.Unit * math.min(sphereYZCenterInCylinderLocalSpace.Magnitude, cylinderRadius)
	local localSpacePointInCylinderClosestToSphereCenter: Vector3 = Vector3.new(
		math.clamp(sphereCenterInCylinderLocalSpace.X, -.5 * cylinderSize.X, .5 * cylinderSize.X),
		closestPointYZ.Y,
		closestPointYZ.Z
	)
	local vectorFromClosestPointToSphereCenter: Vector3 = sphereCenterInCylinderLocalSpace - localSpacePointInCylinderClosestToSphereCenter
	--print(vectorFromClosestPointToSphereCenter)
	return vectorFromClosestPointToSphereCenter:Dot(vectorFromClosestPointToSphereCenter) <= sphereRadius * sphereRadius
end

function SphereIntersectionQueries.doSphereAndWedgeIntersect(sphereCf: CFrame, sphereSize: Vector3, wedgeCf: CFrame, wedgeSize: Vector3): boolean
	local sphereRadius: number = .5 * math.min(sphereSize.X, sphereSize.Y, sphereSize.Z)
	local sphereCenterInWedgeLocalSpace: Vector3 = wedgeCf:PointToObjectSpace(sphereCf.Position)
	
	local closestPoint: Vector3 = sphereCenterInWedgeLocalSpace
	local slantedFaceNormalInLocalSpace: Vector3 = Vector3.new(0, wedgeSize.Z, -wedgeSize.Y).Unit
	local signedDistFromSlantedFacePlane: number = sphereCenterInWedgeLocalSpace:Dot(slantedFaceNormalInLocalSpace)
	if signedDistFromSlantedFacePlane > 0 then
		-- In this case sphereCenterInWedgeLocalSpace is guaranteed to be outside the wedge
		-- and the closest point is guaranteed to be on the slanted face
		-- ("on the slanted face" in this case can also mean on the edge of the slanted face).
		closestPoint -= signedDistFromSlantedFacePlane * slantedFaceNormalInLocalSpace
	end
	closestPoint = Vector3.new(
		math.clamp(closestPoint.X, -.5 * wedgeSize.X, .5 * wedgeSize.X),
		math.clamp(closestPoint.Y, -.5 * wedgeSize.Y, .5 * wedgeSize.Y),
		math.clamp(closestPoint.Z, -.5 * wedgeSize.Z, .5 * wedgeSize.Z)
	)
	local vectorFromClosestPointToSphereCenter: Vector3 = sphereCenterInWedgeLocalSpace - closestPoint
	return vectorFromClosestPointToSphereCenter:Dot(vectorFromClosestPointToSphereCenter) <= sphereRadius * sphereRadius
end

function SphereIntersectionQueries.doSphereAndCornerWedgeIntersect(sphereCf: CFrame, sphereSize: Vector3, cornerWedgeCf: CFrame, cornerWedgeSize: Vector3): boolean
	local sphereRadius: number = .5 * math.min(sphereSize.X, sphereSize.Y, sphereSize.Z)
	local sphereCenterInCornerWedgeLocalSpace: Vector3 = cornerWedgeCf:PointToObjectSpace(sphereCf.Position)
	
	local closestPoint: Vector3 = sphereCenterInCornerWedgeLocalSpace
	
	local firstSlantedFaceNormal: Vector3 = Vector3.new(0, cornerWedgeSize.Z, cornerWedgeSize.Y).Unit
	local signedDistFromFirstSlantedFacePlane: number = closestPoint:Dot(firstSlantedFaceNormal)
	local isAboveFirstSlantedFacePlane: boolean = signedDistFromFirstSlantedFacePlane > 0
	
	local secondSlantedFaceNormal: Vector3 = Vector3.new(-cornerWedgeSize.Y, cornerWedgeSize.X, 0).Unit
	local signedDistFromSecondSlantedFacePlane: number = closestPoint:Dot(secondSlantedFaceNormal)
	local isAboveSecondSlantedFacePlane: boolean = signedDistFromSecondSlantedFacePlane > 0
	if isAboveFirstSlantedFacePlane and isAboveSecondSlantedFacePlane then
		-- Since the external angle between these faces is less than 270 degrees,
		-- I think (but I'm not sure at all) it may be necessary to
		-- check whether the point is above either of the faces' half-planes that are limited by the
		-- intersection line of the faces, and then if it is, project it to
		-- that half-plane, and if it isn't project it to the intersection line of the face planes.
		-- Any other face pairs of the part have an external angle of at least 270 degrees between them.
		
		-- isAboveFirstSlantedFaceHalfPlane and isAboveSecondSlantedFaceHalfPlane are mutually exclusive because the external
		-- angle between the faces is at least 180 degrees (otherwise this wouldn't be a convex polyhedron).
		local planeIntersectionLineVector: Vector3 = Vector3.new(cornerWedgeSize.X, cornerWedgeSize.Y, -cornerWedgeSize.Z)
		local firstSlantedFaceEdgeNormal: Vector3 = planeIntersectionLineVector:Cross(firstSlantedFaceNormal)
		local isAboveFirstSlantedFaceHalfPlane: boolean = closestPoint:Dot(firstSlantedFaceEdgeNormal) > 0
		if isAboveFirstSlantedFaceHalfPlane then
			closestPoint -= signedDistFromFirstSlantedFacePlane * firstSlantedFaceNormal
		else
			local secondSlantedFaceEdgeNormal: Vector3 = secondSlantedFaceNormal:Cross(planeIntersectionLineVector)
			local isAboveSecondSlantedFaceHalfPlane: boolean = closestPoint:Dot(secondSlantedFaceEdgeNormal) > 0
			if isAboveSecondSlantedFaceHalfPlane then
				closestPoint -= signedDistFromSecondSlantedFacePlane * secondSlantedFaceNormal
			else
				local intersectionLineDirection: Vector3 = planeIntersectionLineVector.Unit
				closestPoint = closestPoint:Dot(intersectionLineDirection) * intersectionLineDirection
			end
		end
	elseif isAboveFirstSlantedFacePlane then
		closestPoint -= signedDistFromFirstSlantedFacePlane * firstSlantedFaceNormal
	elseif isAboveSecondSlantedFacePlane then
		closestPoint -= signedDistFromSecondSlantedFacePlane * secondSlantedFaceNormal
	end
	
	closestPoint = Vector3.new(
		math.clamp(closestPoint.X, -.5 * cornerWedgeSize.X, .5 * cornerWedgeSize.X),
		math.clamp(closestPoint.Y, -.5 * cornerWedgeSize.Y, .5 * cornerWedgeSize.Y),
		math.clamp(closestPoint.Z, -.5 * cornerWedgeSize.Z, .5 * cornerWedgeSize.Z)
	)
	local vectorFromClosestPointToSphereCenter: Vector3 = sphereCenterInCornerWedgeLocalSpace - closestPoint
	return vectorFromClosestPointToSphereCenter:Dot(vectorFromClosestPointToSphereCenter) <= sphereRadius * sphereRadius
end

return SphereIntersectionQueries
PrimitiveIntersectionQuery ModuleScript (this is the one that is meant to be used by external code)
--!strict
local primitiveIntersectionModulesFolder = script.Parent

local BoxBoxIntersectionQuery = require(primitiveIntersectionModulesFolder.BoxBoxIntersectionQuery)
local ConvexPolyhedronPrimitivesIntersectionQuery = require(primitiveIntersectionModulesFolder.ConvexPolyhedronPrimitivesIntersectionQuery)
local SphereIntersectionQueries = require(primitiveIntersectionModulesFolder.SphereIntersectionQueries)

local PrimitiveIntersectionQuery = {}

function PrimitiveIntersectionQuery.doShapesIntersect(shape1: Enum.PartType, cf1: CFrame, size1: Vector3, shape2: Enum.PartType, cf2: CFrame, size2: Vector3): boolean
	if shape1 == Enum.PartType.Ball or shape2 == Enum.PartType.Ball then
		if shape2 == Enum.PartType.Ball then
			-- Doing these guarantees that the first shape is a sphere.
			shape1, shape2 = shape2, shape1
			cf1, cf2 = cf2, cf1
			size1, size2 = size2, size1
		end
		return
			if shape2 == Enum.PartType.Ball then SphereIntersectionQueries.doSpheresIntersect(cf1, size1, cf2, size2)
			elseif shape2 == Enum.PartType.Cylinder then SphereIntersectionQueries.doSphereAndCylinderIntersect(cf1, size1, cf2, size2)
			elseif shape2 == Enum.PartType.Block then SphereIntersectionQueries.doSphereAndBoxIntersect(cf1, size1, cf2, size2)
			elseif shape2 == Enum.PartType.Wedge then SphereIntersectionQueries.doSphereAndWedgeIntersect(cf1, size1, cf2, size2)
			else SphereIntersectionQueries.doSphereAndCornerWedgeIntersect(cf1, size1, cf2, size2)
	elseif shape1 == Enum.PartType.Cylinder or shape2 == Enum.PartType.Cylinder then
		error("Unsupported shape pair.")
	elseif shape1 == Enum.PartType.Block and shape2 == Enum.PartType.Block then
		return BoxBoxIntersectionQuery.doBoxesIntersect(cf1, size1, cf2, size2)
	else
		return ConvexPolyhedronPrimitivesIntersectionQuery.doConvexPolyhedronPrimitivesIntersect(shape1, cf1, size1, shape2, cf2, size2)
	end
end

return PrimitiveIntersectionQuery
A test script (creates parts of different shapes which can then be moved, rotated and resized with studio tools during runtime, and prints which one's are intersecting after the CFrame or Size of any of them is changed)
--!strict
local PrimitiveIntersectionQuery = require(script.Parent.PrimitiveIntersectionQuery)

local firstPartPos: Vector3 = Vector3.new(8, .5, 0)
local offsetBetweenInitialPositions: Vector3 = Vector3.new(8, 0, 0)

local box1: Part = Instance.new("Part")
box1.Name = "Box1"
box1.Color = Color3.new(1, 0, 0)
box1.Size = Vector3.new(4, 1, 2)
box1.Position = firstPartPos
box1.Anchored = true
box1.Parent = workspace

local box2: Part = Instance.new("Part")
box2.Name = "Box2"
box2.Color = Color3.new(1, .4, .6)
box2.Size = Vector3.new(4, 1, 2)
box2.Position = firstPartPos + offsetBetweenInitialPositions
box2.Anchored = true
box2.Parent = workspace

local wedge: Part = Instance.new("Part")
wedge.Name = "Wedge"
wedge.Color = Color3.new(1, .5, 0)
wedge.Size = Vector3.new(4, 1, 2)
wedge.Position = firstPartPos + 2 * offsetBetweenInitialPositions
wedge.Shape = Enum.PartType.Wedge
wedge.Anchored = true
wedge.Parent = workspace

local cornerWedge: Part = Instance.new("Part")
cornerWedge.Name = "CornerWedge"
cornerWedge.Color = Color3.new(1, 1, 0)
cornerWedge.Size = Vector3.new(4, 1, 2)
cornerWedge.Position = firstPartPos + 3 * offsetBetweenInitialPositions
cornerWedge.Shape = Enum.PartType.CornerWedge
cornerWedge.Anchored = true
cornerWedge.Parent = workspace

local sphere: Part = Instance.new("Part")
sphere.Name = "Sphere"
sphere.Color = Color3.new(0, 1, 0)
sphere.BottomSurface, sphere.TopSurface = Enum.SurfaceType.Smooth, Enum.SurfaceType.Smooth
sphere.Size = Vector3.new(1, 1, 1)
sphere.Position = firstPartPos + 4 * offsetBetweenInitialPositions
sphere.Shape = Enum.PartType.Ball
sphere.Anchored = true
sphere.Parent = workspace

local cylinder: Part = Instance.new("Part")
cylinder.Name = "Cylinder"
cylinder.Color = Color3.new(0, 0, 1)
cylinder.BottomSurface, cylinder.TopSurface = Enum.SurfaceType.Smooth, Enum.SurfaceType.Smooth
cylinder.Size = Vector3.new(4, 1, 1)
cylinder.Position = firstPartPos + 5 * offsetBetweenInitialPositions
cylinder.Shape = Enum.PartType.Cylinder
cylinder.Anchored = true
cylinder.Parent = workspace

local parts: {Part} = {box1, box2, wedge, cornerWedge, sphere, cylinder}

local function isPairValid(part: Part, otherPart: Part): boolean
	if (part.Shape == Enum.PartType.Cylinder and otherPart.Shape ~= Enum.PartType.Ball) or (otherPart.Shape == Enum.PartType.Cylinder and part.Shape ~= Enum.PartType.Ball) then
		return false
	end
	return true
end

local function onChanged(): ()
	local intersectingPairsString: string = ""
	for iPart, part: Part in parts do
		for iOtherPart: number = iPart + 1, #parts do
			local otherPart: Part = parts[iOtherPart]
			if not isPairValid(part, otherPart) then
				continue
			end
			local doPartsIntersect: boolean = PrimitiveIntersectionQuery.doShapesIntersect(part.Shape, part.CFrame, part.Size, otherPart.Shape, otherPart.CFrame, otherPart.Size)
			if doPartsIntersect then
				if intersectingPairsString ~= "" then
					intersectingPairsString ..= ", "
				end
				intersectingPairsString ..= `({part.Name}, {otherPart.Name})`
			end
		end
	end
	print(`Intersecting part pairs: {intersectingPairsString}`)
end

for _, part: Part in parts do
	part:GetPropertyChangedSignal("CFrame"):Connect(onChanged)
	part:GetPropertyChangedSignal("Size"):Connect(onChanged)
end

If code that works for non-rotated boxes is sufficient then the OP of the following topic has code for that.

@progamers246810 , checking whether at least one corner of one box is in the other box is not always enough. Here are some examples of situations where that isn’t sufficient. In these cases, neither of the boxes has any vertices inside the other but the boxes still intersect. In one of the cases the boxes aren’t even rotated so your suggestion isn’t reliable even for non-rotated boxes.
image image

However, I’m confused about what this intersection check code will be used for (is it somehow used in the greedy meshing or is that an entirely separate problem?) so I’m not sure whether your code still works for OP’s use case.

3 Likes

This is definitely a more in depth response. I know SAT is the way to go in situations like this but I was just trying to go with the more easier route. Obviously I didn’t think it through enough.

I’m making a voxel destruction system. This would be based around basepart hitboxes, which would detect for parts and divide them according to where the hitbox collided with the part. The idea is instead of dividing parts through continuously instancing new parts to make up the volume of the original part, which would inevitably leave tons of invisible parts behind. Instead of this, the idea is to completely remove all invisible parts, which would mean dividing the vector3 volumes of the parts continuously through tables, and removing all unnecessary parts in the table. This would save a ton on performance, as there is no actual instancing and all the calculations are done within tables. The idea behind checking if the hitbox volume and the vector3 volume of the part are colliding, is so that I can detect if the hitbox would be intersecting with this new imaginary part created by the division. Because the parts don’t actually exist, I can’t use GetPartsInPart.

And as for greedy meshing, that is a seperate issue. Performance would improve even more if I could process the table of voxels through a greedy meshing function before constructing them.

1 Like

Thing is the way I did destruction was with perfect voxels . While it seems your destruction system is for all part sizes. I was able to use a grid because I was splitting parts into equal 1x1x1 voxels. Your best bet would to do something like this system: GitHub - royal0959/rblx-nonvoxel-greedy-meshing: module for merging parts that don't need to be adhere to voxel grids. But you would have to find a way to get nearby tables without using spatial queries. In another post I showed how I did greedy meshing I’ll link that as well if you also want to look at that.

1 Like

Ah alright. I was kind of hoping there was a “one size fits all” type of greedy mesher, but I guess I’ve got some work to do

1 Like

An idea I have is you could loop through all your tables after checking which ones aren’t in the hitbox, then group parts into different tables if they share the same properties. Then using size and their position you could figure out if they are pretty much touching each other then merge them together. With the way your voxel destruction works “normal” greedy meshing isn’t going to cut it.