Hi, I am making a 3D Rubik's cube simulation/game. Currently, you're able to rotate the green/red faces using the left/right arrows, and the orange/blue faces using the up/down arrows. This utilises the updated Tweens to animate the sides as they rotate.

There is an issue however, and it isn't consistent either. Basically, if you rotate one of the z-axis sides (green or blue) and proceed to rotate a y axis side (red/orange), everything is completely fine and the pieces rotate without hassle. However, if you have the x-axis sides on any rotate other than 0°x and proceed to rotate the z-axis sides, the pieces merge and do not rotate in the correct ways.

For instance, if I rotate the red side to -90°X and then rotate the green side to -90°Z, instead of moving in a clockwise-fashion the pieces that were "from the red side" move in a clockwise fashion along the Y AXIS. Please, I do not understand how it works one way but not the other and it's hurting my brain a bit.
TEST 1:
Test 1: Before Turning

Test 1: Negative Z Axis Turn Initial turn of the green side, the negative Z axis, everything is smooth and works as expected

Test 1: Negative X Axis Turn Next to turn is the negative x, or red side, and again everything is smooth and works as expected

Test 2: Before Turning

Test 2: Negative X Axis Turn As expected, the negative x axis poses no problems

Test 2: Negative Z Axis Turn 1 However, after turning the negative Z axis after turning the negative x axis just once, the rotations become messed up and merge some pieces (not literally)

Test 2: Negative Z Axis Turn 2 Turn 2 of the negative z axis

Test 2: Negative Z Axis Turn 3 Turn 3 of the negative Z axis. You can see how it isn't any better.

I have tested it with +Z/-X, +Z/+X, -Z /+X, and -Z/-X. All four instances caused them to "merge", and all four instances required the X rotation be on anything above/below 0°X.

If anyone knows a plugin to fix this, or if there even is a single fix, please let me know otherwise my simulation might not work like this. Thanks in advance to any helpers 🙂

' Script:

extends Node3D

@onready var pi1 = $Piece1
@onready var pi2 = $Piece2

@onready var pi3 = $Piece3
@onready var pi4 = $Piece4

@onready var pi5 = $Piece5
@onready var pi6 = $Piece6

@onready var pi7 = $Piece7
@onready var pi8 = $Piece8

var solved = true
var tween

var red_face
var green_face
var orange_face
var blue_face
var yellow_face
var white_face

# Called when the node enters the scene tree for the first time.
func _ready():
	solved = true
	if solved == true:
		pass

func _process(delta):
	if Input.is_action_just_released("ui_right"):
		rotate_red_cw()
	elif Input.is_action_just_released("ui_left"):
		rotate_green_cw()
	elif Input.is_action_just_released("ui_up"):
		rotate_orange_cw()
	elif Input.is_action_just_released("ui_down"):
		rotate_blue_cw()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func rotate_red_cw():
	if tween and tween.is_running():
		return
	red_face = [
		pi1,
		pi2,
		pi3,
		pi4,
	]
	tween = get_tree().create_tween().bind_node(self).set_parallel().set_trans(Tween.TRANS_CUBIC)
	for CharacterBody3D in red_face:
		tween.tween_property(CharacterBody3D, "rotation_degrees", Vector3(CharacterBody3D.rotation_degrees.x-90, CharacterBody3D.rotation_degrees.y+0, CharacterBody3D.rotation_degrees.z+0), 0.5)
	tween.play()
	tween.is_running()
	var tmp = pi1
	pi1 = pi3
	pi3 = pi4
	pi4 = pi2
	pi2 = tmp
func rotate_green_cw():
	if tween and tween.is_running():
		return
	green_face = [
		pi1,
		pi3,
		pi6,
		pi8,
	]
	tween = get_tree().create_tween().bind_node(self).set_parallel().set_trans(Tween.TRANS_CUBIC)
	for CharacterBody3D in green_face:
		tween.tween_property(CharacterBody3D, "rotation_degrees", Vector3(CharacterBody3D.rotation_degrees.x+0, CharacterBody3D.rotation_degrees.y+0, CharacterBody3D.rotation_degrees.z-90), 0.5)
	tween.play()
	tween.is_running()
	var tmp = pi6
	pi6 = pi8
	pi8 = pi3 
	pi3 = pi1
	pi1 = tmp
func rotate_orange_cw():
	if tween and tween.is_running():
		return
	orange_face = [
		pi5,
		pi6,
		pi7,
		pi8,
	]
	tween = get_tree().create_tween().bind_node(self).set_parallel().set_trans(Tween.TRANS_CUBIC)
	for CharacterBody3D in orange_face:
		tween.tween_property(CharacterBody3D, "rotation_degrees", Vector3(CharacterBody3D.rotation_degrees.x+90, CharacterBody3D.rotation_degrees.y+0, CharacterBody3D.rotation_degrees.z+0), 0.5)
	tween.play()
	tween.is_running()
	var tmp = pi5
	pi5 = pi7
	pi7 = pi8 
	pi8 = pi6
	pi6 = tmp
func rotate_blue_cw():
	if tween and tween.is_running():
		return
	blue_face = [
		pi2,
		pi4,
		pi5,
		pi7,
	]
	tween = get_tree().create_tween().bind_node(self).set_parallel().set_trans(Tween.TRANS_CUBIC)
	for CharacterBody3D in blue_face:
		tween.tween_property(CharacterBody3D, "rotation_degrees", Vector3(CharacterBody3D.rotation_degrees.x+0, CharacterBody3D.rotation_degrees.y+0, CharacterBody3D.rotation_degrees.z+90), 0.5)
	tween.play()
	tween.is_running()
	var tmp = pi2
	pi2 = pi4
	pi4 = pi7 
	pi7 = pi5
	pi5 = tmp'
  • xyz replied to this.
  • Adam_Grace03 Well instructing you on linear algebra basics would take a bit too much space and time 🙂. The resources are abundant on the web.

    Here's a demo that handles one cublet. The transparent cube is just a pivot. Multiply the cublets and that's pretty much it.
    The controls are arrow keys for 4 axes and ctrl+left/right for remaining two
    The whole code in the demo:

    extends Node3D
    
    func rotate_cublet(axis):
    	# finish existing tweens immediately
    	for tw in get_tree().get_processed_tweens():
    		tw.custom_step(100000.0) 
    	# tween for the new rotation
    	var t = get_tree().create_tween()
    	var q = Quaternion($cublet_pivot.to_local(axis), deg_to_rad(90))
    	t.tween_property($cublet_pivot, "quaternion", q, .3).as_relative().set_ease(Tween.EASE_OUT)
    	t.play()
    
    func _input(event):
    	if event is InputEventKey:
    		if event.is_pressed():
    			if event.keycode == KEY_LEFT:
    				if event.is_command_or_control_pressed():
    					rotate_cublet(Vector3.BACK)
    				else:
    					rotate_cublet(Vector3.DOWN)
    					
    			if event.keycode == KEY_RIGHT:
    				if event.is_command_or_control_pressed():
    					rotate_cublet(Vector3.FORWARD)
    				else:
    					rotate_cublet(Vector3.UP)
    				
    			if event.keycode == KEY_UP:
    				rotate_cublet(Vector3.LEFT)
    				
    			if event.keycode == KEY_DOWN:
    				rotate_cublet(Vector3.RIGHT)

    rubik.zip
    2kB

    Adam_Grace03 Using rotation property is not a good approach for this. It'll likely end up in chaos as you might have already realized. You should instead utilize quaternion property of the Node3D. Quaternions let you specify rotations/orientations in axis+angle format and can be easily animated with tweens. This is ideal for Rubik's cube where all of the moves are exactly that - rotations around axes.

      xyz okay now my brain hurts lol, but what about basis? I'm very new to Godot 3D so I have no idea about anything

      • xyz replied to this.

        Adam_Grace03 Well instructing you on linear algebra basics would take a bit too much space and time 🙂. The resources are abundant on the web.

        Here's a demo that handles one cublet. The transparent cube is just a pivot. Multiply the cublets and that's pretty much it.
        The controls are arrow keys for 4 axes and ctrl+left/right for remaining two
        The whole code in the demo:

        extends Node3D
        
        func rotate_cublet(axis):
        	# finish existing tweens immediately
        	for tw in get_tree().get_processed_tweens():
        		tw.custom_step(100000.0) 
        	# tween for the new rotation
        	var t = get_tree().create_tween()
        	var q = Quaternion($cublet_pivot.to_local(axis), deg_to_rad(90))
        	t.tween_property($cublet_pivot, "quaternion", q, .3).as_relative().set_ease(Tween.EASE_OUT)
        	t.play()
        
        func _input(event):
        	if event is InputEventKey:
        		if event.is_pressed():
        			if event.keycode == KEY_LEFT:
        				if event.is_command_or_control_pressed():
        					rotate_cublet(Vector3.BACK)
        				else:
        					rotate_cublet(Vector3.DOWN)
        					
        			if event.keycode == KEY_RIGHT:
        				if event.is_command_or_control_pressed():
        					rotate_cublet(Vector3.FORWARD)
        				else:
        					rotate_cublet(Vector3.UP)
        				
        			if event.keycode == KEY_UP:
        				rotate_cublet(Vector3.LEFT)
        				
        			if event.keycode == KEY_DOWN:
        				rotate_cublet(Vector3.RIGHT)

        rubik.zip
        2kB

          xyz E 0:00:03:0068 temp.gd:9 @ rotate_cublet(): The axis Vector3 must be normalized.
          <C++ Error> Condition "!p_axis.is_normalized()" is true.
          <C++ Source> core/math/quaternion.cpp:296 @ Quaternion()
          <Stack Trace> temp.gd:9 @ rotate_cublet()
          temp.gd:24 @ _input()

          • xyz replied to this.

            Adam_Grace03 Is this my code doing out of the box or you were playing with axis vectors passed to the function? I'd guess it's the latter. So as the message says, you need to normalize the axis vector argument, either before passing it or inside the rotate_cublet() function prior to constructing the quaternion. Also if pivot is scaled (which it shouldn't be) the axis should be normalized after transforming it into pivot's local space. To cover all the bases, best to normalize the axis when passing it to the quaternion constructor:

            var q = Quaternion($cublet_pivot.to_local(axis).normalized(), deg_to_rad(90))

            Please note that this is quickly put together demo code, just to demonstrate the approach in shortest possible form. And as such it doesn't make all the checks a robust production or library code should. So rather than just copy-pasting it, try to understand what it does and build your own based on shown principles.

              Adam_Grace03 You should perhaps start with something a bit simpler. Rubik's cube simulation is quite involved.

              xyz Ok I got it working now. For some reason the pivot was scaled even tho I didn't scale it. Thanks