Hi, I’m a postdoctoral researcher in material science. I want to make a molecular editor using Pand3D.
Sometimes a molecular system contains more than 10000 atoms (spheres), so a molecular editor must handle at least 10000 meshes.
I have read all your manuals and tested the given recommendations. However, it still hasn’t been solved because of some problems.
-
Instancing
It works well for thousands of atoms, but it slows down when there are more than 10,000 atoms. -
flattenStrong()
It works well, but it is not suitable because the atom needs to be modified and moved (it’s an editor). -
RigidBodyCombiner
It’s slower than when it’s not in use. In particular, it consumes a great deal of CPU and RAM resources.
My simple OpenGL software can draw 50,000 spheres without frame drop (by instancing). So I think Panda3D can do it also.
Is hardware installing the only way I can try?
Here is my test code:
import numpy as np
import direct.directbase.DirectStart
from panda3d.core import *
from direct.actor.Actor import Actor
from direct.filter.CommonFilters import CommonFilters
USE_RBC = False
N_SPHERES = 10000
# ========= MAKE SPHERE NODE ==============================================
# Original code from http://www.songho.ca/opengl/gl_sphere.html.
def make_sphere(stack_counts, sector_counts, radius=1):
sector_step = 2 * np.pi / sector_counts
stack_step = np.pi / stack_counts
vertices = []
normals = []
tex_coords = []
for i in range(stack_counts + 1):
stack_angle = np.pi / 2 - i * stack_step
xy = radius * np.cos(stack_angle)
z = radius * np.sin(stack_angle)
for j in range(sector_counts + 1):
sector_angle = j * sector_step
x = xy * np.cos(sector_angle)
y = xy * np.sin(sector_angle)
vertices += [x, y, z]
normals += [x/radius, y/radius, z/radius]
tex_coords += [j / sector_counts, i / stack_counts]
# Make indices.
indices = []
for i in range(stack_counts):
k1 = i * (sector_counts + 1)
k2 = k1 + sector_counts + 1
for j in range(sector_counts):
if i != 0:
indices += [k1, k2, k1 + 1]
if i != (stack_counts - 1):
indices += [k1 + 1, k2, k2 + 1]
k1 += 1
k2 += 1
vertices_data = np.array(vertices, dtype=np.float32).reshape(-1, 3)
normals_data = np.array(normals, dtype=np.float32).reshape(-1, 3)
uvs_data = np.array(tex_coords, dtype=np.float32).reshape(-1, 2)
indices = np.array(indices, dtype=np.uint32)
data = {
"vertices": vertices_data,
"normals": normals_data,
"texcoords": uvs_data,
"indices": indices,
}
return data
# Generate custom sphere.
data = make_sphere(stack_counts=50, sector_counts=50)
array_format = GeomVertexArrayFormat()
# Same as GeomVertexArrayFormat.get_v3n3t2().
array_format.addColumn("vertex", 3, Geom.NTFloat32, Geom.CPoint)
array_format.addColumn("normal", 3, Geom.NTFloat32, Geom.CNormal)
array_format.addColumn("texcoord", 2, Geom.NTFloat32, Geom.CTexcoord)
vertex_format = GeomVertexFormat(array_format)
vertex_format = GeomVertexFormat.registerFormat(vertex_format)
vertex_data = GeomVertexData("sphere", vertex_format, Geom.UHStatic)
n_rows = len(data["vertices"])
vertex_data.setNumRows(n_rows)
vertex = GeomVertexWriter(vertex_data, "vertex")
normal = GeomVertexWriter(vertex_data, "normal")
texcoord = GeomVertexWriter(vertex_data, "texcoord")
for v in data["vertices"].tolist():
vertex.addData3(*v)
for v in data["normals"].tolist():
amount = 0.3
v += np.random.uniform(-amount, amount, 3).astype(np.float32)
v = v / np.linalg.norm(v)
normal.addData3(*v)
for v in data["texcoords"].tolist():
texcoord.addData2(*v)
primitive = GeomTriangles(Geom.UHStatic)
for v in data["indices"]:
primitive.addVertex(int(v))
primitive.closePrimitive()
geom = Geom(vertex_data)
geom.addPrimitive(primitive)
node = GeomNode("gnode")
node.addGeom(geom)
# ========= END OF MAKE SPHERE NODE ==============================================
# Convert to NodePath.
# This is used for instancing.
node_path = NodePath(node)
myMaterial = Material()
# Make this material shiny.
myMaterial.setShininess(125.0)
myMaterial.setSpecular((0.8, 0.8, 0.8, 1))
node_path.setMaterial(myMaterial)
molecule_node = render.attachNewNode("molecule")
if USE_RBC:
rbc = RigidBodyCombiner("rbc")
rbcnp = NodePath(rbc)
rbcnp.reparentTo(molecule_node)
else:
rbcnp = molecule_node
for i in range(N_SPHERES):
placeholder = rbcnp.attachNewNode("shpere-placeholder")
placeholder.setPos(*np.random.uniform(low=-500, high=500, size=3))
placeholder.setScale(np.random.uniform(low=5, high=70))
placeholder.setColor(*np.random.uniform(size=3), 1)
node_path.instanceTo(placeholder)
if USE_RBC:
rbcnp.node().collect()
# Position the camera.
base.cam.setPos(0, -3000, 0)
# Now create some lights to apply to everything in the scene.
# Create Ambient Light.
ambientLight = AmbientLight('ambientLight')
ambientLight.setColor((0.7, 0.7, 0.7, 1))
ambientLightNP = render.attachNewNode(ambientLight)
render.setLight(ambientLightNP)
# Directional light.
directionalLight = DirectionalLight('directionalLight')
directionalLight.setColor((1, 1, 1, 1))
directionalLightNP = render.attachNewNode(directionalLight)
# This light is facing forwards, away from the camera.
directionalLightNP.setHpr(50, -30, 50)
render.setLight(directionalLightNP)
# Add shadow caster.
directionalLight.setShadowCaster(True, 1024, 1024)
directionalLight.getLens().setFilmSize(1000, 1000)
directionalLight.getLens().setNearFar(-500, 500)
#directionalLight.showFrustum()
render.setShaderAuto()
# Apply SSAO.
# Note that you need to do lots of tweaking to the parameters to get this
# filter to work for your particular situation.
base.camLens.setNearFar(100, -1000)
filters = CommonFilters(base.win, base.cam)
filters.setAmbientOcclusion(numsamples=16, strength=0.1, radius=0.015, amount=1)
filters.setBloom(intensity=0.3)
base.run()