panda3d/panda/src/text/textNode.cxx
2005-04-22 22:09:37 +00:00

969 lines
32 KiB
C++

// Filename: textNode.cxx
// Created by: drose (13Mar02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////
#include "textNode.h"
#include "textGlyph.h"
#include "stringDecoder.h"
#include "config_text.h"
#include "textAssembler.h"
#include "compose_matrix.h"
#include "geom.h"
#include "geomTristrip.h"
#include "geomLinestrip.h"
#include "geomPoint.h"
#include "qpgeom.h"
#include "qpgeomLinestrips.h"
#include "qpgeomPoints.h"
#include "qpgeomTristrips.h"
#include "qpgeomVertexWriter.h"
#include "geomNode.h"
#include "notify.h"
#include "transformState.h"
#include "colorAttrib.h"
#include "colorScaleAttrib.h"
#include "cullBinAttrib.h"
#include "textureAttrib.h"
#include "transparencyAttrib.h"
#include "sceneGraphReducer.h"
#include "indent.h"
#include "cullTraverser.h"
#include "cullTraverserData.h"
#include "geometricBoundingVolume.h"
#include "accumulatedAttribs.h"
#include "renderState.h"
#include "renderModeAttrib.h"
#include "dcast.h"
#include "bamFile.h"
#include "zStream.h"
#include <stdio.h>
TypeHandle TextNode::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: TextNode::Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
TextNode::
TextNode(const string &name) : PandaNode(name), _assembler(this) {
_flags = 0;
_max_rows = 0;
if (text_small_caps) {
set_small_caps(true);
}
_frame_color.set(1.0f, 1.0f, 1.0f, 1.0f);
_card_color.set(1.0f, 1.0f, 1.0f, 1.0f);
_frame_width = 1.0f;
_frame_ul.set(0.0f, 0.0f);
_frame_lr.set(0.0f, 0.0f);
_card_ul.set(0.0f, 0.0f);
_card_lr.set(0.0f, 0.0f);
_transform = LMatrix4f::ident_mat();
_coordinate_system = CS_default;
_ul3d.set(0.0f, 0.0f, 0.0f);
_lr3d.set(0.0f, 0.0f, 0.0f);
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::Copy Constructor
// Access: Published
// Description: It's sort of a copy constructor: it copies the
// indicated TextProperties, without copying a complete
// TextNode.
////////////////////////////////////////////////////////////////////
TextNode::
TextNode(const string &name, const TextProperties &copy) :
PandaNode(name), TextProperties(copy),
_assembler(this)
{
_flags = 0;
_max_rows = 0;
_frame_color.set(1.0f, 1.0f, 1.0f, 1.0f);
_card_color.set(1.0f, 1.0f, 1.0f, 1.0f);
_frame_width = 1.0f;
_frame_ul.set(0.0f, 0.0f);
_frame_lr.set(0.0f, 0.0f);
_card_ul.set(0.0f, 0.0f);
_card_lr.set(0.0f, 0.0f);
_transform = LMatrix4f::ident_mat();
_coordinate_system = CS_default;
_ul3d.set(0.0f, 0.0f, 0.0f);
_lr3d.set(0.0f, 0.0f, 0.0f);
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::Destructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
TextNode::
~TextNode() {
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::calc_width
// Access: Published
// Description: Returns the width of a single character of the font,
// or 0.0 if the character is not known. This may be a
// wide character (greater than 255).
////////////////////////////////////////////////////////////////////
float TextNode::
calc_width(int character) const {
TextFont *font = get_font();
if (font == (TextFont *)NULL) {
return 0.0f;
}
return _assembler.calc_width(character, *this);
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::output
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void TextNode::
output(ostream &out) const {
PandaNode::output(out);
check_rebuild();
int geom_count = 0;
if (_internal_geom != (PandaNode *)NULL) {
geom_count = count_geoms(_internal_geom);
}
out << " (" << geom_count << " geoms)";
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::write
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void TextNode::
write(ostream &out, int indent_level) const {
indent(out, indent_level)
<< "TextNode " << get_name() << "\n";
TextProperties::write(out, indent_level + 2);
out << "\n";
LVecBase3f scale, shear, hpr, trans;
if (decompose_matrix(_transform, scale, shear, hpr, trans, _coordinate_system)) {
indent(out, indent_level + 2)
<< "transform is:\n"
<< " scale: " << scale << "\n"
<< " shear: " << shear << "\n"
<< " hpr: " << hpr << "\n"
<< " trans: " << hpr << "\n";
} else {
indent(out, indent_level + 2)
<< "transform is:\n" << _transform;
}
indent(out, indent_level + 2)
<< "in coordinate system " << _coordinate_system << "\n";
out << "\n";
indent(out, indent_level + 2)
<< "text is " << get_text() << "\n";
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::generate
// Access: Published
// Description: Generates the text, according to the parameters
// indicated within the TextNode, and returns a Node
// that may be parented within the tree to represent it.
////////////////////////////////////////////////////////////////////
PT(PandaNode) TextNode::
generate() {
if (text_cat.is_debug()) {
text_cat.debug()
<< "Rebuilding " << get_type() << " " << get_name()
<< " with '" << get_text() << "'\n";
}
// The strategy here will be to assemble together a bunch of
// letters, instanced from the letter hierarchy of font_def, into
// our own little hierarchy.
// There will be one root over the whole text block, that
// contains the transform passed in. Under this root there will be
// another node for each row, that moves the row into the right place
// horizontally and vertically, and for each row, there is another
// node for each character.
_ul3d.set(0.0f, 0.0f, 0.0f);
_lr3d.set(0.0f, 0.0f, 0.0f);
// Now build a new sub-tree for all the text components.
PT(PandaNode) root = new PandaNode(get_text());
if (!has_text()) {
return root;
}
TextFont *font = get_font();
if (font == (TextFont *)NULL) {
return root;
}
// Compute the overall text transform matrix. We build the text in
// a Z-up coordinate system and then convert it to whatever the user
// asked for.
LMatrix4f mat =
LMatrix4f::convert_mat(CS_zup_right, _coordinate_system) *
_transform;
root->set_transform(TransformState::make_mat(mat));
wstring wtext = get_wtext();
// Assemble the text.
bool all_set = _assembler.set_wtext(wtext, *this, _max_rows);
if (all_set) {
// No overflow.
_flags &= ~F_has_overflow;
} else {
// Overflow.
_flags |= F_has_overflow;
}
PT(PandaNode) text_root = _assembler.assemble_text();
// Parent the text in. We create an intermediate node so we can
// choose to reinstance the text_root as the shadow, below.
PT(PandaNode) text = new PandaNode("text");
root->add_child(text, get_draw_order() + 2);
text->add_child(text_root);
// Save the bounding-box information about the text in a form
// friendly to the user.
const LVector2f &ul = _assembler.get_ul();
const LVector2f &lr = _assembler.get_lr();
_ul3d.set(ul[0], 0.0f, ul[1]);
_lr3d.set(lr[0], 0.0f, lr[1]);
_ul3d = _ul3d * _transform;
_lr3d = _lr3d * _transform;
// Incidentally, that means we don't need to measure the text now.
_flags &= ~F_needs_measure;
// Now deal with the decorations.
if (has_frame()) {
PT(PandaNode) frame_root = make_frame();
root->add_child(frame_root, get_draw_order() + 1);
frame_root->set_attrib(ColorAttrib::make_flat(get_frame_color()));
if (get_frame_color()[3] != 1.0f) {
frame_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
}
if (has_bin()) {
frame_root->set_attrib(CullBinAttrib::make(get_bin(), get_draw_order() + 1));
}
}
if (has_card()) {
PT(PandaNode) card_root;
if (has_card_border())
card_root = make_card_with_border();
else
card_root = make_card();
root->add_child(card_root, get_draw_order());
card_root->set_attrib(ColorAttrib::make_flat(get_card_color()));
if (get_card_color()[3] != 1.0f) {
card_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
}
if (has_card_texture()) {
card_root->set_attrib(TextureAttrib::make(get_card_texture()));
}
if (has_bin()) {
card_root->set_attrib(CullBinAttrib::make(get_bin(), get_draw_order()));
}
}
// Now flatten our hierarchy to get rid of the transforms we put in,
// applying them to the vertices.
if (text_flatten) {
SceneGraphReducer gr;
gr.apply_attribs(root);
gr.flatten(root, ~SceneGraphReducer::CS_within_radius);
gr.collect_vertex_data(root);
gr.unify(root);
}
return root;
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::calc_width
// Access: Public
// Description: Returns the width of a line of text of arbitrary
// characters. The line should not include the newline
// character or any embedded control characters like \1
// or \3.
////////////////////////////////////////////////////////////////////
float TextNode::
calc_width(const wstring &line) const {
float width = 0.0f;
wstring::const_iterator si;
for (si = line.begin(); si != line.end(); ++si) {
width += calc_width(*si);
}
return width;
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::get_unsafe_to_apply_attribs
// Access: Public, Virtual
// Description: Returns the union of all attributes from
// SceneGraphReducer::AttribTypes that may not safely be
// applied to the vertices of this node. If this is
// nonzero, these attributes must be dropped at this
// node as a state change.
//
// This is a generalization of safe_to_transform().
////////////////////////////////////////////////////////////////////
int TextNode::
get_unsafe_to_apply_attribs() const {
// We have no way to apply these kinds of attributes to our
// TextNode, so insist they get dropped into the PandaNode's basic
// state.
return
SceneGraphReducer::TT_tex_matrix |
SceneGraphReducer::TT_other;
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::apply_attribs_to_vertices
// Access: Public, Virtual
// Description: Applies whatever attributes are specified in the
// AccumulatedAttribs object (and by the attrib_types
// bitmask) to the vertices on this node, if
// appropriate. If this node uses geom arrays like a
// GeomNode, the supplied GeomTransformer may be used to
// unify shared arrays across multiple different nodes.
//
// This is a generalization of xform().
////////////////////////////////////////////////////////////////////
void TextNode::
apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
GeomTransformer &transformer) {
if ((attrib_types & SceneGraphReducer::TT_transform) != 0) {
const LMatrix4f &mat = attribs._transform->get_mat();
_transform *= mat;
if ((_flags & F_needs_measure) == 0) {
// If we already have a measure, transform it too. We don't
// need to invalidate the 2-d parts, since that's not affected
// by the transform anyway.
_ul3d = _ul3d * mat;
_lr3d = _lr3d * mat;
}
}
if ((attrib_types & SceneGraphReducer::TT_color) != 0) {
if (attribs._color != (const RenderAttrib *)NULL) {
const ColorAttrib *ca = DCAST(ColorAttrib, attribs._color);
if (ca->get_color_type() == ColorAttrib::T_flat) {
const Colorf &c = ca->get_color();
set_text_color(c);
set_frame_color(c);
set_card_color(c);
set_shadow_color(c);
}
}
}
if ((attrib_types & SceneGraphReducer::TT_color_scale) != 0) {
if (attribs._color_scale != (const RenderAttrib *)NULL) {
const ColorScaleAttrib *csa = DCAST(ColorScaleAttrib, attribs._color_scale);
const LVecBase4f &s = csa->get_scale();
if (s != LVecBase4f(1.0f, 1.0f, 1.0f, 1.0f)) {
LVecBase4f tc = get_text_color();
tc[0] *= s[0];
tc[1] *= s[1];
tc[2] *= s[2];
tc[3] *= s[3];
set_text_color(tc);
LVecBase4f sc = get_shadow_color();
sc[0] *= s[0];
sc[1] *= s[1];
sc[2] *= s[2];
sc[3] *= s[3];
set_shadow_color(sc);
LVecBase4f fc = get_frame_color();
fc[0] *= s[0];
fc[1] *= s[1];
fc[2] *= s[2];
fc[3] *= s[3];
set_frame_color(fc);
LVecBase4f cc = get_card_color();
cc[0] *= s[0];
cc[1] *= s[1];
cc[2] *= s[2];
cc[3] *= s[3];
set_card_color(cc);
}
}
}
// Now propagate the attributes down to our already-generated
// geometry, if we have any.
if ((_flags & F_needs_rebuild) == 0 &&
_internal_geom != (PandaNode *)NULL) {
SceneGraphReducer gr;
gr.apply_attribs(_internal_geom, attribs, attrib_types, transformer);
}
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::calc_tight_bounds
// Access: Public, Virtual
// Description: This is used to support
// NodePath::calc_tight_bounds(). It is not intended to
// be called directly, and it has nothing to do with the
// normal Panda bounding-volume computation.
//
// If the node contains any geometry, this updates
// min_point and max_point to enclose its bounding box.
// found_any is to be set true if the node has any
// geometry at all, or left alone if it has none. This
// method may be called over several nodes, so it may
// enter with min_point, max_point, and found_any
// already set.
////////////////////////////////////////////////////////////////////
CPT(TransformState) TextNode::
calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point, bool &found_any,
const TransformState *transform) const {
CPT(TransformState) next_transform =
PandaNode::calc_tight_bounds(min_point, max_point, found_any, transform);
check_rebuild();
if (_internal_geom != (PandaNode *)NULL) {
_internal_geom->calc_tight_bounds(min_point, max_point,
found_any, next_transform);
}
return next_transform;
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::has_cull_callback
// Access: Protected, Virtual
// Description: Should be overridden by derived classes to return
// true if cull_callback() has been defined. Otherwise,
// returns false to indicate cull_callback() does not
// need to be called for this node during the cull
// traversal.
////////////////////////////////////////////////////////////////////
bool TextNode::
has_cull_callback() const {
return true;
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::cull_callback
// Access: Protected, Virtual
// Description: If has_cull_callback() returns true, this function
// will be called during the cull traversal to perform
// any additional operations that should be performed at
// cull time. This may include additional manipulation
// of render state or additional visible/invisible
// decisions, or any other arbitrary operation.
//
// By the time this function is called, the node has
// already passed the bounding-volume test for the
// viewing frustum, and the node's transform and state
// have already been applied to the indicated
// CullTraverserData object.
//
// The return value is true if this node should be
// visible, or false if it should be culled.
////////////////////////////////////////////////////////////////////
bool TextNode::
cull_callback(CullTraverser *trav, CullTraverserData &data) {
check_rebuild();
if (_internal_geom != (PandaNode *)NULL) {
// Render the text with this node.
CullTraverserData next_data(data, _internal_geom);
trav->traverse(next_data);
}
// Now continue to render everything else below this node.
return true;
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::recompute_internal_bound
// Access: Protected, Virtual
// Description: Called when needed to recompute the node's
// _internal_bound object. Nodes that contain anything
// of substance should redefine this to do the right
// thing.
////////////////////////////////////////////////////////////////////
BoundingVolume *TextNode::
recompute_internal_bound() {
// First, get ourselves a fresh, empty bounding volume.
BoundingVolume *bound = PandaNode::recompute_internal_bound();
nassertr(bound != (BoundingVolume *)NULL, bound);
GeometricBoundingVolume *gbv = DCAST(GeometricBoundingVolume, bound);
// Now enclose the bounding box around the text. We can do this
// without actually generating the text, if we have at least
// measured it.
check_measure();
LPoint3f vertices[8];
vertices[0].set(_ul3d[0], _ul3d[1], _ul3d[2]);
vertices[1].set(_ul3d[0], _ul3d[1], _lr3d[2]);
vertices[2].set(_ul3d[0], _lr3d[1], _ul3d[2]);
vertices[3].set(_ul3d[0], _lr3d[1], _lr3d[2]);
vertices[4].set(_lr3d[0], _ul3d[1], _ul3d[2]);
vertices[5].set(_lr3d[0], _ul3d[1], _lr3d[2]);
vertices[6].set(_lr3d[0], _lr3d[1], _ul3d[2]);
vertices[7].set(_lr3d[0], _lr3d[1], _lr3d[2]);
gbv->around(vertices, vertices + 8);
return bound;
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::do_rebuild
// Access: Private
// Description: Removes any existing children of the TextNode, and
// adds the newly generated text instead.
////////////////////////////////////////////////////////////////////
void TextNode::
do_rebuild() {
_flags &= ~(F_needs_rebuild | F_needs_measure);
_internal_geom = generate();
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::do_measure
// Access: Private
// Description: Can be called in lieu of do_rebuild() to measure the
// text and set up the bounding boxes properly without
// actually assembling it.
////////////////////////////////////////////////////////////////////
void TextNode::
do_measure() {
// We no longer make this a special case.
do_rebuild();
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::make_frame
// Access: Private
// Description: Creates a frame around the text.
////////////////////////////////////////////////////////////////////
PT(PandaNode) TextNode::
make_frame() {
PT(GeomNode) frame_node = new GeomNode("frame");
LVector4f dimensions = get_frame_actual();
float left = dimensions[0];
float right = dimensions[1];
float bottom = dimensions[2];
float top = dimensions[3];
CPT(RenderAttrib) thick = RenderModeAttrib::make(RenderModeAttrib::M_unchanged, _frame_width);
CPT(RenderState) state = RenderState::make(thick);
if (use_qpgeom) {
PT(qpGeomVertexData) vdata = new qpGeomVertexData
("text", qpGeomVertexFormat::get_v3(), get_usage_hint());
qpGeomVertexWriter vertex(vdata, InternalName::get_vertex());
vertex.add_data3f(left, 0.0f, top);
vertex.add_data3f(left, 0.0f, bottom);
vertex.add_data3f(right, 0.0f, bottom);
vertex.add_data3f(right, 0.0f, top);
PT(qpGeomLinestrips) frame = new qpGeomLinestrips(get_usage_hint());
frame->add_consecutive_vertices(0, 4);
frame->add_vertex(0);
frame->close_primitive();
PT(qpGeom) geom = new qpGeom;
geom->set_vertex_data(vdata);
geom->add_primitive(frame);
frame_node->add_geom(geom, state);
if (get_frame_corners()) {
PT(qpGeomPoints) corners = new qpGeomPoints(get_usage_hint());
corners->add_consecutive_vertices(0, 4);
PT(qpGeom) geom2 = new qpGeom;
geom2->set_vertex_data(vdata);
geom2->add_primitive(corners);
frame_node->add_geom(geom2, state);
}
} else {
GeomLinestrip *geoset = new GeomLinestrip;
PTA_int lengths=PTA_int::empty_array(0);
PTA_Vertexf verts;
lengths.push_back(5);
verts.push_back(Vertexf(left, 0.0f, top));
verts.push_back(Vertexf(left, 0.0f, bottom));
verts.push_back(Vertexf(right, 0.0f, bottom));
verts.push_back(Vertexf(right, 0.0f, top));
verts.push_back(Vertexf(left, 0.0f, top));
geoset->set_num_prims(1);
geoset->set_lengths(lengths);
geoset->set_coords(verts);
frame_node->add_geom(geoset, state);
if (get_frame_corners()) {
GeomPoint *geoset = new GeomPoint;
geoset->set_num_prims(4);
geoset->set_coords(verts);
frame_node->add_geom(geoset, state);
}
}
return frame_node.p();
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::make_card
// Access: Private
// Description: Creates a card behind the text.
////////////////////////////////////////////////////////////////////
PT(PandaNode) TextNode::
make_card() {
PT(GeomNode) card_node = new GeomNode("card");
LVector4f dimensions = get_card_actual();
float left = dimensions[0];
float right = dimensions[1];
float bottom = dimensions[2];
float top = dimensions[3];
if (use_qpgeom) {
PT(qpGeomVertexData) vdata = new qpGeomVertexData
("text", qpGeomVertexFormat::get_v3t2(), get_usage_hint());
qpGeomVertexWriter vertex(vdata, InternalName::get_vertex());
qpGeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
vertex.add_data3f(left, 0.0f, top);
vertex.add_data3f(left, 0.0f, bottom);
vertex.add_data3f(right, 0.0f, top);
vertex.add_data3f(right, 0.0f, bottom);
texcoord.add_data2f(0.0f, 1.0f);
texcoord.add_data2f(0.0f, 0.0f);
texcoord.add_data2f(1.0f, 1.0f);
texcoord.add_data2f(1.0f, 0.0f);
PT(qpGeomTristrips) card = new qpGeomTristrips(get_usage_hint());
card->add_consecutive_vertices(0, 4);
card->close_primitive();
PT(qpGeom) geom = new qpGeom;
geom->set_vertex_data(vdata);
geom->add_primitive(card);
card_node->add_geom(geom);
} else {
GeomTristrip *geoset = new GeomTristrip;
PTA_int lengths=PTA_int::empty_array(0);
lengths.push_back(4);
PTA_Vertexf verts;
verts.push_back(Vertexf(left, 0.02f, top));
verts.push_back(Vertexf(left, 0.02f, bottom));
verts.push_back(Vertexf(right, 0.02f, top));
verts.push_back(Vertexf(right, 0.02f, bottom));
geoset->set_num_prims(1);
geoset->set_lengths(lengths);
geoset->set_coords(verts);
if (has_card_texture()) {
PTA_TexCoordf uvs;
uvs.push_back(TexCoordf(0.0f, 1.0f));
uvs.push_back(TexCoordf(0.0f, 0.0f));
uvs.push_back(TexCoordf(1.0f, 1.0f));
uvs.push_back(TexCoordf(1.0f, 0.0f));
geoset->set_texcoords(uvs, G_PER_VERTEX);
}
card_node->add_geom(geoset);
}
return card_node.p();
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::make_card_with_border
// Access: Private
// Description: Creates a card behind the text with a specified border
// for button edge or what have you.
////////////////////////////////////////////////////////////////////
PT(PandaNode) TextNode::
make_card_with_border() {
PT(GeomNode) card_node = new GeomNode("card");
LVector4f dimensions = get_card_actual();
float left = dimensions[0];
float right = dimensions[1];
float bottom = dimensions[2];
float top = dimensions[3];
// we now create three tri-strips instead of one
// with vertices arranged as follows:
//
// 1 3 5 7 - one
// 2 4 6 8 / \ two
// 9 11 13 15 \ /
// 10 12 14 16 - three
//
if (use_qpgeom) {
PT(qpGeomVertexData) vdata = new qpGeomVertexData
("text", qpGeomVertexFormat::get_v3t2(), get_usage_hint());
qpGeomVertexWriter vertex(vdata, InternalName::get_vertex());
qpGeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
// verts 1,2,3,4
vertex.add_data3f(left, 0.02f, top);
vertex.add_data3f(left, 0.02f, top - _card_border_size);
vertex.add_data3f(left + _card_border_size, 0.02f, top);
vertex.add_data3f(left + _card_border_size, 0.02f,
top - _card_border_size);
// verts 5,6,7,8
vertex.add_data3f(right - _card_border_size, 0.02f, top);
vertex.add_data3f(right - _card_border_size, 0.02f,
top - _card_border_size);
vertex.add_data3f(right, 0.02f, top);
vertex.add_data3f(right, 0.02f, top - _card_border_size);
// verts 9,10,11,12
vertex.add_data3f(left, 0.02f, bottom + _card_border_size);
vertex.add_data3f(left, 0.02f, bottom);
vertex.add_data3f(left + _card_border_size, 0.02f,
bottom + _card_border_size);
vertex.add_data3f(left + _card_border_size, 0.02f, bottom);
// verts 13,14,15,16
vertex.add_data3f(right - _card_border_size, 0.02f,
bottom + _card_border_size);
vertex.add_data3f(right - _card_border_size, 0.02f, bottom);
vertex.add_data3f(right, 0.02f, bottom + _card_border_size);
vertex.add_data3f(right, 0.02f, bottom);
texcoord.add_data2f(0.0f, 1.0f); //1
texcoord.add_data2f(0.0f, 1.0f - _card_border_uv_portion); //2
texcoord.add_data2f(0.0f + _card_border_uv_portion, 1.0f); //3
texcoord.add_data2f(0.0f + _card_border_uv_portion,
1.0f - _card_border_uv_portion); //4
texcoord.add_data2f(1.0f -_card_border_uv_portion, 1.0f); //5
texcoord.add_data2f(1.0f -_card_border_uv_portion,
1.0f - _card_border_uv_portion); //6
texcoord.add_data2f(1.0f, 1.0f); //7
texcoord.add_data2f(1.0f, 1.0f - _card_border_uv_portion); //8
texcoord.add_data2f(0.0f, _card_border_uv_portion); //9
texcoord.add_data2f(0.0f, 0.0f); //10
texcoord.add_data2f(_card_border_uv_portion, _card_border_uv_portion); //11
texcoord.add_data2f(_card_border_uv_portion, 0.0f); //12
texcoord.add_data2f(1.0f - _card_border_uv_portion, _card_border_uv_portion);//13
texcoord.add_data2f(1.0f - _card_border_uv_portion, 0.0f);//14
texcoord.add_data2f(1.0f, _card_border_uv_portion);//15
texcoord.add_data2f(1.0f, 0.0f);//16
PT(qpGeomTristrips) card = new qpGeomTristrips(get_usage_hint());
// tristrip #1
card->add_consecutive_vertices(0, 8);
card->close_primitive();
// tristrip #2
card->add_vertex(1);
card->add_vertex(8);
card->add_vertex(3);
card->add_vertex(10);
card->add_vertex(5);
card->add_vertex(12);
card->add_vertex(7);
card->add_vertex(14);
card->close_primitive();
// tristrip #3
card->add_consecutive_vertices(8, 8);
card->close_primitive();
PT(qpGeom) geom = new qpGeom;
geom->set_vertex_data(vdata);
geom->add_primitive(card);
card_node->add_geom(geom);
} else {
GeomTristrip *geoset = new GeomTristrip;
PTA_int lengths;
lengths.push_back(8);
lengths.push_back(8);
lengths.push_back(8);
PTA_Vertexf verts;
// verts 1,2,3,4
verts.push_back(Vertexf(left, 0.02f, top));
verts.push_back(Vertexf(left, 0.02f, top - _card_border_size));
verts.push_back(Vertexf(left + _card_border_size, 0.02f, top));
verts.push_back(Vertexf(left + _card_border_size, 0.02f,
top - _card_border_size));
// verts 5,6,7,8
verts.push_back(Vertexf(right - _card_border_size, 0.02f, top));
verts.push_back(Vertexf(right - _card_border_size, 0.02f,
top - _card_border_size));
verts.push_back(Vertexf(right, 0.02f, top));
verts.push_back(Vertexf(right, 0.02f, top - _card_border_size));
// verts 9,10,11,12
verts.push_back(Vertexf(left, 0.02f, bottom + _card_border_size));
verts.push_back(Vertexf(left, 0.02f, bottom));
verts.push_back(Vertexf(left + _card_border_size, 0.02f,
bottom + _card_border_size));
verts.push_back(Vertexf(left + _card_border_size, 0.02f, bottom));
// verts 13,14,15,16
verts.push_back(Vertexf(right - _card_border_size, 0.02f,
bottom + _card_border_size));
verts.push_back(Vertexf(right - _card_border_size, 0.02f, bottom));
verts.push_back(Vertexf(right, 0.02f, bottom + _card_border_size));
verts.push_back(Vertexf(right, 0.02f, bottom));
PTA_ushort indices;
// tristrip #1
indices.push_back(0);
indices.push_back(1);
indices.push_back(2);
indices.push_back(3);
indices.push_back(4);
indices.push_back(5);
indices.push_back(6);
indices.push_back(7);
// tristrip #2
indices.push_back(1);
indices.push_back(8);
indices.push_back(3);
indices.push_back(10);
indices.push_back(5);
indices.push_back(12);
indices.push_back(7);
indices.push_back(14);
// tristrip #3
indices.push_back(8);
indices.push_back(9);
indices.push_back(10);
indices.push_back(11);
indices.push_back(12);
indices.push_back(13);
indices.push_back(14);
indices.push_back(15);
geoset->set_num_prims(3);
geoset->set_lengths(lengths);
geoset->set_coords(verts,indices);
if (has_card_texture()) {
PTA_TexCoordf uvs;
uvs.push_back(TexCoordf(0.0f, 1.0f)); //1
uvs.push_back(TexCoordf(0.0f, 1.0f - _card_border_uv_portion)); //2
uvs.push_back(TexCoordf(0.0f + _card_border_uv_portion, 1.0f)); //3
uvs.push_back(TexCoordf(0.0f + _card_border_uv_portion,
1.0f - _card_border_uv_portion)); //4
uvs.push_back(TexCoordf( 1.0f -_card_border_uv_portion, 1.0f)); //5
uvs.push_back(TexCoordf( 1.0f -_card_border_uv_portion,
1.0f - _card_border_uv_portion)); //6
uvs.push_back(TexCoordf(1.0f, 1.0f)); //7
uvs.push_back(TexCoordf(1.0f, 1.0f - _card_border_uv_portion)); //8
uvs.push_back(TexCoordf(0.0f, _card_border_uv_portion)); //9
uvs.push_back(TexCoordf(0.0f, 0.0f)); //10
uvs.push_back(TexCoordf(_card_border_uv_portion, _card_border_uv_portion)); //11
uvs.push_back(TexCoordf(_card_border_uv_portion, 0.0f)); //12
uvs.push_back(TexCoordf(1.0f - _card_border_uv_portion, _card_border_uv_portion));//13
uvs.push_back(TexCoordf(1.0f - _card_border_uv_portion, 0.0f));//14
uvs.push_back(TexCoordf(1.0f, _card_border_uv_portion));//15
uvs.push_back(TexCoordf(1.0f, 0.0f));//16
// we can use same ref's as before (same order)
geoset->set_texcoords(uvs, G_PER_VERTEX, indices);
}
card_node->add_geom(geoset);
}
return card_node.p();
}
////////////////////////////////////////////////////////////////////
// Function: TextNode::count_geoms
// Access: Private, Static
// Description: Recursively counts the number of Geoms at the
// indicated node and below. Strictly for reporting
// this count on output.
////////////////////////////////////////////////////////////////////
int TextNode::
count_geoms(PandaNode *node) {
int num_geoms = 0;
if (node->is_geom_node()) {
GeomNode *geom_node = DCAST(GeomNode, node);
num_geoms += geom_node->get_num_geoms();
}
Children children = node->get_children();
for (int i = 0; i < children.get_num_children(); ++i) {
num_geoms += count_geoms(children.get_child(i));
}
return num_geoms;
}