How to check which face of a dice is on top?

Hello there. Title says it all.

I’m currently making dices with letters on top of them, and I wanted the system to know what face is on top when they roll around.

The dices will fall on the floor and will roll a little, then put a face on top. Now, how do I check which face is on top? What exactly should I do?

If you are making a square dice the best way (in my opinion) to determine the top would be with CFrames and RayCasting. Here’s a script I whipped up to demonstrating my method

local Dice = -- Your Dice Part
	local WidthOfDice = -- the Width of your dice
WidthOfDice = WidthOfDice + 0.1

	repeat
		wait()
	until Dice.Velocity == Vector3.new(0,0,0)
	
	local CastedDirection = "NONE"
	
	for i = 1, 6 do
		local rayOr = Dice.Position
		local rayDir
		if i == 1 then
			CastedDirection = "UP"
			rayDir = Dice.CFrame.UpVector * WidthOfDice/2
		elseif i == 2 then
			CastedDirection = "DOWN"
			rayDir = Dice.CFrame.UpVector * (-1*WidthOfDice/2)
		elseif i == 3 then
			CastedDirection = "FRONT"
			rayDir = Dice.CFrame.LookVector * WidthOfDice/2
		elseif i == 4 then
			CastedDirection = "BACK"
			rayDir = Dice.CFrame.LookVector * (-1*WidthOfDice/2)
		elseif i == 5 then
			CastedDirection = "RIGHT"
			rayDir = Dice.CFrame.RightVector * WidthOfDice/2
		elseif i == 6 then
			CastedDirection = "LEFT"
			rayDir = Dice.CFrame.RightVector * (-1*WidthOfDice/2)
		end
		
		local RayPara = RaycastParams.new()
		RayPara.FilterDescendantsInstances = {Dice}
		RayPara.FilterType = Enum.RaycastFilterType.Blacklist
		
		local raycast = workspace:Raycast(rayOr,rayDir,RayPara)
		if raycast then
			local hit = raycast.Instance
			
			if hit.Name == "Top" then -- Change this to whatever the name of your ground is called
				if CastedDirection == "BACK" then
					-- Front of the Dice
				end
				if CastedDirection == "RIGHT" then
					-- Left of the Dice
				end
				if CastedDirection == "LEFT" then
					-- Right of the Dice
				end
				if CastedDirection == "FRONT" then
					-- Back of the Dice
				end
				if CastedDirection == "DOWN" then
					-- Top of the Dice
				end
				if CastedDirection == "UP" then
					-- Bottom of the Dice
				end
			end
		end
	end

There is probably a better method out there but this one works (apologies for poor formatting)

1 Like

Sounds like you might need to use CFrame lookvector. Look vector gets you the forward direction of a part. If the lookvector is pointing downwards, then you know the opposite side is the top. If it is pointing to the right, then you’ll need to take into account the dice orientation and then see which side it is facing. This method would probably take a little while to figure out (Maths)

Alternatively, you could make the dice have 6 invisible parts, representing each side. Then you can get the dice position + a Y offset, then do a raycast downwards towards the dice. Whatever invisible part it hits would be the number on top of the dice. This method is similar to what @ cjhatter is doing

2 Likes

You’ll want to look at the die’s CFrame.RightVector, .UpVector, and .LookVector Assuming it’s a 6-sided die, also check the negative versions of each vector.

This creates 6 vectors for each of the faces of the 6-sided die. Next,
check the y component of each vector. The vector which has the greatest y component is the face pointed up.

Incidentally, for the math geeks, this is taking the dot product of the up vector Vector3.new(0, 1, 0) with each of the 6 vectors and seeing which one is projects more onto it.

1 Like

You can use this to get the top face of a 6 sided die:

function GetTop(Die)
	local upVec = Vector3.new(0, 1, 0)
	local maxDotValue, maxDotNormalId
	for _, normalId in ipairs(Enum.NormalId:GetEnumItems()) do
		local vec = Vector3.FromNormalId(normalId)
		local diceVecInWorldSpace = Die.CFrame:VectorToWorldSpace(vec)
		local dotValue = upVec:Dot(diceVecInWorldSpace)
		if not maxDotValue or dotValue > maxDotValue then
			maxDotValue, maxDotNormalId = dotValue, normalId
		end
	end
	return maxDotNormalId.Name
end

Source: Roll The Dice! {Open Sourced}

4 Likes

The thing here, forgot to mention, is that it’s going to be 13 dices, and it will be, weirdly enough but justified too, 300 dices later on.

@cjhatter Also comes into here. If I’m doing 300 dices do you think this would be a good idea to do for all 300 dices? maybe if the server did all the computing, but it sounds like berserk processing (I would be abusing the servers pretty much).

@gfxblit Well, SSDD, but ok.

I think I kinda have an answer here, let me see if it works.

What am I supposed to put here?

Nothing. The function works as is you just need to give it a part

1 Like

Nice solve. :slight_smile: next time I need to bring my laptop while on the go. Lol

Man you take a lot of quality into this, it works.

You should post this in community resources or something. This is one of those functions that you can give people and they will find use upon.

It wasn’t my solution, I’m trying to find the source, but it was posted on the devforum a long time ago. Once I do I’m going to update my post to clear the confusion.

Could you explain the mathematics of how this works? I understand that whatever the biggest dot value is, is the top of the dice. But I don’t understand why the biggest dot value means it’s the top of the dice. I don’t fully understand what :dot does, despite trying to learn about it online.

Yeah, I’ll explain it once I find the original source really quick.

I edited the source in (it took a minute to find because I originally found it 2 years ago). Also, the original source has comments explaining it:

local function GetTop()
    -- world-space up direction to compare again
    local upVec = Vector3.new(0, 1, 0)
    -- vars to find maximum
    local maxDotValue, maxDotNormalId
    -- loop through all possible faces
    for _, normalId in ipairs(Enum.NormalId:GetEnumItems()) do
        -- get object-space direction for this face
        local vec = Vector3.FromNormalId(normalId)
        -- get world-space direction for this face
        local diceVecInWorldSpace = workspace.Box.Dice.CFrame:VectorToWorldSpace(vec)
        -- Dot gets acos(angle) between upVec and diceVecInWorldSpace
        -- acos(angle) is a value between 1 (same direction) and -1 (opposite directions)
        -- common values you need to know here:
        --  1: same direction entirely
        --  bigger than 0: same side
        --  0: perpendicular directions
        --  smaller than 0: opposite sides
        --  -1: opposite direction entirely
        local dotValue = upVec:Dot(diceVecInWorldSpace)
        if not maxDotValue or dotValue > maxDotValue then
            maxDotValue, maxDotNormalId = dotValue, normalId
        end
    end
    return maxDotNormalId.Name
end

If you still need help understanding I can provide more assistance, but the comments were done nicely

1 Like

Hey sorry to bother with such a old topic, if your still available.
How can i do this but for a multiple sided die like 12 sides?

Thank you so much, I’ve been looking for this for 1-2 hours and this was the easiest one to add! Even two years later its still the best I guess :sweat_smile: