1017 lines
35 KiB
C++
1017 lines
35 KiB
C++
// Filename: textNode.cxx
|
|
// Created by: drose (13Mar02)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
//
|
|
// All use of this software is subject to the terms of the revised BSD
|
|
// license. You should have received a copy of this license along
|
|
// with this source code in a file named "LICENSE."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "textNode.h"
|
|
#include "textGlyph.h"
|
|
#include "stringDecoder.h"
|
|
#include "config_text.h"
|
|
#include "textAssembler.h"
|
|
|
|
#include "compose_matrix.h"
|
|
#include "geom.h"
|
|
#include "geomLinestrips.h"
|
|
#include "geomPoints.h"
|
|
#include "geomTristrips.h"
|
|
#include "geomVertexWriter.h"
|
|
#include "geomNode.h"
|
|
#include "pnotify.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 "decalEffect.h"
|
|
#include "dcast.h"
|
|
#include "bamFile.h"
|
|
#include "zStream.h"
|
|
#include "pStatCollector.h"
|
|
#include "pStatTimer.h"
|
|
#include "boundingSphere.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
TypeHandle TextNode::_type_handle;
|
|
|
|
PStatCollector TextNode::_text_generate_pcollector("*:Generate Text");
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::Constructor
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
TextNode::
|
|
TextNode(const string &name) : PandaNode(name) {
|
|
set_cull_callback();
|
|
|
|
_flags = 0;
|
|
_max_rows = 0;
|
|
_usage_hint = GeomEnums::UH_static;
|
|
_flatten_flags = 0;
|
|
if (text_flatten) {
|
|
_flatten_flags |= FF_strong;
|
|
}
|
|
if (text_dynamic_merge) {
|
|
_flatten_flags |= FF_dynamic_merge;
|
|
}
|
|
|
|
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 = LMatrix4::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 ©) :
|
|
PandaNode(name), TextProperties(copy)
|
|
{
|
|
_flags = 0;
|
|
_max_rows = 0;
|
|
_usage_hint = GeomEnums::UH_static;
|
|
|
|
_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 = LMatrix4::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: OK, this is a true copy constructor.
|
|
////////////////////////////////////////////////////////////////////
|
|
TextNode::
|
|
TextNode(const TextNode ©) :
|
|
PandaNode(copy),
|
|
TextEncoder(copy),
|
|
TextProperties(copy),
|
|
_card_texture(copy._card_texture),
|
|
_frame_color(copy._frame_color),
|
|
_card_color(copy._card_color),
|
|
_flags(copy._flags),
|
|
_max_rows(copy._max_rows),
|
|
_usage_hint(GeomEnums::UH_static),
|
|
_frame_width(copy._frame_width),
|
|
_card_border_size(copy._card_border_size),
|
|
_card_border_uv_portion(copy._card_border_uv_portion),
|
|
_frame_ul(copy._frame_ul),
|
|
_frame_lr(copy._frame_lr),
|
|
_card_ul(copy._card_ul),
|
|
_card_lr(copy._card_lr),
|
|
_transform(copy._transform),
|
|
_coordinate_system(copy._coordinate_system),
|
|
_ul3d(copy._ul3d),
|
|
_lr3d(copy._lr3d)
|
|
{
|
|
invalidate_with_measure();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::make_copy
|
|
// Access: Protected, Virtual
|
|
// Description: Returns a newly-allocated Node that is a shallow copy
|
|
// of this one. It will be a different Node pointer,
|
|
// but its internal data may or may not be shared with
|
|
// that of the original Node.
|
|
////////////////////////////////////////////////////////////////////
|
|
PandaNode *TextNode::
|
|
make_copy() const {
|
|
return new TextNode(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// 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).
|
|
////////////////////////////////////////////////////////////////////
|
|
PN_stdfloat TextNode::
|
|
calc_width(wchar_t character) const {
|
|
TextFont *font = get_font();
|
|
if (font == (TextFont *)NULL) {
|
|
return 0.0f;
|
|
}
|
|
|
|
return TextAssembler::calc_width(character, *this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::has_exact_character
|
|
// Access: Published
|
|
// Description: Returns true if the named character exists in the
|
|
// font exactly as named, false otherwise. Note that
|
|
// because Panda can assemble glyphs together
|
|
// automatically using cheesy accent marks, this is not
|
|
// a reliable indicator of whether a suitable glyph can
|
|
// be rendered for the character. For that, use
|
|
// has_character() instead.
|
|
//
|
|
// This returns true for whitespace and Unicode
|
|
// whitespace characters (if they exist in the font),
|
|
// but returns false for characters that would render
|
|
// with the "invalid glyph". It also returns false for
|
|
// characters that would be synthesized within Panda,
|
|
// but see has_character().
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextNode::
|
|
has_exact_character(wchar_t character) const {
|
|
TextFont *font = get_font();
|
|
if (font == (TextFont *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
return TextAssembler::has_exact_character(character, *this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::has_character
|
|
// Access: Published
|
|
// Description: Returns true if the named character exists in the
|
|
// font or can be synthesized by Panda, false otherwise.
|
|
// (Panda can synthesize some accented characters by
|
|
// combining similar-looking glyphs from the font.)
|
|
//
|
|
// This returns true for whitespace and Unicode
|
|
// whitespace characters (if they exist in the font),
|
|
// but returns false for characters that would render
|
|
// with the "invalid glyph".
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextNode::
|
|
has_character(wchar_t character) const {
|
|
TextFont *font = get_font();
|
|
if (font == (TextFont *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
return TextAssembler::has_character(character, *this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::is_whitespace
|
|
// Access: Published
|
|
// Description: Returns true if the indicated character represents
|
|
// whitespace in the font, or false if anything visible
|
|
// will be rendered for it.
|
|
//
|
|
// This returns true for whitespace and Unicode
|
|
// whitespace characters (if they exist in the font),
|
|
// and returns false for any other characters, including
|
|
// characters that do not exist in the font (these would
|
|
// be rendered with the "invalid glyph", which is
|
|
// visible).
|
|
//
|
|
// Note that this function can be reliably used to
|
|
// identify Unicode whitespace characters only if the
|
|
// font has all of the whitespace characters defined.
|
|
// It will return false for any character not in the
|
|
// font, even if it is an official Unicode whitespace
|
|
// character.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextNode::
|
|
is_whitespace(wchar_t character) const {
|
|
TextFont *font = get_font();
|
|
if (font == (TextFont *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
return TextAssembler::is_whitespace(character, *this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::calc_width
|
|
// Access: Published
|
|
// 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.
|
|
////////////////////////////////////////////////////////////////////
|
|
PN_stdfloat TextNode::
|
|
calc_width(const wstring &line) const {
|
|
PN_stdfloat width = 0.0f;
|
|
|
|
wstring::const_iterator si;
|
|
for (si = line.begin(); si != line.end(); ++si) {
|
|
width += calc_width(*si);
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// 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 {
|
|
PandaNode::write(out, indent_level);
|
|
TextProperties::write(out, indent_level + 2);
|
|
indent(out, indent_level + 2)
|
|
<< "transform is: " << *TransformState::make_mat(_transform) << "\n";
|
|
indent(out, indent_level + 2)
|
|
<< "in coordinate system " << _coordinate_system << "\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() {
|
|
PStatTimer timer(_text_generate_pcollector);
|
|
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.
|
|
string name = get_text();
|
|
size_t newline = name.find('\n');
|
|
if (newline != string::npos) {
|
|
name = name.substr(0, newline);
|
|
}
|
|
PT(PandaNode) root = new PandaNode(name);
|
|
|
|
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.
|
|
LMatrix4 mat =
|
|
LMatrix4::convert_mat(CS_zup_right, _coordinate_system) *
|
|
_transform;
|
|
|
|
CPT(TransformState) transform = TransformState::make_mat(mat);
|
|
root->set_transform(transform);
|
|
|
|
wstring wtext = get_wtext();
|
|
|
|
// Assemble the text.
|
|
TextAssembler assembler(this);
|
|
assembler.set_properties(*this);
|
|
assembler.set_max_rows(_max_rows);
|
|
assembler.set_usage_hint(_usage_hint);
|
|
assembler.set_dynamic_merge((_flatten_flags & FF_dynamic_merge) != 0);
|
|
bool all_set = assembler.set_wtext(wtext);
|
|
if (all_set) {
|
|
// No overflow.
|
|
_flags &= ~F_has_overflow;
|
|
} else {
|
|
// Overflow.
|
|
_flags |= F_has_overflow;
|
|
}
|
|
|
|
PT(PandaNode) text_root = assembler.assemble_text();
|
|
_text_ul = assembler.get_ul();
|
|
_text_lr = assembler.get_lr();
|
|
_num_rows = assembler.get_num_rows();
|
|
_wordwrapped_wtext = assembler.get_wordwrapped_wtext();
|
|
|
|
// Parent the text in.
|
|
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 LVector2 &ul = assembler.get_ul();
|
|
const LVector2 &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 flatten our hierarchy to get rid of the transforms we put in,
|
|
// applying them to the vertices.
|
|
|
|
NodePath root_np(root);
|
|
if (_flatten_flags & FF_strong) {
|
|
root_np.flatten_strong();
|
|
} else if (_flatten_flags & FF_medium) {
|
|
root_np.flatten_medium();
|
|
} else if (_flatten_flags & FF_light) {
|
|
root_np.flatten_light();
|
|
}
|
|
|
|
// Now deal with the decorations.
|
|
|
|
if (has_card()) {
|
|
PT(PandaNode) card_root;
|
|
if (has_card_border()) {
|
|
card_root = make_card_with_border();
|
|
} else {
|
|
card_root = make_card();
|
|
}
|
|
card_root->set_transform(transform);
|
|
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()));
|
|
}
|
|
|
|
// We always apply attribs down to the card vertices.
|
|
SceneGraphReducer gr;
|
|
gr.apply_attribs(card_root);
|
|
|
|
// In order to decal the text onto the card, the card must
|
|
// become the parent of the text.
|
|
card_root->add_child(root);
|
|
root = card_root;
|
|
|
|
if (get_card_decal()) {
|
|
card_root->set_effect(DecalEffect::make());
|
|
}
|
|
}
|
|
|
|
if (has_frame()) {
|
|
PT(PandaNode) frame_root = make_frame();
|
|
frame_root->set_transform(transform);
|
|
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));
|
|
}
|
|
|
|
SceneGraphReducer gr;
|
|
gr.apply_attribs(frame_root);
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::get_internal_geom
|
|
// Access: Published
|
|
// Description: Returns the actual node that is used internally to
|
|
// render the text, if the TextNode is parented within
|
|
// the scene graph.
|
|
//
|
|
// In general, you should not call this method. Call
|
|
// generate() instead if you want to get a handle to
|
|
// geometry that represents the text. This method is
|
|
// provided as a debugging aid only.
|
|
////////////////////////////////////////////////////////////////////
|
|
PandaNode *TextNode::
|
|
get_internal_geom() const {
|
|
// Output a nuisance warning to discourage the naive from calling
|
|
// this method accidentally.
|
|
text_cat.info()
|
|
<< "TextNode::get_internal_geom() called.\n";
|
|
check_rebuild();
|
|
return _internal_geom;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// 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 LMatrix4 &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 LColor &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 LVecBase4 &s = csa->get_scale();
|
|
if (s != LVecBase4(1.0f, 1.0f, 1.0f, 1.0f)) {
|
|
LVecBase4 tc = get_text_color();
|
|
tc[0] *= s[0];
|
|
tc[1] *= s[1];
|
|
tc[2] *= s[2];
|
|
tc[3] *= s[3];
|
|
set_text_color(tc);
|
|
LVecBase4 sc = get_shadow_color();
|
|
sc[0] *= s[0];
|
|
sc[1] *= s[1];
|
|
sc[2] *= s[2];
|
|
sc[3] *= s[3];
|
|
set_shadow_color(sc);
|
|
LVecBase4 fc = get_frame_color();
|
|
fc[0] *= s[0];
|
|
fc[1] *= s[1];
|
|
fc[2] *= s[2];
|
|
fc[3] *= s[3];
|
|
set_frame_color(fc);
|
|
LVecBase4 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(LPoint3 &min_point, LPoint3 &max_point, bool &found_any,
|
|
const TransformState *transform, Thread *current_thread) const {
|
|
CPT(TransformState) next_transform =
|
|
PandaNode::calc_tight_bounds(min_point, max_point, found_any, transform,
|
|
current_thread);
|
|
|
|
check_rebuild();
|
|
|
|
if (_internal_geom != (PandaNode *)NULL) {
|
|
_internal_geom->calc_tight_bounds(min_point, max_point,
|
|
found_any, next_transform, current_thread);
|
|
}
|
|
|
|
return next_transform;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::cull_callback
|
|
// Access: Protected, Virtual
|
|
// Description: 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.
|
|
//
|
|
// Note that this function will *not* be called unless
|
|
// set_cull_callback() is called in the constructor of
|
|
// the derived class. It is necessary to call
|
|
// set_cull_callback() to indicated that we require
|
|
// cull_callback() to be called.
|
|
//
|
|
// 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::is_renderable
|
|
// Access: Public, Virtual
|
|
// Description: Returns true if there is some value to visiting this
|
|
// particular node during the cull traversal for any
|
|
// camera, false otherwise. This will be used to
|
|
// optimize the result of get_net_draw_show_mask(), so
|
|
// that any subtrees that contain only nodes for which
|
|
// is_renderable() is false need not be visited.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool TextNode::
|
|
is_renderable() const {
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::compute_internal_bounds
|
|
// 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.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextNode::
|
|
compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
|
|
int &internal_vertices,
|
|
int pipeline_stage,
|
|
Thread *current_thread) const {
|
|
// First, get ourselves a fresh, empty bounding volume.
|
|
PT(BoundingVolume) bound = new BoundingSphere;
|
|
|
|
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();
|
|
|
|
LPoint3 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);
|
|
|
|
internal_bounds = bound;
|
|
internal_vertices = 0; // TODO: estimate this better.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: TextNode::r_prepare_scene
|
|
// Access: Protected, Virtual
|
|
// Description: The recursive implementation of prepare_scene().
|
|
// Don't call this directly; call
|
|
// PandaNode::prepare_scene() or
|
|
// NodePath::prepare_scene() instead.
|
|
////////////////////////////////////////////////////////////////////
|
|
void TextNode::
|
|
r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
|
|
GeomTransformer &transformer, Thread *current_thread) {
|
|
check_rebuild();
|
|
|
|
PandaNode *child = _internal_geom;
|
|
if (child != (PandaNode *)NULL) {
|
|
CPT(RenderState) child_state = node_state->compose(child->get_state());
|
|
child->r_prepare_scene(gsg, child_state, transformer, current_thread);
|
|
}
|
|
|
|
PandaNode::r_prepare_scene(gsg, node_state, transformer, current_thread);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// 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");
|
|
|
|
LVector4 dimensions = get_frame_actual();
|
|
PN_stdfloat left = dimensions[0];
|
|
PN_stdfloat right = dimensions[1];
|
|
PN_stdfloat bottom = dimensions[2];
|
|
PN_stdfloat top = dimensions[3];
|
|
|
|
CPT(RenderAttrib) thick = RenderModeAttrib::make(RenderModeAttrib::M_unchanged, _frame_width);
|
|
CPT(RenderState) state = RenderState::make(thick);
|
|
|
|
PT(GeomVertexData) vdata = new GeomVertexData
|
|
("text", GeomVertexFormat::get_v3(), get_usage_hint());
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
|
|
vertex.add_data3(left, 0.0f, top);
|
|
vertex.add_data3(left, 0.0f, bottom);
|
|
vertex.add_data3(right, 0.0f, bottom);
|
|
vertex.add_data3(right, 0.0f, top);
|
|
|
|
PT(GeomLinestrips) frame = new GeomLinestrips(get_usage_hint());
|
|
frame->add_consecutive_vertices(0, 4);
|
|
frame->add_vertex(0);
|
|
frame->close_primitive();
|
|
|
|
PT(Geom) geom = new Geom(vdata);
|
|
geom->add_primitive(frame);
|
|
frame_node->add_geom(geom, state);
|
|
|
|
if (get_frame_corners()) {
|
|
PT(GeomPoints) corners = new GeomPoints(get_usage_hint());
|
|
corners->add_consecutive_vertices(0, 4);
|
|
PT(Geom) geom2 = new Geom(vdata);
|
|
geom2->add_primitive(corners);
|
|
frame_node->add_geom(geom2, 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");
|
|
|
|
LVector4 dimensions = get_card_actual();
|
|
PN_stdfloat left = dimensions[0];
|
|
PN_stdfloat right = dimensions[1];
|
|
PN_stdfloat bottom = dimensions[2];
|
|
PN_stdfloat top = dimensions[3];
|
|
|
|
PT(GeomVertexData) vdata = new GeomVertexData
|
|
("text", GeomVertexFormat::get_v3t2(), get_usage_hint());
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
|
|
|
|
vertex.add_data3(left, 0.0f, top);
|
|
vertex.add_data3(left, 0.0f, bottom);
|
|
vertex.add_data3(right, 0.0f, top);
|
|
vertex.add_data3(right, 0.0f, bottom);
|
|
|
|
texcoord.add_data2(0.0f, 1.0f);
|
|
texcoord.add_data2(0.0f, 0.0f);
|
|
texcoord.add_data2(1.0f, 1.0f);
|
|
texcoord.add_data2(1.0f, 0.0f);
|
|
|
|
PT(GeomTristrips) card = new GeomTristrips(get_usage_hint());
|
|
card->add_consecutive_vertices(0, 4);
|
|
card->close_primitive();
|
|
|
|
PT(Geom) geom = new Geom(vdata);
|
|
geom->add_primitive(card);
|
|
|
|
card_node->add_geom(geom);
|
|
|
|
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");
|
|
|
|
LVector4 dimensions = get_card_actual();
|
|
PN_stdfloat left = dimensions[0];
|
|
PN_stdfloat right = dimensions[1];
|
|
PN_stdfloat bottom = dimensions[2];
|
|
PN_stdfloat 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
|
|
//
|
|
|
|
PT(GeomVertexData) vdata = new GeomVertexData
|
|
("text", GeomVertexFormat::get_v3t2(), get_usage_hint());
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
|
|
|
|
// verts 1,2,3,4
|
|
vertex.add_data3(left, 0.02, top);
|
|
vertex.add_data3(left, 0.02, top - _card_border_size);
|
|
vertex.add_data3(left + _card_border_size, 0.02, top);
|
|
vertex.add_data3(left + _card_border_size, 0.02,
|
|
top - _card_border_size);
|
|
// verts 5,6,7,8
|
|
vertex.add_data3(right - _card_border_size, 0.02, top);
|
|
vertex.add_data3(right - _card_border_size, 0.02,
|
|
top - _card_border_size);
|
|
vertex.add_data3(right, 0.02, top);
|
|
vertex.add_data3(right, 0.02, top - _card_border_size);
|
|
// verts 9,10,11,12
|
|
vertex.add_data3(left, 0.02, bottom + _card_border_size);
|
|
vertex.add_data3(left, 0.02, bottom);
|
|
vertex.add_data3(left + _card_border_size, 0.02,
|
|
bottom + _card_border_size);
|
|
vertex.add_data3(left + _card_border_size, 0.02, bottom);
|
|
// verts 13,14,15,16
|
|
vertex.add_data3(right - _card_border_size, 0.02,
|
|
bottom + _card_border_size);
|
|
vertex.add_data3(right - _card_border_size, 0.02, bottom);
|
|
vertex.add_data3(right, 0.02, bottom + _card_border_size);
|
|
vertex.add_data3(right, 0.02, bottom);
|
|
|
|
texcoord.add_data2(0.0f, 1.0f); //1
|
|
texcoord.add_data2(0.0f, 1.0f - _card_border_uv_portion); //2
|
|
texcoord.add_data2(0.0f + _card_border_uv_portion, 1.0f); //3
|
|
texcoord.add_data2(0.0f + _card_border_uv_portion,
|
|
1.0f - _card_border_uv_portion); //4
|
|
texcoord.add_data2(1.0f -_card_border_uv_portion, 1.0f); //5
|
|
texcoord.add_data2(1.0f -_card_border_uv_portion,
|
|
1.0f - _card_border_uv_portion); //6
|
|
texcoord.add_data2(1.0f, 1.0f); //7
|
|
texcoord.add_data2(1.0f, 1.0f - _card_border_uv_portion); //8
|
|
|
|
texcoord.add_data2(0.0f, _card_border_uv_portion); //9
|
|
texcoord.add_data2(0.0f, 0.0f); //10
|
|
texcoord.add_data2(_card_border_uv_portion, _card_border_uv_portion); //11
|
|
texcoord.add_data2(_card_border_uv_portion, 0.0f); //12
|
|
|
|
texcoord.add_data2(1.0f - _card_border_uv_portion, _card_border_uv_portion);//13
|
|
texcoord.add_data2(1.0f - _card_border_uv_portion, 0.0f);//14
|
|
texcoord.add_data2(1.0f, _card_border_uv_portion);//15
|
|
texcoord.add_data2(1.0f, 0.0f);//16
|
|
|
|
PT(GeomTristrips) card = new GeomTristrips(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(Geom) geom = new Geom(vdata);
|
|
geom->add_primitive(card);
|
|
|
|
card_node->add_geom(geom);
|
|
|
|
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;
|
|
}
|