# Rubiks Cube in Panda

Hello,
I’m new to panda and i’m trying to create simple game with rubiks cube. I’ve already create small cubes from GeomVertexData like in procedural-cube tutorial in samples. Then i arrange 27 cubes so they looks like rubiks cube and now i’m struggling with wall rotation, so i find out in internet that best way to rotate model around point is to create dummy node

``pivotNode = render.attachNewNode('node')``

attach it to model

``cube.wrtReparentTo(pivotNode)``

and then rotate that node. So I’ve done that, and everything is working fine to the moment when i’m trying to rotate multiple rubiks walls e.g. front and then left as they have common cubes.

rotating code looks like that

``````        pivotNode = render.attachNewNode('node')
for cube in getRight(cubes):
cube.wrtReparentTo(pivotNode)
pivotNode.hprInterval(0.25,L_R_rotation).start()``````

and here is my question what would be best option to rotate those walls as when i do:

1. attach 9 cubes of front wall to dummy node
2. rotate dummy node
3. attach 9 cubes of left wall to new dummy node
4. rotate second dummy node
won’t work properly.

In case you haven’t done so already, try putting the cube reparenting into a function, wrap it in a function interval and create a Sequence that contains all of the needed intervals, something like this:

``````pivotNodeFront = render.attachNewNode('pivot_front')
pivotNodeLeft = render.attachNewNode('pivot_left')

def reparentCubes(wall, pivotNode):
for cube in getWall(wall, cubes):
cube.wrtReparentTo(pivotNode)

seq = Sequence(
Func(reparentCubes, "front", pivotNodeFront),
LerpHprInterval(pivotNodeFront, 0.25, frontRotation),
Func(reparentCubes, "left", pivotNodeLeft),
LerpHprInterval(pivotNodeLeft, 0.25, leftRotation)
)
seq.start()``````

I’ve not done it yet and I’ll try your idea tomorrow, but I have one concern when i’m looking at your code. After

``LerpHprInterval(pivotNodeFront, 0.25, frontRotation)``

all my front cubes will be rotated according to pivotNodeFront in the moment when I’ll reparent them, will the front rotation remain on all front cubes and additionally 3 of front cube will receive left rotation?
I’m asking because in my approach when i was reparenting cubes they were losing their rotation.

Well it seems there’s no way to rotate the pivot relative to itself using a LerpHprInterval (unless I’m missing something, as I hardly ever use intervals myself), so maybe that’s why it happened.
Anyway, it means a little bit more work needs to be done, apart from the reparenting. You could make the interval relative to yet another node, whose hpr you set to the pivot hpr just before the interval/sequence starts, but perhaps its easier to detach the pivot’s children, clear the pivot’s transform and then reparent those children. That’s how I did it in the working code sample below:

``````from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.interval.IntervalGlobal import LerpHprInterval, Func, Sequence

def createCube(parent, index, cubeMembership, walls):

vertexFormat = GeomVertexFormat.getV3n3cp()
vertexData = GeomVertexData("cube_data", vertexFormat, Geom.UHStatic)
tris = GeomTriangles(Geom.UHStatic)

posWriter = GeomVertexWriter(vertexData, "vertex")
colWriter = GeomVertexWriter(vertexData, "color")
normalWriter = GeomVertexWriter(vertexData, "normal")

vertexCount = 0

for direction in (-1, 1):

for i in range(3):

normal = VBase3()
normal[i] = direction
rgb = [0., 0., 0.]
rgb[i] = 1.

if direction == 1:
rgb[i-1] = 1.

r, g, b = rgb
color = (r, g, b, 1.)

for a, b in ( (-1., -1.), (-1., 1.), (1., 1.), (1., -1.) ):

pos = VBase3()
pos[i] = direction
pos[(i + direction) % 3] = a
pos[(i + direction * 2) % 3] = b

vertexCount += 4

tris.addVertices(vertexCount - 2, vertexCount - 3, vertexCount - 4)
tris.addVertices(vertexCount - 4, vertexCount - 1, vertexCount - 2)

geom = Geom(vertexData)
node = GeomNode("cube_node")
cube = parent.attachNewNode(node)
x = index % 9 // 3 - 1
y = index // 9 - 1
z = index % 9 % 3 - 1
cube.setScale(.4)
cube.setPos(x, y, z)
membership = set() # the walls this cube belongs to
cubeMembership[cube] = membership

if x == -1:
walls["left"].append(cube)
elif x == 1:
walls["right"].append(cube)
if y == -1:
walls["front"].append(cube)
elif y == 1:
walls["back"].append(cube)
if z == -1:
walls["bottom"].append(cube)
elif z == 1:
walls["top"].append(cube)

return cube

class MyApp(ShowBase):

def __init__(self):

ShowBase.__init__(self)

walls = {}
pivots = {}
rotations = {}
cubeMembership = {}
wallIDs = ("front", "back", "left", "right", "bottom", "top")
hprs = {}
hprs["front"] = hprs["back"] = VBase3(0., 0., 90.)
hprs["left"] = hprs["right"] = VBase3(0., 90., 0.)
hprs["bottom"] = hprs["top"] = VBase3(90., 0., 0.)
wallOrders = {}
wallOrders["front"] = wallOrders["back"] = ["left", "top", "right", "bottom"]
wallOrders["left"] = wallOrders["right"] = ["back", "top", "front", "bottom"]
wallOrders["bottom"] = wallOrders["top"] = ["left", "front", "right", "back"]

for wallID in wallIDs:
walls[wallID] = []
pivots[wallID] = self.render.attachNewNode('pivot_%s' % wallID)
rotations[wallID] = {"hpr": hprs[wallID], "order": wallOrders[wallID]}

for i in range(27):
createCube(self.render, i, cubeMembership, walls)

self.directionalLight = DirectionalLight('directionalLight')
self.directionalLightNP = self.cam.attachNewNode(self.directionalLight)
self.directionalLightNP.setHpr(-20., -20., 0.)
self.render.setLight(self.directionalLightNP)
self.cam.setPos(-7., -10., 4.)
self.cam.lookAt(0., 0., 0.)

def reparentCubes(wallID):
pivot = pivots[wallID]
children = pivot.getChildren()
children.wrtReparentTo(self.render)
pivot.clearTransform()
children.wrtReparentTo(pivot)
for cube in walls[wallID]:
cube.wrtReparentTo(pivot)

def updateCubeMembership(wallID, negRotation=False):
wallOrder = rotations[wallID]["order"]
if not negRotation:
wallOrder = wallOrder[::-1]
for cube in walls[wallID]:
oldMembership = cubeMembership[cube]
newMembership = set()
cubeMembership[cube] = newMembership
for oldWallID in oldMembership:
if oldWallID in wallOrder:
index = wallOrder.index(oldWallID)
newWallID = wallOrder[index-1]
else:
for oldWallID in oldMembership - newMembership:
walls[oldWallID].remove(cube)
for newWallID in newMembership - oldMembership:
walls[newWallID].append(cube)

self.seq = Sequence()

self.seq.append(Func(reparentCubes, wallID))
rot = rotations[wallID]["hpr"]
if negRotation:
rot = rot * -1.
self.seq.append(LerpHprInterval(pivots[wallID], 2.5, rot))
self.seq.append(Func(updateCubeMembership, wallID, negRotation))
print "Added " + ("negative " if negRotation else "") + wallID + " rotation."

def acceptInput():
# <F> adds a positive Front rotation
# <Shift+F> adds a negative Front rotation
self.accept("shift-f", lambda: addInterval("front", True))
# <B> adds a positive Back rotation
# <Shift+B> adds a negative Back rotation
self.accept("shift-b", lambda: addInterval("back", True))
# <L> adds a positive Left rotation
# <Shift+L> adds a negative Left rotation
self.accept("shift-l", lambda: addInterval("left", True))
# <R> adds a positive Right rotation
# <Shift+R> adds a negative Right rotation
self.accept("shift-r", lambda: addInterval("right", True))
# <O> adds a positive bOttom rotation
# <Shift+O> adds a negative bOttom rotation
self.accept("shift-o", lambda: addInterval("bottom", True))
# <T> adds a positive Top rotation
# <Shift+T> adds a negative Top rotation
self.accept("shift-t", lambda: addInterval("top", True))
# <Enter> starts the sequence
self.accept("enter", startSequence)

def ignoreInput():
self.ignore("f")
self.ignore("shift-f")
self.ignore("b")
self.ignore("shift-b")
self.ignore("l")
self.ignore("shift-l")
self.ignore("r")
self.ignore("shift-r")
self.ignore("o")
self.ignore("shift-o")
self.ignore("t")
self.ignore("shift-t")
self.ignore("enter")

def startSequence():
# do not allow input while the sequence is playing...
ignoreInput()
# ...but accept input again once the sequence is finished
self.seq.append(Func(acceptInput))
self.seq.start()
print "Sequence started."
# create a new sequence, so no new intervals will be appended to the started one
self.seq = Sequence()

acceptInput()

app = MyApp()
app.run()``````

Just press any of the keys documented in the acceptInput function to queue up as many rotations as you like and then hit to play back the resulting sequence.

Even though you probably did things quite differently, I hope you will find the above code useful.

WOW! Awesome man! I’m really grateful that you have spend your time and energy to help me )

@Epihaius, @Poxy, thank you so much, it is exactly what I am looking for.

I added the middle slice rotation: Equator, Center, Standing. Press the first letter of Up/Down/Left/Right/Front/Back/Equetor/Center/Standing and Enter for clockwise rotation, and press Shift+the first letter and Enter for anti clockwise rotation.

``````# Based on Epihaius's work.
# https://discourse.panda3d.org/viewtopic.php?f=8&t=19317&p=108866#p108866
# Revision: adding Equator, Center, Standing slices rotation.

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.interval.IntervalGlobal import LerpHprInterval, Func, Sequence

def createCube(parent, x, y, z, position, cubeMembership, walls):

vertexFormat = GeomVertexFormat.getV3n3cp()
vertexData = GeomVertexData("cube_data", vertexFormat, Geom.UHStatic)
tris = GeomTriangles(Geom.UHStatic)

posWriter = GeomVertexWriter(vertexData, "vertex")
colWriter = GeomVertexWriter(vertexData, "color")
normalWriter = GeomVertexWriter(vertexData, "normal")

vertexCount = 0

for direction in (-1, 1):

for i in range(3):

normal = VBase3()
normal[i] = direction
rgb = [0., 0., 0.]
rgb[i] = 1.

if direction == 1:
rgb[i-1] = 1.

r, g, b = rgb
color = (r, g, b, 1.)

for a, b in ( (-1., -1.), (-1., 1.), (1., 1.), (1., -1.) ):

pos = VBase3()
pos[i] = direction
pos[(i + direction) % 3] = a
pos[(i + direction * 2) % 3] = b

vertexCount += 4

tris.addVertices(vertexCount - 2, vertexCount - 3, vertexCount - 4)
tris.addVertices(vertexCount - 4, vertexCount - 1, vertexCount - 2)

geom = Geom(vertexData)
node = GeomNode("cube_node")
cube = parent.attachNewNode(node)
cube.setScale(.4)
cube.setPos(x, y, z)
membership = set() # the walls this cube belongs to
position[cube] = [x, y, z]
cubeMembership[cube] = membership
# In Panda3D, X axis straightly points to right.
# Y axis goes inside perpendicular to the screen.
# Z axis is pointing up.

if x == 1:
walls["right"].append(cube)
elif x == -1:
walls["left"].append(cube)
elif x == 0:
walls["center"].append(cube)

if y == 1:
walls["back"].append(cube)
elif y == -1:
walls["front"].append(cube)
elif y==0:
walls["standing"].append(cube)

if z == -1:
walls["down"].append(cube)
elif z == 1:
walls["up"].append(cube)
elif z==0:
walls["equator"].append(cube)

return cube

class MyApp(ShowBase):

def __init__(self):

ShowBase.__init__(self)

walls = {}
pivots = {}
rotations = {}
position = {}
cubeMembership = {}
#Equator slice is the slice between up and down faces, center slice between left and right faces, standing slice the left one,
wallIDs = ("front", "back", "left", "right", "down", "up", "equator", "center", "standing")
hprs = {}
# VBase(Z,X,Y) if spin around Z, VBase3(90., 0., 0.).
# The degree is positive following the right hand rule.
hprs["right"] = VBase3(0., -90., 0.)
hprs["center"] = VBase3(0., -90., 0.) # The ratation direction of the standing slice follows the front face.
hprs["left"] = VBase3(0., 90., 0.)
hprs["back"] = VBase3(0., 0., -90.)
hprs["front"] = VBase3(0., 0., 90.)
hprs["standing"] = VBase3(0., 0., 90.)# The ratation direction of the center slice follows the right face.
hprs["down"] = VBase3(90., 0., 0.)
hprs["up"] = VBase3(-90., 0., 0.)
hprs["equator"] = VBase3(-90., 0., 0.) # The ratation direction of the equator slice follows the up face.
wallRotate = {}
wallNegRotate = {}
# Each rotation is a matrix.
# The positive front rotation and the negative back rotation have the same matrix.
# The standing slice follows the rules of the front face.

wallRotate["right"] = wallRotate["center"] = wallNegRotate["left"] = [[1, 0, 0], [0, 0, -1], [0, 1, 0]]
wallRotate["left"] = wallNegRotate["right"] = wallNegRotate["center"] = [[1, 0, 0], [0, 0, 1], [0, -1, 0]]

wallRotate["back"] = wallNegRotate["standing"] = wallNegRotate["front"] = [[0, 0, 1], [0, 1, 0], [-1, 0, 0]]
wallRotate["front"] = wallRotate["standing"] = wallNegRotate["back"] = [[0, 0, -1], [0, 1, 0], [1, 0, 0]]

wallRotate["up"] = wallRotate["equator"] = wallNegRotate["down"] = [[0, -1, 0], [1, 0, 0], [0, 0, 1]]
wallRotate["down"] = wallNegRotate["equator"] = wallNegRotate["up"] = [[0, 1, 0], [-1, 0, 0], [0, 0, 1]]

for wallID in wallIDs:
walls[wallID] = []
pivots[wallID] = self.render.attachNewNode('pivot_%s' % wallID)
rotations[wallID] = {"hpr": hprs[wallID]}
#print walls
#print pivots
#print rotations
for x in (-1, 0, 1):
for y in (-1, 0, 1):
for z in (-1, 0, 1):
createCube(self.render, x, y, z, position, cubeMembership, walls)

self.directionalLight = DirectionalLight('directionalLight')
self.directionalLightNP = self.cam.attachNewNode(self.directionalLight)
self.directionalLightNP.setHpr(20., -20., 0.)
self.render.setLight(self.directionalLightNP)
self.cam.setPos(7., -10., 4.)
self.cam.lookAt(0., 0., 0.)

def reparentCubes(wallID):
pivot = pivots[wallID]
children = pivot.getChildren()
children.wrtReparentTo(self.render)
pivot.clearTransform()
children.wrtReparentTo(pivot)
for cube in walls[wallID]:
cube.wrtReparentTo(pivot)

def updateCubeMembership(wallID, negRotation=False):
for cube in walls[wallID]:
oldMembership = cubeMembership[cube]
# print "oldMembership",oldMembership
# print "old position", position[cube]
newMembership = set()
cubeMembership[cube] = newMembership

# X cordinate
newPos = 0
if not negRotation:
for j in range(3):
newPos = newPos + int(position[cube][j]) * int(wallRotate[wallID][j][0])
else:
for j in range(3):
newPos = newPos + int(position[cube][j]) * int(wallNegRotate[wallID][j][0])

if newPos == 1:
elif newPos == -1:
elif newPos == 0:
newPosX = newPos

# Y cordinate
newPos = 0
if not negRotation:
for j in range(3):
newPos = newPos + int(position[cube][j]) * int(wallRotate[wallID][j][1])
else:
for j in range(3):
newPos = newPos + int(position[cube][j]) * int(wallNegRotate[wallID][j][1])

if newPos == 1:
elif newPos == -1:
elif newPos == 0:
newPosY = newPos

# Z cordinate
newPos = 0
if not negRotation:
for j in range(3):
newPos = newPos + int(position[cube][j]) * int(wallRotate[wallID][j][2])
else:
for j in range(3):
newPos = newPos + int(position[cube][j]) * int(wallNegRotate[wallID][j][2])

if newPos == 1:
elif newPos == -1:
elif newPos == 0:
newPosZ=newPos

position[cube] = [newPosX, newPosY, newPosZ]
# print "newMembership",newMembership
# print "new position:", position[cube]

for oldWallID in oldMembership - newMembership:
walls[oldWallID].remove(cube)
for newWallID in newMembership - oldMembership:
walls[newWallID].append(cube)

self.seq = Sequence()

self.seq.append(Func(reparentCubes, wallID))
rot = rotations[wallID]["hpr"]
if negRotation:
rot = rot * -1.
#Revision: 1.0 is the speed of rotation, 2.5 is slower.
self.seq.append(LerpHprInterval(pivots[wallID], 1.0, rot))
self.seq.append(Func(updateCubeMembership, wallID, negRotation))
print "Added " + ("negative " if negRotation else "") + wallID + " rotation."

def acceptInput():# Revision: top-->up, bottom-->down. Reverse rotation: back,up,right
# <F> adds a positive Front rotation
# <Shift+F> adds a negative Front rotation
self.accept("shift-f", lambda: addInterval("front", True))
# <B> adds a positive Back rotation
# <Shift+B> adds a negative Back rotation
self.accept("shift-b", lambda: addInterval("back", True))

# <L> adds a positive Left rotation
# <Shift+L> adds a negative Left rotation
self.accept("shift-l", lambda: addInterval("left", True))
# <R> adds a positive Right rotation
# <Shift+R> adds a negative Right rotation
self.accept("shift-r", lambda: addInterval("right", True))

# <D> adds d positive Down rotation
# <Shift+D> adds a negative Down rotation
self.accept("shift-d", lambda: addInterval("down", True))
# <U> adds a positive Up rotation
# <Shift+U> adds a negative Up rotation
self.accept("shift-u", lambda: addInterval("up", True))

# Rivision: to rotate the center slice
# <C> adds a positive Back rotation
# <Shift+C> adds a negative Back rotation
self.accept("shift-c", lambda: addInterval("center", True))
# Rivision: to rotate the equator slice
# <E> adds a positive Back rotation
# <Shift+E> adds a negative Back rotation
self.accept("shift-e", lambda: addInterval("equator", True))
# Rivision: to rotate the standing slice
# <S> adds a positive Back rotation
# <Shift+S> adds a negative Back rotation
self.accept("shift-s", lambda: addInterval("standing", True))

# <Enter> starts the sequence
self.accept("enter", startSequence)

def ignoreInput():
self.ignore("f")
self.ignore("shift-f")
self.ignore("b")
self.ignore("shift-b")
self.ignore("l")
self.ignore("shift-l")
self.ignore("r")
self.ignore("shift-r")
self.ignore("d")
self.ignore("shift-d")
self.ignore("u")
self.ignore("shift-u")
self.ignore("enter")

def startSequence():
# do not allow input while the sequence is playing...
ignoreInput()
# ...but accept input again once the sequence is finished
self.seq.append(Func(acceptInput))
self.seq.start()
# print "Sequence started."
# create a new sequence, so no new intervals will be appended to the started one
self.seq = Sequence()

acceptInput()

app = MyApp()
app.run()``````

But, is it possible to draw a rubik cube like this picture?

How to set the color brighter alike? In this case the mapping from hexadecimal RGB values to Panda3D color (r, g, b, a) is as below: