How to get a surface light's corners

So what I want to do is to put a attachment at all 8 corners, but I’m having trouble finding the positions of the farther corners. I already know the range of the light and the positions of the corners on the part.

Thanks in advance!

3 Likes

The corners can be derived from Range and Angle.

sin(α) = x/r; x = sin(α) * r
cos(α) = d/r; d = cos(α) * r

As you can discern from the image, horizontal orange line doesn’t lie in the same plane as the four corners, hence why we calculate d.

image

The offsets are applied differently depending on the surface face. I’ll take front face as an example.

1. CFrame.new(x, x, -d)
2. CFrame.new(x, -x, -d)
3. CFrame.new(-x, x, -d)
4. CFrame.new(-x, -x, -d)

In some cases this may be enough, but the attachments are not uperfectly aligned until we additionally offset them by half of the part’s size.

local part = workspace.Part
local light = part.SurfaceLight

local function PlaceAttachment(cf: CFrame): ()
	local attachment = Instance.new("Attachment")
	attachment.Visible = true
	attachment.CFrame = cf
	attachment.Parent = part
end

local x = math.sin(math.rad(light.Angle/2)) * light.Range
local d = math.cos(math.rad(light.Angle/2)) * light.Range

local halfSize = part.Size/2

PlaceAttachment(CFrame.new(x + halfSize.X, x + halfSize.Y, -d - halfSize.Z))
PlaceAttachment(CFrame.new(x + halfSize.X, -x - halfSize.Y, -d - halfSize.Z))
PlaceAttachment(CFrame.new(-x - halfSize.X, x + halfSize.Y, -d - halfSize.Z))
PlaceAttachment(CFrame.new(-x - halfSize.X, -x - halfSize.Y, -d - halfSize.Z))

image

3 Likes

Sorry for replying late, but I tried the code and it would only work for one of the faces. Is there anyway to make it so it work for all faces? Like front, back, right, left, top, and bottom.

I mean I could change the order of the negatives on the Cframe, but is that the only way? Is there a way to make the Cframe local instead of world?

Yes. I typed out the example for front face manually to show procedure without knowing anything about your use case. When you only need one face, it is usually best to keep it simple, readable, with selected few equasions.

I saw your other topic regarding finding face corners from three days ago and allowed myself to extend the function. (@RoBoPoJu thank you, with your permission, your function now already has a place among utilities in my projects.)

local lightPart = workspace.LightPart
local light = lightPart.SurfaceLight

local function getAxisFromValue(value: number): Enum.Axis
	for _, axis: Enum.Axis in Enum.Axis:GetEnumItems() do
		if axis.Value == value then
			return axis
		end
	end
	error("No axis EnumItem with this value exists.")
end

local function findFaceAndLightCorners(part: BasePart, light: SurfaceLight): {Vector3}
	local corners: {Vector3} = {}
	
	local normalAxisValue = light.Face.Value % 3
	local normalAxis = getAxisFromValue(normalAxisValue)
	local otherAxis1 = getAxisFromValue((normalAxisValue + 1) % 3)
	local otherAxis2 = getAxisFromValue((normalAxisValue + 2) % 3)
	
	local x = math.sin(math.rad(light.Angle/2)) * light.Range
	local d = math.cos(math.rad(light.Angle/2)) * light.Range
	
	for dir1Sign = -1, 1, 2 do
		for dir2Sign = -1, 1, 2 do
			-- face corners
			local localSpaceCorner = .5 * (
				part.Size[normalAxis.Name] * Vector3.FromNormalId(light.Face)
					+ dir1Sign * part.Size[otherAxis1.Name] * Vector3.FromAxis(otherAxis1)
					+ dir2Sign * part.Size[otherAxis2.Name] * Vector3.FromAxis(otherAxis2)
			)
			table.insert(corners, localSpaceCorner)
			
			-- projected corners
			local localSpaceCorner = .5 * (
				part.Size[normalAxis.Name] * Vector3.FromNormalId(light.Face)
					+ Vector3.FromNormalId(light.Face) * d * 2
					+ dir1Sign * (part.Size[otherAxis1.Name] + x * 2) * Vector3.FromAxis(otherAxis1)
					+ dir2Sign * (part.Size[otherAxis2.Name] + x * 2) * Vector3.FromAxis(otherAxis2)
			)
			table.insert(corners, localSpaceCorner)
		end
	end
	return corners
end

for _,corner in findFaceAndLightCorners(lightPart, light) do
	local attachment = Instance.new("Attachment")
	attachment.Visible = true
	attachment.Position = corner
	attachment.Parent = lightPart
end
3 Likes

Dude this worked like a charm, thanks a lot! I also have a question where did you learn all this math? Cause I want to know how to do this too.

1 Like

It’s basic trigonometry which you can find loads of resources for. If you take geometry or algebra 2 you should be able to understand it :smiley:

Hmm I’m in algerbra 2 trig rn. Do you learn it like near the end of the school year?

For honors classes it should be around the start of second semester, and around the end for regular ed at least in the US

Well, I guess I’ll just learn it online. Thanks!

Happy to help!

As Vectue said, trigonometry and algebra are (sooner or later) taught in high schools (though there are excellent courses online too). You’ll see how math that you learn during the course of standard education, even if you find it boring, can really help with understanding lots of concepts in 3D game development. Hopefully high school math is enough to build our intuition to be able to grasp more complicated topics on our own.

Granted, in theory, this problem is not hard to solve. It usually takes me more work to find the most convenient application.

Best of luck!

More on what the function does.

Image 1 below visualises what findFaceAndLightCorners() does with the farther corners. All of them are exposed to the same translations but with different signs. The two nested for-loops are a fancy way to find pairs of (1,1), (-1,1), (1,-1), and (-1,-1).
Vector3.FromNormalId() finds the direction based on surface. Once the function determines direction and the other two axis, we know which axis to extend by d. The other two axis are extended by half the part’s size depending on the axis. We can chip in and add x to them, given the sign pair used.

I was a bit unfocused yesterday and, to not overcomplicate, I multiplied both d and x by two, equivalent to:

local localSpaceCorner = (
	part.Size[normalAxis.Name]/2 * Vector3.FromNormalId(light.Face)
		+ Vector3.FromNormalId(light.Face) * d
		+ dir1Sign * (part.Size[otherAxis1.Name]/2 + x) * Vector3.FromAxis(otherAxis1)
		+ dir2Sign * (part.Size[otherAxis2.Name]/2 + x) * Vector3.FromAxis(otherAxis2)
)

Alternatively, assuming all four face corners are known, we could find the vectors connecting each of them with the centre on the surface, mirror them, and extend by x. See image 2: red vector helps determine the direction purple vector is facing. This is very convenient if we can easily determine the centre point, but since we don’t know which face corners are opposites, finding it leads close to what we have right now.

Another potential solution is using CFrame.lookAt(), facing in the direction of Vector3.FromNormalId(), and applying d and x. Finding the sign, in my findings, still takes too many steps.

All in all, given how the first four corners are queried, I believe it’s a win if we simply take the same path for their extended coordinates.

image image

1 Like

(part.Size[otherAxis1.Name]/2 + x) I’m a little confused on this part. Isn’t x the distance to the edge of the light already? Why add half the part size.

Never mind sorry, I figured it out

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.