If you want to procedurally implement skinning, rigging and animation using Panda3D’s own classes for these purposes (Actor
, PartBundle
, AnimBundle
, etc.), then I’m afraid I can’t really help with that (although I’d like to try out these things for my own project at some point), but perhaps you just want to transform a specific set of vertices explicitly?
To do this, you’ll need to create a matrix that describes the transformation, as well as a SparseArray
to define a set of vertex indices.
In the following example, the top half of a procedurally generated cylinder is rotated 30 degrees about an arbitrary axis, with the center of the cylinder as pivot point:
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from math import pi, sin, cos
import array
def create_cylinder(parent, radius, height, segments):
segs_c = segments["circular"]
segs_h = segments["height"]
delta_angle = 2 * pi / segs_c
data = array.array("f", [])
tri_data = array.array("I", [])
for i in range(segs_h + 1):
z = height * i / segs_h
for j in range(segs_c):
angle = delta_angle * j
x = radius * cos(angle)
y = radius * sin(angle)
data.extend((x, y, z))
data.extend(Vec3(x, y, 0.).normalized())
for i in range(segs_h):
for j in range(segs_c - 1):
vi1 = (i + 1) * segs_c + j
vi2 = vi1 - segs_c
vi3 = vi2 + 1
vi4 = vi1 + 1
tri_data.extend((vi1, vi2, vi3))
tri_data.extend((vi1, vi3, vi4))
vi1 = i * segs_c
vi2 = vi1 + segs_c
vi3 = vi2 + segs_c - 1
vi4 = vi1 + segs_c - 1
tri_data.extend((vi1, vi2, vi3))
tri_data.extend((vi1, vi3, vi4))
vert_count = segs_c * (segs_h + 1)
vertex_format = GeomVertexFormat.get_v3n3()
vertex_data = GeomVertexData("vertex_data", vertex_format, Geom.UH_static)
vertex_data.unclean_set_num_rows(vert_count)
data_array = vertex_data.modify_array(0)
memview = memoryview(data_array).cast("B").cast("f")
memview[:] = data
tris_prim = GeomTriangles(Geom.UH_static)
tris_prim.set_index_type(Geom.NT_uint32)
tris_prim.reserve_num_vertices(len(tri_data))
tris_array = tris_prim.modify_vertices()
tris_array.unclean_set_num_rows(len(tri_data))
memview = memoryview(tris_array).cast("B").cast("I")
memview[:] = tri_data
geom = Geom(vertex_data)
geom.add_primitive(tris_prim)
node = GeomNode("cylinder_node")
node.add_geom(geom)
cylinder = parent.attach_new_node(node)
return cylinder
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# set up a light source
p_light = PointLight("point_light")
p_light.set_color((1., 1., 1., 1.))
self.light = self.camera.attach_new_node(p_light)
self.light.set_pos(5., -10., 7.)
self.render.set_light(self.light)
radius = 2.
height = 5.
segments = {"circular": 5, "height": 3}
# create a cylinder parented to the scene root
self.cylinder = create_cylinder(self.render, radius, height, segments)
# create a matrix describing the inverse of the pivot position
# (the "object-origin-to-pivot" transformation; in this example,
# the pivot is located at the center of the cylinder)...
mat = Mat4.translate_mat(0., 0., -height * .5)
# ...combine it with a matrix describing a rotation about that
# pivot point...
mat *= Mat4.rotate_mat(30., Vec3(1., -1., 0.))
# ...and finally combine this with another matrix, this time
# describing the pivot position itself
# (the "pivot-to-object-origin" transformation)
mat *= Mat4.translate_mat(0., 0., height * .5)
vert_count = segments["circular"] * (segments["height"] + 1)
vert_count_half = vert_count // 2
rows = SparseArray()
# set the bits corresponding to the row indices of the vertices to be
# transformed to "on" (in this example the vertices in the top half
# of the cylinder)
rows.set_range(vert_count - vert_count_half, vert_count_half)
vertex_data = self.cylinder.node().modify_geom(0).modify_vertex_data()
vertex_data.transform_vertices(mat, rows)
app = MyApp()
app.run()
To make sure the transformation is applied relative to the pivot point, the matrix describing it has to be the combination of the “object-origin-to-pivot” matrix, the matrix describing the actual transformation and the “pivot-to-object-origin” matrix (the inverse of the first one). Note that these are “object space” matrices, i.e. they are relative to the GeomNode
containing the cylinder geometry.
In the above example, the vertices were added from bottom to top, so the last half of the vertex indices is set as “on-bits” in the SparseArray
passed into the call to GeomVertexData.transform_vertices
.