Here is an attempt to use a shader that warps text of a TextNode’s internal GeomNode to fit along a circle:
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
VERT_SHADER = """
#version 130
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform mat4 p3d_TextureMatrix;
in vec4 p3d_Vertex;
in vec4 p3d_Color;
in vec2 p3d_MultiTexCoord0;
out vec2 v_texcoord;
out vec4 v_color;
void main() {
vec4 pos;
float radius = 10.;
float angle, x, y;
// the angle of the vertex depends on its x-coordinate as well as the
// radius of the circle to warp the text along
angle = atan(p3d_Vertex.x, radius);
x = sin(angle) * radius;
y = -cos(angle) * radius;
pos = vec4(x, y, p3d_Vertex.z, 1.);
gl_Position = p3d_ModelViewProjectionMatrix * pos;
v_texcoord = (p3d_TextureMatrix * vec4(p3d_MultiTexCoord0, 0, 1)).xy;
v_color = p3d_Color;
}
"""
FRAG_SHADER = """
#version 130
uniform sampler2D p3d_Texture0;
in vec2 v_texcoord;
in vec4 v_color;
out vec4 color;
void main() {
color = v_color * texture2D(p3d_Texture0, v_texcoord);
}
"""
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.disable_mouse()
self.camera.set_pos(0., -30., 25.)
self.camera.look_at(0., 0., 0.)
text_node = TextNode("text")
text_node.set_text("This is some curved text, warped to fit around a circle.")
text_node.set_align(TextNode.A_center)
text_node.set_slant(0.3)
text_node.set_text_color(1, 0.5, 0.5, 1)
text_node.set_shadow(0.05, 0.05)
text_node.set_shadow_color(1, 1, 0, 1)
text_node.set_frame_color(0, 0, 1, 1)
text_node.set_frame_as_margin(0.2, 0.2, 0.1, 0.1)
text_np = self.render.attach_new_node(text_node)
shader = Shader.make(Shader.SL_GLSL, VERT_SHADER, FRAG_SHADER)
geom_node_root = NodePath(text_node.get_internal_geom())
text_geom_node = geom_node_root.find("**/text")
text_geom_node.set_shader(shader)
frame_geom_node = geom_node_root.find("**/frame")
frame_geom_node.set_shader(shader)
app = MyApp()
app.run()
The shader isn’t perfect, as I don’t really know how to render it with the correct colors. Maybe someone else knows how to fix this.
When you run the code sample, you should see that the frame isn’t curved:
This is because the line geom used for the frame only contains 4 vertices. It should be possible to use an additional geometry shader that creates a number of intermediate vertices adaptively, based on the angle between the original vertices on the circle. This is something I might still try when I find some time.
[edit]
Okay, I think I managed to curve the frame as well:
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
VERT_SHADER = """
#version 130
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform mat4 p3d_TextureMatrix;
uniform float radius;
in vec4 p3d_Vertex;
in vec4 p3d_Color;
in vec2 p3d_MultiTexCoord0;
out vec2 v_texcoord;
out vec4 v_color;
void main() {
vec4 pos;
float angle, x, y;
// the angle of the vertex depends on its x-coordinate as well as the
// radius of the circle to warp the text along
angle = atan(p3d_Vertex.x, radius);
x = sin(angle) * radius;
y = -cos(angle) * radius;
pos = vec4(x, y, p3d_Vertex.z, 1.);
gl_Position = p3d_ModelViewProjectionMatrix * pos;
v_texcoord = (p3d_TextureMatrix * vec4(p3d_MultiTexCoord0, 0, 1)).xy;
v_color = p3d_Color;
}
"""
FRAG_SHADER = """
#version 130
uniform sampler2D p3d_Texture0;
in vec2 v_texcoord;
in vec4 v_color;
out vec4 color;
void main() {
color = v_color * texture2D(p3d_Texture0, v_texcoord);
}
"""
VERT_SHADER_FRAME = """
#version 330
uniform mat4 p3d_ModelViewProjectionMatrix;
in vec4 p3d_Vertex;
in vec4 p3d_Color;
out Vertex
{
vec4 color;
} vertex;
void main() {
gl_Position = p3d_Vertex;
vertex.color = p3d_Color;
}
"""
GEOM_SHADER_FRAME = """
#version 330
layout(lines) in;
layout(line_strip, max_vertices=100) out;
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform mat4 p3d_ModelViewMatrix;
uniform float radius;
in Vertex
{
vec4 color;
} vertex[];
out vec4 g_color;
void main()
{
vec4 pos;
float angle1, angle2, angle, delta_angle, x, y;
float min_angle = .1745; // minimum angle between points in radians
int count;
vec4 P1 = gl_in[0].gl_Position;
vec4 P2 = gl_in[1].gl_Position;
g_color = vertex[0].color;
// the angle of the vertex depends on its x-coordinate as well as the
// radius of the circle to warp the text along
angle1 = atan(P1.x, radius);
angle2 = atan(P2.x, radius);
// draw first point of original line
x = sin(angle1) * radius;
y = -cos(angle1) * radius;
pos = vec4(x, y, P1.z, 1.);
gl_Position = p3d_ModelViewProjectionMatrix * pos;
EmitVertex();
if (P2.z == P1.z) {
// determine how many intermediate points to add in order to obtain
// a smooth curve, based on the minimum angle between any 2 points
angle = angle2 - angle1;
count = int(abs(angle) / min_angle);
delta_angle = angle / count;
for (int i = 0; i < count; ++i) {
angle = angle1 + delta_angle * i;
x = sin(angle) * radius;
y = -cos(angle) * radius;
pos = vec4(x, y, P1.z, 1.);
gl_Position = p3d_ModelViewProjectionMatrix * pos;
EmitVertex();
}
}
// draw second point of original line
x = sin(angle2) * radius;
y = -cos(angle2) * radius;
pos = vec4(x, y, P2.z, 1.);
gl_Position = p3d_ModelViewProjectionMatrix * pos;
EmitVertex();
EndPrimitive();
}
"""
FRAG_SHADER_FRAME = """
#version 330
uniform vec4 p3d_ColorScale;
in vec4 g_color;
out vec4 out_color;
void main() {
vec3 base_color;
out_color = g_color * p3d_ColorScale;
}
"""
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.disable_mouse()
self.camera.set_pos(0., -30., 25.)
self.camera.look_at(0., 0., 0.)
text_node = TextNode("text")
text_node.set_text("This is some curved text, warped to fit around a circle.")
text_node.set_align(TextNode.A_center)
text_node.set_slant(0.3)
text_node.set_text_color(1, 0.5, 0.5, 1)
text_node.set_shadow(0.05, 0.05)
text_node.set_shadow_color(1, 1, 0, 1)
text_node.set_frame_color(0, 0, 1, 1)
text_node.set_frame_as_margin(0.2, 0.2, 0.1, 0.1)
text_np = self.render.attach_new_node(text_node)
shader = Shader.make(Shader.SL_GLSL, VERT_SHADER, FRAG_SHADER)
frame_shader = Shader.make(Shader.SL_GLSL, VERT_SHADER_FRAME, FRAG_SHADER_FRAME, GEOM_SHADER_FRAME)
radius = 10.
geom_node_root = NodePath(text_node.get_internal_geom())
text_geom_node = geom_node_root.find("**/text")
text_geom_node.set_shader(shader)
text_geom_node.set_shader_input("radius", radius)
frame_geom_node = geom_node_root.find("**/frame")
frame_geom_node.set_shader(frame_shader)
frame_geom_node.set_shader_input("radius", radius)
app = MyApp()
app.run()
[/edit]
Even if you’re not interested in using a shader, the code does show how to access a TextNode’s internal GeomNode – actually there can be multiple: one for the text itself, another for the frame and a third one for the card.
To get the one with the text, just call find("**/text")
on the NodePath that you wrap around the node obtained by the call to TextNode.get_internal_geom
.
Anyway, I hope this has been helpful to at least some extent!