panda3d/panda/src/pgraph/cullableObject.cxx

425 lines
16 KiB
C++

// Filename: cullableObject.cxx
// Created by: drose (04Mar02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "cullableObject.h"
#include "textureAttrib.h"
#include "renderState.h"
#include "clockObject.h"
#include "cullTraverser.h"
#include "sceneSetup.h"
#include "lens.h"
#include "pStatTimer.h"
#include "qpgeomVertexWriter.h"
#include "qpgeomVertexReader.h"
#include "qpgeomTriangles.h"
PStatCollector CullableObject::_munge_points_pcollector("Cull:Munge:Points");
CullableObject *CullableObject::_deleted_chain = (CullableObject *)NULL;
int CullableObject::_num_ever_allocated = 0;
TypeHandle CullableObject::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: CullableObject::munge_geom
// Access: Public
// Description: Uses the indicated GeomMunger to transform the geom
// and/or its vertices.
////////////////////////////////////////////////////////////////////
void CullableObject::
munge_geom(GraphicsStateGuardianBase *gsg,
const qpGeomMunger *munger, const CullTraverser *traverser) {
if (_geom != (Geom *)NULL) {
// Temporary test and dcast until the experimental Geom rewrite
// becomes the actual Geom rewrite.
if (_geom->is_of_type(qpGeom::get_class_type())) {
_munger = munger;
CPT(qpGeom) qpgeom = DCAST(qpGeom, _geom);
_munged_data = qpgeom->get_vertex_data();
int geom_rendering = _state->get_geom_rendering(qpgeom->get_geom_rendering());
GraphicsStateGuardianBase *gsg = traverser->get_gsg();
int gsg_bits = gsg->get_supported_geom_rendering();
if (!hardware_point_sprites) {
// If support for hardware point sprites or perspective-scaled
// points is disabled, we don't allow the GSG to tell us it
// supports them.
gsg_bits &= ~(qpGeom::GR_point_perspective | qpGeom::GR_point_sprite);
}
int unsupported_bits = geom_rendering & ~gsg_bits;
if ((unsupported_bits & qpGeom::GR_point_bits) != 0) {
// The GSG doesn't support rendering these fancy points
// directly; we have to render them in software instead.
// Munge them into quads. This will replace the _geom and
// _munged_data, and might also replace _state.
munge_points_to_quads(traverser);
qpgeom = DCAST(qpGeom, _geom);
}
// Now invoke the munger to ensure the resulting geometry is in
// a GSG-friendly form.
qpgeom->munge_geom(munger, qpgeom, _munged_data);
CPT(qpGeomVertexData) animated_vertices =
_munged_data->animate_vertices_cull();
#ifndef NDEBUG
if (show_cpu_animation && animated_vertices != _munged_data) {
// These vertices were CPU-animated, so flash them.
static const double flash_rate = 1.0; // 1 state change per second
int cycle = (int)(ClockObject::get_global_clock()->get_frame_time() * flash_rate);
if ((cycle & 1) == 0) {
static Colorf flash_color(0.8f, 0.2f, 0.2f, 1.0f);
if (animated_vertices->has_color()) {
animated_vertices = animated_vertices->set_color(flash_color);
} else {
// We have to add a color column, which means we have to
// re-munge.
animated_vertices = animated_vertices->set_color
(flash_color, 1, qpGeom::NT_packed_dabc, qpGeom::C_color);
animated_vertices = munger->munge_data(animated_vertices);
}
_state = _state->remove_attrib(TextureAttrib::get_class_type());
}
}
#endif
_munged_data = animated_vertices;
_geom = qpgeom;
}
}
if (_next != (CullableObject *)NULL) {
if (_next->_state != (RenderState *)NULL) {
_next->munge_geom(gsg, gsg->get_geom_munger(_next->_state), traverser);
} else {
_next->munge_geom(gsg, munger, traverser);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullableObject::Destructor
// Access: Public
// Description: Automatically deletes the whole chain of these things.
////////////////////////////////////////////////////////////////////
CullableObject::
~CullableObject() {
if (_next != (CullableObject *)NULL) {
delete _next;
}
}
////////////////////////////////////////////////////////////////////
// Function: CullableObject::output
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void CullableObject::
output(ostream &out) const {
if (_geom != (Geom *)NULL) {
out << *_geom;
} else {
out << "(null)";
}
}
////////////////////////////////////////////////////////////////////
// Function: CullableObject::munge_points_to_quads
// Access: Private
// Description: Converts a table of points to quads for rendering on
// systems that don't support fancy points.
//
// This may replace _geom, _munged_data, and _state.
////////////////////////////////////////////////////////////////////
void CullableObject::
munge_points_to_quads(const CullTraverser *traverser) {
PStatTimer timer(_munge_points_pcollector);
GraphicsStateGuardianBase *gsg = traverser->get_gsg();
CPT(qpGeom) qpgeom = DCAST(qpGeom, _geom);
qpGeomVertexReader vertex(_munged_data, InternalName::get_vertex());
qpGeomVertexReader normal(_munged_data, InternalName::get_normal());
qpGeomVertexReader color(_munged_data, InternalName::get_color());
qpGeomVertexReader texcoord(_munged_data, InternalName::get_texcoord());
qpGeomVertexReader rotate(_munged_data, InternalName::get_rotate());
qpGeomVertexReader size(_munged_data, InternalName::get_size());
qpGeomVertexReader aspect_ratio(_munged_data, InternalName::get_aspect_ratio());
bool has_normal = (normal.has_column());
bool has_color = (color.has_column());
bool has_texcoord = (texcoord.has_column());
bool has_rotate = (rotate.has_column());
bool has_size = (size.has_column());
bool has_aspect_ratio = (aspect_ratio.has_column());
bool sprite_texcoord = false;
const TexGenAttrib *tex_gen = _state->get_tex_gen();
if (tex_gen != (TexGenAttrib *)NULL) {
if (tex_gen->get_mode(TextureStage::get_default()) == TexGenAttrib::M_point_sprite) {
sprite_texcoord = true;
// Turn off the TexGenAttrib, since we don't want it now.
_state = _state->add_attrib(tex_gen->remove_stage(TextureStage::get_default()));
}
}
PT(qpGeomVertexArrayFormat) new_array_format =
new qpGeomVertexArrayFormat(InternalName::get_vertex(), 4,
qpGeom::NT_float32,
qpGeom::C_clip_point);
if (has_normal) {
const qpGeomVertexColumn *c = normal.get_column();
new_array_format->add_column
(InternalName::get_normal(), c->get_num_components(),
c->get_numeric_type(), c->get_contents());
}
if (has_color) {
const qpGeomVertexColumn *c = color.get_column();
new_array_format->add_column
(InternalName::get_color(), c->get_num_components(),
c->get_numeric_type(), c->get_contents());
}
if (sprite_texcoord) {
new_array_format->add_column
(InternalName::get_texcoord(), 2,
qpGeom::NT_float32,
qpGeom::C_texcoord);
} else if (has_texcoord) {
const qpGeomVertexColumn *c = texcoord.get_column();
new_array_format->add_column
(InternalName::get_texcoord(), c->get_num_components(),
c->get_numeric_type(), c->get_contents());
}
CPT(qpGeomVertexFormat) new_format =
qpGeomVertexFormat::register_format(new_array_format);
PT(qpGeomVertexData) new_data = new qpGeomVertexData
(_munged_data->get_name(), new_format, qpGeom::UH_client);
qpGeomVertexWriter new_vertex(new_data, InternalName::get_vertex());
qpGeomVertexWriter new_normal(new_data, InternalName::get_normal());
qpGeomVertexWriter new_color(new_data, InternalName::get_color());
qpGeomVertexWriter new_texcoord(new_data, InternalName::get_texcoord());
int new_vi = 0;
PT(qpGeom) new_geom = new qpGeom();
new_geom->set_vertex_data(new_data);
const LMatrix4f &modelview = _transform->get_mat();
SceneSetup *scene = traverser->get_scene();
const Lens *lens = scene->get_lens();
const LMatrix4f &lens_mat = lens->get_projection_mat();
LMatrix4f projection =
LMatrix4f::convert_mat(gsg->get_internal_coordinate_system(),
lens->get_coordinate_system()) *
lens_mat;
LMatrix4f render_transform;
if (has_normal) {
render_transform = modelview * projection;
}
int viewport_width = scene->get_viewport_width();
int viewport_height = scene->get_viewport_height();
float point_size = 1.0f;
bool perspective = false;
const RenderModeAttrib *render_mode = _state->get_render_mode();
if (render_mode != (RenderModeAttrib *)NULL) {
point_size = render_mode->get_thickness();
perspective = render_mode->get_perspective();
if (render_mode->get_mode() != RenderModeAttrib::M_filled) {
// Be sure to turn on polygon render mode, since we're actually
// rendering polygons, not points any more.
_state = _state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_filled));
}
}
// Replace each primitive in the Geom (it's presumably a GeomPoints
// primitive, although it might be some other kind of primitive if
// we got here because RenderModeAttrib::M_point is enabled) with a
// new primitive that replaces each vertex with a quad of the
// appropriate scale and orientation.
// BUG: if we're rendering polygons in M_point mode with a
// CullFaceAttrib in effect, we won't actually apply the
// CullFaceAttrib but will always render all of the vertices of the
// polygons. This is certainly a bug, but in order to fix it we'd
// have to do the face culling ourselves--not sure if it's worth it.
int num_primitives = qpgeom->get_num_primitives();
for (int pi = 0; pi < num_primitives; ++pi) {
const qpGeomPrimitive *primitive = qpgeom->get_primitive(pi);
// We must first convert all of the points to eye space.
int num_points = primitive->get_max_vertex() + 1;
int num_vertices = primitive->get_num_vertices();
PointData *points = (PointData *)alloca(num_points * sizeof(PointData));
unsigned int *vertices = (unsigned int *)alloca(num_vertices * sizeof(unsigned int));
unsigned int *vertices_end = vertices + num_vertices;
if (primitive->is_indexed()) {
qpGeomVertexReader index(primitive->get_vertices(), 0);
for (unsigned int *vi = vertices; vi != vertices_end; ++vi) {
// Get the point in eye-space coordinates.
unsigned int v = index.get_data1i();
nassertv(v < (unsigned int)num_points);
(*vi) = v;
vertex.set_row(v);
points[v]._eye = modelview.xform_point(vertex.get_data3f());
points[v]._dist = gsg->compute_distance_to(points[v]._eye);
}
} else {
// Nonindexed case.
unsigned int first_vertex = primitive->get_first_vertex();
for (int i = 0; i < num_vertices; ++i) {
unsigned int v = i + first_vertex;
nassertv(v < (unsigned int)num_points);
vertices[i] = v;
vertex.set_row(i + first_vertex);
points[v]._eye = modelview.xform_point(vertex.get_data3f());
points[v]._dist = gsg->compute_distance_to(points[v]._eye);
}
}
// Now sort the points in order from back-to-front so they will
// render properly with transparency, at least with each other.
sort(vertices, vertices_end, SortPoints(points));
// Go through the points, now in sorted order, and generate a pair
// of triangles for each one. We generate indexed triangles
// instead of two-triangle strips, since this seems to be
// generally faster on PC hardware (otherwise, we'd have to nearly
// double the vertices to stitch all the little triangle strips
// together).
PT(qpGeomPrimitive) new_primitive = new qpGeomTriangles(qpGeom::UH_client);
for (unsigned int *vi = vertices; vi != vertices_end; ++vi) {
// The point in eye coordinates.
const LPoint3f &eye = points[*vi]._eye;
// The point in clip coordinates.
LPoint4f p4 = LPoint4f(eye[0], eye[1], eye[2], 1.0f) * projection;
if (has_size) {
size.set_row(*vi);
point_size = size.get_data1f();
}
float scale_y = point_size;
if (perspective) {
// Perspective-sized points. Here point_size is a width in 3-d
// units. To arrange that, we need to figure out the appropriate
// scaling factor based on the current viewport and projection
// matrix.
LVector3f height(0.0f, point_size, 1.0f);
height = height * projection;
scale_y = height[1] * viewport_height;
// We should then divide the radius by the distance from the
// camera plane, to emulate the glPointParameters() behavior.
scale_y /= gsg->compute_distance_to(eye);
}
// Also factor in the homogeneous scale for being in clip
// coordinates still.
scale_y *= p4[3];
float scale_x = scale_y;
if (has_aspect_ratio) {
aspect_ratio.set_row(*vi);
scale_x *= aspect_ratio.get_data1f();
}
// Define the first two corners based on the scales in X and Y.
LPoint2f c0(scale_x, scale_y);
LPoint2f c1(-scale_x, scale_y);
if (has_rotate) {
// If we have a rotate factor, apply it to those two corners.
rotate.set_row(*vi);
float r = rotate.get_data1f();
LMatrix3f mat = LMatrix3f::rotate_mat(r);
c0 = c0 * mat;
c1 = c1 * mat;
}
// Finally, scale the corners in their newly-rotated position,
// to compensate for the aspect ratio of the viewport.
float rx = 1.0f / viewport_width;
float ry = 1.0f / viewport_height;
c0.set(c0[0] * rx, c0[1] * ry);
c1.set(c1[0] * rx, c1[1] * ry);
new_vertex.add_data4f(p4[0] + c0[0], p4[1] + c0[1], p4[2], p4[3]);
new_vertex.add_data4f(p4[0] + c1[0], p4[1] + c1[1], p4[2], p4[3]);
new_vertex.add_data4f(p4[0] - c1[0], p4[1] - c1[1], p4[2], p4[3]);
new_vertex.add_data4f(p4[0] - c0[0], p4[1] - c0[1], p4[2], p4[3]);
if (has_normal) {
normal.set_row(*vi);
Normalf c = render_transform.xform_vec(normal.get_data3f());
new_normal.add_data3f(c);
new_normal.add_data3f(c);
new_normal.add_data3f(c);
new_normal.add_data3f(c);
}
if (has_color) {
color.set_row(*vi);
const Colorf &c = color.get_data4f();
new_color.add_data4f(c);
new_color.add_data4f(c);
new_color.add_data4f(c);
new_color.add_data4f(c);
}
if (sprite_texcoord) {
new_texcoord.add_data2f(1.0f, 0.0f);
new_texcoord.add_data2f(0.0f, 0.0f);
new_texcoord.add_data2f(1.0f, 1.0f);
new_texcoord.add_data2f(0.0f, 1.0f);
} else if (has_texcoord) {
texcoord.set_row(*vi);
const LVecBase4f &c = texcoord.get_data4f();
new_texcoord.add_data4f(c);
new_texcoord.add_data4f(c);
new_texcoord.add_data4f(c);
new_texcoord.add_data4f(c);
}
new_primitive->add_vertex(new_vi);
new_primitive->add_vertex(new_vi + 1);
new_primitive->add_vertex(new_vi + 2);
new_primitive->close_primitive();
new_primitive->add_vertex(new_vi + 2);
new_primitive->add_vertex(new_vi + 1);
new_primitive->add_vertex(new_vi + 3);
new_primitive->close_primitive();
new_vi += 4;
}
new_geom->add_primitive(new_primitive);
}
_geom = new_geom.p();
_munged_data = new_data;
}