988 lines
35 KiB
C++
988 lines
35 KiB
C++
// Filename: ropeNode.cxx
|
|
// Created by: drose (04Dec02)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 "ropeNode.h"
|
|
#include "cullTraverser.h"
|
|
#include "cullTraverserData.h"
|
|
#include "cullableObject.h"
|
|
#include "cullHandler.h"
|
|
#include "renderState.h"
|
|
#include "renderModeAttrib.h"
|
|
#include "colorAttrib.h"
|
|
#include "bamWriter.h"
|
|
#include "bamReader.h"
|
|
#include "datagram.h"
|
|
#include "datagramIterator.h"
|
|
#include "pStatTimer.h"
|
|
#include "geom.h"
|
|
#include "geomLines.h"
|
|
#include "geomTristrips.h"
|
|
#include "geomVertexWriter.h"
|
|
#include "boundingSphere.h"
|
|
|
|
TypeHandle RopeNode::_type_handle;
|
|
|
|
PStatCollector RopeNode::_rope_node_pcollector("*:RopeNode");
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::CData::make_copy
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
CycleData *RopeNode::CData::
|
|
make_copy() const {
|
|
return new CData(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::CData::write_datagram
|
|
// Access: Public, Virtual
|
|
// Description: Writes the contents of this object to the datagram
|
|
// for shipping out to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::CData::
|
|
write_datagram(BamWriter *writer, Datagram &dg) const {
|
|
// For now, we write a NULL pointer. Eventually we will write out
|
|
// the NurbsCurveEvaluator pointer.
|
|
writer->write_pointer(dg, (TypedWritable *)NULL);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::CData::fillin
|
|
// Access: Public, Virtual
|
|
// Description: This internal function is called by make_from_bam to
|
|
// read in all of the relevant data from the BamFile for
|
|
// the new RopeNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::CData::
|
|
fillin(DatagramIterator &scan, BamReader *reader) {
|
|
// For now, we skip over the NULL pointer that we wrote out.
|
|
reader->skip_pointer(scan);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
RopeNode::
|
|
RopeNode(const string &name) :
|
|
PandaNode(name)
|
|
{
|
|
set_cull_callback();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::Copy Constructor
|
|
// Access: Protected
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
RopeNode::
|
|
RopeNode(const RopeNode ©) :
|
|
PandaNode(copy),
|
|
_cycler(copy._cycler)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::make_copy
|
|
// Access: Public, 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 *RopeNode::
|
|
make_copy() const {
|
|
return new RopeNode(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::safe_to_transform
|
|
// Access: Public, Virtual
|
|
// Description: Returns true if it is generally safe to transform
|
|
// this particular kind of Node by calling the xform()
|
|
// method, false otherwise. For instance, it's usually
|
|
// a bad idea to attempt to xform a RopeNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool RopeNode::
|
|
safe_to_transform() const {
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::cull_callback
|
|
// Access: Public, 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 RopeNode::
|
|
cull_callback(CullTraverser *trav, CullTraverserData &data) {
|
|
// Statistics
|
|
PStatTimer timer(_rope_node_pcollector);
|
|
|
|
// Create some geometry on-the-fly to render the rope.
|
|
if (get_num_subdiv() > 0) {
|
|
NurbsCurveEvaluator *curve = get_curve();
|
|
if (curve != (NurbsCurveEvaluator *)NULL) {
|
|
PT(NurbsCurveResult) result;
|
|
if (has_matrix()) {
|
|
result = curve->evaluate(data._node_path.get_node_path(), get_matrix());
|
|
} else {
|
|
result = curve->evaluate(data._node_path.get_node_path());
|
|
}
|
|
|
|
if (result->get_num_segments() > 0) {
|
|
switch (get_render_mode()) {
|
|
case RM_thread:
|
|
render_thread(trav, data, result);
|
|
break;
|
|
|
|
case RM_tape:
|
|
render_tape(trav, data, result);
|
|
break;
|
|
|
|
case RM_billboard:
|
|
render_billboard(trav, data, result);
|
|
break;
|
|
|
|
case RM_tube:
|
|
render_tube(trav, data, result);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::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 RopeNode::
|
|
is_renderable() const {
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::output
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
output(ostream &out) const {
|
|
PandaNode::output(out);
|
|
NurbsCurveEvaluator *curve = get_curve();
|
|
if (curve != (NurbsCurveEvaluator *)NULL) {
|
|
out << " " << *curve;
|
|
} else {
|
|
out << " (no curve)";
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::write
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
write(ostream &out, int indent_level) const {
|
|
PandaNode::write(out, indent_level);
|
|
indent(out, indent_level) << *get_curve() << "\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::reset_bound
|
|
// Access: Published
|
|
// Description: Recomputes the bounding volume. This is normally
|
|
// called automatically, but it must occasionally be
|
|
// called explicitly when the curve has changed
|
|
// properties outside of this node's knowledge.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
reset_bound(const NodePath &rel_to) {
|
|
Thread *current_thread = Thread::get_current_thread();
|
|
int pipeline_stage = current_thread->get_pipeline_stage();
|
|
do_recompute_bounds(rel_to, pipeline_stage, current_thread);
|
|
mark_internal_bounds_stale(current_thread);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::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 RopeNode::
|
|
compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
|
|
int &internal_vertices,
|
|
int pipeline_stage,
|
|
Thread *current_thread) const {
|
|
PT(BoundingVolume) bounds =
|
|
do_recompute_bounds(NodePath((PandaNode *)this), pipeline_stage,
|
|
current_thread);
|
|
|
|
internal_bounds = bounds;
|
|
internal_vertices = 0; // TODO--estimate this better.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::get_format
|
|
// Access: Private
|
|
// Description: Returns the appropriate GeomVertexFormat for
|
|
// rendering, according to the user-specified
|
|
// requirements.
|
|
////////////////////////////////////////////////////////////////////
|
|
CPT(GeomVertexFormat) RopeNode::
|
|
get_format(bool support_normals) const {
|
|
PT(GeomVertexArrayFormat) array_format = new GeomVertexArrayFormat
|
|
(InternalName::get_vertex(), 3, Geom::NT_stdfloat,
|
|
Geom::C_point);
|
|
|
|
if (support_normals && get_normal_mode() == NM_vertex) {
|
|
array_format->add_column
|
|
(InternalName::get_normal(), 3, Geom::NT_stdfloat,
|
|
Geom::C_vector);
|
|
}
|
|
if (get_use_vertex_color()) {
|
|
array_format->add_column
|
|
(InternalName::get_color(), 1, Geom::NT_packed_dabc,
|
|
Geom::C_color);
|
|
}
|
|
if (get_uv_mode() != UV_none) {
|
|
array_format->add_column
|
|
(InternalName::get_texcoord(), 2, Geom::NT_stdfloat,
|
|
Geom::C_texcoord);
|
|
}
|
|
|
|
return GeomVertexFormat::register_format(array_format);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::do_recompute_bounds
|
|
// Access: Private
|
|
// Description: Does the actual internal recompute.
|
|
////////////////////////////////////////////////////////////////////
|
|
PT(BoundingVolume) RopeNode::
|
|
do_recompute_bounds(const NodePath &rel_to, int pipeline_stage,
|
|
Thread *current_thread) const {
|
|
// TODO: fix the bounds so that it properly reflects the indicated
|
|
// pipeline stage. At the moment, we cheat and get some of the
|
|
// properties from the current pipeline stage, the lazy way.
|
|
|
|
// First, get ourselves a fresh, empty bounding volume.
|
|
PT(BoundingVolume) bound = new BoundingSphere;
|
|
|
|
NurbsCurveEvaluator *curve = get_curve();
|
|
if (curve != (NurbsCurveEvaluator *)NULL) {
|
|
NurbsCurveEvaluator::Vert3Array verts;
|
|
get_curve()->get_vertices(verts, rel_to);
|
|
|
|
if (has_matrix()) {
|
|
// And then apply the indicated matrix.
|
|
const LMatrix4 &mat = get_matrix();
|
|
NurbsCurveEvaluator::Vert3Array::iterator vi;
|
|
for (vi = verts.begin(); vi != verts.end(); ++vi) {
|
|
(*vi) = LPoint3(*vi) * mat;
|
|
}
|
|
}
|
|
|
|
GeometricBoundingVolume *gbv;
|
|
DCAST_INTO_R(gbv, bound, bound);
|
|
gbv->around(&verts[0], &verts[0] + verts.size());
|
|
}
|
|
return bound;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::render_thread
|
|
// Access: Private
|
|
// Description: Draws the rope in RM_thread mode. This uses a
|
|
// GeomLinestrip to draw the rope in the simplest
|
|
// possible method, generally resulting in a
|
|
// one-pixel-wide curve.
|
|
//
|
|
// In this mode, the thickness parameter represents a
|
|
// thickness in pixels, and is passed to the linestrip.
|
|
// However, you should be aware the DirectX does not
|
|
// support line thickness. This mode does not support
|
|
// per-vertex thickness.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
render_thread(CullTraverser *trav, CullTraverserData &data,
|
|
NurbsCurveResult *result) const {
|
|
CurveSegments curve_segments;
|
|
int num_curve_verts = get_connected_segments(curve_segments, result);
|
|
|
|
// Now we have stored one or more sequences of vertices down the
|
|
// thread. These map directly to primitive vertices.
|
|
PT(GeomVertexData) vdata = new GeomVertexData
|
|
("rope", get_format(false), Geom::UH_stream);
|
|
compute_thread_vertices(vdata, curve_segments, num_curve_verts);
|
|
|
|
// We use GeomLines instead of GeomLinestrips, since that can more
|
|
// easily be rendered directly.
|
|
PT(GeomLines) lines = new GeomLines(Geom::UH_stream);
|
|
lines->reserve_num_vertices((num_curve_verts - 1) * 2);
|
|
|
|
for (int vi = 0; vi < num_curve_verts - 1; ++vi) {
|
|
lines->add_vertex(vi);
|
|
lines->add_vertex(vi + 1);
|
|
lines->close_primitive();
|
|
}
|
|
|
|
PT(Geom) geom = new Geom(vdata);
|
|
geom->add_primitive(lines);
|
|
|
|
CPT(RenderAttrib) thick = RenderModeAttrib::make(RenderModeAttrib::M_unchanged, get_thickness());
|
|
CPT(RenderState) state = data._state->add_attrib(thick);
|
|
if (get_use_vertex_color()) {
|
|
state = state->add_attrib(ColorAttrib::make_vertex());
|
|
}
|
|
|
|
CullableObject *object =
|
|
new CullableObject(geom, state,
|
|
data.get_net_transform(trav),
|
|
data.get_modelview_transform(trav),
|
|
trav->get_scene());
|
|
trav->get_cull_handler()->record_object(object, trav);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::render_tape
|
|
// Access: Private
|
|
// Description: Draws the rope in RM_tape mode. This draws a
|
|
// series of triangle strips oriented to be
|
|
// perpendicular to the tube_up vector.
|
|
//
|
|
// In this mode, thickness is in spatial units, and
|
|
// determines the width of the triangle strips.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
render_tape(CullTraverser *trav, CullTraverserData &data,
|
|
NurbsCurveResult *result) const {
|
|
CurveSegments curve_segments;
|
|
int num_curve_verts = get_connected_segments(curve_segments, result);
|
|
|
|
// Now we have stored one or more sequences of vertices down the
|
|
// center strips. Go back through and calculate the vertices on
|
|
// either side.
|
|
PT(GeomVertexData) vdata = new GeomVertexData
|
|
("rope", get_format(false), Geom::UH_stream);
|
|
|
|
compute_billboard_vertices(vdata, -get_tube_up(),
|
|
curve_segments, num_curve_verts, result);
|
|
|
|
// Since this will be a nonindexed primitive, no need to pre-reserve
|
|
// the number of vertices.
|
|
PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
|
|
CurveSegments::const_iterator si;
|
|
for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
|
|
const CurveSegment &segment = (*si);
|
|
|
|
strip->add_next_vertices(segment.size() * 2);
|
|
strip->close_primitive();
|
|
}
|
|
|
|
PT(Geom) geom = new Geom(vdata);
|
|
geom->add_primitive(strip);
|
|
|
|
CPT(RenderState) state = data._state;
|
|
if (get_use_vertex_color()) {
|
|
state = state->add_attrib(ColorAttrib::make_vertex());
|
|
}
|
|
|
|
CullableObject *object =
|
|
new CullableObject(geom, state,
|
|
data.get_net_transform(trav),
|
|
data.get_modelview_transform(trav),
|
|
trav->get_scene());
|
|
trav->get_cull_handler()->record_object(object, trav);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::render_billboard
|
|
// Access: Private
|
|
// Description: Draws the rope in RM_billboard mode. This draws a
|
|
// series of triangle strips oriented to be
|
|
// perpendicular to the camera plane.
|
|
//
|
|
// In this mode, thickness is in spatial units, and
|
|
// determines the width of the triangle strips.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
render_billboard(CullTraverser *trav, CullTraverserData &data,
|
|
NurbsCurveResult *result) const {
|
|
const TransformState *net_transform = data.get_net_transform(trav);
|
|
const TransformState *camera_transform = trav->get_camera_transform();
|
|
|
|
CPT(TransformState) rel_transform =
|
|
net_transform->invert_compose(camera_transform);
|
|
LVector3 camera_vec = LVector3::forward() * rel_transform->get_mat();
|
|
|
|
CurveSegments curve_segments;
|
|
int num_curve_verts = get_connected_segments(curve_segments, result);
|
|
|
|
// Now we have stored one or more sequences of vertices down the
|
|
// center strips. Go back through and calculate the vertices on
|
|
// either side.
|
|
PT(GeomVertexData) vdata = new GeomVertexData
|
|
("rope", get_format(false), Geom::UH_stream);
|
|
|
|
compute_billboard_vertices(vdata, camera_vec,
|
|
curve_segments, num_curve_verts, result);
|
|
|
|
// Since this will be a nonindexed primitive, no need to pre-reserve
|
|
// the number of vertices.
|
|
PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
|
|
CurveSegments::const_iterator si;
|
|
for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
|
|
const CurveSegment &segment = (*si);
|
|
|
|
strip->add_next_vertices(segment.size() * 2);
|
|
strip->close_primitive();
|
|
}
|
|
|
|
PT(Geom) geom = new Geom(vdata);
|
|
geom->add_primitive(strip);
|
|
|
|
CPT(RenderState) state = data._state;
|
|
if (get_use_vertex_color()) {
|
|
state = state->add_attrib(ColorAttrib::make_vertex());
|
|
}
|
|
|
|
CullableObject *object =
|
|
new CullableObject(geom, state,
|
|
data.get_net_transform(trav),
|
|
data.get_modelview_transform(trav),
|
|
trav->get_scene());
|
|
trav->get_cull_handler()->record_object(object, trav);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::render_tube
|
|
// Access: Private
|
|
// Description: Draws the rope in RM_tube mode. This draws a hollow
|
|
// tube centered around the string.
|
|
//
|
|
// In this mode, thickness is in spatial units, and
|
|
// determines the diameter of the tube.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
render_tube(CullTraverser *trav, CullTraverserData &data,
|
|
NurbsCurveResult *result) const {
|
|
CurveSegments curve_segments;
|
|
int num_curve_verts = get_connected_segments(curve_segments, result);
|
|
|
|
// Now, we build up a table of vertices, in a series of rings
|
|
// around the circumference of the tube.
|
|
|
|
int num_slices = get_num_slices();
|
|
int num_verts_per_slice;
|
|
|
|
PT(GeomVertexData) vdata = new GeomVertexData
|
|
("rope", get_format(true), Geom::UH_stream);
|
|
|
|
compute_tube_vertices(vdata, num_verts_per_slice,
|
|
curve_segments, num_curve_verts, result);
|
|
|
|
// Finally, go through and build up the index array, to tie all the
|
|
// triangle strips together. This is difficult to pre-calculate the
|
|
// number of vertices we'll use, so we'll just let it dynamically
|
|
// allocate.
|
|
PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
|
|
int vi = 0;
|
|
CurveSegments::const_iterator si;
|
|
for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
|
|
const CurveSegment &segment = (*si);
|
|
|
|
for (int s = 0; s < num_slices; ++s) {
|
|
int s1 = (s + 1) % num_verts_per_slice;
|
|
|
|
for (size_t j = 0; j < segment.size(); ++j) {
|
|
strip->add_vertex((vi + j) * num_verts_per_slice + s);
|
|
strip->add_vertex((vi + j) * num_verts_per_slice + s1);
|
|
}
|
|
|
|
strip->close_primitive();
|
|
}
|
|
vi += (int)segment.size();
|
|
}
|
|
|
|
PT(Geom) geom = new Geom(vdata);
|
|
geom->add_primitive(strip);
|
|
|
|
CPT(RenderState) state = data._state;
|
|
if (get_use_vertex_color()) {
|
|
state = state->add_attrib(ColorAttrib::make_vertex());
|
|
}
|
|
|
|
CullableObject *object =
|
|
new CullableObject(geom, state,
|
|
data.get_net_transform(trav),
|
|
data.get_modelview_transform(trav),
|
|
trav->get_scene());
|
|
trav->get_cull_handler()->record_object(object, trav);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::get_connected_segments
|
|
// Access: Private
|
|
// Description: Evaluates the string of vertices along the curve, and
|
|
// also breaks them up into connected segments.
|
|
//
|
|
// Since the NurbsCurveEvaluator describes the curve as
|
|
// a sequence of possibly-connected piecewise continuous
|
|
// segments, this means joining together some adjacent
|
|
// segments from the NurbsCurveEvaluator into a single
|
|
// CurveSegment, if they happen to be connected (as most
|
|
// will be).
|
|
//
|
|
// The return value is the total number of points across
|
|
// all segments.
|
|
////////////////////////////////////////////////////////////////////
|
|
int RopeNode::
|
|
get_connected_segments(RopeNode::CurveSegments &curve_segments,
|
|
const NurbsCurveResult *result) const {
|
|
int num_curve_verts = 0;
|
|
|
|
int num_verts = get_num_subdiv() + 1;
|
|
int num_segments = result->get_num_segments();
|
|
bool use_vertex_color = get_use_vertex_color();
|
|
bool use_vertex_thickness = get_use_vertex_thickness();
|
|
|
|
CurveSegment *curve_segment = NULL;
|
|
LPoint3 last_point;
|
|
|
|
for (int segment = 0; segment < num_segments; ++segment) {
|
|
LPoint3 point;
|
|
result->eval_segment_point(segment, 0.0f, point);
|
|
|
|
if (curve_segment == (CurveSegment *)NULL ||
|
|
!point.almost_equal(last_point)) {
|
|
// If the first point of this segment is different from the last
|
|
// point of the previous segment, end the previous segment and
|
|
// begin a new one.
|
|
curve_segments.push_back(CurveSegment());
|
|
curve_segment = &curve_segments.back();
|
|
|
|
CurveVertex vtx;
|
|
vtx._p = point;
|
|
vtx._t = result->get_segment_t(segment, 0.0f);
|
|
if (use_vertex_color) {
|
|
result->eval_segment_extended_points(segment, 0.0f,
|
|
get_vertex_color_dimension(),
|
|
&vtx._c[0], 4);
|
|
}
|
|
if (use_vertex_thickness) {
|
|
vtx._thickness =
|
|
result->eval_segment_extended_point(segment, 0.0f,
|
|
get_vertex_thickness_dimension());
|
|
}
|
|
|
|
curve_segment->push_back(vtx);
|
|
++num_curve_verts;
|
|
}
|
|
|
|
// Store all the remaining points in this segment.
|
|
for (int i = 1; i < num_verts; ++i) {
|
|
PN_stdfloat t = (PN_stdfloat)i / (PN_stdfloat)(num_verts - 1);
|
|
|
|
CurveVertex vtx;
|
|
result->eval_segment_point(segment, t, vtx._p);
|
|
vtx._t = result->get_segment_t(segment, t);
|
|
if (use_vertex_color) {
|
|
result->eval_segment_extended_points(segment, t,
|
|
get_vertex_color_dimension(),
|
|
&vtx._c[0], 4);
|
|
}
|
|
if (use_vertex_thickness) {
|
|
vtx._thickness =
|
|
result->eval_segment_extended_point(segment, t,
|
|
get_vertex_thickness_dimension());
|
|
}
|
|
|
|
curve_segment->push_back(vtx);
|
|
++num_curve_verts;
|
|
|
|
last_point = vtx._p;
|
|
}
|
|
}
|
|
|
|
return num_curve_verts;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::compute_thread_vertices
|
|
// Access: Private
|
|
// Description: Calculates the vertices for a RM_thread render. This
|
|
// just copies the vertices more-or-less directly into
|
|
// the array.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
compute_thread_vertices(GeomVertexData *vdata,
|
|
const RopeNode::CurveSegments &curve_segments,
|
|
int num_curve_verts) const {
|
|
vdata->set_num_rows(num_curve_verts);
|
|
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
GeomVertexWriter color(vdata, InternalName::get_color());
|
|
GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
|
|
|
|
UVMode uv_mode = get_uv_mode();
|
|
PN_stdfloat uv_scale = get_uv_scale();
|
|
bool u_dominant = get_uv_direction();
|
|
bool use_vertex_color = get_use_vertex_color();
|
|
|
|
PN_stdfloat dist = 0.0f;
|
|
CurveSegments::const_iterator si;
|
|
for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
|
|
const CurveSegment &segment = (*si);
|
|
for (size_t j = 0; j < segment.size(); ++j) {
|
|
vertex.add_data3(segment[j]._p);
|
|
|
|
if (use_vertex_color) {
|
|
color.add_data4(segment[j]._c);
|
|
}
|
|
|
|
PN_stdfloat uv_t = compute_uv_t(dist, uv_mode, uv_scale, segment, j);
|
|
|
|
if (uv_mode != UV_none) {
|
|
if (u_dominant) {
|
|
texcoord.add_data2(uv_t, 0.0f);
|
|
} else {
|
|
texcoord.add_data2(0.0f, uv_t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nassertv(vdata->get_num_rows() == num_curve_verts);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::compute_billboard_vertices
|
|
// Access: Private
|
|
// Description: Calculates the vertices for a RM_billboard render. This
|
|
// puts a pair of vertices on either side of each
|
|
// computed point in curve_segments.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
compute_billboard_vertices(GeomVertexData *vdata,
|
|
const LVector3 &camera_vec,
|
|
const RopeNode::CurveSegments &curve_segments,
|
|
int num_curve_verts,
|
|
NurbsCurveResult *result) const {
|
|
int expected_num_verts = num_curve_verts * 2;
|
|
vdata->set_num_rows(expected_num_verts);
|
|
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
GeomVertexWriter color(vdata, InternalName::get_color());
|
|
GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
|
|
|
|
PN_stdfloat thickness = get_thickness();
|
|
PN_stdfloat overall_radius = thickness * 0.5f;
|
|
PN_stdfloat radius = overall_radius;
|
|
UVMode uv_mode = get_uv_mode();
|
|
PN_stdfloat uv_scale = get_uv_scale();
|
|
bool u_dominant = get_uv_direction();
|
|
bool use_vertex_color = get_use_vertex_color();
|
|
bool use_vertex_thickness = get_use_vertex_thickness();
|
|
|
|
PN_stdfloat dist = 0.0f;
|
|
CurveSegments::const_iterator si;
|
|
for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
|
|
const CurveSegment &segment = (*si);
|
|
for (size_t j = 0; j < segment.size(); ++j) {
|
|
LVector3 tangent;
|
|
compute_tangent(tangent, segment, j, result);
|
|
|
|
LVector3 norm = cross(tangent, camera_vec);
|
|
norm.normalize();
|
|
|
|
if (use_vertex_thickness) {
|
|
radius = overall_radius * segment[j]._thickness;
|
|
}
|
|
|
|
vertex.add_data3(segment[j]._p + norm * radius);
|
|
vertex.add_data3(segment[j]._p - norm * radius);
|
|
|
|
if (use_vertex_color) {
|
|
color.add_data4(segment[j]._c);
|
|
color.add_data4(segment[j]._c);
|
|
}
|
|
|
|
PN_stdfloat uv_t = compute_uv_t(dist, uv_mode, uv_scale, segment, j);
|
|
|
|
if (uv_mode != UV_none) {
|
|
if (u_dominant) {
|
|
texcoord.add_data2(uv_t, 1.0f);
|
|
texcoord.add_data2(uv_t, 0.0f);
|
|
} else {
|
|
texcoord.add_data2(1.0f, uv_t);
|
|
texcoord.add_data2(0.0f, uv_t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nassertv(vdata->get_num_rows() == expected_num_verts);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::compute_tube_vertices
|
|
// Access: Private
|
|
// Description: Calculates the vertices for a RM_tube render. This
|
|
// puts a ring of vertices around each computed point in
|
|
// curve_segments.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
compute_tube_vertices(GeomVertexData *vdata,
|
|
int &num_verts_per_slice,
|
|
const RopeNode::CurveSegments &curve_segments,
|
|
int num_curve_verts,
|
|
NurbsCurveResult *result) const {
|
|
int num_slices = get_num_slices();
|
|
num_verts_per_slice = num_slices;
|
|
|
|
PN_stdfloat thickness = get_thickness();
|
|
PN_stdfloat overall_radius = thickness * 0.5f;
|
|
PN_stdfloat radius = overall_radius;
|
|
UVMode uv_mode = get_uv_mode();
|
|
PN_stdfloat uv_scale = get_uv_scale();
|
|
bool u_dominant = get_uv_direction();
|
|
NormalMode normal_mode = get_normal_mode();
|
|
bool use_vertex_color = get_use_vertex_color();
|
|
bool use_vertex_thickness = get_use_vertex_thickness();
|
|
|
|
// If we are generating UV's, we will need to duplicate the vertices
|
|
// along the seam so that the UV's go through the whole range of
|
|
// 0..1 instead of reflecting in the last polygon before the seam.
|
|
if (uv_mode != UV_none) {
|
|
++num_verts_per_slice;
|
|
}
|
|
|
|
int expected_num_verts = num_curve_verts * num_verts_per_slice;
|
|
vdata->set_num_rows(expected_num_verts);
|
|
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
GeomVertexWriter normal(vdata, InternalName::get_normal());
|
|
GeomVertexWriter color(vdata, InternalName::get_color());
|
|
GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
|
|
|
|
LVector3 up = get_tube_up();
|
|
|
|
PN_stdfloat dist = 0.0f;
|
|
CurveSegments::const_iterator si;
|
|
for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
|
|
const CurveSegment &segment = (*si);
|
|
for (size_t j = 0; j < segment.size(); ++j) {
|
|
LVector3 tangent;
|
|
compute_tangent(tangent, segment, j, result);
|
|
|
|
LVector3 norm = cross(tangent, up);
|
|
norm.normalize();
|
|
up = cross(norm, tangent);
|
|
|
|
LMatrix3 rotate = LMatrix3::rotate_mat(360.0f / (PN_stdfloat)num_slices,
|
|
tangent);
|
|
|
|
PN_stdfloat uv_t = compute_uv_t(dist, uv_mode, uv_scale, segment, j);
|
|
|
|
for (int s = 0; s < num_verts_per_slice; ++s) {
|
|
if (use_vertex_thickness) {
|
|
radius = overall_radius * segment[j]._thickness;
|
|
}
|
|
|
|
vertex.add_data3(segment[j]._p + norm * radius);
|
|
|
|
if (normal_mode == NM_vertex) {
|
|
normal.add_data3(norm);
|
|
}
|
|
|
|
if (use_vertex_color) {
|
|
color.add_data4(segment[j]._c);
|
|
}
|
|
|
|
norm = norm * rotate;
|
|
|
|
if (uv_mode != UV_none) {
|
|
PN_stdfloat uv_s = (PN_stdfloat)s / (PN_stdfloat)num_slices;
|
|
if (u_dominant) {
|
|
texcoord.add_data2(uv_t, uv_s);
|
|
} else {
|
|
texcoord.add_data2(uv_s, uv_t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nassertv(vdata->get_num_rows() == expected_num_verts);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::compute_tangent
|
|
// Access: Private, Static
|
|
// Description: Computes the tangent to the curve at the indicated
|
|
// point in the segment.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
compute_tangent(LVector3 &tangent, const RopeNode::CurveSegment &segment,
|
|
size_t j, NurbsCurveResult *result) {
|
|
// First, try to evaluate the tangent at the curve. This gives
|
|
// better results at the ends at the endpoints where the tangent
|
|
// does not go to zero.
|
|
|
|
/*
|
|
Actually, on second thought this looks terrible.
|
|
|
|
if (result->eval_tangent(segment[j]._t, tangent)) {
|
|
if (!tangent.almost_equal(LVector3::zero())) {
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
|
|
// If that failed (or produced a zero tangent), then derive the
|
|
// tangent from the neighboring points instead.
|
|
if (j == 0) {
|
|
tangent = segment[j + 1]._p - segment[j]._p;
|
|
} else if (j == segment.size() - 1) {
|
|
tangent = segment[j]._p - segment[j - 1]._p;
|
|
} else {
|
|
tangent = segment[j + 1]._p - segment[j - 1]._p;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::compute_uv_t
|
|
// Access: Private, Static
|
|
// Description: Computes the texture coordinate along the curve for
|
|
// the indicated point in the segment.
|
|
////////////////////////////////////////////////////////////////////
|
|
PN_stdfloat RopeNode::
|
|
compute_uv_t(PN_stdfloat &dist, const RopeNode::UVMode &uv_mode,
|
|
PN_stdfloat uv_scale, const RopeNode::CurveSegment &segment,
|
|
size_t j) {
|
|
switch (uv_mode) {
|
|
case UV_none:
|
|
return 0.0f;
|
|
|
|
case UV_parametric:
|
|
return segment[j]._t * uv_scale;
|
|
|
|
case UV_distance:
|
|
if (j != 0) {
|
|
LVector3 vec = segment[j]._p - segment[j - 1]._p;
|
|
dist += vec.length();
|
|
}
|
|
return dist * uv_scale;
|
|
|
|
case UV_distance2:
|
|
if (j != 0) {
|
|
LVector3 vec = segment[j]._p - segment[j - 1]._p;
|
|
dist += vec.length_squared();
|
|
}
|
|
return dist * uv_scale;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::register_with_read_factory
|
|
// Access: Public, Static
|
|
// Description: Tells the BamReader how to create objects of type
|
|
// RopeNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
register_with_read_factory() {
|
|
BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::write_datagram
|
|
// Access: Public, Virtual
|
|
// Description: Writes the contents of this object to the datagram
|
|
// for shipping out to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
write_datagram(BamWriter *manager, Datagram &dg) {
|
|
PandaNode::write_datagram(manager, dg);
|
|
manager->write_cdata(dg, _cycler);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::make_from_bam
|
|
// Access: Protected, Static
|
|
// Description: This function is called by the BamReader's factory
|
|
// when a new object of type RopeNode is encountered
|
|
// in the Bam file. It should create the RopeNode
|
|
// and extract its information from the file.
|
|
////////////////////////////////////////////////////////////////////
|
|
TypedWritable *RopeNode::
|
|
make_from_bam(const FactoryParams ¶ms) {
|
|
RopeNode *node = new RopeNode("");
|
|
DatagramIterator scan;
|
|
BamReader *manager;
|
|
|
|
parse_params(params, scan, manager);
|
|
node->fillin(scan, manager);
|
|
|
|
return node;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: RopeNode::fillin
|
|
// Access: Protected
|
|
// Description: This internal function is called by make_from_bam to
|
|
// read in all of the relevant data from the BamFile for
|
|
// the new RopeNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void RopeNode::
|
|
fillin(DatagramIterator &scan, BamReader *manager) {
|
|
PandaNode::fillin(scan, manager);
|
|
manager->read_cdata(scan, _cycler);
|
|
}
|