2000 lines
77 KiB
C++
2000 lines
77 KiB
C++
// Filename: speedTreeNode.cxx
|
|
// Created by: drose (13Mar09)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 "pandabase.h"
|
|
#include "speedTreeNode.h"
|
|
#include "stBasicTerrain.h"
|
|
#include "virtualFileSystem.h"
|
|
#include "config_util.h"
|
|
#include "cullTraverser.h"
|
|
#include "cullableObject.h"
|
|
#include "cullHandler.h"
|
|
#include "omniBoundingVolume.h"
|
|
#include "boundingSphere.h"
|
|
#include "boundingBox.h"
|
|
#include "clockObject.h"
|
|
#include "geomDrawCallbackData.h"
|
|
#include "graphicsStateGuardian.h"
|
|
#include "textureAttrib.h"
|
|
#include "lightAttrib.h"
|
|
#include "directionalLight.h"
|
|
#include "ambientLight.h"
|
|
#include "loader.h"
|
|
#include "deg_2_rad.h"
|
|
#include "sceneGraphReducer.h"
|
|
#include "pStatTimer.h"
|
|
|
|
#ifdef SPEEDTREE_OPENGL
|
|
#include "glew/glew.h"
|
|
#endif // SPEEDTREE_OPENGL
|
|
|
|
#ifdef SPEEDTREE_DIRECTX9
|
|
#include "dxGraphicsStateGuardian9.h"
|
|
#endif
|
|
|
|
double SpeedTreeNode::_global_time_delta = 0.0;
|
|
bool SpeedTreeNode::_authorized;
|
|
bool SpeedTreeNode::_done_first_init;
|
|
TypeHandle SpeedTreeNode::_type_handle;
|
|
TypeHandle SpeedTreeNode::DrawCallback::_type_handle;
|
|
|
|
PStatCollector SpeedTreeNode::_cull_speedtree_pcollector("Cull:SpeedTree");
|
|
PStatCollector SpeedTreeNode::_cull_speedtree_shadows_pcollector("Cull:SpeedTree:Shadows");
|
|
PStatCollector SpeedTreeNode::_cull_speedtree_trees_pcollector("Cull:SpeedTree:Trees");
|
|
PStatCollector SpeedTreeNode::_cull_speedtree_terrain_pcollector("Cull:SpeedTree:Terrain");
|
|
PStatCollector SpeedTreeNode::_draw_speedtree_pcollector("Draw:SpeedTree");
|
|
PStatCollector SpeedTreeNode::_draw_speedtree_shadows_pcollector("Draw:SpeedTree:Shadows");
|
|
PStatCollector SpeedTreeNode::_draw_speedtree_trees_pcollector("Draw:SpeedTree:Trees");
|
|
PStatCollector SpeedTreeNode::_draw_speedtree_terrain_pcollector("Draw:SpeedTree:Terrain");
|
|
PStatCollector SpeedTreeNode::_draw_speedtree_terrain_update_pcollector("Draw:SpeedTree:Terrain:Update");
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::Constructor
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
SpeedTreeNode::
|
|
SpeedTreeNode(const string &name) :
|
|
PandaNode(name),
|
|
#ifdef ST_DELETE_FOREST_HACK
|
|
// Early versions of SpeedTree don't destruct unused CForestRender
|
|
// objects correctly. To avoid crashes, we have to leak these
|
|
// things.
|
|
_forest_render(*(new SpeedTree::CForestRender)),
|
|
#endif
|
|
_time_delta(0.0)
|
|
{
|
|
init_node();
|
|
// For now, set an infinite bounding volume. Maybe in the future
|
|
// we'll change this to match whatever set of trees we're holding,
|
|
// though it probably doesn't really matter too much.
|
|
//set_internal_bounds(new OmniBoundingVolume);
|
|
// set_internal_bounds(new BoundingSphere(LPoint3::zero(), 10.0f));
|
|
|
|
// Intialize the render params. First, get the shader directory.
|
|
Filename shaders_dir = speedtree_shaders_dir;
|
|
|
|
// We expect the shader directory to contain at least this one token
|
|
// filename (to prove it's the right directory).
|
|
Filename token_filename = "Branch.hlsl";
|
|
if (!Filename(shaders_dir, token_filename).exists()) {
|
|
// If that shader directory doesn't work, look along the model-path.
|
|
if (token_filename.resolve_filename(get_model_path())) {
|
|
shaders_dir = token_filename.get_dirname();
|
|
} else {
|
|
if (!shaders_dir.is_directory()) {
|
|
speedtree_cat.warning()
|
|
<< "speedtree-shaders-dir is set to " << shaders_dir
|
|
<< ", which doesn't exist.\n";
|
|
} else {
|
|
speedtree_cat.warning()
|
|
<< "speedtree-shaders-dir is set to " << shaders_dir
|
|
<< ", which exists but doesn't contain " << token_filename
|
|
<< ".\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
_os_shaders_dir = shaders_dir.to_os_specific();
|
|
// Ensure the path ends with a terminal slash; SpeedTree requires this.
|
|
#if defined(WIN32) || defined(WIN64)
|
|
if (!_os_shaders_dir.empty() && _os_shaders_dir[_os_shaders_dir.length() - 1] != '\\') {
|
|
_os_shaders_dir += "\\";
|
|
}
|
|
#else
|
|
if (!_os_shaders_dir.empty() && _os_shaders_dir[_os_shaders_dir.length() - 1] != '/') {
|
|
_os_shaders_dir += "/";
|
|
}
|
|
#endif
|
|
|
|
SpeedTree::SForestRenderInfo render_info;
|
|
render_info.m_strShaderPath = _os_shaders_dir.c_str();
|
|
_forest_render.SetRenderInfo(render_info);
|
|
|
|
// Now apply the rest of the config settings.
|
|
reload_config();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::count_total_instances
|
|
// Access: Published
|
|
// Description: Returns the total number of trees that will be
|
|
// rendered by this node, counting all instances of all
|
|
// trees.
|
|
////////////////////////////////////////////////////////////////////
|
|
int SpeedTreeNode::
|
|
count_total_instances() const {
|
|
int total_instances = 0;
|
|
Trees::const_iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
total_instances += instance_list->get_num_instances();
|
|
}
|
|
|
|
return total_instances;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_tree
|
|
// Access: Published
|
|
// Description: Adds a new tree for rendering. Returns the
|
|
// InstanceList which can be used to add to the
|
|
// instances for this tree. If the tree has previously
|
|
// been added, returns the existing InstanceList.
|
|
////////////////////////////////////////////////////////////////////
|
|
SpeedTreeNode::InstanceList &SpeedTreeNode::
|
|
add_tree(const STTree *tree) {
|
|
nassertr(is_valid(), *(InstanceList *)NULL);
|
|
nassertr(tree->is_valid(), *(InstanceList *)NULL);
|
|
|
|
InstanceList ilist(tree);
|
|
Trees::iterator ti = _trees.find(&ilist);
|
|
if (ti == _trees.end()) {
|
|
// This is the first time that this particular tree has been
|
|
// added.
|
|
InstanceList *instance_list = new InstanceList(tree);
|
|
pair<Trees::iterator, bool> result = _trees.insert(instance_list);
|
|
ti = result.first;
|
|
bool inserted = result.second;
|
|
nassertr(inserted, *(*ti));
|
|
|
|
if (!_forest_render.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to register tree " << tree->get_fullpath() << "\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
}
|
|
|
|
_needs_repopulate = true;
|
|
mark_internal_bounds_stale();
|
|
InstanceList *instance_list = (*ti);
|
|
return *instance_list;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::remove_tree
|
|
// Access: Published
|
|
// Description: Removes all instances of the indicated tree. Returns
|
|
// the number of instances removed.
|
|
////////////////////////////////////////////////////////////////////
|
|
int SpeedTreeNode::
|
|
remove_tree(const STTree *tree) {
|
|
InstanceList ilist(tree);
|
|
Trees::iterator ti = _trees.find(&ilist);
|
|
if (ti == _trees.end()) {
|
|
// The tree was not already present.
|
|
return 0;
|
|
}
|
|
|
|
if (!_forest_render.UnregisterTree(tree->get_tree())) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to unregister tree " << tree->get_fullpath() << "\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
|
|
_needs_repopulate = true;
|
|
mark_internal_bounds_stale();
|
|
|
|
InstanceList *instance_list = (*ti);
|
|
int num_removed = instance_list->get_num_instances();
|
|
_trees.erase(ti);
|
|
delete instance_list;
|
|
|
|
return num_removed;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::remove_all_trees
|
|
// Access: Published
|
|
// Description: Removes all instances of all trees from the node.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
remove_all_trees() {
|
|
Trees::iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
const STTree *tree = instance_list->get_tree();
|
|
if (!_forest_render.UnregisterTree(tree->get_tree())) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to unregister tree " << tree->get_fullpath() << "\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
delete instance_list;
|
|
}
|
|
|
|
_trees.clear();
|
|
_needs_repopulate = true;
|
|
mark_internal_bounds_stale();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::has_instance_list
|
|
// Access: Published
|
|
// Description: Returns true if the indicated tree has any instances
|
|
// within this node, false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool SpeedTreeNode::
|
|
has_instance_list(const STTree *tree) const {
|
|
InstanceList ilist(tree);
|
|
Trees::const_iterator ti = _trees.find(&ilist);
|
|
return (ti != _trees.end());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::get_instance_list
|
|
// Access: Published
|
|
// Description: Returns a list of transforms that corresponds to the
|
|
// instances at which the indicated tree appears. You
|
|
// should ensure that has_instance_list() returns true
|
|
// before calling this method.
|
|
////////////////////////////////////////////////////////////////////
|
|
const SpeedTreeNode::InstanceList &SpeedTreeNode::
|
|
get_instance_list(const STTree *tree) const {
|
|
InstanceList ilist(tree);
|
|
Trees::const_iterator ti = _trees.find(&ilist);
|
|
if (ti == _trees.end()) {
|
|
// The tree was not already present.
|
|
static InstanceList empty_list((STTree *)NULL);
|
|
return empty_list;
|
|
}
|
|
|
|
InstanceList *instance_list = (*ti);
|
|
return *instance_list;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::modify_instance_list
|
|
// Access: Published
|
|
// Description: Returns a modifiable list of transforms that
|
|
// corresponds to the instances of this tree. This is
|
|
// equivalent to add_tree().
|
|
////////////////////////////////////////////////////////////////////
|
|
SpeedTreeNode::InstanceList &SpeedTreeNode::
|
|
modify_instance_list(const STTree *tree) {
|
|
return add_tree(tree);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_instance
|
|
// Access: Published
|
|
// Description: Adds a new instance of the indicated tree at the
|
|
// indicated transform.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
add_instance(const STTree *tree, const STTransform &transform) {
|
|
if (speedtree_follow_terrain && has_terrain()) {
|
|
STTransform new_transform = transform;
|
|
new_transform._pos[2] = _terrain->get_height(new_transform._pos[0], new_transform._pos[1]);
|
|
add_tree(tree).add_instance(new_transform);
|
|
} else {
|
|
add_tree(tree).add_instance(transform);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_instances
|
|
// Access: Published
|
|
// Description: Walks the scene graph beginning at root, looking for
|
|
// nested SpeedTreeNodes. For each SpeedTreeNode found,
|
|
// adds all of the instances defined within that
|
|
// SpeedTreeNode as instances of this node, after
|
|
// applying the indicated scene-graph transform.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
add_instances(const NodePath &root, const TransformState *transform) {
|
|
nassertv(!root.is_empty());
|
|
r_add_instances(root.node(), transform->compose(root.get_transform()),
|
|
Thread::get_current_thread());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_instances_from
|
|
// Access: Published
|
|
// Description: Adds all of the instances defined within the
|
|
// indicated SpeedTreeNode as instances of this node.
|
|
// Does not recurse to children.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
add_instances_from(const SpeedTreeNode *other) {
|
|
int num_trees = other->get_num_trees();
|
|
for (int ti = 0; ti < num_trees; ++ti) {
|
|
const InstanceList &other_instance_list = other->get_instance_list(ti);
|
|
const STTree *tree = other_instance_list.get_tree();
|
|
InstanceList &this_instance_list = add_tree(tree);
|
|
|
|
int num_instances = other_instance_list.get_num_instances();
|
|
for (int i = 0; i < num_instances; ++i) {
|
|
STTransform other_trans = other_instance_list.get_instance(i);
|
|
this_instance_list.add_instance(other_trans);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_instances_from
|
|
// Access: Published
|
|
// Description: Adds all of the instances defined within the
|
|
// indicated SpeedTreeNode as instances of this node,
|
|
// after applying the indicated scene-graph transform.
|
|
// Does not recurse to children.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
add_instances_from(const SpeedTreeNode *other, const TransformState *transform) {
|
|
int num_trees = other->get_num_trees();
|
|
for (int ti = 0; ti < num_trees; ++ti) {
|
|
const InstanceList &other_instance_list = other->get_instance_list(ti);
|
|
const STTree *tree = other_instance_list.get_tree();
|
|
InstanceList &this_instance_list = add_tree(tree);
|
|
|
|
int num_instances = other_instance_list.get_num_instances();
|
|
for (int i = 0; i < num_instances; ++i) {
|
|
CPT(TransformState) other_trans = other_instance_list.get_instance(i);
|
|
CPT(TransformState) new_trans = transform->compose(other_trans);
|
|
|
|
if (speedtree_follow_terrain && has_terrain()) {
|
|
STTransform new_transform = new_trans;
|
|
new_transform._pos[2] = _terrain->get_height(new_transform._pos[0], new_transform._pos[1]);
|
|
this_instance_list.add_instance(new_transform);
|
|
|
|
} else {
|
|
this_instance_list.add_instance(new_trans.p());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_random_instances
|
|
// Access: Published
|
|
// Description: Creates a number of random instances of the indicated
|
|
// true, within the indicated range. If a terrain is
|
|
// present, height_min and height_max restrict trees to
|
|
// the (x, y) positions that fall within the indicated
|
|
// terrain, and slope_min and slope_max restrict trees
|
|
// to the (x, y) positions that have a matching slope.
|
|
// If a terrain is not present, height_min and
|
|
// height_max specify a random range of Z heights, and
|
|
// slope_min and slope_max are ignored.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
add_random_instances(const STTree *tree, int quantity,
|
|
PN_stdfloat x_min, PN_stdfloat x_max,
|
|
PN_stdfloat y_min, PN_stdfloat y_max,
|
|
PN_stdfloat scale_min, PN_stdfloat scale_max,
|
|
PN_stdfloat height_min, PN_stdfloat height_max,
|
|
PN_stdfloat slope_min, PN_stdfloat slope_max,
|
|
Randomizer &randomizer) {
|
|
InstanceList &instance_list = add_tree(tree);
|
|
_needs_repopulate = true;
|
|
|
|
for (int i = 0; i < quantity; ++i) {
|
|
STTransform transform;
|
|
transform._pos[0] = randomizer.random_real(x_max - x_min) + x_min;
|
|
transform._pos[1] = randomizer.random_real(y_max - y_min) + y_min;
|
|
transform._rotate = randomizer.random_real(360.0);
|
|
transform._scale = randomizer.random_real(scale_max - scale_min) + scale_min;
|
|
|
|
if (has_terrain()) {
|
|
// Spin till we find a valid match with terrain.
|
|
int repeat_count = speedtree_max_random_try_count;
|
|
while (!_terrain->placement_is_acceptable(transform._pos[0], transform._pos[1], height_min, height_max, slope_min, slope_max)) {
|
|
transform._pos[0] = randomizer.random_real(x_max - x_min) + x_min;
|
|
transform._pos[1] = randomizer.random_real(y_max - y_min) + y_min;
|
|
if (--repeat_count == 0) {
|
|
nassert_raise("Exceeded speedtree-max-random-try-count; bad placement parameters?");
|
|
return;
|
|
}
|
|
}
|
|
transform._pos[2] = _terrain->get_height(transform._pos[0], transform._pos[1]);
|
|
|
|
} else {
|
|
// No terrain; just pick a random height.
|
|
transform._pos[2] = randomizer.random_real(height_max - height_min) + height_min;
|
|
}
|
|
instance_list.add_instance(transform);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_from_stf
|
|
// Access: Published
|
|
// Description: Opens and reads the named STF (SpeedTree Forest)
|
|
// file, and adds the SRT files named within as
|
|
// instances of this node. Returns true on success,
|
|
// false on failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool SpeedTreeNode::
|
|
add_from_stf(const Filename &stf_filename, const LoaderOptions &options) {
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
|
|
Filename fullpath = Filename::text_filename(stf_filename);
|
|
vfs->resolve_filename(fullpath, get_model_path());
|
|
|
|
if (!vfs->exists(fullpath)) {
|
|
speedtree_cat.warning()
|
|
<< "Couldn't find " << stf_filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
PT(VirtualFile) file = vfs->get_file(fullpath);
|
|
if (file == (VirtualFile *)NULL) {
|
|
// No such file.
|
|
speedtree_cat.error()
|
|
<< "Could not find " << stf_filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
if (speedtree_cat.is_debug()) {
|
|
speedtree_cat.debug()
|
|
<< "Reading STF file " << fullpath << "\n";
|
|
}
|
|
|
|
istream *in = file->open_read_file(true);
|
|
bool success = add_from_stf(*in, fullpath, options);
|
|
vfs->close_read_file(in);
|
|
|
|
return success;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_from_stf
|
|
// Access: Published
|
|
// Description: Reads text data from the indicated stream, which is
|
|
// understood to represent the named STF (SpeedTree
|
|
// Forest) file, and adds the SRT files named within as
|
|
// instances of this node. Returns true on success,
|
|
// false on failure.
|
|
//
|
|
// The pathname is used for reference only; if nonempty,
|
|
// it provides a search directory for named SRT files.
|
|
//
|
|
// The Loader and LoaderOptions, if provided, are used
|
|
// to load the SRT files. If the Loader pointer is
|
|
// NULL, the default global Loader is used instead.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool SpeedTreeNode::
|
|
add_from_stf(istream &in, const Filename &pathname,
|
|
const LoaderOptions &options, Loader *loader) {
|
|
if (loader == NULL) {
|
|
loader = Loader::get_global_ptr();
|
|
}
|
|
string os_filename;
|
|
|
|
Filename dirname = pathname.get_dirname();
|
|
dirname.make_absolute();
|
|
DSearchPath search;
|
|
search.append_directory(dirname);
|
|
|
|
typedef pmap<Filename, CPT(STTree) > AlreadyLoaded;
|
|
AlreadyLoaded already_loaded;
|
|
|
|
// The STF file format doesn't allow for spaces in the SRT filename.
|
|
in >> os_filename;
|
|
while (in && !in.eof()) {
|
|
CPT(STTree) tree;
|
|
Filename srt_filename = Filename::from_os_specific(os_filename);
|
|
AlreadyLoaded::iterator ai = already_loaded.find(srt_filename);
|
|
if (ai != already_loaded.end()) {
|
|
tree = (*ai).second;
|
|
} else {
|
|
// Resolve the SRT filename relative to the STF file first.
|
|
srt_filename.resolve_filename(search);
|
|
|
|
// Now load up the SRT file using the Panda loader (which will
|
|
// also search the model-path if necessary).
|
|
PT(PandaNode) srt_root = loader->load_sync(srt_filename);
|
|
|
|
if (srt_root != NULL) {
|
|
NodePath srt(srt_root);
|
|
NodePath srt_np = srt.find("**/+SpeedTreeNode");
|
|
if (!srt_np.is_empty()) {
|
|
SpeedTreeNode *srt_node = DCAST(SpeedTreeNode, srt_np.node());
|
|
if (srt_node->get_num_trees() >= 1) {
|
|
tree = srt_node->get_tree(0);
|
|
}
|
|
}
|
|
}
|
|
already_loaded[srt_filename] = tree;
|
|
}
|
|
|
|
// Now we've loaded the SRT data, so apply it the appropriate
|
|
// number of times to the locations specified.
|
|
int num_instances;
|
|
in >> num_instances;
|
|
for (int ni = 0; ni < num_instances && in && !in.eof(); ++ni) {
|
|
LPoint3 pos;
|
|
PN_stdfloat rotate, scale;
|
|
in >> pos[0] >> pos[1] >> pos[2] >> rotate >> scale;
|
|
|
|
if (!speedtree_5_2_stf) {
|
|
// 5.1 or earlier stf files also included these additional
|
|
// values, which we will ignore:
|
|
PN_stdfloat height_min, height_max, slope_min, slope_max;
|
|
in >> height_min >> height_max >> slope_min >> slope_max;
|
|
}
|
|
|
|
if (tree != NULL) {
|
|
add_instance(tree, STTransform(pos, rad_2_deg(rotate), scale));
|
|
}
|
|
}
|
|
in >> os_filename;
|
|
}
|
|
|
|
// Consume any whitespace at the end of the file.
|
|
in >> ws;
|
|
|
|
if (!in.eof()) {
|
|
// If we didn't read all the way to end-of-file, there was an
|
|
// error.
|
|
in.clear();
|
|
string text;
|
|
in >> text;
|
|
speedtree_cat.error()
|
|
<< "Unexpected text in " << pathname << " at \"" << text << "\"\n";
|
|
return false;
|
|
}
|
|
|
|
// Return true if we successfully read all the way to end-of-file.
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::setup_terrain
|
|
// Access: Published
|
|
// Description: A convenience function to set up terrain geometry by
|
|
// reading a terrain.txt file as defined by SpeedTree.
|
|
// This file names the various map files that define the
|
|
// terrain, as well as defining parameters size as its
|
|
// size and color.
|
|
//
|
|
// This method implicitly creates a STBasicTerrain
|
|
// object and passes it to set_terrain().
|
|
////////////////////////////////////////////////////////////////////
|
|
bool SpeedTreeNode::
|
|
setup_terrain(const Filename &terrain_file) {
|
|
PT(STBasicTerrain) terrain = new STBasicTerrain;
|
|
if (terrain->setup_terrain(terrain_file)) {
|
|
set_terrain(terrain);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::set_terrain
|
|
// Access: Published
|
|
// Description: Associated a terrain with the node. If the terrain
|
|
// has not already been loaded prior to this call,
|
|
// load_data() will be called immediately.
|
|
//
|
|
// The terrain will be rendered using SpeedTree
|
|
// callbacks, and trees may be repositioned with a call
|
|
// to snap_to_terrain().
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
set_terrain(STTerrain *terrain) {
|
|
_terrain = NULL;
|
|
_needs_repopulate = true;
|
|
|
|
if (terrain == (STTerrain *)NULL) {
|
|
return;
|
|
}
|
|
|
|
if (!terrain->is_valid()) {
|
|
// If the terrain was not already loaded, load it immediately.
|
|
terrain->load_data();
|
|
}
|
|
|
|
nassertv(terrain->is_valid());
|
|
nassertv(terrain->get_num_splat_layers() == SpeedTree::c_nNumTerrainSplatLayers);
|
|
_terrain = terrain;
|
|
|
|
_terrain_render.SetShaderLoader(_forest_render.GetShaderLoader());
|
|
|
|
SpeedTree::STerrainRenderInfo trender_info;
|
|
trender_info.m_strShaderPath = _os_shaders_dir.c_str();
|
|
|
|
string os_specific = terrain->get_normal_map().to_os_specific();
|
|
trender_info.m_strNormalMap = os_specific.c_str();
|
|
os_specific = terrain->get_splat_map().to_os_specific();
|
|
trender_info.m_strSplatMap = os_specific.c_str();
|
|
|
|
for (int i = 0; i < SpeedTree::c_nNumTerrainSplatLayers; ++i) {
|
|
os_specific = terrain->get_splat_layer(i).to_os_specific();
|
|
trender_info.m_astrSplatLayers[i] = os_specific.c_str();
|
|
trender_info.m_afSplatTileValues[i] = terrain->get_splat_layer_tiling(i);
|
|
}
|
|
|
|
trender_info.m_fNormalMapBlueScale = 1.0f;
|
|
trender_info.m_bShadowsEnabled = false; // what does this do?
|
|
trender_info.m_bZPrePass = false;
|
|
|
|
_terrain_render.SetRenderInfo(trender_info);
|
|
|
|
_terrain_render.SetHeightHints(terrain->get_min_height(), terrain->get_max_height());
|
|
|
|
if (speedtree_follow_terrain) {
|
|
snap_to_terrain();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::snap_to_terrain
|
|
// Access: Published
|
|
// Description: Adjusts all the trees in this node so that their Z
|
|
// position matches the height of the terrain at their
|
|
// X, Y position.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
snap_to_terrain() {
|
|
Trees::iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
|
|
int num_instances = instance_list->get_num_instances();
|
|
if (_terrain != (STTerrain *)NULL) {
|
|
for (int i = 0; i < num_instances; ++i) {
|
|
STTransform trans = instance_list->get_instance(i);
|
|
LPoint3 pos = trans.get_pos();
|
|
pos[2] = _terrain->get_height(pos[0], pos[1]);
|
|
trans.set_pos(pos);
|
|
instance_list->set_instance(i, trans);
|
|
}
|
|
} else {
|
|
for (int i = 0; i < num_instances; ++i) {
|
|
STTransform trans = instance_list->get_instance(i);
|
|
LPoint3 pos = trans.get_pos();
|
|
pos[2] = 0.0f;
|
|
trans.set_pos(pos);
|
|
instance_list->set_instance(i, trans);
|
|
}
|
|
}
|
|
}
|
|
|
|
_needs_repopulate = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::reload_config
|
|
// Access: Published
|
|
// Description: Re-reads the current setting of all of the relevant
|
|
// config variables and applies them to this node. This
|
|
// can be called after changing config settings, to make
|
|
// them apply to this particular node.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
reload_config() {
|
|
|
|
_shadow_infos.clear();
|
|
int num_shadow_maps = speedtree_cascading_shadow_splits.get_num_words();
|
|
if (num_shadow_maps > SpeedTree::c_nMaxNumShadowMaps) {
|
|
speedtree_cat.warning()
|
|
<< "SpeedTree is current compiled to support a maximum of "
|
|
<< SpeedTree::c_nMaxNumShadowMaps << " shadow maps.\n";
|
|
num_shadow_maps = SpeedTree::c_nMaxNumShadowMaps;
|
|
}
|
|
_shadow_infos.insert(_shadow_infos.begin(), num_shadow_maps, ShadowInfo());
|
|
for (int smi = 0; smi < num_shadow_maps; ++smi) {
|
|
_shadow_infos[smi]._shadow_split = speedtree_cascading_shadow_splits[smi];
|
|
}
|
|
|
|
SpeedTree::SForestRenderInfo render_info = _forest_render.GetRenderInfo();
|
|
|
|
render_info.m_nMaxAnisotropy = speedtree_max_anisotropy;
|
|
render_info.m_bHorizontalBillboards = speedtree_horizontal_billboards;
|
|
render_info.m_fAlphaTestScalar = speedtree_alpha_test_scalar;
|
|
render_info.m_bZPrePass = speedtree_z_pre_pass;
|
|
render_info.m_nMaxBillboardImagesByBase = speedtree_max_billboard_images_by_base;
|
|
render_info.m_fVisibility = speedtree_visibility;
|
|
render_info.m_fGlobalLightScalar = speedtree_global_light_scalar;
|
|
render_info.m_sLightMaterial.m_vSpecular = SpeedTree::Vec4(speedtree_specular_color[0], speedtree_specular_color[1], speedtree_specular_color[2], 1.0f);
|
|
render_info.m_sLightMaterial.m_vEmissive = SpeedTree::Vec4(speedtree_emissive_color[0], speedtree_emissive_color[1], speedtree_emissive_color[2], 1.0f);
|
|
render_info.m_bSpecularLighting = speedtree_specular_lighting;
|
|
render_info.m_bTransmissionLighting = speedtree_transmission_lighting;
|
|
render_info.m_bDetailLayer = speedtree_detail_layer;
|
|
render_info.m_bDetailNormalMapping = speedtree_detail_normal_mapping;
|
|
render_info.m_bAmbientContrast = speedtree_ambient_contrast;
|
|
render_info.m_fTransmissionScalar = speedtree_transmission_scalar;
|
|
render_info.m_fFogStartDistance = speedtree_fog_distance[0];
|
|
render_info.m_fFogEndDistance = speedtree_fog_distance[1];
|
|
render_info.m_vFogColor = SpeedTree::Vec3(speedtree_fog_color[0], speedtree_fog_color[1], speedtree_fog_color[2]);
|
|
render_info.m_vSkyColor = SpeedTree::Vec3(speedtree_sky_color[0], speedtree_sky_color[1], speedtree_sky_color[2]);
|
|
render_info.m_fSkyFogMin = speedtree_sky_fog[0];
|
|
render_info.m_fSkyFogMax = speedtree_sky_fog[1];
|
|
render_info.m_vSunColor = SpeedTree::Vec3(speedtree_sun_color[0], speedtree_sun_color[1], speedtree_sun_color[2]);
|
|
render_info.m_fSunSize = speedtree_sun_size;
|
|
render_info.m_fSunSpreadExponent = speedtree_sun_spread_exponent;
|
|
render_info.m_fSunFogBloom = speedtree_sun_fog_bloom;
|
|
render_info.m_nNumShadowMaps = num_shadow_maps;
|
|
render_info.m_nShadowMapResolution = speedtree_shadow_map_resolution;
|
|
render_info.m_bSmoothShadows = speedtree_smooth_shadows;
|
|
render_info.m_bShowShadowSplitsOnTerrain = speedtree_show_shadow_splits_on_terrain;
|
|
render_info.m_bWindEnabled = speedtree_wind_enabled;
|
|
render_info.m_bFrondRippling = speedtree_frond_rippling;
|
|
|
|
_forest_render.SetRenderInfo(render_info);
|
|
|
|
_terrain_render.SetMaxAnisotropy(speedtree_max_anisotropy);
|
|
_terrain_render.SetHint(SpeedTree::CTerrain::HINT_MAX_NUM_VISIBLE_CELLS,
|
|
speedtree_max_num_visible_cells);
|
|
_visible_terrain.Reserve(speedtree_max_num_visible_cells);
|
|
|
|
_needs_repopulate = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::set_wind
|
|
// Access: Published
|
|
// Description: Specifies the overall wind strength and direction.
|
|
// Gusts are controlled internally.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
set_wind(double strength, const LVector3 &direction) {
|
|
_forest_render.SetGlobalWindStrength(strength);
|
|
_forest_render.SetGlobalWindDirection(SpeedTree::Vec3(direction[0], direction[1], direction[2]));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::authorize
|
|
// Access: Published, Static
|
|
// Description: Make this call to initialized the SpeedTree API and
|
|
// verify the license. If an empty string is passed for
|
|
// the license, the config variable speedtree-license is
|
|
// consulted. Returns true on success, false on
|
|
// failure. If this call is not made explicitly, it
|
|
// will be made implicitly the first time a
|
|
// SpeedTreeNode is created.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool SpeedTreeNode::
|
|
authorize(const string &license) {
|
|
if (!_authorized) {
|
|
if (!license.empty()) {
|
|
SpeedTree::CCore::Authorize(license.c_str());
|
|
} else {
|
|
if (!speedtree_license.empty()) {
|
|
SpeedTree::CCore::Authorize(speedtree_license.c_str());
|
|
}
|
|
}
|
|
|
|
_authorized = SpeedTree::CCore::IsAuthorized();
|
|
|
|
SpeedTree::CCore::SetTextureFlip(true);
|
|
}
|
|
|
|
return _authorized;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::Copy Constructor
|
|
// Access: Protected
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
SpeedTreeNode::
|
|
SpeedTreeNode(const SpeedTreeNode ©) :
|
|
PandaNode(copy),
|
|
_os_shaders_dir(copy._os_shaders_dir),
|
|
_shadow_infos(copy._shadow_infos),
|
|
#ifdef ST_DELETE_FOREST_HACK
|
|
// Early versions of SpeedTree don't destruct unused CForestRender
|
|
// objects correctly. To avoid crashes, we have to leak these
|
|
// things.
|
|
_forest_render(*(new SpeedTree::CForestRender)),
|
|
#endif
|
|
_time_delta(copy._time_delta)
|
|
{
|
|
init_node();
|
|
|
|
_forest_render.SetRenderInfo(copy._forest_render.GetRenderInfo());
|
|
_terrain_render.SetRenderInfo(copy._terrain_render.GetRenderInfo());
|
|
|
|
// No way to copy these parameters, so we just re-assign them.
|
|
_terrain_render.SetMaxAnisotropy(speedtree_max_anisotropy);
|
|
_terrain_render.SetHint(SpeedTree::CTerrain::HINT_MAX_NUM_VISIBLE_CELLS,
|
|
speedtree_max_num_visible_cells);
|
|
_visible_terrain.Reserve(speedtree_max_num_visible_cells);
|
|
|
|
Trees::const_iterator ti;
|
|
for (ti = copy._trees.begin(); ti != copy._trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
const STTree *tree = instance_list->get_tree();
|
|
if (!_forest_render.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to register tree " << tree->get_fullpath() << "\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
|
|
_trees.push_back(new InstanceList(*instance_list));
|
|
}
|
|
_trees.sort();
|
|
|
|
set_terrain(copy._terrain);
|
|
|
|
_needs_repopulate = true;
|
|
mark_internal_bounds_stale();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::Destructor
|
|
// Access: Published, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
SpeedTreeNode::
|
|
~SpeedTreeNode() {
|
|
remove_all_trees();
|
|
// Help reduce memory waste from ST_DELETE_FOREST_HACK.
|
|
_forest_render.ClearInstances();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::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 *SpeedTreeNode::
|
|
make_copy() const {
|
|
return new SpeedTreeNode(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::combine_with
|
|
// Access: Public, Virtual
|
|
// Description: Collapses this node with the other node, if possible,
|
|
// and returns a pointer to the combined node, or NULL
|
|
// if the two nodes cannot safely be combined.
|
|
//
|
|
// The return value may be this, other, or a new node
|
|
// altogether.
|
|
//
|
|
// This function is called from GraphReducer::flatten(),
|
|
// and need not deal with children; its job is just to
|
|
// decide whether to collapse the two nodes and what the
|
|
// collapsed node should look like.
|
|
////////////////////////////////////////////////////////////////////
|
|
PandaNode *SpeedTreeNode::
|
|
combine_with(PandaNode *other) {
|
|
if (is_exact_type(get_class_type()) &&
|
|
other->is_exact_type(get_class_type())) {
|
|
// Two SpeedTreeNodes can combine by moving trees from one to the
|
|
// other, similar to the way GeomNodes combine.
|
|
SpeedTreeNode *gother = DCAST(SpeedTreeNode, other);
|
|
|
|
// But, not if they both have a terrain set.
|
|
if (has_terrain() && gother->has_terrain()) {
|
|
return NULL;
|
|
|
|
} else if (gother->has_terrain()) {
|
|
set_terrain(gother->get_terrain());
|
|
}
|
|
|
|
add_instances_from(gother);
|
|
return this;
|
|
}
|
|
|
|
return PandaNode::combine_with(other);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::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 SpeedTreeNode::
|
|
apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
|
|
GeomTransformer &transformer) {
|
|
if ((attrib_types & SceneGraphReducer::TT_transform) != 0) {
|
|
STTransform xform = attribs._transform;
|
|
Trees::iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
STInstances &instances = instance_list->_instances;
|
|
STInstances::iterator sti;
|
|
for (sti = instances.begin(); sti != instances.end(); ++sti) {
|
|
STTransform orig_transform = *sti;
|
|
(*sti) = orig_transform * xform;
|
|
}
|
|
}
|
|
}
|
|
mark_internal_bounds_stale();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::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 SpeedTreeNode::
|
|
cull_callback(CullTraverser *trav, CullTraverserData &data) {
|
|
if (!_is_valid) {
|
|
return false;
|
|
}
|
|
PStatTimer timer(_cull_speedtree_pcollector);
|
|
|
|
GraphicsStateGuardian *gsg = DCAST(GraphicsStateGuardian, trav->get_gsg());
|
|
nassertr(gsg != (GraphicsStateGuardian *)NULL, true);
|
|
if (!validate_api(gsg)) {
|
|
return false;
|
|
}
|
|
|
|
ClockObject *clock = ClockObject::get_global_clock();
|
|
_forest_render.SetGlobalTime(clock->get_frame_time() + _time_delta + _global_time_delta);
|
|
_forest_render.AdvanceGlobalWind();
|
|
|
|
// Compute the modelview and camera transforms, to pass to the
|
|
// SpeedTree CView structure.
|
|
CPT(TransformState) orig_modelview = data.get_modelview_transform(trav);
|
|
CPT(TransformState) modelview = trav->get_scene()->get_cs_transform()->compose(orig_modelview);
|
|
CPT(TransformState) camera_transform = modelview->invert_compose(TransformState::make_identity());
|
|
LMatrix4f modelview_mat = LCAST(float, modelview->get_mat());
|
|
const LPoint3 &camera_pos = camera_transform->get_pos();
|
|
const Lens *lens = trav->get_scene()->get_lens();
|
|
|
|
LMatrix4f projection_mat =
|
|
LCAST(float, LMatrix4::convert_mat(gsg->get_internal_coordinate_system(), lens->get_coordinate_system()) *
|
|
lens->get_projection_mat());
|
|
|
|
_view.Set(SpeedTree::Vec3(camera_pos[0], camera_pos[1], camera_pos[2]),
|
|
SpeedTree::Mat4x4(projection_mat.get_data()),
|
|
SpeedTree::Mat4x4(modelview_mat.get_data()),
|
|
lens->get_near(), lens->get_far());
|
|
|
|
// Convert the render state to SpeedTree's input.
|
|
const RenderState *state = data._state;
|
|
|
|
// Check texture state. If all textures are disabled, then we ask
|
|
// SpeedTree to disable textures.
|
|
bool show_textures = true;
|
|
const TextureAttrib *ta = DCAST(TextureAttrib, state->get_attrib(TextureAttrib::get_class_slot()));
|
|
if (ta != (TextureAttrib *)NULL) {
|
|
show_textures = !ta->has_all_off();
|
|
}
|
|
_forest_render.EnableTexturing(show_textures);
|
|
_terrain_render.EnableTexturing(show_textures);
|
|
|
|
// Check lighting state. SpeedTree only supports a single
|
|
// directional light; we look for a directional light in the
|
|
// lighting state and pass its direction and color to SpeedTree. We
|
|
// also accumulate the ambient light colors.
|
|
LColor ambient_color(0.0f, 0.0f, 0.0f, 0.0f);
|
|
DirectionalLight *dlight = NULL;
|
|
NodePath dlight_np;
|
|
LColor diffuse_color;
|
|
|
|
int diffuse_priority = 0;
|
|
const LightAttrib *la = DCAST(LightAttrib, state->get_attrib(LightAttrib::get_class_slot()));
|
|
if (la != (LightAttrib *)NULL) {
|
|
for (int i = 0; i < la->get_num_on_lights(); ++i) {
|
|
NodePath light = la->get_on_light(i);
|
|
if (!light.is_empty() && light.node()->is_of_type(DirectionalLight::get_class_type())) {
|
|
// A directional light.
|
|
DirectionalLight *light_obj = DCAST(DirectionalLight, light.node());
|
|
if (dlight == NULL || light_obj->get_priority() > dlight->get_priority()) {
|
|
// Here's the most important directional light.
|
|
dlight = light_obj;
|
|
dlight_np = light;
|
|
}
|
|
} else if (!light.is_empty() && light.node()->is_of_type(AmbientLight::get_class_type())) {
|
|
// An ambient light. We keep the color only.
|
|
AmbientLight *light_obj = DCAST(AmbientLight, light.node());
|
|
ambient_color += light_obj->get_color();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dlight != (DirectionalLight *)NULL) {
|
|
CPT(TransformState) transform = dlight_np.get_transform(trav->get_scene()->get_scene_root().get_parent());
|
|
LVector3 dir = dlight->get_direction() * transform->get_mat();
|
|
dir.normalize();
|
|
_light_dir = SpeedTree::Vec3(dir[0], dir[1], dir[2]);
|
|
diffuse_color = dlight->get_color();
|
|
|
|
} else {
|
|
// No light. But there's no way to turn off lighting in
|
|
// SpeedTree. In lieu of this, we just shine a light from
|
|
// above.
|
|
_light_dir = SpeedTree::Vec3(0.0, 0.0, -1.0);
|
|
|
|
// Also, we set ambient and diffuse colors to the same full-white
|
|
// value.
|
|
ambient_color.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
diffuse_color.set(1.0f, 1.0f, 1.0f, 1.0f);
|
|
}
|
|
|
|
SpeedTree::SForestRenderInfo render_info = _forest_render.GetRenderInfo();
|
|
render_info.m_sLightMaterial.m_vAmbient = SpeedTree::Vec4(ambient_color[0], ambient_color[1], ambient_color[2], 1.0f);
|
|
render_info.m_sLightMaterial.m_vDiffuse = SpeedTree::Vec4(diffuse_color[0], diffuse_color[1], diffuse_color[2], 1.0f);
|
|
_forest_render.SetRenderInfo(render_info);
|
|
|
|
_forest_render.SetLightDir(_light_dir);
|
|
|
|
SpeedTree::st_float32 updated_splits[SpeedTree::c_nMaxNumShadowMaps];
|
|
memset(updated_splits, 0, sizeof(updated_splits));
|
|
for (int smi = 0; smi < (int)_shadow_infos.size(); ++smi) {
|
|
updated_splits[smi] = _shadow_infos[smi]._shadow_split;
|
|
};
|
|
|
|
_forest_render.SetCascadedShadowMapDistances(updated_splits, lens->get_far());
|
|
_forest_render.SetShadowFadePercentage(speedtree_shadow_fade);
|
|
|
|
if (!_needs_repopulate) {
|
|
// Don't bother culling now unless we're correctly fully
|
|
// populated. (Culling won't be accurate unless the forest has
|
|
// been populated, but we have to be in the draw traversal to
|
|
// populate.)
|
|
cull_forest();
|
|
}
|
|
|
|
// Recurse onto the node's children.
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::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 SpeedTreeNode::
|
|
is_renderable() const {
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::add_for_draw
|
|
// Access: Public, Virtual
|
|
// Description: Adds the node's contents to the CullResult we are
|
|
// building up during the cull traversal, so that it
|
|
// will be drawn at render time. For most nodes other
|
|
// than GeomNodes, this is a do-nothing operation.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
add_for_draw(CullTraverser *trav, CullTraverserData &data) {
|
|
if (_is_valid) {
|
|
// We create a CullableObject that has an explicit draw_callback
|
|
// into this node, so that we can make the appropriate calls into
|
|
// SpeedTree to render the forest during the actual draw.
|
|
CullableObject *object =
|
|
new CullableObject(NULL, data._state,
|
|
TransformState::make_identity());
|
|
object->set_draw_callback(new DrawCallback(this));
|
|
trav->get_cull_handler()->record_object(object, trav);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::prepare_scene
|
|
// Access: Published
|
|
// Description: Walks through the scene graph beginning at this node,
|
|
// and does whatever initialization is required to
|
|
// render the scene properly with the indicated GSG. It
|
|
// is not strictly necessary to call this, since the GSG
|
|
// will initialize itself when the scene is rendered,
|
|
// but this may take some of the overhead away from that
|
|
// process.
|
|
//
|
|
// In particular, this will ensure that textures within
|
|
// the scene are loaded in texture memory, and display
|
|
// lists are built up from static geometry.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
prepare_scene(GraphicsStateGuardianBase *gsgbase, const RenderState *) {
|
|
GraphicsStateGuardian *gsg = DCAST(GraphicsStateGuardian, gsgbase);
|
|
if (validate_api(gsg)) {
|
|
setup_for_render(gsg);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::compute_internal_bounds
|
|
// Access: Protected, Virtual
|
|
// Description: Returns a newly-allocated BoundingVolume that
|
|
// represents the internal contents of the node. Should
|
|
// be overridden by PandaNode classes that contain
|
|
// something internally.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
|
|
int &internal_vertices,
|
|
int pipeline_stage,
|
|
Thread *current_thread) const {
|
|
internal_vertices = 0;
|
|
|
|
SpeedTree::CExtents extents;
|
|
Trees::const_iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
const STTree *tree = instance_list->get_tree();
|
|
|
|
const STInstances &st_instances = instance_list->_instances;
|
|
STInstances::const_iterator ii;
|
|
for (ii = st_instances.begin(); ii != st_instances.end(); ++ii) {
|
|
SpeedTree::CExtents tree_extents = tree->get_tree()->GetExtents();
|
|
tree_extents.Rotate((*ii).GetRotationAngle());
|
|
tree_extents.Scale((*ii).GetScale());
|
|
tree_extents.Translate((*ii).GetPos());
|
|
extents.ExpandAround(tree_extents);
|
|
}
|
|
}
|
|
|
|
const SpeedTree::Vec3 &emin = extents.Min();
|
|
const SpeedTree::Vec3 &emax = extents.Max();
|
|
internal_bounds = new BoundingBox(LPoint3(emin[0], emin[1], emin[2]),
|
|
LPoint3(emax[0], emax[1], emax[2]));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::output
|
|
// Access: Public, Virtual
|
|
// Description: Writes a brief description of the node to the
|
|
// indicated output stream. This is invoked by the <<
|
|
// operator. It may be overridden in derived classes to
|
|
// include some information relevant to the class.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
output(ostream &out) const {
|
|
PandaNode::output(out);
|
|
out
|
|
<< " (" << get_num_trees() << " unique trees with "
|
|
<< count_total_instances() << " total instances)";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::write
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
write(ostream &out, int indent_level) const {
|
|
PandaNode::write(out, indent_level);
|
|
|
|
// This makes NodePath.ls() too confusing.
|
|
/*
|
|
Trees::const_iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
indent(out, indent_level + 2)
|
|
<< *instance_list << "\n";
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::write_error
|
|
// Access: Public, Static
|
|
// Description: Writes the current SpeedTree error message to the
|
|
// indicated stream.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
write_error(ostream &out) {
|
|
const char *error = SpeedTree::CCore::GetError();
|
|
if (error != (const char *)NULL) {
|
|
out << error;
|
|
}
|
|
out << "\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::set_transparent_texture_mode
|
|
// Access: Protected
|
|
// Description: Uses SpeedTree::CRenderState to set the indicated
|
|
// transparency mode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
set_transparent_texture_mode(SpeedTree::ETextureAlphaRenderMode eMode) const {
|
|
// turn all modes off (no telling what render state the client
|
|
// application might be in before this call)
|
|
SpeedTree::CRenderState::SetBlending(false);
|
|
SpeedTree::CRenderState::SetAlphaTesting(false);
|
|
SpeedTree::CRenderState::SetAlphaToCoverage(false);
|
|
|
|
switch (eMode) {
|
|
case SpeedTree::TRANS_TEXTURE_ALPHA_TESTING:
|
|
SpeedTree::CRenderState::SetAlphaTesting(true);
|
|
break;
|
|
case SpeedTree::TRANS_TEXTURE_ALPHA_TO_COVERAGE:
|
|
SpeedTree::CRenderState::SetAlphaToCoverage(true);
|
|
break;
|
|
case SpeedTree::TRANS_TEXTURE_BLENDING:
|
|
SpeedTree::CRenderState::SetBlending(true);
|
|
break;
|
|
default:
|
|
// intentionally do nothing (TRANS_TEXTURE_NOTHING)
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::init_node
|
|
// Access: Private
|
|
// Description: Called from the constructor to initialize some
|
|
// internal values.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
init_node() {
|
|
PandaNode::set_cull_callback();
|
|
|
|
_is_valid = false;
|
|
_needs_repopulate = false;
|
|
|
|
// Ensure we have a license.
|
|
if (!authorize()) {
|
|
speedtree_cat.warning()
|
|
<< "SpeedTree license not available.\n";
|
|
return;
|
|
}
|
|
|
|
_forest_render.SetHint(SpeedTree::CForest::HINT_MAX_NUM_VISIBLE_CELLS,
|
|
speedtree_max_num_visible_cells);
|
|
|
|
_forest_render.SetCullCellSize(speedtree_cull_cell_size);
|
|
|
|
// Doesn't appear to be necessary to call this explicitly.
|
|
//_forest_render.EnableWind(true);
|
|
|
|
_is_valid = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::r_add_instances
|
|
// Access: Private
|
|
// Description: The recursive implementation of add_instances().
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
r_add_instances(PandaNode *node, const TransformState *transform,
|
|
Thread *current_thread) {
|
|
if (node->is_of_type(SpeedTreeNode::get_class_type()) && node != this) {
|
|
SpeedTreeNode *other = DCAST(SpeedTreeNode, node);
|
|
add_instances_from(other, transform);
|
|
}
|
|
|
|
Children children = node->get_children(current_thread);
|
|
for (int i = 0; i < children.get_num_children(); i++) {
|
|
PandaNode *child = children.get_child(i);
|
|
CPT(TransformState) child_transform = transform->compose(child->get_transform());
|
|
r_add_instances(child, child_transform, current_thread);
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::repopulate
|
|
// Access: Private
|
|
// Description: Rebuilds the internal structures as necessary for
|
|
// rendering.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
repopulate() {
|
|
_forest_render.ClearInstances();
|
|
|
|
Trees::iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
const STTree *tree = instance_list->get_tree();
|
|
const STInstances &instances = instance_list->_instances;
|
|
if (instances.empty()) {
|
|
// There are no instances, so don't bother. (This shouldn't
|
|
// happen often, because we remove trees from the SpeedTreeNode
|
|
// when their instance list goes empty, though it's possible if
|
|
// the user has explicitly removed all of the instances.)
|
|
continue;
|
|
}
|
|
|
|
if (!_forest_render.AddInstances(tree->get_tree(), &instances[0], instances.size())) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to add " << instances.size()
|
|
<< " instances for " << *tree << "\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
}
|
|
|
|
_forest_render.GetPopulationStats(_population_stats);
|
|
print_forest_stats(_population_stats);
|
|
|
|
// setup billboard caps based on instances-per-cell stats
|
|
int max_instances_by_cell = 1;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
const STTree *tree = instance_list->get_tree();
|
|
const STInstances &instances = instance_list->_instances;
|
|
if (instances.empty()) {
|
|
continue;
|
|
}
|
|
|
|
int max_instances = 1;
|
|
SpeedTree::CMap<const SpeedTree::CTree*, SpeedTree::st_int32>::const_iterator si;
|
|
si = _population_stats.m_mMaxNumInstancesPerCellPerBase.find(tree->get_tree());
|
|
if (si != _population_stats.m_mMaxNumInstancesPerCellPerBase.end()) {
|
|
max_instances = max(max_instances, (int)si->second);
|
|
}
|
|
|
|
max_instances_by_cell = max(max_instances_by_cell, max_instances);
|
|
}
|
|
|
|
_visible_trees.Reserve(_forest_render.GetBaseTrees(),
|
|
_forest_render.GetBaseTrees().size(),
|
|
speedtree_max_num_visible_cells,
|
|
max_instances_by_cell,
|
|
speedtree_horizontal_billboards);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::update_terrain_cells
|
|
// Access: Private
|
|
// Description: Called once a frame to load vertex data for
|
|
// newly-visible terrain cells.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
update_terrain_cells() {
|
|
nassertv(has_terrain());
|
|
|
|
SpeedTree::TTerrainCellArray &cells = _visible_terrain.m_aCellsToUpdate;
|
|
|
|
int num_tile_res = _terrain_render.GetMaxTileRes();
|
|
PN_stdfloat cell_size = _terrain_render.GetCellSize();
|
|
|
|
// A temporary vertex data object for populating terrain.
|
|
PT(GeomVertexData) vertex_data =
|
|
new GeomVertexData("terrain", _terrain->get_vertex_format(),
|
|
GeomEnums::UH_static);
|
|
int num_vertices = num_tile_res * num_tile_res;
|
|
vertex_data->set_num_rows(num_vertices);
|
|
size_t num_bytes = vertex_data->get_array(0)->get_data_size_bytes();
|
|
|
|
int num_cells = (int)cells.size();
|
|
for (int ci = 0; ci < num_cells; ++ci) {
|
|
SpeedTree::CTerrainCell *cell = cells[ci];
|
|
nassertv(cell != NULL && cell->GetVbo() != NULL);
|
|
int cell_yi = cell->Row();
|
|
int cell_xi = cell->Col();
|
|
//cerr << "populating cell " << cell_xi << " " << cell_yi << "\n";
|
|
|
|
_terrain->fill_vertices(vertex_data,
|
|
cell_xi * cell_size, cell_yi * cell_size,
|
|
cell_size, num_tile_res);
|
|
|
|
const GeomVertexArrayData *array_data = vertex_data->get_array(0);
|
|
CPT(GeomVertexArrayDataHandle) handle = array_data->get_handle();
|
|
const unsigned char *data_pointer = handle->get_read_pointer(true);
|
|
SpeedTree::CGeometryBuffer *vbo = (SpeedTree::CGeometryBuffer *)cell->GetVbo();
|
|
|
|
nassertv(vbo->NumVertices() == num_tile_res * num_tile_res);
|
|
nassertv(vbo->NumVertices() * vbo->VertexSize() == handle->get_data_size_bytes());
|
|
vbo->OverwriteVertices(data_pointer, num_vertices, 0);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::validate_api
|
|
// Access: Private
|
|
// Description: Returns true if the indicated GSG shares the
|
|
// appropriate API for this SpeedTreeNode, false
|
|
// otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool SpeedTreeNode::
|
|
validate_api(GraphicsStateGuardian *gsg) {
|
|
GraphicsPipe *pipe = gsg->get_pipe();
|
|
nassertr(pipe != (GraphicsPipe *)NULL, true);
|
|
|
|
#if defined(SPEEDTREE_OPENGL)
|
|
static const string compiled_api = "OpenGL";
|
|
#elif defined(SPEEDTREE_DIRECTX9)
|
|
static const string compiled_api = "DirectX9";
|
|
#else
|
|
#error Unexpected graphics API.
|
|
#endif
|
|
|
|
if (pipe->get_interface_name() != compiled_api) {
|
|
speedtree_cat.error()
|
|
<< "SpeedTree is compiled for " << compiled_api
|
|
<< ", cannot render with " << pipe->get_interface_name()
|
|
<< "\n";
|
|
_is_valid = false;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::draw_callback
|
|
// Access: Private
|
|
// Description: Called when the node is visited during the draw
|
|
// traversal, by virtue of our DrawCallback construct.
|
|
// This makes the calls into SpeedTree to perform the
|
|
// actual rendering.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
draw_callback(CallbackData *data) {
|
|
PStatTimer timer(_draw_speedtree_pcollector);
|
|
GeomDrawCallbackData *geom_cbdata;
|
|
DCAST_INTO_V(geom_cbdata, data);
|
|
|
|
GraphicsStateGuardian *gsg = DCAST(GraphicsStateGuardian, geom_cbdata->get_gsg());
|
|
|
|
setup_for_render(gsg);
|
|
|
|
// Set some initial state requirements.
|
|
SpeedTree::CRenderState::SetAlphaFunction(SpeedTree::ALPHAFUNC_GREATER, 0.0f);
|
|
|
|
// start the forest render
|
|
_forest_render.StartRender();
|
|
|
|
if (_forest_render.ShadowsAreEnabled()) {
|
|
// Update the shadow maps. TODO: consider updating these only
|
|
// every once in a while, instead of every frame, as a simple
|
|
// optimization.
|
|
PStatTimer timer(_draw_speedtree_shadows_pcollector);
|
|
render_forest_into_shadow_maps();
|
|
_forest_render.ClearBoundTextures( );
|
|
}
|
|
|
|
if (!_forest_render.UploadViewShaderParameters(_view)) {
|
|
speedtree_cat.warning()
|
|
<< "Couldn't set view parameters\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
|
|
if (has_terrain()) {
|
|
PStatTimer timer1(_draw_speedtree_terrain_pcollector);
|
|
// Is this needed for terrain?
|
|
_terrain_render.UploadShaderConstants
|
|
(&_forest_render, _light_dir,
|
|
_forest_render.GetRenderInfo().m_sLightMaterial);
|
|
|
|
// set terrain render states
|
|
set_transparent_texture_mode(SpeedTree::TRANS_TEXTURE_NOTHING);
|
|
|
|
// render actual terrain
|
|
bool terrain = _terrain_render.Render
|
|
(&_forest_render, _visible_terrain, SpeedTree::RENDER_PASS_STANDARD,
|
|
_light_dir, _forest_render.GetRenderInfo().m_sLightMaterial,
|
|
&_forest_render.GetRenderStats());
|
|
|
|
if (!terrain) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to render terrain\n";
|
|
write_error(speedtree_cat.warning());
|
|
|
|
// Clear the terrain so we don't keep spamming error messages.
|
|
_terrain = NULL;
|
|
}
|
|
}
|
|
|
|
{
|
|
// Now draw the actual trees.
|
|
PStatTimer timer1(_draw_speedtree_trees_pcollector);
|
|
|
|
// SpeedTree::ETextureAlphaRenderMode mode = SpeedTree::TRANS_TEXTURE_ALPHA_TESTING;
|
|
SpeedTree::ETextureAlphaRenderMode mode = SpeedTree::TRANS_TEXTURE_ALPHA_TO_COVERAGE;
|
|
//SpeedTree::ETextureAlphaRenderMode mode = SpeedTree::TRANS_TEXTURE_BLENDING;
|
|
//SpeedTree::ETextureAlphaRenderMode mode = SpeedTree::TRANS_TEXTURE_NOTHING;
|
|
set_transparent_texture_mode(SpeedTree::ETextureAlphaRenderMode(mode));
|
|
|
|
bool branches = _forest_render.RenderBranches(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
|
|
bool fronds = _forest_render.RenderFronds(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
|
|
bool leaf_meshes = _forest_render.RenderLeafMeshes(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
|
|
bool leaf_cards = _forest_render.RenderLeafCards(_visible_trees, SpeedTree::RENDER_PASS_STANDARD, _view);
|
|
bool billboards = _forest_render.RenderBillboards(_visible_trees, SpeedTree::RENDER_PASS_STANDARD, _view);
|
|
|
|
// Sometimes billboards comes back false, particularly if wind is
|
|
// disabled; but the billboards appear to have been rendered
|
|
// successfully. Weird. Just removing this test from the
|
|
// condition.
|
|
|
|
if (!branches || !fronds || !leaf_meshes || !leaf_cards /* || !billboards */) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to render forest completely: "
|
|
<< branches << " " << fronds << " " << leaf_meshes << " " << leaf_cards << " " << billboards << "\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
}
|
|
|
|
_forest_render.EndRender();
|
|
|
|
if (_forest_render.ShadowsAreEnabled() && speedtree_show_overlays) {
|
|
_forest_render.RenderOverlays();
|
|
}
|
|
|
|
// SpeedTree leaves the graphics state indeterminate. Make sure
|
|
// Panda doesn't rely on anything in the state.
|
|
geom_cbdata->set_lost_state(true);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::render_forest_into_shadow_maps
|
|
// Access: Private
|
|
// Description: Renders the forest from the point of view of the
|
|
// light, to fill up the shadow map(s).
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
render_forest_into_shadow_maps() {
|
|
bool success = true;
|
|
|
|
// d3d10 allows A2C on render targets, so make sure to turn it off
|
|
SpeedTree::CRenderState::SetMultisampling(false);
|
|
SpeedTree::CRenderState::SetAlphaToCoverage(false);
|
|
|
|
#if defined(SPEEDTREE_OPENGL)
|
|
// Ensure the viewport is not constrained. SpeedTree doesn't expect
|
|
// that.
|
|
glDisable(GL_SCISSOR_TEST);
|
|
#endif
|
|
|
|
for (int smi = 0; smi < (int)_shadow_infos.size(); ++smi) {
|
|
const SpeedTree::CView &light_view = _shadow_infos[smi]._light_view;
|
|
const SpeedTree::SForestCullResults &light_cull = _shadow_infos[smi]._light_cull;
|
|
|
|
if (_forest_render.BeginShadowMap(smi, light_view)) {
|
|
success &= _forest_render.UploadViewShaderParameters(light_view);
|
|
|
|
// branch geometry can be rendered with backfacing triangle
|
|
// removed, so a closer tolerance can be used
|
|
SpeedTree::CRenderState::SetPolygonOffset(1.0f, 0.125f);
|
|
|
|
success &= _forest_render.RenderBranches(light_cull, SpeedTree::RENDER_PASS_SHADOW);
|
|
|
|
// the remaining geometry types cannot be backface culled, so we
|
|
// need a much more aggressive offset
|
|
SpeedTree::CRenderState::SetPolygonOffset(10.0f, 1.0f);
|
|
|
|
success &= _forest_render.RenderFronds(light_cull, SpeedTree::RENDER_PASS_SHADOW);
|
|
success &= _forest_render.RenderLeafMeshes(light_cull, SpeedTree::RENDER_PASS_SHADOW);
|
|
success &= _forest_render.RenderLeafCards(light_cull, SpeedTree::RENDER_PASS_SHADOW, light_view);
|
|
|
|
// We don't bother to render billboard geometry into the shadow
|
|
// map(s).
|
|
|
|
success &= _forest_render.EndShadowMap(smi);
|
|
}
|
|
}
|
|
|
|
// SpeedTree::CRenderState::SetMultisampling(m_sUserSettings.m_nSampleCount > 0);
|
|
|
|
if (!success) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to render shadow maps\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::setup_for_render
|
|
// Access: Private
|
|
// Description: Does whatever calls are necessary to set up the
|
|
// forest for rendering--create vbuffers, load shaders,
|
|
// and whatnot. Primarily, this is the calls to
|
|
// InitTreeGraphics and the like.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
setup_for_render(GraphicsStateGuardian *gsg) {
|
|
if (!_done_first_init) {
|
|
// This is the first time we have entered the draw callback since
|
|
// creating any SpeedTreeNode. Now we have an opportunity to do
|
|
// any initial setup that requires a graphics context.
|
|
|
|
#ifdef SPEEDTREE_OPENGL
|
|
// For OpenGL, we have to ensure GLEW has been initialized.
|
|
// (SpeedTree uses it, though Panda doesn't.)
|
|
GLenum err = glewInit();
|
|
if (err != GLEW_OK) {
|
|
speedtree_cat.error()
|
|
<< "GLEW initialization failed: %s\n", glewGetErrorString(err);
|
|
// Can't proceed without GLEW.
|
|
_is_valid = false;
|
|
return;
|
|
}
|
|
|
|
// Insist that OpenGL 2.0 is available as the SpeedTree renderer
|
|
// requires it.
|
|
if (!GLEW_VERSION_2_0) {
|
|
speedtree_cat.error()
|
|
<< "The SpeedTree OpenGL implementation requires OpenGL 2.0 or better to run; this system has version " << glGetString(GL_VERSION) << "\n";
|
|
_is_valid = false;
|
|
return;
|
|
}
|
|
#endif // SPEEDTREE_OPENGL
|
|
|
|
_done_first_init = true;
|
|
}
|
|
|
|
#ifdef SPEEDTREE_DIRECTX9
|
|
// In DirectX, we have to tell SpeedTree our device pointer.
|
|
DXGraphicsStateGuardian9 *dxgsg = DCAST(DXGraphicsStateGuardian9, gsg);
|
|
SpeedTree::DX9::SetDevice(dxgsg->_screen->_d3d_device);
|
|
#endif // SPEEDTREE_DIRECTX9
|
|
|
|
if (_needs_repopulate) {
|
|
repopulate();
|
|
|
|
// Now init per-tree graphics
|
|
Trees::const_iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
const STTree *tree = instance_list->get_tree();
|
|
const STInstances &instances = instance_list->_instances;
|
|
if (instances.empty()) {
|
|
continue;
|
|
}
|
|
|
|
int max_instances = 2;
|
|
SpeedTree::CMap<const SpeedTree::CTree*, SpeedTree::st_int32>::const_iterator si;
|
|
si = _population_stats.m_mMaxNumInstancesPerCellPerBase.find(tree->get_tree());
|
|
if (si != _population_stats.m_mMaxNumInstancesPerCellPerBase.end()) {
|
|
max_instances = max(max_instances, (int)si->second);
|
|
}
|
|
|
|
// Get the speedtree-textures-dir to pass for initialization.
|
|
string os_textures_dir;
|
|
if (!speedtree_textures_dir.empty()) {
|
|
os_textures_dir = speedtree_textures_dir.get_value().to_os_specific();
|
|
// Ensure the path ends with a terminal slash; SpeedTree requires this.
|
|
#if defined(WIN32) || defined(WIN64)
|
|
if (!os_textures_dir.empty() && os_textures_dir[os_textures_dir.length() - 1] != '\\') {
|
|
os_textures_dir += "\\";
|
|
}
|
|
#else
|
|
if (!os_textures_dir.empty() && os_textures_dir[os_textures_dir.length() - 1] != '/') {
|
|
os_textures_dir += "/";
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (!_forest_render.InitTreeGraphics((SpeedTree::CTreeRender *)tree->get_tree(),
|
|
max_instances, speedtree_horizontal_billboards,
|
|
os_textures_dir.c_str())) {
|
|
if (speedtree_cat.is_debug()) {
|
|
speedtree_cat.debug()
|
|
<< "Failed to init tree graphics for " << *tree << "\n";
|
|
write_error(speedtree_cat.debug());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Init overall graphics
|
|
if (!_forest_render.InitGraphics(false)) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to init graphics\n";
|
|
write_error(speedtree_cat.warning());
|
|
_is_valid = false;
|
|
return;
|
|
}
|
|
|
|
// This call apparently must be made at draw time, not earlier,
|
|
// because it might attempt to create OpenGL index buffers and
|
|
// such.
|
|
_forest_render.UpdateTreeCellExtents();
|
|
|
|
if (has_terrain()) {
|
|
// Now initialize the terrain.
|
|
if (!_terrain_render.Init(speedtree_terrain_num_lods,
|
|
speedtree_terrain_resolution,
|
|
speedtree_terrain_cell_size,
|
|
_terrain->get_st_vertex_format())) {
|
|
speedtree_cat.warning()
|
|
<< "Failed to init terrain\n";
|
|
write_error(speedtree_cat.warning());
|
|
}
|
|
}
|
|
|
|
// If we needed to repopulate, it means we didn't cull in the cull
|
|
// traversal. Do it now.
|
|
cull_forest();
|
|
_needs_repopulate = false;
|
|
}
|
|
if (has_terrain()) {
|
|
PStatTimer timer1(_draw_speedtree_terrain_update_pcollector);
|
|
update_terrain_cells();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::cull_forest
|
|
// Access: Private
|
|
// Description: Calls the SpeedTree methods to perform the needed
|
|
// cull calculations.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
cull_forest() {
|
|
{
|
|
PStatTimer timer1(_cull_speedtree_trees_pcollector);
|
|
_forest_render.CullAndComputeLOD(_view, _visible_trees);
|
|
}
|
|
if (has_terrain()) {
|
|
PStatTimer timer1(_cull_speedtree_terrain_pcollector);
|
|
_terrain_render.CullAndComputeLOD(_view, _visible_terrain);
|
|
}
|
|
|
|
if (_forest_render.ShadowsAreEnabled()) {
|
|
PStatTimer timer1(_cull_speedtree_shadows_pcollector);
|
|
for (int smi = 0; smi < (int)_shadow_infos.size(); ++smi) {
|
|
SpeedTree::CView &light_view = _shadow_infos[smi]._light_view;
|
|
SpeedTree::SForestCullResultsRender &light_cull = _shadow_infos[smi]._light_cull;
|
|
|
|
_forest_render.ComputeLightView
|
|
(_forest_render.GetLightDir(), _view.GetFrustumPoints(), smi,
|
|
light_view, 0.0f);
|
|
|
|
light_view.SetLodRefPoint(_view.GetCameraPos());
|
|
_forest_render.CullAndComputeLOD(light_view, light_cull, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::print_forest_stats
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
print_forest_stats(const SpeedTree::CForest::SPopulationStats &forest_stats) const {
|
|
fprintf(stderr, "\n Forest Population Statistics\n");
|
|
fprintf(stderr, " ---------------------------------------------------\n");
|
|
fprintf(stderr, " # of tree cull cells: %d\n", forest_stats.m_nNumCells);
|
|
fprintf(stderr, " # of unique base trees: %d\n", forest_stats.m_nNumBaseTrees);
|
|
fprintf(stderr, " total # of instances: %d\n", forest_stats.m_nNumInstances);
|
|
fprintf(stderr, " average # of instances per base: %g\n", forest_stats.m_fAverageNumInstancesPerBase);
|
|
fprintf(stderr, " max # of billboards/instances per cell: %d\n", forest_stats.m_nMaxNumBillboardsPerCell);
|
|
fprintf(stderr, " max # of instances per cell per base:\n");
|
|
SpeedTree::CMap<const SpeedTree::CTree*, SpeedTree::st_int32>::const_iterator i;
|
|
for (i = forest_stats.m_mMaxNumInstancesPerCellPerBase.begin( ); i != forest_stats.m_mMaxNumInstancesPerCellPerBase.end( ); ++i) {
|
|
fprintf(stderr, " %35s: %4d\n", SpeedTree::CFixedString(i->first->GetFilename( )).NoPath( ).c_str( ), i->second);
|
|
}
|
|
fprintf(stderr, " average # instances per cell: %g\n", forest_stats.m_fAverageInstancesPerCell);
|
|
fprintf(stderr, " max # of billboard images: %d\n", forest_stats.m_nMaxNumBillboardImages);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::register_with_read_factory
|
|
// Access: Public, Static
|
|
// Description: Tells the BamReader how to create objects of type
|
|
// SpeedTreeNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
register_with_read_factory() {
|
|
BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::write_datagram
|
|
// Access: Public, Virtual
|
|
// Description: Writes the contents of this object to the datagram
|
|
// for shipping out to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
write_datagram(BamWriter *manager, Datagram &dg) {
|
|
PandaNode::write_datagram(manager, dg);
|
|
|
|
int num_trees = _trees.size();
|
|
dg.add_uint32(num_trees);
|
|
Trees::const_iterator ti;
|
|
for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
|
|
InstanceList *instance_list = (*ti);
|
|
instance_list->write_datagram(manager, dg);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::make_from_bam
|
|
// Access: Protected, Static
|
|
// Description: This function is called by the BamReader's factory
|
|
// when a new object of type SpeedTreeNode is encountered
|
|
// in the Bam file. It should create the SpeedTreeNode
|
|
// and extract its information from the file.
|
|
////////////////////////////////////////////////////////////////////
|
|
TypedWritable *SpeedTreeNode::
|
|
make_from_bam(const FactoryParams ¶ms) {
|
|
SpeedTreeNode *node = new SpeedTreeNode("");
|
|
DatagramIterator scan;
|
|
BamReader *manager;
|
|
|
|
parse_params(params, scan, manager);
|
|
node->fillin(scan, manager);
|
|
|
|
return node;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::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 SpeedTreeNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::
|
|
fillin(DatagramIterator &scan, BamReader *manager) {
|
|
PandaNode::fillin(scan, manager);
|
|
|
|
int num_trees = scan.get_uint32();
|
|
_trees.reserve(num_trees);
|
|
for (int i = 0; i < num_trees; i++) {
|
|
InstanceList *instance_list = new InstanceList(NULL);
|
|
instance_list->fillin(scan, manager);
|
|
if (instance_list->get_tree() == (STTree *)NULL) {
|
|
// The tree wasn't successfully loaded. Don't keep it.
|
|
delete instance_list;
|
|
} else {
|
|
_trees.push_back(instance_list);
|
|
}
|
|
}
|
|
|
|
_trees.sort();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::InstanceList::output
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::InstanceList::
|
|
output(ostream &out) const {
|
|
out << *_tree << ": " << _instances.size() << " instances";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::InstanceList::write
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::InstanceList::
|
|
write(ostream &out, int indent_level) const {
|
|
indent(out, indent_level)
|
|
<< *_tree << ": " << _instances.size() << " instances.\n";
|
|
STInstances::const_iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
indent(out, indent_level + 2)
|
|
<< STTransform(*ii) << "\n";
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::InstanceList::write_datagram
|
|
// Access: Public
|
|
// Description: Writes the contents of this object to the datagram
|
|
// for shipping out to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::InstanceList::
|
|
write_datagram(BamWriter *manager, Datagram &dg) {
|
|
// Compute the relative pathname to the SRT file.
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
|
|
bool has_bam_dir = !manager->get_filename().empty();
|
|
Filename bam_dir = manager->get_filename().get_dirname();
|
|
Filename srt_filename = _tree->get_fullpath();
|
|
|
|
bam_dir.make_absolute(vfs->get_cwd());
|
|
if (!has_bam_dir || !srt_filename.make_relative_to(bam_dir, true)) {
|
|
srt_filename.find_on_searchpath(get_model_path());
|
|
}
|
|
|
|
dg.add_string(srt_filename);
|
|
|
|
// Now record the instances.
|
|
int num_instances = _instances.size();
|
|
dg.add_uint32(num_instances);
|
|
STInstances::const_iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
STTransform transform = (*ii);
|
|
transform.write_datagram(manager, dg);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::InstanceList::fillin
|
|
// Access: Public
|
|
// Description: This internal function is called by make_from_bam to
|
|
// read in all of the relevant data from the BamFile for
|
|
// the new SpeedTreeNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::InstanceList::
|
|
fillin(DatagramIterator &scan, BamReader *manager) {
|
|
// Get the relative pathname to the SRT file.
|
|
string srt_filename = scan.get_string();
|
|
|
|
// Now load up the SRT file using the Panda loader (which will
|
|
// also search the model-path if necessary).
|
|
Loader *loader = Loader::get_global_ptr();
|
|
PT(PandaNode) srt_root = loader->load_sync(srt_filename);
|
|
|
|
if (srt_root != NULL) {
|
|
NodePath srt(srt_root);
|
|
NodePath srt_np = srt.find("**/+SpeedTreeNode");
|
|
if (!srt_np.is_empty()) {
|
|
SpeedTreeNode *srt_node = DCAST(SpeedTreeNode, srt_np.node());
|
|
if (srt_node->get_num_trees() >= 1) {
|
|
_tree = (STTree *)srt_node->get_tree(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now read the instances.
|
|
int num_instances = scan.get_uint32();
|
|
_instances.reserve(num_instances);
|
|
for (int i = 0; i < num_instances; i++) {
|
|
STTransform transform;
|
|
transform.fillin(scan, manager);
|
|
_instances.push_back(transform);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: SpeedTreeNode::DrawCallback::do_callback
|
|
// Access: Public, Virtual
|
|
// Description: This method called when the callback is triggered; it
|
|
// *replaces* the original function. To continue
|
|
// performing the original function, you must call
|
|
// cbdata->upcall() during the callback.
|
|
////////////////////////////////////////////////////////////////////
|
|
void SpeedTreeNode::DrawCallback::
|
|
do_callback(CallbackData *data) {
|
|
_node->draw_callback(data);
|
|
}
|