diff --git a/pandatool/Config.pp b/pandatool/Config.pp new file mode 100644 index 0000000000..a17d61df91 --- /dev/null +++ b/pandatool/Config.pp @@ -0,0 +1,18 @@ +// +// Config.pp +// +// This file defines certain configuration variables that are written +// into the various make scripts. It is processed by ppremake (along +// with the Sources.pp files in each of the various directories) to +// generate build scripts appropriate to each environment. +// +// There are not too many variables to declare at this level; most of +// them are defined in the DTOOL-specific Config.pp. + + +// Where should we find PANDA? This will come from the environment +// variable if it is set. +#if $[eq $[PANDA],] + #define PANDA /usr/local/panda +#endif + diff --git a/pandatool/Package.pp b/pandatool/Package.pp new file mode 100644 index 0000000000..5f2d91b91b --- /dev/null +++ b/pandatool/Package.pp @@ -0,0 +1,34 @@ +// +// Package.pp +// +// This file defines certain configuration variables that are to be +// written into the various make scripts. It is processed by ppremake +// (along with the Sources.pp files in each of the various +// directories) to generate build scripts appropriate to each +// environment. +// +// This is the package-specific file, which should be at the top of +// every source hierarchy. It generally gets the ball rolling, and is +// responsible for explicitly including all of the relevent Config.pp +// files. + + + +// What is the name and version of this source tree? +#if $[eq $[PACKAGE],] + #define PACKAGE pandatool + #define VERSION 0.80 +#endif + + +// Pull in the package-level Config file. This contains a few +// configuration variables that the user might want to fine-tune. +#include $[THISDIRPREFIX]Config.pp + + +// Also get the PANDA Package file and everything that includes. +#if $[eq $[wildcard $[PANDA]],] + #error Directory defined by $PANDA not found! Are you attached properly? +#endif + +#include $[PANDA]/Package.pp diff --git a/pandatool/Sources.pp b/pandatool/Sources.pp new file mode 100644 index 0000000000..1c4bbdb313 --- /dev/null +++ b/pandatool/Sources.pp @@ -0,0 +1,10 @@ +// This is the toplevel directory. It contains configure.in and other +// stuff. + +#define DIR_TYPE toplevel + +#define SAMPLE_SOURCE_FILE src/pandatoolbase/pandatoolbase.cxx +#define REQUIRED_TREES dtool panda + +#define EXTRA_DIST \ + Config.Irix.pp Config.Linux.pp Config.Win32.pp Package.pp diff --git a/pandatool/src/Sources.pp b/pandatool/src/Sources.pp new file mode 100644 index 0000000000..fb8681dab2 --- /dev/null +++ b/pandatool/src/Sources.pp @@ -0,0 +1,4 @@ +// This is a group directory: a directory level above a number of +// source subdirectories. + +#define DIR_TYPE group diff --git a/pandatool/src/bam/Sources.pp b/pandatool/src/bam/Sources.pp new file mode 100644 index 0000000000..6492541bc6 --- /dev/null +++ b/pandatool/src/bam/Sources.pp @@ -0,0 +1,32 @@ +#begin bin_target + #define TARGET bam-info + #define LOCAL_LIBS \ + eggbase progbase config compiler + #define OTHER_LIBS \ + loader:c egg:c sgraphutil:c sgattrib:c sgraph:c pnmimagetypes:c \ + graph:c putil:c express:c panda:m interrogatedb:c dtool:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + bamInfo.cxx bamInfo.h + + #define INSTALL_HEADERS \ + +#end bin_target + +#begin bin_target + #define TARGET egg2bam + #define LOCAL_LIBS \ + eggbase progbase config compiler + #define OTHER_LIBS \ + loader:c egg2sg:c builder:c egg:c pnmimagetypes:c graph:c putil:c \ + express:c panda:m interrogatedb:c dtool:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + eggToBam.cxx eggToBam.h + +#end bin_target + diff --git a/pandatool/src/bam/bamInfo.cxx b/pandatool/src/bam/bamInfo.cxx new file mode 100644 index 0000000000..0646c9a95d --- /dev/null +++ b/pandatool/src/bam/bamInfo.cxx @@ -0,0 +1,167 @@ +// Filename: bamInfo.cxx +// Created by: drose (02Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "bamInfo.h" + +#include +#include +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Function: BamInfo::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +BamInfo:: +BamInfo() { + set_program_description + ("This program scans one or more Bam files--Panda's Binary Animation " + "and Models native binary format--and describes their contents."); + + clear_runlines(); + add_runline("[opts] input.bam [input.bam ... ]"); + + _num_scene_graphs = 0; +} + + +//////////////////////////////////////////////////////////////////// +// Function: BamInfo::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void BamInfo:: +run() { + bool okflag = true; + + Filenames::const_iterator fi; + for (fi = _filenames.begin(); fi != _filenames.end(); ++fi) { + if (!get_info(*fi)) { + okflag = false; + } + } + + if (_num_scene_graphs > 0) { + nout << "\rScene graph statistics:\n"; + _analyzer.write(nout, 2); + } + nout << "\r"; + + if (!okflag) { + // Exit with an error if any of the files was unreadable. + exit(1); + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: BamInfo::handle_args +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool BamInfo:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the Bam file(s) to read on the command line.\n"; + return false; + } + + ProgramBase::Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + _filenames.push_back(*ai); + } + + return true; +} + + +//////////////////////////////////////////////////////////////////// +// Function: BamInfo::get_info +// Access: Private +// Description: Reads a single Bam file and displays its contents. +// Returns true if successful, false on error. +//////////////////////////////////////////////////////////////////// +bool BamInfo:: +get_info(const Filename &filename) { + BamFile bam_file; + + if (!bam_file.open_read(filename)) { + nout << "Unable to read.\n"; + return false; + } + + nout << filename << " : Bam version " << bam_file.get_file_major_ver() + << "." << bam_file.get_file_minor_ver() << "\n"; + + typedef vector Objects; + Objects objects; + TypedWriteable *object = bam_file.read_object(); + while (object != (TypedWriteable *)NULL) { + objects.push_back(object); + object = bam_file.read_object(); + } + bam_file.resolve(); + bam_file.close(); + + if (objects.size() == 1 && objects[0]->is_of_type(Node::get_class_type())) { + describe_scene_graph(DCAST(Node, objects[0])); + + } else { + for (int i = 0; i < (int)objects.size(); i++) { + describe_general_object(objects[i]); + } + } + + return true; +} + + +//////////////////////////////////////////////////////////////////// +// Function: BamInfo::describe_scene_graph +// Access: Private +// Description: Called for Bam files that contain a single scene +// graph and no other objects. This should describe +// that scene graph in some meaningful way. +//////////////////////////////////////////////////////////////////// +void BamInfo:: +describe_scene_graph(Node *node) { + // Parent the node to our own scene graph root, so we can (a) + // guarantee it won't accidentally be deleted before we're done, (b) + // easily determine the bounding volume of the scene, and (c) report + // statistics on all the bam file's scene graphs together when we've + // finished. + + PT_Node root = new Node; + NodeRelation *arc = new RenderRelation(root, node); + _num_scene_graphs++; + + int num_nodes = _analyzer._num_nodes; + _analyzer.add_node(node); + num_nodes = _analyzer._num_nodes - num_nodes; + + nout << " " << num_nodes << " nodes, bounding volume is " + << arc->get_bound() << "\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: BamInfo::describe_general_object +// Access: Private +// Description: Called for Bam files that contain multiple objects +// which may or may not be scene graph nodes. This +// should describe each object in some meaningful way. +//////////////////////////////////////////////////////////////////// +void BamInfo:: +describe_general_object(TypedWriteable *object) { + nout << " " << object->get_type() << "\n"; +} + +int main(int argc, char *argv[]) { + BamInfo prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/bam/bamInfo.h b/pandatool/src/bam/bamInfo.h new file mode 100644 index 0000000000..386ed42b72 --- /dev/null +++ b/pandatool/src/bam/bamInfo.h @@ -0,0 +1,45 @@ +// Filename: bamInfo.h +// Created by: drose (02Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef BAMINFO_H +#define BAMINFO_H + +#include + +#include +#include +#include +#include + +#include + +class TypedWriteable; + +//////////////////////////////////////////////////////////////////// +// Class : BamInfo +// Description : +//////////////////////////////////////////////////////////////////// +class BamInfo : public ProgramBase { +public: + BamInfo(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + +private: + bool get_info(const Filename &filename); + void describe_scene_graph(Node *node); + void describe_general_object(TypedWriteable *object); + + typedef vector Filenames; + Filenames _filenames; + + int _num_scene_graphs; + SceneGraphAnalyzer _analyzer; +}; + +#endif diff --git a/pandatool/src/bam/eggToBam.cxx b/pandatool/src/bam/eggToBam.cxx new file mode 100644 index 0000000000..58e51a7633 --- /dev/null +++ b/pandatool/src/bam/eggToBam.cxx @@ -0,0 +1,74 @@ +// Filename: eggToBam.cxx +// Created by: drose (28Jun00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggToBam.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Function: EggToBam::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +EggToBam:: +EggToBam() : + EggToSomething("Bam", ".bam", true, false) +{ + set_program_description + ("This program reads Egg files and outputs Bam files, the binary format " + "suitable for direct loading of animation and models into Panda."); + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting " + _format_name + + " file. This may be " + "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is z-up."); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggToBam::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void EggToBam:: +run() { + if (!_got_coordinate_system) { + // If the user didn't specify otherwise, ensure the coordinate + // system is Z-up. + _data.set_coordinate_system(CS_zup_right); + } + + PT_NamedNode root = load_egg_data(_data); + if (root == (NamedNode *)NULL) { + nout << "Unable to build scene graph from egg file.\n"; + exit(1); + } + + // This should be guaranteed because we pass false to the + // constructor, above. + nassertv(has_output_filename()); + + nout << "Writing " << get_output_filename() << "\n"; + BamFile bam_file; + if (!bam_file.open_write(get_output_filename())) { + nout << "Error in writing.\n"; + exit(1); + } + + if (!bam_file.write_object(root)) { + nout << "Error in writing.\n"; + exit(1); + } +} + + +int main(int argc, char *argv[]) { + EggToBam prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/bam/eggToBam.h b/pandatool/src/bam/eggToBam.h new file mode 100644 index 0000000000..60954a12a9 --- /dev/null +++ b/pandatool/src/bam/eggToBam.h @@ -0,0 +1,24 @@ +// Filename: eggToBam.h +// Created by: drose (28Jun00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGTOBAM_H +#define EGGTOBAM_H + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : EggToBam +// Description : +//////////////////////////////////////////////////////////////////// +class EggToBam : public EggToSomething { +public: + EggToBam(); + + void run(); +}; + +#endif diff --git a/pandatool/src/configfiles/Sources.pp b/pandatool/src/configfiles/Sources.pp new file mode 100644 index 0000000000..079954ccfd --- /dev/null +++ b/pandatool/src/configfiles/Sources.pp @@ -0,0 +1 @@ +#define INSTALL_DATA pandatool.init diff --git a/pandatool/src/configfiles/pandatool.init b/pandatool/src/configfiles/pandatool.init new file mode 100644 index 0000000000..3af8d9434a --- /dev/null +++ b/pandatool/src/configfiles/pandatool.init @@ -0,0 +1,3 @@ +ATTACH panda +DOCSH if { \which gtkmm-config >& /dev/null } setenv HAVE_GTK yes +DOSH if which gtkmm-config >/dev/null 2>&1; then HAVE_GTK="yes"; export HAVE_GTK; fi diff --git a/pandatool/src/eggbase/Sources.pp b/pandatool/src/eggbase/Sources.pp new file mode 100644 index 0000000000..7368cb2cbc --- /dev/null +++ b/pandatool/src/eggbase/Sources.pp @@ -0,0 +1,19 @@ +#begin lib_target + #define TARGET eggbase + #define LOCAL_LIBS \ + progbase + #define OTHER_LIBS \ + egg:c panda:m + + #define SOURCES \ + eggBase.cxx eggBase.h eggConverter.cxx eggConverter.h eggFilter.cxx \ + eggFilter.h eggReader.cxx eggReader.h eggToSomething.cxx \ + eggToSomething.h eggWriter.cxx eggWriter.h somethingToEgg.cxx \ + somethingToEgg.h + + #define INSTALL_HEADERS \ + eggBase.h eggConverter.h eggFilter.h eggReader.h eggToSomething.h \ + eggWriter.h somethingToEgg.h + +#end lib_target + diff --git a/pandatool/src/eggbase/eggBase.cxx b/pandatool/src/eggbase/eggBase.cxx new file mode 100644 index 0000000000..8bb4d1ff95 --- /dev/null +++ b/pandatool/src/eggbase/eggBase.cxx @@ -0,0 +1,93 @@ +// Filename: eggBase.cxx +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggBase.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: EggBase::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +EggBase:: +EggBase() { + add_option + ("cs", "coordinate-system", 80, + "Specify the coordinate system to operate in. This may be one of " + "'y-up', 'z-up', 'y-up-left', or 'z-up-left'.", + &EggBase::dispatch_coordinate_system, + &_got_coordinate_system, &_coordinate_system); +} + + +//////////////////////////////////////////////////////////////////// +// Function: EggBase::post_command_line +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool EggBase:: +post_command_line() { + if (_got_coordinate_system) { + _data.set_coordinate_system(_coordinate_system); + } + + return ProgramBase::post_command_line(); +} + + +//////////////////////////////////////////////////////////////////// +// Function: EggBase::append_command_comment +// Access: Protected +// Description: Inserts a comment into the beginning of the indicated +// egg file corresponding to the command line that +// invoked this program. +// +// Normally this function is called automatically when +// appropriate by EggWriter, and it's not necessary to +// call it explicitly. +//////////////////////////////////////////////////////////////////// +void EggBase:: +append_command_comment(EggData &data) { + string comment; + + comment = _program_name; + Args::const_iterator ai; + for (ai = _program_args.begin(); ai != _program_args.end(); ++ai) { + const string &arg = (*ai); + + // First, check to see if the string is shell-acceptable. + bool legal = true; + string::const_iterator si; + for (si = arg.begin(); legal && si != arg.end(); ++si) { + switch (*si) { + case ' ': + case '\n': + case '\t': + case '*': + case '?': + case '\\': + case '(': + case ')': + case '|': + case '&': + case '<': + case '>': + case '"': + case ';': + case '$': + legal = false; + } + } + + if (legal) { + comment += " " + arg; + } else { + comment += " '" + arg + "'"; + } + } + + data.insert(_data.begin(), new EggComment("", comment)); +} diff --git a/pandatool/src/eggbase/eggBase.h b/pandatool/src/eggbase/eggBase.h new file mode 100644 index 0000000000..fb69535687 --- /dev/null +++ b/pandatool/src/eggbase/eggBase.h @@ -0,0 +1,41 @@ +// Filename: eggBase.h +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGBASE_H +#define EGGBASE_H + +#include + +#include +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : EggBase +// Description : This specialization of ProgramBase is intended for +// programs that read and/or write a single egg file. +// (See EggMultiBase for programs that operate on +// multiple egg files at once.) +// +// This is just a base class; see EggReader, EggWriter, +// or EggFilter according to your particular I/O needs. +//////////////////////////////////////////////////////////////////// +class EggBase : public ProgramBase { +public: + EggBase(); + +protected: + virtual bool post_command_line(); + void append_command_comment(EggData &_data); + +protected: + bool _got_coordinate_system; + CoordinateSystem _coordinate_system; + EggData _data; +}; + +#endif + + diff --git a/pandatool/src/eggbase/eggConverter.cxx b/pandatool/src/eggbase/eggConverter.cxx new file mode 100644 index 0000000000..f4aebdbc80 --- /dev/null +++ b/pandatool/src/eggbase/eggConverter.cxx @@ -0,0 +1,28 @@ +// Filename: eggConverter.cxx +// Created by: drose (15Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggConverter.h" + +//////////////////////////////////////////////////////////////////// +// Function: EggConverter::Constructor +// Access: Public +// Description: The first parameter to the constructor should be the +// one-word name of the alien file format that is to be +// read or written, for instance "OpenFlight" or +// "Alias". It's just used in printing error messages +// and such. The second parameter is the preferred +// extension of files of this form, if any, with a +// leading dot. +//////////////////////////////////////////////////////////////////// +EggConverter:: +EggConverter(const string &format_name, + const string &preferred_extension, + bool allow_last_param, + bool allow_stdout) : + EggFilter(allow_last_param, allow_stdout), + _format_name(format_name), + _preferred_extension(preferred_extension) +{ +} diff --git a/pandatool/src/eggbase/eggConverter.h b/pandatool/src/eggbase/eggConverter.h new file mode 100644 index 0000000000..01b6de843a --- /dev/null +++ b/pandatool/src/eggbase/eggConverter.h @@ -0,0 +1,33 @@ +// Filename: eggConverter.h +// Created by: drose (15Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGCONVERTER_H +#define EGGCONVERTER_H + +#include + +#include "eggFilter.h" + +//////////////////////////////////////////////////////////////////// +// Class : EggConverter +// Description : This is a general base class for programs that +// convert between egg files and some other format. See +// EggToSomething and SomethingToEgg. +//////////////////////////////////////////////////////////////////// +class EggConverter : public EggFilter { +public: + EggConverter(const string &format_name, + const string &preferred_extension = string(), + bool allow_last_param = true, + bool allow_stdout = true); + +protected: + string _format_name; + string _preferred_extension; +}; + +#endif + + diff --git a/pandatool/src/eggbase/eggFilter.cxx b/pandatool/src/eggbase/eggFilter.cxx new file mode 100644 index 0000000000..7bef3f06a2 --- /dev/null +++ b/pandatool/src/eggbase/eggFilter.cxx @@ -0,0 +1,56 @@ +// Filename: eggFilter.cxx +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggFilter.h" + +//////////////////////////////////////////////////////////////////// +// Function: EggFilter::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +EggFilter:: +EggFilter(bool allow_last_param, bool allow_stdout) : + EggWriter(allow_last_param, allow_stdout) +{ + clear_runlines(); + if (allow_last_param) { + add_runline("[opts] input.egg output.egg"); + } + add_runline("[opts] -o output.egg input.egg"); + if (allow_stdout) { + add_runline("[opts] input.egg >output.egg"); + } + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting egg file. This may be " + " one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is the same coordinate system as the input egg file. If this is " + "different from the input egg file, a conversion will be performed."); +} + + +//////////////////////////////////////////////////////////////////// +// Function: EggFilter::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool EggFilter:: +handle_args(ProgramBase::Args &args) { + if (_allow_last_param && !_got_output_filename && args.size() > 1) { + _got_output_filename = true; + _output_filename = args.back(); + args.pop_back(); + + if (!verify_output_file_safe()) { + return false; + } + } + + return EggReader::handle_args(args); +} diff --git a/pandatool/src/eggbase/eggFilter.h b/pandatool/src/eggbase/eggFilter.h new file mode 100644 index 0000000000..2eb41abae3 --- /dev/null +++ b/pandatool/src/eggbase/eggFilter.h @@ -0,0 +1,28 @@ +// Filename: eggFilter.h +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGFILTER_H +#define EGGFILTER_H + +#include + +#include "eggReader.h" +#include "eggWriter.h" + +//////////////////////////////////////////////////////////////////// +// Class : EggFilter +// Description : This is the base class for a program that reads an +// egg file, operates on it, and writes another egg file +// out. +//////////////////////////////////////////////////////////////////// +class EggFilter : public EggReader, public EggWriter { +public: + EggFilter(bool allow_last_param = false, bool allow_stdout = true); + virtual bool handle_args(Args &args); +}; + +#endif + + diff --git a/pandatool/src/eggbase/eggReader.cxx b/pandatool/src/eggbase/eggReader.cxx new file mode 100644 index 0000000000..692c74e247 --- /dev/null +++ b/pandatool/src/eggbase/eggReader.cxx @@ -0,0 +1,51 @@ +// Filename: eggReader.cxx +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggReader.h" + +//////////////////////////////////////////////////////////////////// +// Function: EggReader::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +EggReader:: +EggReader() { + clear_runlines(); + add_runline("[opts] input.egg"); + + redescribe_option + ("cs", + "Specify the coordinate system to operate in. This may be " + " one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is the coordinate system of the input egg file."); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggReader::handle_args +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool EggReader:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the egg file(s) to read on the command line.\n"; + return false; + } + + // Any separate egg files that are listed on the command line will + // get implicitly loaded up into one big egg file. + + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + if (!_data.read(*ai)) { + // Rather than returning false, we simply exit here, so the + // ProgramBase won't try to tell the user how to run the program + // just because we got a bad egg file. + exit(1); + } + } + + return true; +} diff --git a/pandatool/src/eggbase/eggReader.h b/pandatool/src/eggbase/eggReader.h new file mode 100644 index 0000000000..65a752c4da --- /dev/null +++ b/pandatool/src/eggbase/eggReader.h @@ -0,0 +1,30 @@ +// Filename: eggReader.h +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGREADER_H +#define EGGREADER_H + +#include + +#include "eggBase.h" + + +//////////////////////////////////////////////////////////////////// +// Class : EggReader +// Description : This is the base class for a program that reads egg +// files, but doesn't write an egg file. +//////////////////////////////////////////////////////////////////// +class EggReader : virtual public EggBase { +public: + EggReader(); + +protected: + virtual bool handle_args(Args &args); + +}; + +#endif + + diff --git a/pandatool/src/eggbase/eggToSomething.cxx b/pandatool/src/eggbase/eggToSomething.cxx new file mode 100644 index 0000000000..b742eeaaf1 --- /dev/null +++ b/pandatool/src/eggbase/eggToSomething.cxx @@ -0,0 +1,104 @@ +// Filename: eggToSomething.cxx +// Created by: drose (15Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggToSomething.h" + +//////////////////////////////////////////////////////////////////// +// Function: EggToSomething::Constructor +// Access: Public +// Description: The first parameter to the constructor should be the +// one-word name of the file format that is to be read, +// for instance "OpenFlight" or "Alias". It's just used +// in printing error messages and such. +//////////////////////////////////////////////////////////////////// +EggToSomething:: +EggToSomething(const string &format_name, + const string &preferred_extension, + bool allow_last_param, bool allow_stdout) : + EggConverter(format_name, preferred_extension, allow_last_param, + allow_stdout) +{ + clear_runlines(); + if (_allow_last_param) { + add_runline("[opts] input.egg output" + _preferred_extension); + } + add_runline("[opts] -o output" + _preferred_extension + " input.egg"); + if (_allow_stdout) { + add_runline("[opts] input.egg >output" + _preferred_extension); + } + + string o_description; + + if (_allow_stdout) { + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting " + format_name + + " file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file, or standard output is used if there are no " + "other parameters."; + } else { + o_description = + "Specify the filename to which the resulting " + format_name + + " file will be written. " + "If this option is omitted, the " + format_name + + " file is written to standard output."; + } + } else { + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting " + format_name + + " file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file."; + } else { + o_description = + "Specify the filename to which the resulting " + format_name + + " file will be written."; + } + } + + redescribe_option("o", o_description); + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting " + _format_name + + " file. This may be " + "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is the same coordinate system as the input egg file. If this is " + "different from the input egg file, a conversion will be performed."); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggToSomething::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool EggToSomething:: +handle_args(ProgramBase::Args &args) { + if (_allow_last_param && !_got_output_filename && args.size() > 1) { + _got_output_filename = true; + _output_filename = args.back(); + args.pop_back(); + + if (!_preferred_extension.empty() && + ("." + _output_filename.get_extension()) != _preferred_extension) { + nout << "Output filename " << _output_filename + << " does not end in " << _preferred_extension + << ". If this is really what you intended, " + "use the -o output_file syntax.\n"; + return false; + } + + if (!verify_output_file_safe()) { + return false; + } + } + + return EggConverter::handle_args(args); +} diff --git a/pandatool/src/eggbase/eggToSomething.h b/pandatool/src/eggbase/eggToSomething.h new file mode 100644 index 0000000000..815a76c15f --- /dev/null +++ b/pandatool/src/eggbase/eggToSomething.h @@ -0,0 +1,32 @@ +// Filename: eggToSomething.h +// Created by: drose (15Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGTOSOMETHING_H +#define EGGTOSOMETHING_H + +#include + +#include "eggConverter.h" + +//////////////////////////////////////////////////////////////////// +// Class : EggToSomething +// Description : This is the general base class for a file-converter +// program that reads some model file format and +// generates an egg file. +//////////////////////////////////////////////////////////////////// +class EggToSomething : public EggConverter { +public: + EggToSomething(const string &format_name, + const string &preferred_extension = string(), + bool allow_last_param = true, + bool allow_stdout = true); + +protected: + virtual bool handle_args(Args &args); +}; + +#endif + + diff --git a/pandatool/src/eggbase/eggWriter.cxx b/pandatool/src/eggbase/eggWriter.cxx new file mode 100644 index 0000000000..1ef7687339 --- /dev/null +++ b/pandatool/src/eggbase/eggWriter.cxx @@ -0,0 +1,211 @@ +// Filename: eggWriter.cxx +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggWriter.h" + +//////////////////////////////////////////////////////////////////// +// Function: EggWriter::Constructor +// Access: Public +// Description: Egg-writing type programs may specify their output +// file using either the last-filename convention, the +// -o convention, and/or implicitly writing the result +// to standard output. Not all interfaces are +// appropriate for all applications; some may be +// confusing or dangerous. +// +// The calling application should pass allow_last_param +// true to allow the user to specify the output filename +// as the last parameter on the command line (the most +// dangerous, but convenient, method), and allow_stdout +// true to allow the user to omit the output filename +// altogether and have the output implicitly go to +// standard output (not terribly dangerous, but +// inappropriate when writing binary file formats). +//////////////////////////////////////////////////////////////////// +EggWriter:: +EggWriter(bool allow_last_param, bool allow_stdout) { + _allow_last_param = allow_last_param; + _allow_stdout = allow_stdout; + + clear_runlines(); + if (_allow_last_param) { + add_runline("[opts] output.egg"); + } + add_runline("[opts] -o output.egg"); + if (_allow_stdout) { + add_runline("[opts] >output.egg"); + } + + string o_description; + + if (_allow_stdout) { + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting egg file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file, or standard output is used if there are no " + "other parameters."; + } else { + o_description = + "Specify the filename to which the resulting egg file will be written. " + "If this option is omitted, the egg file is written to standard output."; + } + } else { + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting egg file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file."; + } else { + o_description = + "Specify the filename to which the resulting egg file will be written."; + } + } + + add_option + ("o", "filename", 50, o_description, + &EggWriter::dispatch_filename, &_got_output_filename, &_output_filename); + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting egg file. This may be " + " one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default is " + "y-up."); + + _output_ptr = (ostream *)NULL; +} + + +//////////////////////////////////////////////////////////////////// +// Function: EggWriter::get_output +// Access: Public +// Description: Returns an output stream that corresponds to the +// user's intended egg file output--either stdout, or +// the named output file. +//////////////////////////////////////////////////////////////////// +ostream &EggWriter:: +get_output() { + if (_output_ptr == (ostream *)NULL) { + if (!_got_output_filename) { + // No filename given; use standard output. + assert(_allow_stdout); + _output_ptr = &cout; + + } else { + // Attempt to open the named file. + unlink(_output_filename.c_str()); + _output_stream.open(_output_filename.c_str(), ios::out, 0666); + if (!_output_stream) { + nout << "Unable to write to " << _output_filename << "\n"; + exit(1); + } + nout << "Writing " << _output_filename << "\n"; + _output_ptr = &_output_stream; + } + } + return *_output_ptr; +} + +//////////////////////////////////////////////////////////////////// +// Function: EggWriter::has_output_filename +// Access: Public +// Description: Returns true if the user specified an output +// filename, false otherwise (e.g. the output file is +// implicitly stdout). +//////////////////////////////////////////////////////////////////// +bool EggWriter:: +has_output_filename() const { + return _got_output_filename; +} + +//////////////////////////////////////////////////////////////////// +// Function: EggWriter::get_output_filename +// Access: Public +// Description: If has_output_filename() returns true, this is the +// filename that the user specified. Otherwise, it +// returns the empty string. +//////////////////////////////////////////////////////////////////// +Filename EggWriter:: +get_output_filename() const { + if (_got_output_filename) { + return _output_filename; + } + return Filename(); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggWriter::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool EggWriter:: +handle_args(ProgramBase::Args &args) { + if (_allow_last_param && !_got_output_filename && !args.empty()) { + _got_output_filename = true; + _output_filename = args.back(); + args.pop_back(); + + if (!verify_output_file_safe()) { + return false; + } + } + + if (!args.empty()) { + nout << "Unexpected arguments on command line:\n"; + copy(args.begin(), args.end(), ostream_iterator(nout, " ")); + nout << "\r"; + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: EggWriter::post_command_line +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool EggWriter:: +post_command_line() { + if (!_allow_stdout && !_got_output_filename) { + nout << "You must specify the filename to write with -o.\n"; + return false; + } + + append_command_comment(_data); + + return EggBase::post_command_line(); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggWriter::verify_output_file_safe +// Access: Protected +// Description: This is called when the output file is given as the +// last parameter on the command line. Since this is a +// fairly dangerous way to specify the output file (it's +// easy to accidentally overwrite an input file this +// way), the convention is to disallow this syntax if +// the output file already exists. +// +// This function will test if the output file exists, +// and issue a warning message if it does, returning +// false. If all is well, it will return true. +//////////////////////////////////////////////////////////////////// +bool EggWriter:: +verify_output_file_safe() const { + nassertr(_got_output_filename, false); + + if (_output_filename.exists()) { + nout << "The output filename " << _output_filename << " already exists. " + "If you wish to overwrite it, you must use the -o option to specify " + "the output filename, instead of simply specifying it as the last " + "parameter.\n"; + return false; + } + return true; +} diff --git a/pandatool/src/eggbase/eggWriter.h b/pandatool/src/eggbase/eggWriter.h new file mode 100644 index 0000000000..7edf7e4f32 --- /dev/null +++ b/pandatool/src/eggbase/eggWriter.h @@ -0,0 +1,49 @@ +// Filename: eggWriter.h +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGWRITER_H +#define EGGWRITER_H + +#include + +#include "eggBase.h" + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : EggWriter +// Description : This is the base class for a program that generates +// an egg file output, but doesn't read any for input. +//////////////////////////////////////////////////////////////////// +class EggWriter : virtual public EggBase { +public: + EggWriter(bool allow_last_param = false, bool allow_stdout = true); + + ostream &get_output(); + bool has_output_filename() const; + Filename get_output_filename() const; + +protected: + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + + bool verify_output_file_safe() const; + +protected: + bool _allow_last_param; + bool _allow_stdout; + bool _got_output_filename; + Filename _output_filename; + +private: + ofstream _output_stream; + ostream *_output_ptr; +}; + +#endif + + diff --git a/pandatool/src/eggbase/somethingToEgg.cxx b/pandatool/src/eggbase/somethingToEgg.cxx new file mode 100644 index 0000000000..096c89401d --- /dev/null +++ b/pandatool/src/eggbase/somethingToEgg.cxx @@ -0,0 +1,86 @@ +// Filename: somethingToEgg.cxx +// Created by: drose (15Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "somethingToEgg.h" + +//////////////////////////////////////////////////////////////////// +// Function: SomethingToEgg::Constructor +// Access: Public +// Description: The first parameter to the constructor should be the +// one-word name of the file format that is to be read, +// for instance "OpenFlight" or "Alias". It's just used +// in printing error messages and such. +//////////////////////////////////////////////////////////////////// +SomethingToEgg:: +SomethingToEgg(const string &format_name, + const string &preferred_extension, + bool allow_last_param, bool allow_stdout) : + EggConverter(format_name, preferred_extension, allow_last_param, allow_stdout) +{ + clear_runlines(); + if (_allow_last_param) { + add_runline("[opts] input" + _preferred_extension + " output.egg"); + } + add_runline("[opts] -o output.egg input" + _preferred_extension); + if (_allow_stdout) { + add_runline("[opts] input" + _preferred_extension + " >output.egg"); + } + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting egg file. This may be " + " one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is the same coordinate system as the input " + _format_name + + " file, if this can be determined."); +} + +//////////////////////////////////////////////////////////////////// +// Function: SomethingToEgg::handle_args +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +bool SomethingToEgg:: +handle_args(Args &args) { + if (_allow_last_param && !_got_output_filename && args.size() > 1) { + _got_output_filename = true; + _output_filename = args.back(); + args.pop_back(); + + if (!(_output_filename.get_extension() == "egg")) { + nout << "Output filename " << _output_filename + << " does not end in .egg. If this is really what you intended, " + "use the -o output_file syntax.\n"; + return false; + } + + if (!verify_output_file_safe()) { + return false; + } + } + + if (args.empty()) { + nout << "You must specify the " << _format_name + << " file to read on the command line.\n"; + return false; + } + + if (args.size() != 1) { + nout << "You may only specify one " << _format_name + << " file to read on the command line. " + << "You specified: "; + copy(args.begin(), args.end(), ostream_iterator(nout, " ")); + nout << "\n"; + return false; + } + + _input_filename = args[0]; + + if (access(_input_filename.c_str(), R_OK) != 0) { + nout << "Cannot find input file " << _input_filename << "\n"; + return false; + } + + return true; +} diff --git a/pandatool/src/eggbase/somethingToEgg.h b/pandatool/src/eggbase/somethingToEgg.h new file mode 100644 index 0000000000..b15ae826bc --- /dev/null +++ b/pandatool/src/eggbase/somethingToEgg.h @@ -0,0 +1,34 @@ +// Filename: somethingToEgg.h +// Created by: drose (15Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef SOMETHINGTOEGG_H +#define SOMETHINGTOEGG_H + +#include + +#include "eggConverter.h" + +//////////////////////////////////////////////////////////////////// +// Class : SomethingToEgg +// Description : This is the general base class for a file-converter +// program that reads some model file format and +// generates an egg file. +//////////////////////////////////////////////////////////////////// +class SomethingToEgg : public EggConverter { +public: + SomethingToEgg(const string &format_name, + const string &preferred_extension = string(), + bool allow_last_param = true, + bool allow_stdout = true); + +protected: + virtual bool handle_args(Args &args); + + Filename _input_filename; +}; + +#endif + + diff --git a/pandatool/src/eggprogs/Sources.pp b/pandatool/src/eggprogs/Sources.pp new file mode 100644 index 0000000000..5fe970a1f5 --- /dev/null +++ b/pandatool/src/eggprogs/Sources.pp @@ -0,0 +1,16 @@ +#begin bin_target + #define TARGET egg-trans + #define LOCAL_LIBS \ + eggbase progbase config compiler + #define OTHER_LIBS \ + egg:c linmath:c putil:c express:c panda:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + eggTrans.cxx eggTrans.h + + #define INSTALL_HEADERS \ + +#end bin_target + diff --git a/pandatool/src/eggprogs/eggTrans.cxx b/pandatool/src/eggprogs/eggTrans.cxx new file mode 100644 index 0000000000..5a2e05c9f0 --- /dev/null +++ b/pandatool/src/eggprogs/eggTrans.cxx @@ -0,0 +1,37 @@ +// Filename: eggTrans.cxx +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "eggTrans.h" + +//////////////////////////////////////////////////////////////////// +// Function: EggTrans::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +EggTrans:: +EggTrans() { + set_program_description + ("This program reads an egg file and writes an essentially equivalent " + "egg file to the standard output, or to the file specified with -o. " + "Some simple operations on the egg file are supported."); +} + +//////////////////////////////////////////////////////////////////// +// Function: EggTrans::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void EggTrans:: +run() { + _data.write_egg(get_output()); +} + + +int main(int argc, char *argv[]) { + EggTrans prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggTrans.h b/pandatool/src/eggprogs/eggTrans.h new file mode 100644 index 0000000000..d6f38ff794 --- /dev/null +++ b/pandatool/src/eggprogs/eggTrans.h @@ -0,0 +1,27 @@ +// Filename: eggTrans.h +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef EGGTRANS_H +#define EGGTRANS_H + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : EggTrans +// Description : A program to read an egg file and write an equivalent +// egg file, possibly performing some minor operations +// along the way. +//////////////////////////////////////////////////////////////////// +class EggTrans : public EggFilter { +public: + EggTrans(); + + void run(); +}; + +#endif + diff --git a/pandatool/src/flt/Sources.pp b/pandatool/src/flt/Sources.pp new file mode 100644 index 0000000000..eeb01e5308 --- /dev/null +++ b/pandatool/src/flt/Sources.pp @@ -0,0 +1,60 @@ +#begin lib_target + #define TARGET flt + #define LOCAL_LIBS \ + compiler + #define OTHER_LIBS \ + mathutil:c linmath:c putil:c express:c panda:m dtool:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + config_flt.cxx config_flt.h fltBead.cxx fltBead.h fltBeadID.cxx \ + fltBeadID.h fltError.cxx fltError.h fltExternalReference.cxx \ + fltExternalReference.h fltEyepoint.cxx fltEyepoint.h fltFace.I \ + fltFace.cxx fltFace.h fltGroup.cxx fltGroup.h fltHeader.cxx \ + fltHeader.h fltInstanceDefinition.cxx fltInstanceDefinition.h \ + fltInstanceRef.cxx fltInstanceRef.h fltLOD.cxx fltLOD.h \ + fltLightSourceDefinition.cxx fltLightSourceDefinition.h \ + fltMaterial.cxx fltMaterial.h fltObject.cxx fltObject.h \ + fltOpcode.cxx fltOpcode.h fltPackedColor.I fltPackedColor.cxx \ + fltPackedColor.h fltRecord.I fltRecord.cxx fltRecord.h \ + fltRecordReader.cxx fltRecordReader.h fltRecordWriter.cxx \ + fltRecordWriter.h fltTexture.cxx fltTexture.h fltTrackplane.cxx \ + fltTrackplane.h fltTransformGeneralMatrix.cxx \ + fltTransformGeneralMatrix.h fltTransformPut.cxx fltTransformPut.h \ + fltTransformRecord.cxx fltTransformRecord.h \ + fltTransformRotateAboutEdge.cxx fltTransformRotateAboutEdge.h \ + fltTransformRotateAboutPoint.cxx fltTransformRotateAboutPoint.h \ + fltTransformRotateScale.cxx fltTransformRotateScale.h \ + fltTransformScale.cxx fltTransformScale.h fltTransformTranslate.cxx \ + fltTransformTranslate.h fltUnsupportedRecord.cxx \ + fltUnsupportedRecord.h fltVertex.I fltVertex.cxx fltVertex.h \ + fltVertexList.cxx fltVertexList.h + + #define INSTALL_HEADERS \ + fltBead.h fltBeadID.h fltError.h fltExternalReference.h \ + fltEyepoint.h fltFace.I fltFace.h fltGroup.h fltHeader.h \ + fltInstanceDefinition.h fltInstanceRef.h fltLOD.h \ + fltLightSourceDefinition.h fltMaterial.h fltObject.h fltOpcode.h \ + fltPackedColor.I fltPackedColor.h fltRecord.I fltRecord.h \ + fltRecordReader.h fltRecordWriter.h fltTexture.h fltTrackplane.h \ + fltTransformGeneralMatrix.h fltTransformPut.h fltTransformRecord.h \ + fltTransformRotateAboutEdge.h fltTransformRotateAboutPoint.h \ + fltTransformRotateScale.h fltTransformScale.h \ + fltTransformTranslate.h fltUnsupportedRecord.h fltVertex.I \ + fltVertex.h fltVertexList.h + +#end lib_target + +#begin test_bin_target + #define TARGET test_flt + #define LOCAL_LIBS \ + flt + #define OTHER_LIBS \ + pystub + + #define SOURCES \ + test_flt.cxx + +#end test_bin_target + diff --git a/pandatool/src/flt/config_flt.cxx b/pandatool/src/flt/config_flt.cxx new file mode 100644 index 0000000000..72b23d724b --- /dev/null +++ b/pandatool/src/flt/config_flt.cxx @@ -0,0 +1,63 @@ +// Filename: config_flt.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "config_flt.h" +#include "fltRecord.h" +#include "fltBead.h" +#include "fltBeadID.h" +#include "fltGroup.h" +#include "fltObject.h" +#include "fltFace.h" +#include "fltVertexList.h" +#include "fltLOD.h" +#include "fltInstanceDefinition.h" +#include "fltInstanceRef.h" +#include "fltHeader.h" +#include "fltVertex.h" +#include "fltMaterial.h" +#include "fltTexture.h" +#include "fltLightSourceDefinition.h" +#include "fltUnsupportedRecord.h" +#include "fltTransformRecord.h" +#include "fltTransformGeneralMatrix.h" +#include "fltTransformPut.h" +#include "fltTransformRotateAboutEdge.h" +#include "fltTransformRotateAboutPoint.h" +#include "fltTransformScale.h" +#include "fltTransformTranslate.h" +#include "fltTransformRotateScale.h" +#include "fltExternalReference.h" + +#include + +Configure(config_flt); + +ConfigureFn(config_flt) { + FltRecord::init_type(); + FltBead::init_type(); + FltBeadID::init_type(); + FltGroup::init_type(); + FltObject::init_type(); + FltFace::init_type(); + FltVertexList::init_type(); + FltLOD::init_type(); + FltInstanceDefinition::init_type(); + FltInstanceRef::init_type(); + FltHeader::init_type(); + FltVertex::init_type(); + FltMaterial::init_type(); + FltTexture::init_type(); + FltLightSourceDefinition::init_type(); + FltUnsupportedRecord::init_type(); + FltTransformRecord::init_type(); + FltTransformGeneralMatrix::init_type(); + FltTransformPut::init_type(); + FltTransformRotateAboutEdge::init_type(); + FltTransformRotateAboutPoint::init_type(); + FltTransformScale::init_type(); + FltTransformTranslate::init_type(); + FltTransformRotateScale::init_type(); + FltExternalReference::init_type(); +} diff --git a/pandatool/src/flt/config_flt.h b/pandatool/src/flt/config_flt.h new file mode 100644 index 0000000000..82116f21f7 --- /dev/null +++ b/pandatool/src/flt/config_flt.h @@ -0,0 +1,11 @@ +// Filename: config_flt.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef CONFIG_FLT_H +#define CONFIG_FLT_H + +// No configure variables. + +#endif diff --git a/pandatool/src/flt/fltBead.cxx b/pandatool/src/flt/fltBead.cxx new file mode 100644 index 0000000000..82910625f9 --- /dev/null +++ b/pandatool/src/flt/fltBead.cxx @@ -0,0 +1,391 @@ +// Filename: fltBead.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltBead.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltTransformGeneralMatrix.h" +#include "fltTransformPut.h" +#include "fltTransformRotateAboutEdge.h" +#include "fltTransformRotateAboutPoint.h" +#include "fltTransformScale.h" +#include "fltTransformTranslate.h" +#include "fltTransformRotateScale.h" + +TypeHandle FltBead::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltBead:: +FltBead(FltHeader *header) : FltRecord(header) { + _has_transform = false; + _transform = LMatrix4d::ident_mat(); + _replicate_count = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::has_transform +// Access: Public +// Description: Returns true if the bead has been transformed, false +// otherwise. If this returns true, get_transform() +// will return the single-precision net transformation, +// and get_num_transform_steps() will return nonzero. +//////////////////////////////////////////////////////////////////// +bool FltBead:: +has_transform() const { + return _has_transform; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::get_transform +// Access: Public +// Description: Returns the single-precision 4x4 matrix that +// represents the transform applied to this bead, or the +// identity matrix if the bead has not been transformed. +//////////////////////////////////////////////////////////////////// +const LMatrix4d &FltBead:: +get_transform() const { + return _has_transform ? _transform : LMatrix4d::ident_mat(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::set_transform +// Access: Public +// Description: Replaces the transform matrix on this bead. This +// implicitly removes all of the transform steps added +// previously, and replaces them with a single 4x4 +// general matrix transform step. +//////////////////////////////////////////////////////////////////// +void FltBead:: +set_transform(const LMatrix4d &mat) { + clear_transform(); + FltTransformGeneralMatrix *step = new FltTransformGeneralMatrix(_header); + step->set_matrix(mat); + add_transform_step(step); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::clear_transform +// Access: Public +// Description: Removes any transform matrix and all transform steps +// on this bead. +//////////////////////////////////////////////////////////////////// +void FltBead:: +clear_transform() { + _has_transform = false; + _transform = LMatrix4d::ident_mat(); + _transform_steps.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::get_num_transform_steps +// Access: Public +// Description: Returns the number of individual steps that define +// the net transform on this bead as returned by +// set_transform(). Each step is a single +// transformation; the concatenation of all +// transformations will produce the matrix represented +// by set_transform(). +//////////////////////////////////////////////////////////////////// +int FltBead:: +get_num_transform_steps() const { + return _transform_steps.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::get_transform_step +// Access: Public +// Description: Returns the nth individual step that defines +// the net transform on this bead. See +// get_num_transform_steps(). +//////////////////////////////////////////////////////////////////// +FltTransformRecord *FltBead:: +get_transform_step(int n) { + nassertr(n >= 0 && n < (int)_transform_steps.size(), + (FltTransformRecord *)NULL); + return _transform_steps[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::add_transform_step +// Access: Public +// Description: Applies the indicated transform step to the net +// transformation applied to the bead. +//////////////////////////////////////////////////////////////////// +void FltBead:: +add_transform_step(FltTransformRecord *record) { + if (!_has_transform) { + _has_transform = true; + _transform = record->get_matrix(); + } else { + _transform = record->get_matrix() * _transform; + } + _transform_steps.push_back(record); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::get_replicate_count +// Access: Public +// Description: Returns the replicate count of this bead. If this is +// nonzero, it means that the bead is implicitly copied +// this number of additional times (for replicate_count +// + 1 total copies), applying the transform on this +// bead for each copy. In this case, the transform does +// *not* apply to the initial copy of the bead. +//////////////////////////////////////////////////////////////////// +int FltBead:: +get_replicate_count() const { + return _replicate_count; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::set_replicate_count +// Access: Public +// Description: Changes the replicate count of this bead. If you are +// setting the replicate count to some nonzero number, +// you must also set a transform on the bead. See +// set_replicate_count(). +//////////////////////////////////////////////////////////////////// +void FltBead:: +set_replicate_count(int count) { + _replicate_count = count; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltBead:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::extract_ancillary +// Access: Protected, Virtual +// Description: Checks whether the given bead, which follows this +// bead sequentially in the file, is an ancillary record +// of this bead. If it is, extracts the relevant +// information and returns true; otherwise, leaves it +// alone and returns false. +//////////////////////////////////////////////////////////////////// +bool FltBead:: +extract_ancillary(FltRecordReader &reader) { + FltTransformRecord *step = (FltTransformRecord *)NULL; + + switch (reader.get_opcode()) { + case FO_transform_matrix: + return extract_transform_matrix(reader); + + case FO_general_matrix: + step = new FltTransformGeneralMatrix(_header); + break; + + case FO_put: + step = new FltTransformPut(_header); + break; + + case FO_rotate_about_edge: + step = new FltTransformRotateAboutEdge(_header); + break; + + case FO_rotate_about_point: + step = new FltTransformRotateAboutPoint(_header); + break; + + case FO_scale: + step = new FltTransformScale(_header); + break; + + case FO_translate: + step = new FltTransformTranslate(_header); + break; + + case FO_rotate_and_scale: + step = new FltTransformRotateScale(_header); + break; + + case FO_replicate: + return extract_replicate_count(reader); + + default: + return FltRecord::extract_ancillary(reader); + } + + // A transform step. + nassertr(step != (FltTransformRecord *)NULL, false); + step->extract_record(reader); + _transform_steps.push_back(DCAST(FltTransformRecord, step)); + + /* + cerr << "Added transform step: " << step->get_type() << "\n"; + cerr << "Net matrix is:\n"; + _transform.write(cerr, 2); + cerr << "Step is:\n"; + step->get_matrix().write(cerr, 2); + */ + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltBead:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::write_ancillary +// Access: Protected, Virtual +// Description: Writes whatever ancillary records are required for +// this record. Returns FE_ok on success, or something +// else if there is some error. +//////////////////////////////////////////////////////////////////// +FltError FltBead:: +write_ancillary(FltRecordWriter &writer) const { + if (_has_transform) { + FltError result = write_transform(writer); + if (result != FE_ok) { + return result; + } + } + if (_replicate_count != 0) { + FltError result = write_replicate_count(writer); + if (result != FE_ok) { + return result; + } + } + + + return FltRecord::write_ancillary(writer); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::extract_transform_matrix +// Access: Private +// Description: Reads a transform matrix ancillary bead. This +// defines the net transformation that has been applied +// to the bead, and precedes the set of individual +// transform steps that define how this net transform +// was computed. +//////////////////////////////////////////////////////////////////// +bool FltBead:: +extract_transform_matrix(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_transform_matrix, false); + DatagramIterator &iterator = reader.get_iterator(); + + LMatrix4d matrix; + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + matrix(r, c) = iterator.get_be_float32(); + } + } + nassertr(iterator.get_remaining_size() == 0, true); + + _transform_steps.clear(); + _has_transform = true; + _transform = matrix; + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::extract_replicate_count +// Access: Private +// Description: Reads a replicate count ancillary bead. +//////////////////////////////////////////////////////////////////// +bool FltBead:: +extract_replicate_count(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_replicate, false); + DatagramIterator &iterator = reader.get_iterator(); + + _replicate_count = iterator.get_be_int16(); + iterator.skip_bytes(2); + + nassertr(iterator.get_remaining_size() == 0, true); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::write_transform +// Access: Private +// Description: Writes out the transformation and all of its defining +// steps. +//////////////////////////////////////////////////////////////////// +FltError FltBead:: +write_transform(FltRecordWriter &writer) const { + // First, write out the initial transform indication. + writer.set_opcode(FO_transform_matrix); + Datagram &datagram = writer.update_datagram(); + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + datagram.add_be_float32(_transform(r, c)); + } + } + + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + + // Now, write out each of the steps of the transform. + Transforms::const_iterator ti; + for (ti = _transform_steps.begin(); ti != _transform_steps.end(); ++ti) { + if (!(*ti)->build_record(writer)) { + return FE_invalid_record; + } + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBead::write_replicate_count +// Access: Private +// Description: Writes out the replicate count, if needed. +//////////////////////////////////////////////////////////////////// +FltError FltBead:: +write_replicate_count(FltRecordWriter &writer) const { + if (_replicate_count != 0) { + writer.set_opcode(FO_replicate); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int16(_replicate_count); + datagram.pad_bytes(2); + + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} diff --git a/pandatool/src/flt/fltBead.h b/pandatool/src/flt/fltBead.h new file mode 100644 index 0000000000..16dc13dd71 --- /dev/null +++ b/pandatool/src/flt/fltBead.h @@ -0,0 +1,83 @@ +// Filename: fltBead.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTBEAD_H +#define FLTBEAD_H + +#include + +#include "fltRecord.h" +#include "fltTransformRecord.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltBead +// Description : A base class for any of a broad family of flt records +// that represent particular beads in the hierarchy. +// These are things like group beads and object beads, +// as opposed to things like push and pop or comment +// records. +//////////////////////////////////////////////////////////////////// +class FltBead : public FltRecord { +public: + FltBead(FltHeader *header); + + bool has_transform() const; + const LMatrix4d &get_transform() const; + void set_transform(const LMatrix4d &mat); + void clear_transform(); + + int get_num_transform_steps() const; + FltTransformRecord *get_transform_step(int n); + void add_transform_step(FltTransformRecord *record); + + int get_replicate_count() const; + void set_replicate_count(int count); + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +private: + bool extract_transform_matrix(FltRecordReader &reader); + bool extract_replicate_count(FltRecordReader &reader); + + FltError write_transform(FltRecordWriter &writer) const; + FltError write_replicate_count(FltRecordWriter &writer) const; + +private: + bool _has_transform; + LMatrix4d _transform; + + typedef vector Transforms; + Transforms _transform_steps; + + int _replicate_count; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltBead", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltBeadID.cxx b/pandatool/src/flt/fltBeadID.cxx new file mode 100644 index 0000000000..09e052a157 --- /dev/null +++ b/pandatool/src/flt/fltBeadID.cxx @@ -0,0 +1,110 @@ +// Filename: fltBeadID.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltBeadID.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltBeadID::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltBeadID::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltBeadID:: +FltBeadID(FltHeader *header) : FltBead(header) { +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBeadID::output +// Access: Public +// Description: Writes a quick one-line description of the record, but +// not its children. This is a human-readable +// description, primarily for debugging; to write a flt +// file, use FltHeader::write_flt(). +//////////////////////////////////////////////////////////////////// +void FltBeadID:: +output(ostream &out) const { + out << get_type(); + if (!_id.empty()) { + out << " " << _id; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBeadID::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltBeadID:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + _id = reader.get_iterator().get_fixed_string(8); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBeadID::extract_ancillary +// Access: Protected, Virtual +// Description: Checks whether the given bead, which follows this +// bead sequentially in the file, is an ancillary record +// of this bead. If it is, extracts the relevant +// information and returns true; otherwise, leaves it +// alone and returns false. +//////////////////////////////////////////////////////////////////// +bool FltBeadID:: +extract_ancillary(FltRecordReader &reader) { + if (reader.get_opcode() == FO_long_id) { + _id = reader.get_iterator().get_remaining_bytes(); + return true; + } + + return FltBead::extract_ancillary(reader); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBeadID::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltBeadID:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.update_datagram().add_fixed_string(_id.substr(0, 7), 8); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltBeadID::write_ancillary +// Access: Protected, Virtual +// Description: Writes whatever ancillary records are required for +// this record. Returns FE_ok on success, or something +// else if there is some error. +//////////////////////////////////////////////////////////////////// +FltError FltBeadID:: +write_ancillary(FltRecordWriter &writer) const { + if (_id.length() > 7) { + Datagram dc(_id); + FltError result = writer.write_record(FO_long_id, dc); + if (result != FE_ok) { + return result; + } + } + + return FltBead::write_ancillary(writer); +} diff --git a/pandatool/src/flt/fltBeadID.h b/pandatool/src/flt/fltBeadID.h new file mode 100644 index 0000000000..b3741daf81 --- /dev/null +++ b/pandatool/src/flt/fltBeadID.h @@ -0,0 +1,57 @@ +// Filename: fltBeadID.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTBEADID_H +#define FLTBEADID_H + +#include + +#include "fltBead.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltBeadID +// Description : A base class for any of a broad family of flt beads +// that include an ID. +//////////////////////////////////////////////////////////////////// +class FltBeadID : public FltBead { +public: + FltBeadID(FltHeader *header); + + const string &get_id() const; + void set_id(const string &id); + + virtual void output(ostream &out) const; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +private: + string _id; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltBeadID", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltError.cxx b/pandatool/src/flt/fltError.cxx new file mode 100644 index 0000000000..7b8bce74a6 --- /dev/null +++ b/pandatool/src/flt/fltError.cxx @@ -0,0 +1,47 @@ +// Filename: fltError.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltError.h" + +ostream & +operator << (ostream &out, FltError error) { + switch (error) { + case FE_ok: + return out << "no error"; + + case FE_could_not_open: + return out << "could not open file"; + + case FE_empty_file: + return out << "empty file"; + + case FE_end_of_file: + return out << "unexpected end of file"; + + case FE_read_error: + return out << "read error on file"; + + case FE_invalid_record: + return out << "invalid record"; + + case FE_extra_data: + return out << "extra data at end of file"; + + case FE_write_error: + return out << "write error on file"; + + case FE_bad_data: + return out << "bad data"; + + case FE_not_implemented: + return out << "not implemented"; + + case FE_internal: + return out << "internal error"; + + default: + return out << "unknown error " << (int)error; + } +} diff --git a/pandatool/src/flt/fltError.h b/pandatool/src/flt/fltError.h new file mode 100644 index 0000000000..243642bdcd --- /dev/null +++ b/pandatool/src/flt/fltError.h @@ -0,0 +1,32 @@ +// Filename: fltError.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTERROR_H +#define FLTERROR_H + +#include + +// Return values for various functions in the flt library. +enum FltError { + FE_ok = 0, + FE_could_not_open, + FE_empty_file, + FE_end_of_file, + FE_read_error, + FE_invalid_record, + FE_extra_data, + FE_write_error, + FE_bad_data, + FE_not_implemented, + FE_undefined_instance, + FE_internal +}; + +ostream &operator << (ostream &out, FltError error); + +#endif + + + diff --git a/pandatool/src/flt/fltExternalReference.cxx b/pandatool/src/flt/fltExternalReference.cxx new file mode 100644 index 0000000000..7fdf995080 --- /dev/null +++ b/pandatool/src/flt/fltExternalReference.cxx @@ -0,0 +1,107 @@ +// Filename: fltExternalReference.cxx +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltExternalReference.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltExternalReference::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltExternalReference::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltExternalReference:: +FltExternalReference(FltHeader *header) : FltBead(header) { + _flags = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltExternalReference::output +// Access: Public +// Description: Writes a quick one-line description of the record, but +// not its children. This is a human-readable +// description, primarily for debugging; to write a flt +// file, use FltHeader::write_flt(). +//////////////////////////////////////////////////////////////////// +void FltExternalReference:: +output(ostream &out) const { + out << "External " << _filename; + if (!_bead_id.empty()) { + out << " (" << _bead_id << ")"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltExternalReference::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltExternalReference:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_external_ref, false); + DatagramIterator &iterator = reader.get_iterator(); + + string name = iterator.get_fixed_string(200); + iterator.skip_bytes(1 + 1); + iterator.skip_bytes(2); // Undocumented additional padding. + _flags = iterator.get_be_uint32(); + iterator.skip_bytes(2); + iterator.skip_bytes(2); // Undocumented additional padding. + + _filename = name; + + if (!name.empty() && name[name.length() - 1] == '>') { + // Extract out the bead name. + size_t open = name.rfind('<'); + if (open != string::npos) { + _filename = name.substr(0, open); + _bead_id = name.substr(open + 1, name.length() - open - 2); + } + } + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltExternalReference::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltExternalReference:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_external_ref); + Datagram &datagram = writer.update_datagram(); + + string name = _filename; + if (!_bead_id.empty()) { + name += "<" + _bead_id + ">"; + } + + datagram.add_fixed_string(name.substr(0, 199), 200); + datagram.pad_bytes(1 + 1); + datagram.pad_bytes(2); // Undocumented additional padding. + datagram.add_be_uint32(_flags); + datagram.pad_bytes(2); + datagram.pad_bytes(2); // Undocumented additional padding. + + return true; +} diff --git a/pandatool/src/flt/fltExternalReference.h b/pandatool/src/flt/fltExternalReference.h new file mode 100644 index 0000000000..d9d121eade --- /dev/null +++ b/pandatool/src/flt/fltExternalReference.h @@ -0,0 +1,63 @@ +// Filename: fltExternalReference.h +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTEXTERNALREFERENCE_H +#define FLTEXTERNALREFERENCE_H + +#include + +#include "fltBead.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltExternalReference +// Description : An external reference to another flt file (possibly +// to a specific bead within the flt file). +//////////////////////////////////////////////////////////////////// +class FltExternalReference : public FltBead { +public: + FltExternalReference(FltHeader *header); + + virtual void output(ostream &out) const; + + enum Flags { + F_color_palette_override = 0x80000000, + F_material_palette_override = 0x40000000, + F_texture_palette_override = 0x20000000, + F_line_style_palette_override = 0x10000000, + F_sound_palette_override = 0x08000000, + F_light_palette_override = 0x04000000 + }; + + Filename _filename; + string _bead_id; + int _flags; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltExternalReference", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltEyepoint.cxx b/pandatool/src/flt/fltEyepoint.cxx new file mode 100644 index 0000000000..f0b97a493c --- /dev/null +++ b/pandatool/src/flt/fltEyepoint.cxx @@ -0,0 +1,133 @@ +// Filename: fltEyepoint.cxx +// Created by: drose (26Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltEyepoint.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +//////////////////////////////////////////////////////////////////// +// Function: FltEyepoint::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltEyepoint:: +FltEyepoint() { + _rotation_center.set(0.0, 0.0, 0.0); + _hpr.set(0.0, 0.0, 0.0); + _rotation = LMatrix4f::ident_mat(); + _fov = 60.0; + _scale = 1.0; + _near_clip = 0.1; + _far_clip = 10000.0; + _fly_through = LMatrix4f::ident_mat(); + _eyepoint.set(0.0, 0.0, 0.0); + _fly_through_yaw = 0.0; + _fly_through_pitch = 0.0; + _eyepoint_direction.set(0.0, 1.0, 0.0); + _no_fly_through = true; + _ortho_mode = false; + _is_valid = true; + _image_offset_x = 0; + _image_offset_y = 0; + _image_zoom = 1; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltEyepoint::extract_record +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +bool FltEyepoint:: +extract_record(FltRecordReader &reader) { + DatagramIterator &iterator = reader.get_iterator(); + + _rotation_center[0] = iterator.get_be_float64(); + _rotation_center[1] = iterator.get_be_float64(); + _rotation_center[2] = iterator.get_be_float64(); + _hpr[0] = iterator.get_be_float32(); + _hpr[1] = iterator.get_be_float32(); + _hpr[2] = iterator.get_be_float32(); + int r; + for (r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + _rotation(r, c) = iterator.get_be_float32(); + } + } + _fov = iterator.get_be_float32(); + _scale = iterator.get_be_float32(); + _near_clip = iterator.get_be_float32(); + _far_clip = iterator.get_be_float32(); + for (r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + _fly_through(r, c) = iterator.get_be_float32(); + } + } + _eyepoint[0] = iterator.get_be_float32(); + _eyepoint[1] = iterator.get_be_float32(); + _eyepoint[2] = iterator.get_be_float32(); + _fly_through_yaw = iterator.get_be_float32(); + _fly_through_pitch = iterator.get_be_float32(); + _eyepoint_direction[0] = iterator.get_be_float32(); + _eyepoint_direction[1] = iterator.get_be_float32(); + _eyepoint_direction[2] = iterator.get_be_float32(); + _no_fly_through = (iterator.get_be_int32() != 0); + _ortho_mode = (iterator.get_be_int32() != 0); + _is_valid = (iterator.get_be_int32() != 0); + _image_offset_x = iterator.get_be_int32(); + _image_offset_y = iterator.get_be_int32(); + _image_zoom = iterator.get_be_int32(); + iterator.skip_bytes(4*9); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltEyepoint::build_record +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +bool FltEyepoint:: +build_record(FltRecordWriter &writer) const { + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_float64(_rotation_center[0]); + datagram.add_be_float64(_rotation_center[1]); + datagram.add_be_float64(_rotation_center[2]); + datagram.add_be_float32(_hpr[0]); + datagram.add_be_float32(_hpr[1]); + datagram.add_be_float32(_hpr[2]); + int r; + for (r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + datagram.add_be_float32(_rotation(r, c)); + } + } + datagram.add_be_float32(_fov); + datagram.add_be_float32(_scale); + datagram.add_be_float32(_near_clip); + datagram.add_be_float32(_far_clip); + for (r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + datagram.add_be_float32(_fly_through(r, c)); + } + } + datagram.add_be_float32(_eyepoint[0]); + datagram.add_be_float32(_eyepoint[1]); + datagram.add_be_float32(_eyepoint[2]); + datagram.add_be_float32(_fly_through_yaw); + datagram.add_be_float32(_fly_through_pitch); + datagram.add_be_float32(_eyepoint_direction[0]); + datagram.add_be_float32(_eyepoint_direction[1]); + datagram.add_be_float32(_eyepoint_direction[2]); + datagram.add_be_int32(_no_fly_through); + datagram.add_be_int32(_ortho_mode); + datagram.add_be_int32(_is_valid); + datagram.add_be_int32(_image_offset_x); + datagram.add_be_int32(_image_offset_y); + datagram.add_be_int32(_image_zoom); + datagram.pad_bytes(4*9); + + return true; +} diff --git a/pandatool/src/flt/fltEyepoint.h b/pandatool/src/flt/fltEyepoint.h new file mode 100644 index 0000000000..2d9328627a --- /dev/null +++ b/pandatool/src/flt/fltEyepoint.h @@ -0,0 +1,52 @@ +// Filename: fltEyepoint.h +// Created by: drose (26Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTEYEPOINT_H +#define FLTEYEPOINT_H + +#include + +#include + +class FltRecordReader; +class FltRecordWriter; + +//////////////////////////////////////////////////////////////////// +// Class : FltEyepoint +// Description : A single eyepoint entry in the eyepoint/trackplane +// palette. +//////////////////////////////////////////////////////////////////// +class FltEyepoint { +public: + FltEyepoint(); + + bool extract_record(FltRecordReader &reader); + bool build_record(FltRecordWriter &writer) const; + +public: + LPoint3d _rotation_center; + LVecBase3f _hpr; + LMatrix4f _rotation; + float _fov; + float _scale; + float _near_clip; + float _far_clip; + LMatrix4f _fly_through; + LPoint3f _eyepoint; + float _fly_through_yaw; + float _fly_through_pitch; + LVector3f _eyepoint_direction; + bool _no_fly_through; + bool _ortho_mode; + bool _is_valid; + int _image_offset_x; + int _image_offset_y; + int _image_zoom; +}; + +#endif + + + diff --git a/pandatool/src/flt/fltFace.I b/pandatool/src/flt/fltFace.I new file mode 100644 index 0000000000..6abd553edf --- /dev/null +++ b/pandatool/src/flt/fltFace.I @@ -0,0 +1,60 @@ +// Filename: fltFace.I +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::has_texture +// Access: Public +// Description: Returns true if the face has a texture applied, false +// otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool FltFace:: +has_texture() const { + return (_texture_index >= 0 && _header->has_texture(_texture_index)); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::get_texture +// Access: Public +// Description: Returns the texture applied to this face, or NULL if +// no texture was applied. +//////////////////////////////////////////////////////////////////// +INLINE FltTexture *FltFace:: +get_texture() const { + return _header->get_texture(_texture_index); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::has_material +// Access: Public +// Description: Returns true if the face has a material applied, false +// otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool FltFace:: +has_material() const { + return (_material_index >= 0 && _header->has_material(_material_index)); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::get_material +// Access: Public +// Description: Returns the material applied to this face, or NULL if +// no material was applied. +//////////////////////////////////////////////////////////////////// +INLINE FltMaterial *FltFace:: +get_material() const { + return _header->get_material(_material_index); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::has_color +// Access: Public +// Description: Returns true if the face has a primary color +// indicated, false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool FltFace:: +has_color() const { + return ((_flags & F_no_color) == 0) || has_material(); +} diff --git a/pandatool/src/flt/fltFace.cxx b/pandatool/src/flt/fltFace.cxx new file mode 100644 index 0000000000..4b62cbff88 --- /dev/null +++ b/pandatool/src/flt/fltFace.cxx @@ -0,0 +1,250 @@ +// Filename: fltFace.cxx +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltFace.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltMaterial.h" + +TypeHandle FltFace::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltFace:: +FltFace(FltHeader *header) : FltBeadID(header) { + _ir_color = 0; + _relative_priority = 0; + _draw_type = DT_solid_backface; + _texwhite = false; + _color_name_index = 0; + _alt_color_name_index = 0; + _billboard_type = BT_none; + _detail_texture_index = -1; + _texture_index = -1; + _material_index = -1; + _dfad_material_code = 0; + _dfad_feature_id = 0; + _ir_material_code = 0; + _transparency = 0; + _lod_generation_control = 0; + _line_style_index = 0; + _flags = 0; + _light_mode = LM_face_no_normal; + _texture_mapping_index = 0; + _color_index = 0; + _alt_color_index = 0; +} + + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::get_color +// Access: Public +// Description: If has_color() indicates true, returns the primary +// color of the face, as a four-component value +// (including alpha as the transparency channel). +//////////////////////////////////////////////////////////////////// +Colorf FltFace:: +get_color() const { + nassertr(has_color(), Colorf(0.0, 0.0, 0.0, 0.0)); + + if (_texwhite && has_texture()) { + // Force this one white. + return Colorf(1.0, 1.0, 1.0, 1.0 - (_transparency / 65535.0)); + } + + if (has_material()) { + // If we have a material, that replaces the color. + FltMaterial *material = get_material(); + return Colorf(material->_diffuse[0], + material->_diffuse[1], + material->_diffuse[2], + 1.0 - (_transparency / 65535.0)); + } + + return _header->get_color(_color_index, (_flags & F_packed_color) != 0, + _packed_color, _transparency); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::get_rgb +// Access: Public +// Description: If has_color() indicates true, returns the primary +// color of the face, as a three-component value +// ignoring transparency. +//////////////////////////////////////////////////////////////////// +RGBColorf FltFace:: +get_rgb() const { + nassertr(has_color(), RGBColorf(0.0, 0.0, 0.0)); + + if (_texwhite && has_texture()) { + // Force this one white. + return RGBColorf(1.0, 1.0, 1.0); + } + + if (has_material()) { + // If we have a material, that replaces the color. + FltMaterial *material = get_material(); + return material->_diffuse; + } + + return _header->get_rgb(_color_index, (_flags & F_packed_color) != 0, + _packed_color); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::has_alt_color +// Access: Public +// Description: Returns true if the face has an alternate color +// indicated, false otherwise. +//////////////////////////////////////////////////////////////////// +bool FltFace:: +has_alt_color() const { + return (_flags & F_no_alt_color) == 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::get_alt_color +// Access: Public +// Description: If has_alt_color() indicates true, returns the alternate +// color of the face, as a four-component value +// (including alpha as the transparency channel). +//////////////////////////////////////////////////////////////////// +Colorf FltFace:: +get_alt_color() const { + nassertr(has_alt_color(), Colorf(0.0, 0.0, 0.0, 0.0)); + + return _header->get_color(_alt_color_index, (_flags & F_packed_color) != 0, + _alt_packed_color, _transparency); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::get_alt_rgb +// Access: Public +// Description: If has_alt_color() indicates true, returns the alternate +// color of the face, as a three-component value +// ignoring transparency. +//////////////////////////////////////////////////////////////////// +RGBColorf FltFace:: +get_alt_rgb() const { + nassertr(has_alt_color(), RGBColorf(0.0, 0.0, 0.0)); + + return _header->get_rgb(_alt_color_index, (_flags & F_packed_color) != 0, + _alt_packed_color); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltFace:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_face, false); + DatagramIterator &iterator = reader.get_iterator(); + + _ir_color = iterator.get_be_int32(); + _relative_priority = iterator.get_be_int16(); + _draw_type = (DrawType)iterator.get_int8(); + _texwhite = (iterator.get_int8() != 0); + _color_name_index = iterator.get_be_int16(); + _alt_color_name_index = iterator.get_be_int16(); + iterator.skip_bytes(1); + _billboard_type = (BillboardType)iterator.get_int8(); + _detail_texture_index = iterator.get_be_int16(); + _texture_index = iterator.get_be_int16(); + _material_index = iterator.get_be_int16(); + _dfad_material_code = iterator.get_be_int16(); + _dfad_feature_id = iterator.get_be_int16(); + _ir_material_code = iterator.get_be_int32(); + _transparency = iterator.get_be_uint16(); + _lod_generation_control = iterator.get_uint8(); + _line_style_index = iterator.get_uint8(); + _flags = iterator.get_be_uint32(); + _light_mode = (LightMode)iterator.get_uint8(); + iterator.skip_bytes(1 + 4); + iterator.skip_bytes(2); // Undocumented padding. + + if (!_packed_color.extract_record(reader)) { + return false; + } + if (!_alt_packed_color.extract_record(reader)) { + return false; + } + + _texture_mapping_index = iterator.get_be_int16(); + iterator.skip_bytes(2); + _color_index = iterator.get_be_uint32(); + _alt_color_index = iterator.get_be_uint32(); + iterator.skip_bytes(2 + 2); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltFace::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltFace:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_face); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int32(_ir_color); + datagram.add_be_int16(_relative_priority); + datagram.add_int8(_draw_type); + datagram.add_int8(_texwhite); + datagram.add_be_uint16(_color_name_index); + datagram.add_be_uint16(_alt_color_name_index); + datagram.pad_bytes(1); + datagram.add_int8(_billboard_type); + datagram.add_be_int16(_detail_texture_index); + datagram.add_be_int16(_texture_index); + datagram.add_be_int16(_material_index); + datagram.add_be_int16(_dfad_material_code); + datagram.add_be_int16(_dfad_feature_id); + datagram.add_be_int32(_ir_material_code); + datagram.add_be_uint16(_transparency); + datagram.add_uint8(_lod_generation_control); + datagram.add_uint8(_line_style_index); + datagram.add_be_uint32(_flags); + datagram.add_uint8(_light_mode); + datagram.pad_bytes(1 + 4); + datagram.pad_bytes(2); // Undocumented padding. + + if (!_packed_color.build_record(writer)) { + return false; + } + if (!_alt_packed_color.build_record(writer)) { + return false; + } + + datagram.add_be_int16(_texture_mapping_index); + datagram.pad_bytes(2); + datagram.add_be_uint32(_color_index); + datagram.add_be_uint32(_alt_color_index); + datagram.pad_bytes(2 + 2); + + return true; +} diff --git a/pandatool/src/flt/fltFace.h b/pandatool/src/flt/fltFace.h new file mode 100644 index 0000000000..ce0ffcaa14 --- /dev/null +++ b/pandatool/src/flt/fltFace.h @@ -0,0 +1,128 @@ +// Filename: fltFace.h +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTFACE_H +#define FLTFACE_H + +#include + +#include "fltBeadID.h" +#include "fltPackedColor.h" +#include "fltHeader.h" + +#include + +class FltTexture; +class FltMaterial; + +//////////////////////////////////////////////////////////////////// +// Class : FltFace +// Description : A single face bead, e.g. a polygon. +//////////////////////////////////////////////////////////////////// +class FltFace : public FltBeadID { +public: + FltFace(FltHeader *header); + + enum DrawType { + DT_solid_backface = 0, + DT_solid_no_backface = 1, + DT_wireframe = 2, + DT_wireframe_close = 3, + DT_wireframe_highlight = 4, + DT_omni_light = 5, + DT_uni_light = 6, + DT_bi_light = 7 + }; + + enum BillboardType { + BT_none = 0, + BT_fixed = 1, + BT_axial = 2, + BT_point = 4 + }; + + enum Flags { + F_terrain = 0x80000000, + F_no_color = 0x40000000, + F_no_alt_color = 0x20000000, + F_packed_color = 0x10000000, + F_terrain_footprint = 0x08000000, + F_hidden = 0x04000000 + }; + + enum LightMode { + LM_face_no_normal = 0, + LM_vertex_no_normal = 1, + LM_face_with_normal = 2, + LM_vertex_with_normal = 3 + }; + + int _ir_color; + int _relative_priority; + DrawType _draw_type; + bool _texwhite; + int _color_name_index; + int _alt_color_name_index; + BillboardType _billboard_type; + int _detail_texture_index; + int _texture_index; + int _material_index; + int _dfad_material_code; + int _dfad_feature_id; + int _ir_material_code; + int _transparency; + int _lod_generation_control; + int _line_style_index; + unsigned int _flags; + LightMode _light_mode; + FltPackedColor _packed_color; + FltPackedColor _alt_packed_color; + int _texture_mapping_index; + unsigned int _color_index; + unsigned int _alt_color_index; + +public: + INLINE bool has_texture() const; + INLINE FltTexture *get_texture() const; + + INLINE bool has_material() const; + INLINE FltMaterial *get_material() const; + + INLINE bool has_color() const; + Colorf get_color() const; + RGBColorf get_rgb() const; + + bool has_alt_color() const; + Colorf get_alt_color() const; + RGBColorf get_alt_rgb() const; + + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltFace", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "fltFace.I" + +#endif + + diff --git a/pandatool/src/flt/fltGroup.cxx b/pandatool/src/flt/fltGroup.cxx new file mode 100644 index 0000000000..4fa8d8220c --- /dev/null +++ b/pandatool/src/flt/fltGroup.cxx @@ -0,0 +1,84 @@ +// Filename: fltGroup.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltGroup.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltGroup::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltGroup::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltGroup:: +FltGroup(FltHeader *header) : FltBeadID(header) { + _relative_priority = 0; + _flags = 0; + _special_id1 = 0; + _special_id2 = 0; + _significance = 0; + _layer_id = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltGroup::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltGroup:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_group, false); + DatagramIterator &iterator = reader.get_iterator(); + + _relative_priority = iterator.get_be_int16(); + iterator.skip_bytes(2); + _flags = iterator.get_be_uint32(); + _special_id1 = iterator.get_be_int16(); + _special_id2 = iterator.get_be_int16(); + _significance = iterator.get_be_int16(); + _layer_id = iterator.get_int8(); + iterator.skip_bytes(5); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltGroup::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltGroup:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_group); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int16(_relative_priority); + datagram.pad_bytes(2); + datagram.add_be_uint32(_flags); + datagram.add_be_int16(_special_id1); + datagram.add_be_int16(_special_id2); + datagram.add_be_int16(_significance); + datagram.add_int8(_layer_id); + datagram.pad_bytes(5); + + return true; +} diff --git a/pandatool/src/flt/fltGroup.h b/pandatool/src/flt/fltGroup.h new file mode 100644 index 0000000000..64f422fd58 --- /dev/null +++ b/pandatool/src/flt/fltGroup.h @@ -0,0 +1,59 @@ +// Filename: fltGroup.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTGROUP_H +#define FLTGROUP_H + +#include + +#include "fltBeadID.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltGroup +// Description : The main grouping bead of the flt file. +//////////////////////////////////////////////////////////////////// +class FltGroup : public FltBeadID { +public: + FltGroup(FltHeader *header); + + enum Flags { + F_forward_animation = 0x40000000, + F_swing_animation = 0x20000000, + F_bounding_box = 0x10000000, + F_freeze_bounding_box = 0x08000000, + F_default_parent = 0x04000000, + }; + + int _relative_priority; + unsigned int _flags; + int _special_id1, _special_id2; + int _significance; + int _layer_id; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltGroup", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltHeader.cxx b/pandatool/src/flt/fltHeader.cxx new file mode 100644 index 0000000000..589335f181 --- /dev/null +++ b/pandatool/src/flt/fltHeader.cxx @@ -0,0 +1,1556 @@ +// Filename: fltHeader.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltHeader.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltHeader::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltHeader:: +FltHeader() : FltBeadID(this) { + _format_revision_level = 1520; + _edit_revision_level = 1520; + _next_group_id = 1; + _next_lod_id = 1; + _next_object_id = 1; + _next_face_id = 1; + _unit_multiplier = 1; + _vertex_units = U_feet; + _texwhite_new = false; + _flags = 0; + _projection_type = PT_flat_earth; + _next_dof_id = 1; + _vertex_storage_type = VTS_double; + _database_origin = DO_open_flight; + _sw_x = 0.0; + _sw_y = 0.0; + _delta_x = 0.0; + _delta_y = 0.0; + _next_sound_id = 1; + _next_path_id = 1; + _next_clip_id = 1; + _next_text_id = 1; + _next_bsp_id = 1; + _next_switch_id = 1; + _sw_lat = 0.0; + _sw_long = 0.0; + _ne_lat = 0.0; + _ne_long = 0.0; + _origin_lat = 0.0; + _origin_long = 0.0; + _lambert_upper_lat = 0.0; + _lambert_lower_lat = 0.0; + _next_light_id = 1; + _next_road_id = 1; + _next_cat_id = 1; + _earth_model = EM_wgs84; + + _vertex_lookups_stale = false; + _current_vertex_offset = 0; + _got_eyepoint_trackplane_palette = false; + + _auto_attr_update = AU_if_missing; + +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::read_flt +// Access: Public +// Description: Opens the indicated filename for reading and attempts +// to read the complete Flt file. Returns FE_ok on +// success, otherwise on failure. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +read_flt(Filename filename) { + filename.set_binary(); + + ifstream in; + if (!filename.open_read(in)) { + return FE_could_not_open; + } + + // By default, the filename's directory is added to the texture + // search path. + string dirname = filename.get_dirname(); + if (!dirname.empty()) { + _texture_path.append_directory(dirname); + } + + return read_flt(in); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::read_flt +// Access: Public +// Description: Attempts to read a complete Flt file from the +// already-opened stream. Returns FE_ok on success, +// otherwise on failure. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +read_flt(istream &in) { + FltRecordReader reader(in); + FltError result = reader.advance(); + if (result == FE_end_of_file) { + return FE_empty_file; + } else if (result != FE_ok) { + return result; + } + + result = read_record_and_children(reader); + if (result != FE_ok) { + return result; + } + + if (!reader.eof()) { + return FE_extra_data; + } + + return FE_ok; +} + + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_flt +// Access: Public +// Description: Opens the indicated filename for writing and attempts +// to write the complete Flt file. Returns FE_ok on +// success, otherwise on failure. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_flt(Filename filename) { + filename.set_binary(); + + ofstream out; + if (!filename.open_write(out)) { + return FE_could_not_open; + } + + return write_flt(out); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_flt +// Access: Public +// Description: Attempts to write a complete Flt file to the +// already-opened stream. Returns FE_ok on success, +// otherwise on failure. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_flt(ostream &out) { + FltRecordWriter writer(out); + FltError result = write_record_and_children(writer); + + if (out.fail()) { + return FE_write_error; + } + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::set_auto_attr_update +// Access: Public +// Description: Controls whether texture .attr files are written +// automatically when write_flt() is called. There are +// three possibilities: +// +// AU_none: the .attr files are not written +// automatically; they must be written explicitly via a +// call to FltTexture::write_attr_data() if you want +// them to be written. +// +// AU_if_missing: the .attr files are written only if +// they do not already exist. This will not update any +// .attr files, even if the data is changed. +// +// AU_always: the .attr files are always rewritten, even +// if they already exist and even if the data has not +// changed. +// +// The default is AU_if_missing. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +set_auto_attr_update(FltHeader::AttrUpdate attr) { + _auto_attr_update = attr; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_auto_attr_update +// Access: Public +// Description: Returns the current setting of the auto_attr_update +// flag. See sett_auto_attr_update(). +//////////////////////////////////////////////////////////////////// +FltHeader::AttrUpdate FltHeader:: +get_auto_attr_update() const { + return _auto_attr_update; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_flt_version +// Access: Public +// Description: Returns the version number of the flt file as +// reported in the header. +//////////////////////////////////////////////////////////////////// +double FltHeader:: +get_flt_version() const { + if (_format_revision_level < 1420) { + return _format_revision_level; + } else { + return _format_revision_level / 100.0; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::min_flt_version +// Access: Public, Static +// Description: Returns the earliest flt version number that this +// codebase supports. Earlier versions will probably +// not work. +//////////////////////////////////////////////////////////////////// +double FltHeader:: +min_flt_version() { + return 15.2; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::max_flt_version +// Access: Public, Static +// Description: Returns the latest flt version number that this +// codebase is known to support. Later versions might +// work, but then again they may not. +//////////////////////////////////////////////////////////////////// +double FltHeader:: +max_flt_version() { + return 15.2; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::has_instance +// Access: Public +// Description: Returns true if a instance subtree with the given +// index has been defined. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +has_instance(int instance_index) const { + return (_instances.count(instance_index) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_instance +// Access: Public +// Description: Returns the instance subtree associated with the +// given index, or NULL if there is no such instance. +//////////////////////////////////////////////////////////////////// +FltInstanceDefinition *FltHeader:: +get_instance(int instance_index) const { + Instances::const_iterator mi; + mi = _instances.find(instance_index); + if (mi != _instances.end()) { + return (*mi).second; + } + return (FltInstanceDefinition *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::clear_instances +// Access: Public +// Description: Removes all instance subtrees from the instance pool. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +clear_instances() { + _instances.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::add_instance +// Access: Public +// Description: Defines a new instance subtree. This subtree is not +// itself part of the hierarchy; it marks geometry that +// may be instanced to various beads elsewhere in the +// hierarchy by creating a corresponding FltInstanceRef +// bead. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +add_instance(FltInstanceDefinition *instance) { + _instances[instance->_instance_index] = instance; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::remove_instance +// Access: Public +// Description: Removes a particular instance subtree from the pool, +// if it exists. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +remove_instance(int instance_index) { + _instances.erase(instance_index); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_num_vertices +// Access: Public +// Description: Returns the number of vertices in the vertex palette. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_num_vertices() const { + return _vertices.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_vertex +// Access: Public +// Description: Returns the nth vertex of the vertex palette. +//////////////////////////////////////////////////////////////////// +FltVertex *FltHeader:: +get_vertex(int n) const { + nassertr(n >= 0 && n < (int)_vertices.size(), 0); + return _vertices[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::clear_vertices +// Access: Public +// Description: Removes all vertices from the vertex palette. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +clear_vertices() { + _vertices.clear(); + _unique_vertices.clear(); + _vertices_by_offset.clear(); + _offsets_by_vertex.clear(); + _vertex_lookups_stale = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::add_vertex +// Access: Public +// Description: Adds a new vertex to the end of the vertex palette. +// If this particular vertex was already present in the +// palette, does nothing. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +add_vertex(FltVertex *vertex) { + bool inserted = _unique_vertices.insert(vertex).second; + if (inserted) { + _vertices.push_back(vertex); + } + _vertex_lookups_stale = true; + nassertv(_unique_vertices.size() == _vertices.size()); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_vertex_by_offset +// Access: Public +// Description: Returns the particular vertex pointer associated with +// the given byte offset into the vertex palette. If +// there is no such vertex in the palette, this +// generates an error message and returns NULL. +//////////////////////////////////////////////////////////////////// +FltVertex *FltHeader:: +get_vertex_by_offset(int offset) { + if (_vertex_lookups_stale) { + update_vertex_lookups(); + } + + VerticesByOffset::const_iterator vi; + vi = _vertices_by_offset.find(offset); + if (vi == _vertices_by_offset.end()) { + nout << "No vertex with offset " << offset << "\n"; + return (FltVertex *)NULL; + } + return (*vi).second; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_offset_by_vertex +// Access: Public +// Description: Returns the byte offset into the vertex palette +// associated with the given vertex pointer. If there +// is no such vertex in the palette, this generates an +// error message and returns 0. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_offset_by_vertex(FltVertex *vertex) { + if (_vertex_lookups_stale) { + update_vertex_lookups(); + } + + OffsetsByVertex::const_iterator vi; + vi = _offsets_by_vertex.find(vertex); + if (vi == _offsets_by_vertex.end()) { + nout << "Vertex does not appear in palette.\n"; + return 0; + } + return (*vi).second; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_num_colors +// Access: Public +// Description: Returns the total number of different colors in the +// color palette. This includes all different colors, +// and represents the complete range of alloable color +// indices. This is different from the actual number of +// color entries as read directly from the color +// palette, since each color entry defines a number of +// different intensity levels--the value returned by +// get_num_colors() is equal to get_num_color_entries() +// * get_num_color_shades(). +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_num_colors() const { + return _colors.size() * get_num_color_shades(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_color +// Access: Public +// Description: Returns the four-component color corresponding to the +// given color index. Each component will be in the +// range [0, 1]. +//////////////////////////////////////////////////////////////////// +Colorf FltHeader:: +get_color(int color_index) const { + nassertr(color_index >= 0 && color_index < get_num_colors(), + Colorf(0.0, 0.0, 0.0, 0.0)); + int num_color_shades = get_num_color_shades(); + + int index = (color_index / num_color_shades); + int level = (color_index % num_color_shades); + nassertr(index >= 0 && index < (int)_colors.size(), + Colorf(0.0, 0.0, 0.0, 0.0)); + + Colorf color = _colors[index].get_color(); + return color * ((double)level / (double)(num_color_shades - 1)); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_rgb +// Access: Public +// Description: Returns the three-component color corresponding to +// the given color index, ignoring the alpha component. +// Each component will be in the range [0, 1]. +//////////////////////////////////////////////////////////////////// +RGBColorf FltHeader:: +get_rgb(int color_index) const { + nassertr(color_index >= 0 && color_index < get_num_colors(), + RGBColorf(0.0, 0.0, 0.0)); + int num_color_shades = get_num_color_shades(); + + int index = (color_index / num_color_shades); + int level = (color_index % num_color_shades); + nassertr(index >= 0 && index < (int)_colors.size(), + RGBColorf(0.0, 0.0, 0.0)); + + RGBColorf color = _colors[index].get_rgb(); + return color * ((double)level / (double)(num_color_shades - 1)); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::has_color_name +// Access: Public +// Description: Returns true if the given color is named, false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +has_color_name(int color_index) const { + return (_color_names.count(color_index) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_color_name +// Access: Public +// Description: Returns the name associated with the given color, if +// any. +//////////////////////////////////////////////////////////////////// +string FltHeader:: +get_color_name(int color_index) const { + ColorNames::const_iterator ni; + ni = _color_names.find(color_index); + if (ni != _color_names.end()) { + return (*ni).second; + } + return string(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_closest_color +// Access: Public +// Description: Returns the color index of the nearest color in the +// palette that matches the given four-component color, +// including alpha. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_closest_color(Colorf color) const { + // Since the colortable stores the brightest colors, with + // num_color_shades scaled versions of each color implicitly + // available, we really only care about the relative brightnesses of + // the various components. Normalize the color in terms of the + // largest of these. + + double scale = 1.0; + + if (color[0] == 0.0 && color[1] == 0.0 && color[2] == 0.0 && color[3] == 0.0) { + // Oh, this is invisible black. + scale = 0.0; + color.set(1.0, 1.0, 1.0, 1.0); + + } else { + if (color[0] >= color[1] && color[0] >= color[2] && color[0] >= color[3]) { + // color[0] is largest. + scale = color[0]; + + } else if (color[1] >= color[2] && color[1] >= color[3]) { + // color[1] is largest. + scale = color[1]; + + } else if (color[2] >= color[3]) { + // color[2] is largest. + scale = color[2]; + + } else { + // color[3] is largest. + scale = color[3]; + } + color /= scale; + } + + // Now search for the best match. + float best_dist = 5.0; // Greater than 4. + int best_i = -1; + + int num_color_entries = get_num_color_entries(); + for (int i = 0; i < num_color_entries; i++) { + Colorf consider = _colors[i].get_color(); + float dist2 = dot(consider - color, consider - color); + nassertr(dist2 < 5.0, 0); + + if (dist2 < best_dist) { + best_dist = dist2; + best_i = i; + } + } + nassertr(best_i >= 0, 0); + + int num_color_shades = get_num_color_shades(); + int shade_index = (int)floor((num_color_shades-1) * scale + 0.5); + + return (best_i * num_color_shades) + shade_index; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_closest_color +// Access: Public +// Description: Returns the color index of the nearest color in the +// palette that matches the given three-component color, +// ignoring alpha. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_closest_rgb(RGBColorf color) const { + // Since the colortable stores the brightest colors, with + // num_color_shades scaled versions of each color implicitly + // available, we really only care about the relative brightnesses of + // the various components. Normalize the color in terms of the + // largest of these. + + double scale = 1.0; + + if (color[0] == 0.0 && color[1] == 0.0 && color[2] == 0.0) { + // Oh, this is black. + scale = 0.0; + color.set(1.0, 1.0, 1.0); + + } else { + if (color[0] >= color[1] && color[0] >= color[2]) { + // color[0] is largest. + scale = color[0]; + + } else if (color[1] >= color[2]) { + // color[1] is largest. + scale = color[1]; + + } else { + // color[2] is largest. + scale = color[2]; + } + color /= scale; + } + + // Now search for the best match. + float best_dist = 5.0; // Greater than 4. + int best_i = -1; + + int num_color_entries = get_num_color_entries(); + for (int i = 0; i < num_color_entries; i++) { + RGBColorf consider = _colors[i].get_rgb(); + float dist2 = dot(consider - color, consider - color); + nassertr(dist2 < 5.0, 0); + + if (dist2 < best_dist) { + best_dist = dist2; + best_i = i; + } + } + nassertr(best_i >= 0, 0); + + int num_color_shades = get_num_color_shades(); + int shade_index = (int)floor((num_color_shades-1) * scale + 0.5); + + return (best_i * num_color_shades) + shade_index; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_num_color_entries +// Access: Public +// Description: Returns the number of actual entries in the color +// palette. This is based on the version of the flt +// file, and is usually either 512 or 1024. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_num_color_entries() const { + return _colors.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_num_color_shades +// Access: Public +// Description: Returns the number of shades of brightness of each +// entry in the color palette. This is a fixed property +// of MultiGen files: each entry in the palette actually +// represents a range of this many colors. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_num_color_shades() const { + return 128; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_color +// Access: Public +// Description: Decodes a MultiGen color, as stored on a face or +// vertex, into an actual four-component Colorf. +// Normally you need not call this directly; there are +// color accessors defined on faces and vertices that do +// this. +//////////////////////////////////////////////////////////////////// +Colorf FltHeader:: +get_color(int color_index, bool use_packed_color, + const FltPackedColor &packed_color, + int transparency) { + if (!use_packed_color) { + return get_color(color_index); + } + + Colorf color; + color[0] = packed_color._r / 255.0; + color[1] = packed_color._g / 255.0; + color[2] = packed_color._b / 255.0; + // MultiGen doesn't yet use the A component of RGBA. + //color[3] = packed_color._a / 255.0; + color[3] = 1.0 - (transparency / 65535.0); + return color; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_color +// Access: Public +// Description: Decodes a MultiGen color, as stored on a face or +// vertex, into an actual three-component RGBColorf. +// Normally you need not call this directly; there are +// color accessors defined on faces and vertices that do +// this. +//////////////////////////////////////////////////////////////////// +RGBColorf FltHeader:: +get_rgb(int color_index, bool use_packed_color, + const FltPackedColor &packed_color) { + if (!use_packed_color) { + return get_rgb(color_index); + } + + RGBColorf color; + color[0] = packed_color._r / 255.0; + color[1] = packed_color._g / 255.0; + color[2] = packed_color._b / 255.0; + return color; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::has_material +// Access: Public +// Description: Returns true if a material with the given index has +// been defined. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +has_material(int material_index) const { + return (_materials.count(material_index) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_material +// Access: Public +// Description: Returns the material associated with the given index, +// or NULL if there is no such material. +//////////////////////////////////////////////////////////////////// +FltMaterial *FltHeader:: +get_material(int material_index) const { + Materials::const_iterator mi; + mi = _materials.find(material_index); + if (mi != _materials.end()) { + return (*mi).second; + } + return (FltMaterial *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::clear_materials +// Access: Public +// Description: Removes all materials from the palette. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +clear_materials() { + _materials.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::add_material +// Access: Public +// Description: Defines a new material. The material is added in the +// position indicated by the material's index number. +// If there is already a material defined for that index +// number, it is replaced. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +add_material(FltMaterial *material) { + _materials[material->_material_index] = material; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::remove_material +// Access: Public +// Description: Removes a particular material from the material +// palette, if it exists. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +remove_material(int material_index) { + _materials.erase(material_index); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::has_texture +// Access: Public +// Description: Returns true if a texture with the given index has +// been defined. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +has_texture(int texture_index) const { + return (_textures.count(texture_index) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_texture +// Access: Public +// Description: Returns the texture associated with the given index, +// or NULL if there is no such texture. +//////////////////////////////////////////////////////////////////// +FltTexture *FltHeader:: +get_texture(int texture_index) const { + Textures::const_iterator mi; + mi = _textures.find(texture_index); + if (mi != _textures.end()) { + return (*mi).second; + } + return (FltTexture *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::clear_textures +// Access: Public +// Description: Removes all textures from the palette. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +clear_textures() { + _textures.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::add_texture +// Access: Public +// Description: Defines a new texture. The texture is added in the +// position indicated by the texture's index number. +// If there is already a texture defined for that index +// number, it is replaced. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +add_texture(FltTexture *texture) { + _textures[texture->_pattern_index] = texture; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::remove_texture +// Access: Public +// Description: Removes a particular texture from the texture +// palette, if it exists. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +remove_texture(int texture_index) { + _textures.erase(texture_index); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::set_texture_path +// Access: Public +// Description: Sets the search path that relative texture filenames +// will be looked for along. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +set_texture_path(const DSearchPath &path) { + _texture_path = path; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::update_texture_path +// Access: Public +// Description: Returns a non-const reference to the texture search +// path, so that it may be appended to or otherwise +// modified. +//////////////////////////////////////////////////////////////////// +DSearchPath &FltHeader:: +update_texture_path() { + return _texture_path; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_texture_path +// Access: Public +// Description: Returns the search path for looking up texture +// filenames. +//////////////////////////////////////////////////////////////////// +const DSearchPath &FltHeader:: +get_texture_path() const { + return _texture_path; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::has_light_source +// Access: Public +// Description: Returns true if a light source with the given index +// has been defined. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +has_light_source(int light_index) const { + return (_light_sources.count(light_index) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_light_source +// Access: Public +// Description: Returns the light source associated with the given +// index, or NULL if there is no such light source. +//////////////////////////////////////////////////////////////////// +FltLightSourceDefinition *FltHeader:: +get_light_source(int light_index) const { + LightSources::const_iterator li; + li = _light_sources.find(light_index); + if (li != _light_sources.end()) { + return (*li).second; + } + return (FltLightSourceDefinition *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::clear_light_sources +// Access: Public +// Description: Removes all light sources from the palette. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +clear_light_sources() { + _light_sources.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::add_light_source +// Access: Public +// Description: Defines a new light source. The light source is +// added in the position indicated by its light index +// number. If there is already a light source defined +// for that index number, it is replaced. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +add_light_source(FltLightSourceDefinition *light_source) { + _light_sources[light_source->_light_index] = light_source; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::remove_light_source +// Access: Public +// Description: Removes a particular light source from the light +// source palette, if it exists. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +remove_light_source(int light_index) { + _light_sources.erase(light_index); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::got_eyepoint_trackplane_palette +// Access: Public +// Description: Returns true if we have read an eyepoint/trackplane +// palette, and at least some of the eyepoints and +// trackplanes are therefore expected to be meaningful. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +got_eyepoint_trackplane_palette() const { + return _got_eyepoint_trackplane_palette; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::set_eyepoint_trackplane_palette +// Access: Public +// Description: Sets the state of the eyepoint/trackplane palette +// flag. When this is false, the palette is believed to +// be meaningless, and will not be written; when it is +// true, the palette is believed to contain at least +// some meaningful data, and will be written. +//////////////////////////////////////////////////////////////////// +void FltHeader:: +set_eyepoint_trackplane_palette(bool flag) { + _got_eyepoint_trackplane_palette = flag; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_num_eyepoints +// Access: Public +// Description: Returns the number of eyepoints in the +// eyepoint/trackplane palette. This is presently fixed +// at 10, according to the MultiGen specs. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_num_eyepoints() const { + return 10; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_eyepoint +// Access: Public +// Description: Returns the nth eyepoint in the eyepoint/trackplane +// palette. +//////////////////////////////////////////////////////////////////// +FltEyepoint *FltHeader:: +get_eyepoint(int n) { + nassertr(n >= 0 && n < get_num_eyepoints(), (FltEyepoint *)NULL); + return &_eyepoints[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_num_trackplanes +// Access: Public +// Description: Returns the number of trackplanes in the +// eyepoint/trackplane palette. This is presently fixed +// at 10, according to the MultiGen specs. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +get_num_trackplanes() const { + return 10; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::get_trackplane +// Access: Public +// Description: Returns the nth trackplane in the eyepoint/trackplane +// palette. +//////////////////////////////////////////////////////////////////// +FltTrackplane *FltHeader:: +get_trackplane(int n) { + nassertr(n >= 0 && n < get_num_trackplanes(), (FltTrackplane *)NULL); + return &_trackplanes[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::update_vertex_lookups +// Access: Public +// Description: Recomputes the offsets_by_vertex and +// vertices_by_offset tables. This reflects the flt +// file as it will be written out, but not necessarily +// as it was read in. +// +// The return value is the total length of the vertex +// palette, including the header record. +//////////////////////////////////////////////////////////////////// +int FltHeader:: +update_vertex_lookups() { + // We start with the length of the vertex palette record itself. + int offset = 8; + + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + FltVertex *vertex = (*vi); + + _offsets_by_vertex[vertex] = offset; + _vertices_by_offset[offset] = vertex; + offset += vertex->get_record_length(); + } + + _vertex_lookups_stale = false; + + return offset; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_header, false); + DatagramIterator &iterator = reader.get_iterator(); + + _format_revision_level = iterator.get_be_int32(); + _edit_revision_level = iterator.get_be_int32(); + _last_revision = iterator.get_fixed_string(32); + _next_group_id = iterator.get_be_int16(); + _next_lod_id = iterator.get_be_int16(); + _next_object_id = iterator.get_be_int16(); + _next_face_id = iterator.get_be_int16(); + _unit_multiplier = iterator.get_be_int16(); + _vertex_units = (Units)iterator.get_int8(); + _texwhite_new = (iterator.get_int8() != 0); + _flags = iterator.get_be_uint32(); + iterator.skip_bytes(24); + _projection_type = (ProjectionType)iterator.get_be_int32(); + iterator.skip_bytes(28); + _next_dof_id = iterator.get_be_int16(); + _vertex_storage_type = (VertexStorageType)iterator.get_be_int16(); + _database_origin = (DatabaseOrigin)iterator.get_be_int32(); + _sw_x = iterator.get_be_float64(); + _sw_y = iterator.get_be_float64(); + _delta_x = iterator.get_be_float64(); + _delta_y = iterator.get_be_float64(); + _next_sound_id = iterator.get_be_int16(); + _next_path_id = iterator.get_be_int16(); + iterator.skip_bytes(8); + _next_clip_id = iterator.get_be_int16(); + _next_text_id = iterator.get_be_int16(); + _next_bsp_id = iterator.get_be_int16(); + _next_switch_id = iterator.get_be_int16(); + iterator.skip_bytes(4); + _sw_lat = iterator.get_be_float64(); + _sw_long = iterator.get_be_float64(); + _ne_lat = iterator.get_be_float64(); + _ne_long = iterator.get_be_float64(); + _origin_lat = iterator.get_be_float64(); + _origin_long = iterator.get_be_float64(); + _lambert_upper_lat = iterator.get_be_float64(); + _lambert_lower_lat = iterator.get_be_float64(); + _next_light_id = iterator.get_be_int16(); + iterator.skip_bytes(2); + _next_road_id = iterator.get_be_int16(); + _next_cat_id = iterator.get_be_int16(); + iterator.skip_bytes(2 + 2 + 2 + 2); + _earth_model = (EarthModel)iterator.get_be_int32(); + + // Undocumented additional padding. + iterator.skip_bytes(4); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::extract_ancillary +// Access: Protected, Virtual +// Description: Checks whether the given bead, which follows this +// bead sequentially in the file, is an ancillary record +// of this bead. If it is, extracts the relevant +// information and returns true; otherwise, leaves it +// alone and returns false. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +extract_ancillary(FltRecordReader &reader) { + switch (reader.get_opcode()) { + case FO_vertex_palette: + // We're about to begin the vertex palette! + clear_vertices(); + _current_vertex_offset = reader.get_record_length(); + return true; + + case FO_vertex_c: + case FO_vertex_cn: + case FO_vertex_cnu: + case FO_vertex_cu: + // Here's a new vertex for the palette. + return extract_vertex(reader); + + case FO_color_palette: + return extract_color_palette(reader); + + case FO_15_material: + return extract_material(reader); + + case FO_texture: + return extract_texture(reader); + + case FO_light_definition: + return extract_light_source(reader); + + case FO_eyepoint_palette: + return extract_eyepoint_palette(reader); + + default: + return FltBeadID::extract_ancillary(reader); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_header); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int32(_format_revision_level); + datagram.add_be_int32(_edit_revision_level); + datagram.add_fixed_string(_last_revision, 32); + datagram.add_be_int16(_next_group_id); + datagram.add_be_int16(_next_lod_id); + datagram.add_be_int16(_next_object_id); + datagram.add_be_int16(_next_face_id); + datagram.add_be_int16(_unit_multiplier); + datagram.add_int8(_vertex_units); + datagram.add_int8(_texwhite_new); + datagram.add_be_uint32(_flags); + datagram.pad_bytes(24); + datagram.add_be_int32(_projection_type); + datagram.pad_bytes(28); + datagram.add_be_int16(_next_dof_id); + datagram.add_be_int16(_vertex_storage_type); + datagram.add_be_int32(_database_origin); + datagram.add_be_float64(_sw_x); + datagram.add_be_float64(_sw_y); + datagram.add_be_float64(_delta_x); + datagram.add_be_float64(_delta_y); + datagram.add_be_int16(_next_sound_id); + datagram.add_be_int16(_next_path_id); + datagram.pad_bytes(8); + datagram.add_be_int16(_next_clip_id); + datagram.add_be_int16(_next_text_id); + datagram.add_be_int16(_next_bsp_id); + datagram.add_be_int16(_next_switch_id); + datagram.pad_bytes(4); + datagram.add_be_float64(_sw_lat); + datagram.add_be_float64(_sw_long); + datagram.add_be_float64(_ne_lat); + datagram.add_be_float64(_ne_long); + datagram.add_be_float64(_origin_lat); + datagram.add_be_float64(_origin_long); + datagram.add_be_float64(_lambert_upper_lat); + datagram.add_be_float64(_lambert_lower_lat); + datagram.add_be_int16(_next_light_id); + datagram.pad_bytes(2); + datagram.add_be_int16(_next_road_id); + datagram.add_be_int16(_next_cat_id); + datagram.pad_bytes(2 + 2 + 2 + 2); + datagram.add_be_int32(_earth_model); + + // Undocumented additional padding. + datagram.pad_bytes(4); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_ancillary +// Access: Protected, Virtual +// Description: Writes whatever ancillary records are required for +// this bead. Returns FE_ok on success, or something +// else on error. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_ancillary(FltRecordWriter &writer) const { + FltError result; + + result = write_color_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_material_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_texture_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_light_source_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_eyepoint_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_vertex_palette(writer); + if (result != FE_ok) { + return result; + } + + return FltBeadID::write_ancillary(writer); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::extract_vertex +// Access: Private +// Description: Reads a single vertex ancillary record. It is +// assumed that all the vertex records will immediately +// follow the vertex palette record. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +extract_vertex(FltRecordReader &reader) { + FltVertex *vertex = new FltVertex(this); + if (!vertex->extract_record(reader)) { + return false; + } + _vertices.push_back(vertex); + _unique_vertices.insert(vertex); + _offsets_by_vertex[vertex] = _current_vertex_offset; + _vertices_by_offset[_current_vertex_offset] = vertex; + _current_vertex_offset += reader.get_record_length(); + + // _vertex_lookups_stale remains false. + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::extract_color_palette +// Access: Private +// Description: Reads the color palette. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +extract_color_palette(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_color_palette, false); + DatagramIterator &iterator = reader.get_iterator(); + + static const int expected_color_entries = 1024; + + iterator.skip_bytes(128); + _colors.clear(); + for (int i = 0; i < expected_color_entries; i++) { + if (iterator.get_remaining_size() == 0) { + // An early end to the palette is acceptable. + return true; + } + FltPackedColor color; + if (!color.extract_record(reader)) { + return false; + } + _colors.push_back(color); + } + + // Now pull out the color names. + while (iterator.get_remaining_size() > 0) { + int entry_length = iterator.get_be_uint16(); + iterator.skip_bytes(2); + int color_index = iterator.get_be_int16(); + iterator.skip_bytes(2); + + int name_length = entry_length - 8; + nassertr(color_index >= 0 && color_index < (int)_colors.size(), false); + _color_names[color_index] = iterator.get_fixed_string(name_length); + } + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::extract_material +// Access: Private +// Description: Reads a single material ancillary record. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +extract_material(FltRecordReader &reader) { + FltMaterial *material = new FltMaterial(this); + if (!material->extract_record(reader)) { + return false; + } + add_material(material); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::extract_texture +// Access: Private +// Description: Reads a single texture ancillary record. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +extract_texture(FltRecordReader &reader) { + FltTexture *texture = new FltTexture(this); + if (!texture->extract_record(reader)) { + return false; + } + add_texture(texture); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::extract_light_source +// Access: Private +// Description: Reads a single light source ancillary record. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +extract_light_source(FltRecordReader &reader) { + FltLightSourceDefinition *light_source = new FltLightSourceDefinition(this); + if (!light_source->extract_record(reader)) { + return false; + } + add_light_source(light_source); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::extract_eyepoint_palette +// Access: Private +// Description: Reads the eyepoint/trackplane palette. +//////////////////////////////////////////////////////////////////// +bool FltHeader:: +extract_eyepoint_palette(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_eyepoint_palette, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); + + int i; + int num_eyepoints = get_num_eyepoints(); + for (i = 0; i < num_eyepoints; i++) { + if (!_eyepoints[i].extract_record(reader)) { + return false; + } + } + + int num_trackplanes = get_num_trackplanes(); + for (i = 0; i < num_trackplanes; i++) { + if (!_trackplanes[i].extract_record(reader)) { + return false; + } + } + + _got_eyepoint_trackplane_palette = true; + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_vertex_palette +// Access: Private +// Description: Writes out the vertex palette with all of its +// vertices. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_vertex_palette(FltRecordWriter &writer) const { + FltError result; + + int vertex_palette_length = + ((FltHeader *)this)->update_vertex_lookups(); + Datagram vertex_palette; + vertex_palette.add_be_int32(vertex_palette_length); + result = writer.write_record(FO_vertex_palette, vertex_palette); + if (result != FE_ok) { + return result; + } + // Now write out each vertex in the palette. + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + FltVertex *vertex = (*vi); + vertex->build_record(writer); + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_color_palette +// Access: Private +// Description: Writes out the color palette. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_color_palette(FltRecordWriter &writer) const { + writer.set_opcode(FO_color_palette); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(128); + + // How many colors should we write? + int num_colors = 1024; + + Colors::const_iterator ci; + for (ci = _colors.begin(); num_colors > 0 && ci != _colors.end(); ++ci) { + if (!(*ci).build_record(writer)) { + return FE_invalid_record; + } + num_colors--; + } + + // Now we might need to pad the record to fill up the required + // number of colors. + if (num_colors > 0) { + FltPackedColor empty; + while (num_colors > 0) { + if (!empty.build_record(writer)) { + return FE_invalid_record; + } + num_colors--; + } + } + + // Now append all the names at the end. + ColorNames::const_iterator ni; + for (ni = _color_names.begin(); ni != _color_names.end(); ++ni) { + string name = (*ni).second.substr(0, 80); + int entry_length = name.length() + 8; + datagram.add_be_uint16(entry_length); + datagram.pad_bytes(2); + datagram.add_be_uint16((*ni).first); + datagram.pad_bytes(2); + datagram.add_fixed_string(name, name.length()); + } + + return writer.advance(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_material_palette +// Access: Private +// Description: Writes out the material palette. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_material_palette(FltRecordWriter &writer) const { + FltError result; + + Materials::const_iterator mi; + for (mi = _materials.begin(); mi != _materials.end(); ++mi) { + FltMaterial *material = (*mi).second; + material->build_record(writer); + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_texture_palette +// Access: Private +// Description: Writes out the texture palette. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_texture_palette(FltRecordWriter &writer) const { + FltError result; + + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + FltTexture *texture = (*ti).second; + texture->build_record(writer); + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_light_source_palette +// Access: Private +// Description: Writes out the light source palette. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_light_source_palette(FltRecordWriter &writer) const { + FltError result; + + LightSources::const_iterator li; + for (li = _light_sources.begin(); li != _light_sources.end(); ++li) { + FltLightSourceDefinition *light_source = (*li).second; + light_source->build_record(writer); + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltHeader::write_eyepoint_palette +// Access: Private +// Description: Writes out the eyepoint/trackplane palette, if we +// have one. +//////////////////////////////////////////////////////////////////// +FltError FltHeader:: +write_eyepoint_palette(FltRecordWriter &writer) const { + if (!_got_eyepoint_trackplane_palette) { + return FE_ok; + } + + writer.set_opcode(FO_color_palette); + Datagram &datagram = writer.update_datagram(); + datagram.pad_bytes(4); + + int i; + int num_eyepoints = get_num_eyepoints(); + for (i = 0; i < num_eyepoints; i++) { + if (!_eyepoints[i].build_record(writer)) { + return FE_bad_data; + } + } + + int num_trackplanes = get_num_trackplanes(); + for (i = 0; i < num_trackplanes; i++) { + if (!_trackplanes[i].build_record(writer)) { + return FE_bad_data; + } + } + + return writer.advance(); +} diff --git a/pandatool/src/flt/fltHeader.h b/pandatool/src/flt/fltHeader.h new file mode 100644 index 0000000000..80a3d5b691 --- /dev/null +++ b/pandatool/src/flt/fltHeader.h @@ -0,0 +1,310 @@ +// Filename: fltHeader.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTHEADER_H +#define FLTHEADER_H + +#include + +#include "fltBeadID.h" +#include "fltVertex.h" +#include "fltMaterial.h" +#include "fltTexture.h" +#include "fltLightSourceDefinition.h" +#include "fltEyepoint.h" +#include "fltTrackplane.h" +#include "fltInstanceDefinition.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltHeader +// Description : This is the first bead in the file, the top of the +// bead hierarchy, and the primary interface to reading +// and writing a Flt file. You always read a Flt file +// by creating a header and calling read_flt(), which +// fills in its children beads automatically; you write +// a Flt file by creating a header, adding its children, +// and calling write_flt(). +//////////////////////////////////////////////////////////////////// +class FltHeader : public FltBeadID { +public: + FltHeader(); + + FltError read_flt(Filename filename); + FltError read_flt(istream &in); + FltError write_flt(Filename filename); + FltError write_flt(ostream &out); + + enum AttrUpdate { + AU_none, + AU_if_missing, + AU_always + }; + + void set_auto_attr_update(AttrUpdate attr); + AttrUpdate get_auto_attr_update() const; + + enum Units { + U_meters = 0, + U_kilometers = 1, + U_feet = 4, + U_inches = 5, + U_nautical_miles = 8 + }; + + enum Flags { + F_save_vertex_normals = 0x80000000 + }; + + enum ProjectionType { + PT_flat_earth = 0, + PT_trapezoidal = 1, + PT_round_earth = 2, + PT_lambert = 3, + PT_utm = 4 + }; + + enum VertexStorageType { + VTS_double = 1 + }; + + enum DatabaseOrigin { + DO_open_flight = 100, + DO_dig = 200, + DO_es_ct6 = 300, + DO_psp = 400, + DO_ge_civ = 600, + DO_es_gdf = 700, + }; + + enum EarthModel { + EM_wgs84 = 0, + EM_wgs72 = 1, + EM_bessel = 2, + EM_clarke_1866 = 3, + EM_nad27 = 4 + }; + + int _format_revision_level; + int _edit_revision_level; + string _last_revision; + int _next_group_id; + int _next_lod_id; + int _next_object_id; + int _next_face_id; + int _unit_multiplier; + Units _vertex_units; + bool _texwhite_new; + unsigned int _flags; + ProjectionType _projection_type; + int _next_dof_id; + VertexStorageType _vertex_storage_type; + DatabaseOrigin _database_origin; + double _sw_x, _sw_y; + double _delta_x, _delta_y; + int _next_sound_id; + int _next_path_id; + int _next_clip_id; + int _next_text_id; + int _next_bsp_id; + int _next_switch_id; + double _sw_lat, _sw_long; + double _ne_lat, _ne_long; + double _origin_lat, _origin_long; + double _lambert_upper_lat, _lambert_lower_lat; + int _next_light_id; + int _next_road_id; + int _next_cat_id; + EarthModel _earth_model; + +public: + double get_flt_version() const; + static double min_flt_version(); + static double max_flt_version(); + + + // Accessors into the instance pool. + bool has_instance(int instance_index) const; + FltInstanceDefinition *get_instance(int instance_index) const; + void clear_instances(); + void add_instance(FltInstanceDefinition *instance); + void remove_instance(int instance_index); + + + // Accessors into the vertex palette. + int get_num_vertices() const; + FltVertex *get_vertex(int n) const; + void clear_vertices(); + void add_vertex(FltVertex *vertex); + + FltVertex *get_vertex_by_offset(int offset); + int get_offset_by_vertex(FltVertex *vertex); + + + // Accessors into the color palette. This is read-only; why would + // you want to mess with building a new color palette? + int get_num_colors() const; + Colorf get_color(int color_index) const; + RGBColorf get_rgb(int color_index) const; + bool has_color_name(int color_index) const; + string get_color_name(int color_index) const; + + int get_closest_color(Colorf color) const; + int get_closest_rgb(RGBColorf color) const; + + int get_num_color_entries() const; + int get_num_color_shades() const; + + // These functions are mainly used behind-the-scenes to decode the + // strange forest of color options defined for faces and vertices. + Colorf get_color(int color_index, bool use_packed_color, + const FltPackedColor &packed_color, + int transparency); + RGBColorf get_rgb(int color_index, bool use_packed_color, + const FltPackedColor &packed_color); + + // Accessors into the material palette. + bool has_material(int material_index) const; + FltMaterial *get_material(int material_index) const; + void clear_materials(); + void add_material(FltMaterial *material); + void remove_material(int material_index); + + + // Accessors into the texture palette. + bool has_texture(int texture_index) const; + FltTexture *get_texture(int texture_index) const; + void clear_textures(); + void add_texture(FltTexture *texture); + void remove_texture(int texture_index); + + // Sometimes Flt files store textures as relative pathnames. + // Setting this search path helps resolve that tendency. + void set_texture_path(const DSearchPath &path); + DSearchPath &update_texture_path(); + const DSearchPath &get_texture_path() const; + + + // Accessors into the light source palette. + bool has_light_source(int light_index) const; + FltLightSourceDefinition *get_light_source(int light_index) const; + void clear_light_sources(); + void add_light_source(FltLightSourceDefinition *light_source); + void remove_light_source(int light_index); + + + // Accessors into the eyepoint/trackplane palette. + bool got_eyepoint_trackplane_palette() const; + void set_eyepoint_trackplane_palette(bool flag); + + int get_num_eyepoints() const; + FltEyepoint *get_eyepoint(int n); + int get_num_trackplanes() const; + FltTrackplane *get_trackplane(int n); + +private: + // Instance subtrees. These are standalone subtrees, which may be + // referenced by various points in the hierarchy, stored by instance + // ID number. + typedef map Instances; + Instances _instances; + + + // Support for the vertex palette. + int update_vertex_lookups(); + + typedef vector Vertices; + typedef set UniqueVertices; + + typedef map VerticesByOffset; + typedef map OffsetsByVertex; + + Vertices _vertices; + UniqueVertices _unique_vertices; + VerticesByOffset _vertices_by_offset; + OffsetsByVertex _offsets_by_vertex; + + bool _vertex_lookups_stale; + + // This is maintained while the header is being read, to map the + // vertices to their corresponding offsets in the vertex palette. + int _current_vertex_offset; + + + // Support for the color palette. + typedef vector Colors; + typedef map ColorNames; + Colors _colors; + ColorNames _color_names; + + + // Support for the material palette. + typedef map Materials; + Materials _materials; + + + // Support for the texture palette. + AttrUpdate _auto_attr_update; + typedef map Textures; + Textures _textures; + DSearchPath _texture_path; + + + // Support for the light source palette. + typedef map LightSources; + LightSources _light_sources; + + + // Support for the eyepoint/trackplane palette. + bool _got_eyepoint_trackplane_palette; + FltEyepoint _eyepoints[10]; + FltTrackplane _trackplanes[10]; + + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +private: + bool extract_vertex(FltRecordReader &reader); + bool extract_color_palette(FltRecordReader &reader); + bool extract_material(FltRecordReader &reader); + bool extract_texture(FltRecordReader &reader); + bool extract_light_source(FltRecordReader &reader); + bool extract_eyepoint_palette(FltRecordReader &reader); + + FltError write_vertex_palette(FltRecordWriter &writer) const; + FltError write_color_palette(FltRecordWriter &writer) const; + FltError write_material_palette(FltRecordWriter &writer) const; + FltError write_texture_palette(FltRecordWriter &writer) const; + FltError write_light_source_palette(FltRecordWriter &writer) const; + FltError write_eyepoint_palette(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltHeader", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltInstanceDefinition.cxx b/pandatool/src/flt/fltInstanceDefinition.cxx new file mode 100644 index 0000000000..45506b8684 --- /dev/null +++ b/pandatool/src/flt/fltInstanceDefinition.cxx @@ -0,0 +1,67 @@ +// Filename: fltInstanceDefinition.cxx +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltInstanceDefinition.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltInstanceDefinition::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltInstanceDefinition::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltInstanceDefinition:: +FltInstanceDefinition(FltHeader *header) : FltBead(header) { + _instance_index = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltInstanceDefinition::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltInstanceDefinition:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_instance, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(2); + _instance_index = iterator.get_be_int16(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltInstanceDefinition::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltInstanceDefinition:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_instance); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(2); + datagram.add_be_int16(_instance_index); + + return true; +} diff --git a/pandatool/src/flt/fltInstanceDefinition.h b/pandatool/src/flt/fltInstanceDefinition.h new file mode 100644 index 0000000000..92a95529c0 --- /dev/null +++ b/pandatool/src/flt/fltInstanceDefinition.h @@ -0,0 +1,56 @@ +// Filename: fltInstanceDefinition.h +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTINSTANCEDEFINITION_H +#define FLTINSTANCEDEFINITION_H + +#include + +#include "fltBead.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltInstanceDefinition +// Description : This special kind of record marks the top node of an +// instance subtree. This subtree lives outside of the +// normal hierarchy, and is MultiGen's way of support +// instancing--each instance subtree has a unique index, +// which may be referenced in a FltInstanceRef object to +// make the instance appear in various places in the +// hierarchy. +//////////////////////////////////////////////////////////////////// +class FltInstanceDefinition : public FltBead { +public: + FltInstanceDefinition(FltHeader *header); + + int _instance_index; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltInstanceDefinition", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltInstanceRef; + friend class FltRecordWriter; +}; + +#endif + + diff --git a/pandatool/src/flt/fltInstanceRef.cxx b/pandatool/src/flt/fltInstanceRef.cxx new file mode 100644 index 0000000000..100adc0744 --- /dev/null +++ b/pandatool/src/flt/fltInstanceRef.cxx @@ -0,0 +1,108 @@ +// Filename: fltInstanceRef.cxx +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltInstanceRef.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltInstanceDefinition.h" +#include "fltHeader.h" + +TypeHandle FltInstanceRef::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltInstanceRef::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltInstanceRef:: +FltInstanceRef(FltHeader *header) : FltBead(header) { + _instance_index = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltInstanceRef::write +// Access: Public +// Description: Writes a multiple-line description of the record and +// all of its children. This is a human-readable +// description, primarily for debugging; to write a flt +// file, use FltHeader::write_flt(). +//////////////////////////////////////////////////////////////////// +void FltInstanceRef:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) << "instance"; + FltInstanceDefinition *def = _header->get_instance(_instance_index); + if (def != (FltInstanceDefinition *)NULL) { + def->write_children(out, indent_level + 2); + indent(out, indent_level) << "}\n"; + } else { + out << "\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltInstanceRef::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltInstanceRef:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_instance_ref, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(2); + _instance_index = iterator.get_be_int16(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltInstanceRef::write_record_and_children +// Access: Protected, Virtual +// Description: Writes this record out to the flt file, along with all +// of its ancillary records and children records. Returns +// FE_ok on success, or something else on error. +//////////////////////////////////////////////////////////////////// +FltError FltInstanceRef:: +write_record_and_children(FltRecordWriter &writer) const { + // First, make sure our instance definition has already been written. + FltError result = writer.write_instance_def(_header, _instance_index); + if (result != FE_ok) { + return result; + } + + // Then write out our own record. + return FltBead::write_record_and_children(writer); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltInstanceRef::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltInstanceRef:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_instance_ref); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(2); + datagram.add_be_int16(_instance_index); + + return true; +} diff --git a/pandatool/src/flt/fltInstanceRef.h b/pandatool/src/flt/fltInstanceRef.h new file mode 100644 index 0000000000..c6c2bdfd65 --- /dev/null +++ b/pandatool/src/flt/fltInstanceRef.h @@ -0,0 +1,54 @@ +// Filename: fltInstanceRef.h +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTINSTANCEREF_H +#define FLTINSTANCEREF_H + +#include + +#include "fltBead.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltInstanceRef +// Description : This bead appears in the hierarchy to refer to a +// FltInstanceDefinition node defined elsewhere. It +// indicates that the subtree beginning at the +// FltInstanceDefinition should be considered to be +// instanced here. +//////////////////////////////////////////////////////////////////// +class FltInstanceRef : public FltBead { +public: + FltInstanceRef(FltHeader *header); + + int _instance_index; + + virtual void write(ostream &out, int indent_level = 0) const; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual FltError write_record_and_children(FltRecordWriter &writer) const; + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltInstanceRef", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltLOD.cxx b/pandatool/src/flt/fltLOD.cxx new file mode 100644 index 0000000000..4a3bc6dc4c --- /dev/null +++ b/pandatool/src/flt/fltLOD.cxx @@ -0,0 +1,91 @@ +// Filename: fltLOD.cxx +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltLOD.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltLOD::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltLOD::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltLOD:: +FltLOD(FltHeader *header) : FltBeadID(header) { + _switch_in = 0.0; + _switch_out = 0.0; + _special_id1 = 0; + _special_id2 = 0; + _flags = 0; + _center_x = 0.0; + _center_y = 0.0; + _center_z = 0.0; + _transition_range = 0.0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltLOD::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltLOD:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_lod, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); + _switch_in = iterator.get_be_float64(); + _switch_out = iterator.get_be_float64(); + _special_id1 = iterator.get_be_int16(); + _special_id2 = iterator.get_be_int16(); + _flags = iterator.get_be_uint32(); + _center_x = iterator.get_be_float64(); + _center_y = iterator.get_be_float64(); + _center_z = iterator.get_be_float64(); + _transition_range = iterator.get_be_float64(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltLOD::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltLOD:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_lod); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); + datagram.add_be_float64(_switch_in); + datagram.add_be_float64(_switch_out); + datagram.add_be_int16(_special_id1); + datagram.add_be_int16(_special_id2); + datagram.add_be_uint32(_flags); + datagram.add_be_float64(_center_x); + datagram.add_be_float64(_center_y); + datagram.add_be_float64(_center_z); + datagram.add_be_float64(_transition_range); + + return true; +} diff --git a/pandatool/src/flt/fltLOD.h b/pandatool/src/flt/fltLOD.h new file mode 100644 index 0000000000..64b1fe5e77 --- /dev/null +++ b/pandatool/src/flt/fltLOD.h @@ -0,0 +1,59 @@ +// Filename: fltLOD.h +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTLOD_H +#define FLTLOD_H + +#include + +#include "fltBeadID.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltLOD +// Description : A Level-of-Detail record. +//////////////////////////////////////////////////////////////////// +class FltLOD : public FltBeadID { +public: + FltLOD(FltHeader *header); + + enum Flags { + F_use_previous_slant = 0x80000000, + F_freeze_center = 0x20000000 + }; + + double _switch_in; + double _switch_out; + int _special_id1, _special_id2; + unsigned int _flags; + double _center_x; + double _center_y; + double _center_z; + double _transition_range; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltLOD", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltLightSourceDefinition.cxx b/pandatool/src/flt/fltLightSourceDefinition.cxx new file mode 100644 index 0000000000..ab91d996f8 --- /dev/null +++ b/pandatool/src/flt/fltLightSourceDefinition.cxx @@ -0,0 +1,129 @@ +// Filename: fltLightSourceDefinition.cxx +// Created by: drose (26Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltLightSourceDefinition.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltLightSourceDefinition::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltLightSourceDefinition::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltLightSourceDefinition:: +FltLightSourceDefinition(FltHeader *header) : FltRecord(header) { + _light_index = 0; + _ambient.set(0.0, 0.0, 0.0, 1.0); + _diffuse.set(1.0, 1.0, 1.0, 1.0); + _specular.set(0.0, 0.0, 0.0, 1.0); + _light_type = LT_infinite; + _exponential_dropoff = 1.0; + _cutoff_angle = 180.0; + _yaw = 0.0; + _pitch = 0.0; + _constant_coefficient = 0.0; + _linear_coefficient = 0.0; + _quadratic_coefficient = 1.0; + _modeling_light = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltLightSourceDefinition::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltLightSourceDefinition:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_light_definition, false); + DatagramIterator &iterator = reader.get_iterator(); + + _light_index = iterator.get_be_int32(); + iterator.skip_bytes(2*4); + _light_name = iterator.get_fixed_string(20); + iterator.skip_bytes(4); + _ambient[0] = iterator.get_be_float32(); + _ambient[1] = iterator.get_be_float32(); + _ambient[2] = iterator.get_be_float32(); + _ambient[3] = iterator.get_be_float32(); + _diffuse[0] = iterator.get_be_float32(); + _diffuse[1] = iterator.get_be_float32(); + _diffuse[2] = iterator.get_be_float32(); + _diffuse[3] = iterator.get_be_float32(); + _specular[0] = iterator.get_be_float32(); + _specular[1] = iterator.get_be_float32(); + _specular[2] = iterator.get_be_float32(); + _specular[3] = iterator.get_be_float32(); + _light_type = (LightType)iterator.get_be_int32(); + iterator.skip_bytes(4*10); + _exponential_dropoff = iterator.get_be_float32(); + _cutoff_angle = iterator.get_be_float32(); + _yaw = iterator.get_be_float32(); + _pitch = iterator.get_be_float32(); + _constant_coefficient = iterator.get_be_float32(); + _linear_coefficient = iterator.get_be_float32(); + _quadratic_coefficient = iterator.get_be_float32(); + _modeling_light = (iterator.get_be_int32() != 0); + iterator.skip_bytes(4*19); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltLightSourceDefinition::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltLightSourceDefinition:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_light_definition); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int32(_light_index); + datagram.pad_bytes(2*4); + datagram.add_fixed_string(_light_name, 20); + datagram.pad_bytes(4); + datagram.add_be_float32(_ambient[0]); + datagram.add_be_float32(_ambient[1]); + datagram.add_be_float32(_ambient[2]); + datagram.add_be_float32(_ambient[3]); + datagram.add_be_float32(_diffuse[0]); + datagram.add_be_float32(_diffuse[1]); + datagram.add_be_float32(_diffuse[2]); + datagram.add_be_float32(_diffuse[3]); + datagram.add_be_float32(_specular[0]); + datagram.add_be_float32(_specular[1]); + datagram.add_be_float32(_specular[2]); + datagram.add_be_float32(_specular[3]); + datagram.add_be_int32(_light_type); + datagram.pad_bytes(4*10); + datagram.add_be_float32(_exponential_dropoff); + datagram.add_be_float32(_cutoff_angle); + datagram.add_be_float32(_yaw); + datagram.add_be_float32(_pitch); + datagram.add_be_float32(_constant_coefficient); + datagram.add_be_float32(_linear_coefficient); + datagram.add_be_float32(_quadratic_coefficient); + datagram.add_be_int32(_modeling_light); + datagram.pad_bytes(4*19); + + return true; +} diff --git a/pandatool/src/flt/fltLightSourceDefinition.h b/pandatool/src/flt/fltLightSourceDefinition.h new file mode 100644 index 0000000000..0eeba9ae83 --- /dev/null +++ b/pandatool/src/flt/fltLightSourceDefinition.h @@ -0,0 +1,77 @@ +// Filename: fltLightSourceDefinition.h +// Created by: drose (26Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTLIGHTSOURCEDEFINITION_H +#define FLTLIGHTSOURCEDEFINITION_H + +#include + +#include "fltRecord.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltLightSourceDefinition +// Description : Represents a single entry in the light source +// palette. This completely defines the color, etc. of +// a single light source, which may be referenced later +// by a FltLightSource bead in the hierarchy. +//////////////////////////////////////////////////////////////////// +class FltLightSourceDefinition : public FltRecord { +public: + FltLightSourceDefinition(FltHeader *header); + + enum LightType { + LT_infinite = 0, + LT_local = 1, + LT_spot = 2 + }; + + int _light_index; + string _light_name; + Colorf _ambient; + Colorf _diffuse; + Colorf _specular; + LightType _light_type; + float _exponential_dropoff; + float _cutoff_angle; // in degrees + + // yaw and pitch only for modeling lights, which are positioned at + // the eyepoint. + float _yaw; + float _pitch; + + float _constant_coefficient; + float _linear_coefficient; + float _quadratic_coefficient; + bool _modeling_light; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltLightSourceDefinition", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#endif + + diff --git a/pandatool/src/flt/fltMaterial.cxx b/pandatool/src/flt/fltMaterial.cxx new file mode 100644 index 0000000000..81a7bdb170 --- /dev/null +++ b/pandatool/src/flt/fltMaterial.cxx @@ -0,0 +1,106 @@ +// Filename: fltMaterial.cxx +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltMaterial.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltMaterial::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltMaterial::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltMaterial:: +FltMaterial(FltHeader *header) : FltRecord(header) { + _material_index = 0; + _flags = 0; + _ambient.set(0.0, 0.0, 0.0); + _diffuse.set(0.0, 0.0, 0.0); + _specular.set(0.0, 0.0, 0.0); + _emissive.set(0.0, 0.0, 0.0); + _shininess = 0.0; + _alpha = 1.0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltMaterial::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltMaterial:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_15_material, false); + DatagramIterator &iterator = reader.get_iterator(); + + _material_index = iterator.get_be_int32(); + _material_name = iterator.get_fixed_string(12); + _flags = iterator.get_be_uint32(); + _ambient[0] = iterator.get_be_float32(); + _ambient[1] = iterator.get_be_float32(); + _ambient[2] = iterator.get_be_float32(); + _diffuse[0] = iterator.get_be_float32(); + _diffuse[1] = iterator.get_be_float32(); + _diffuse[2] = iterator.get_be_float32(); + _specular[0] = iterator.get_be_float32(); + _specular[1] = iterator.get_be_float32(); + _specular[2] = iterator.get_be_float32(); + _emissive[0] = iterator.get_be_float32(); + _emissive[1] = iterator.get_be_float32(); + _emissive[2] = iterator.get_be_float32(); + _shininess = iterator.get_be_float32(); + _alpha = iterator.get_be_float32(); + iterator.skip_bytes(4); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltMaterial::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltMaterial:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_15_material); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int32(_material_index); + datagram.add_fixed_string(_material_name, 12); + datagram.add_be_uint32(_flags); + datagram.add_be_float32(_ambient[0]); + datagram.add_be_float32(_ambient[1]); + datagram.add_be_float32(_ambient[2]); + datagram.add_be_float32(_diffuse[0]); + datagram.add_be_float32(_diffuse[1]); + datagram.add_be_float32(_diffuse[2]); + datagram.add_be_float32(_specular[0]); + datagram.add_be_float32(_specular[1]); + datagram.add_be_float32(_specular[2]); + datagram.add_be_float32(_emissive[0]); + datagram.add_be_float32(_emissive[1]); + datagram.add_be_float32(_emissive[2]); + datagram.add_be_float32(_shininess); + datagram.add_be_float32(_alpha); + datagram.pad_bytes(4); + + return true; +} diff --git a/pandatool/src/flt/fltMaterial.h b/pandatool/src/flt/fltMaterial.h new file mode 100644 index 0000000000..aaa1974451 --- /dev/null +++ b/pandatool/src/flt/fltMaterial.h @@ -0,0 +1,63 @@ +// Filename: fltMaterial.h +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTMATERIAL_H +#define FLTMATERIAL_H + +#include + +#include "fltRecord.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltMaterial +// Description : Represents a single material in the material palette. +//////////////////////////////////////////////////////////////////// +class FltMaterial : public FltRecord { +public: + FltMaterial(FltHeader *header); + + enum Flags { + F_materials_used = 0x80000000, + }; + + int _material_index; + string _material_name; + unsigned int _flags; + RGBColorf _ambient; + RGBColorf _diffuse; + RGBColorf _specular; + RGBColorf _emissive; + float _shininess; + float _alpha; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltMaterial", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#endif + + diff --git a/pandatool/src/flt/fltObject.cxx b/pandatool/src/flt/fltObject.cxx new file mode 100644 index 0000000000..1056845daa --- /dev/null +++ b/pandatool/src/flt/fltObject.cxx @@ -0,0 +1,76 @@ +// Filename: fltObject.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltObject.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltObject::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltObject::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltObject:: +FltObject(FltHeader *header) : FltBeadID(header) { +} + +//////////////////////////////////////////////////////////////////// +// Function: FltObject::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltObject:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_object, false); + DatagramIterator &iterator = reader.get_iterator(); + + _flags = iterator.get_be_uint32(); + _relative_priority = iterator.get_be_int16(); + _transparency = iterator.get_be_int16(); + _special_id1 = iterator.get_be_int16(); + _special_id2 = iterator.get_be_int16(); + _significance = iterator.get_be_int16(); + iterator.skip_bytes(2); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltObject::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltObject:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_object); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_uint32(_flags); + datagram.add_be_int16(_relative_priority); + datagram.add_be_int16(_transparency); + datagram.add_be_int16(_special_id1); + datagram.add_be_int16(_special_id2); + datagram.add_be_int16(_significance); + datagram.pad_bytes(2); + + return true; +} diff --git a/pandatool/src/flt/fltObject.h b/pandatool/src/flt/fltObject.h new file mode 100644 index 0000000000..1f19d3d281 --- /dev/null +++ b/pandatool/src/flt/fltObject.h @@ -0,0 +1,60 @@ +// Filename: fltObject.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTOBJECT_H +#define FLTOBJECT_H + +#include + +#include "fltBeadID.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltObject +// Description : The main objecting bead of the flt file. +//////////////////////////////////////////////////////////////////// +class FltObject : public FltBeadID { +public: + FltObject(FltHeader *header); + + enum Flags { + F_no_daylight = 0x80000000, + F_no_dusk = 0x40000000, + F_no_night = 0x20000000, + F_no_illuminate = 0x10000000, + F_flat_shaded = 0x08000000, + F_shadow_object = 0x04000000, + }; + + unsigned int _flags; + int _relative_priority; + int _transparency; + int _special_id1, _special_id2; + int _significance; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltObject", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltOpcode.cxx b/pandatool/src/flt/fltOpcode.cxx new file mode 100644 index 0000000000..c963d7492e --- /dev/null +++ b/pandatool/src/flt/fltOpcode.cxx @@ -0,0 +1,253 @@ +// Filename: fltOpcode.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltOpcode.h" + +ostream & +operator << (ostream &out, FltOpcode opcode) { + switch (opcode) { + case FO_none: + return out << "null opcode"; + + case FO_header: + return out << "header"; + + case FO_group: + return out << "group"; + + case FO_OB_scale: + case FO_OB_scale2: + case FO_OB_scale3: + return out << "(obsolete) scale"; + + case FO_object: + return out << "object"; + + case FO_face: + return out << "face"; + + case FO_OB_vertex_i: + return out << "(obsolete) vertex with ID"; + + case FO_OB_short_vertex: + return out << "(obsolete) short vertex"; + + case FO_OB_vertex_c: + return out << "(obsolete) vertex with color"; + + case FO_OB_vertex_cn: + return out << "(obsolete) vertex with color and normal"; + + case FO_push: + return out << "push"; + + case FO_pop: + return out << "pop"; + + case FO_OB_translate: + case FO_OB_translate2: + case FO_OB_translate3: + return out << "(obsolete) translate"; + + case FO_OB_dof: + return out << "(obsolete) degree-of-freedom"; + + case FO_dof: + return out << "degree-of-freedom"; + + case FO_OB_instance_ref: + return out << "(obsolete) instance reference"; + + case FO_OB_instance: + return out << "(obsolete) instance definition"; + + case FO_push_face: + return out << "push subface"; + + case FO_pop_face: + return out << "pop subface"; + + case FO_comment: + return out << "comment"; + + case FO_color_palette: + return out << "color palette"; + + case FO_long_id: + return out << "long ID"; + + case FO_transform_matrix: + return out << "transformation matrix"; + + case FO_OB_rotate_point: + case FO_OB_rotate_point2: + return out << "(obsolete) rotate about point"; + + case FO_OB_rotate_edge: + return out << "(obsolete) rotate about edge"; + + case FO_OB_nu_scale: + return out << "(obsolete) non-uniform scale"; + + case FO_OB_rotate_to_point: + return out << "(obsolete) rotate to point"; + + case FO_OB_put: + return out << "(obsolete) put"; + + case FO_OB_bounding_box: + return out << "(obsolete) bounding box"; + + case FO_vector: + return out << "vector"; + + case FO_bsp: + return out << "BSP"; + + case FO_replicate: + return out << "replicate"; + + case FO_instance_ref: + return out << "instance reference"; + + case FO_instance: + return out << "instance definition"; + + case FO_external_ref: + return out << "external reference"; + + case FO_texture: + return out << "texture"; + + case FO_OB_eyepoint_palette: + return out << "(obsolete) eyepoint palette"; + + case FO_14_material_palette: + return out << "v14 material palette"; + + case FO_vertex_palette: + return out << "vertex palette"; + + case FO_vertex_c: + return out << "vertex with color"; + + case FO_vertex_cn: + return out << "vertex with color and normal"; + + case FO_vertex_cnu: + return out << "vertex with color, normal, and uv"; + + case FO_vertex_cu: + return out << "vertex with color and uv"; + + case FO_vertex_list: + return out << "vertex list"; + + case FO_lod: + return out << "LOD"; + + case FO_bounding_box: + return out << "bounding box"; + + case FO_rotate_about_edge: + return out << "rotate about edge"; + + case FO_translate: + return out << "translate"; + + case FO_scale: + return out << "scale"; + + case FO_rotate_about_point: + return out << "rotate about point"; + + case FO_rotate_and_scale: + return out << "rotate and/or scale"; + + case FO_put: + return out << "put"; + + case FO_eyepoint_palette: + return out << "eyepoint palette"; + + case FO_road_segment: + return out << "road segment"; + + case FO_road_zone: + return out << "road zone"; + + case FO_morph_list: + return out << "morph vertex list"; + + case FO_behavior_palette: + return out << "behavior palette"; + + case FO_sound: + return out << "sound"; + + case FO_road_path: + return out << "road path"; + + case FO_sound_palette: + return out << "sound palette"; + + case FO_general_matrix: + return out << "general matrix"; + + case FO_text: + return out << "text"; + + case FO_switch: + return out << "switch"; + + case FO_line_style: + return out << "line style"; + + case FO_clip_region: + return out << "clip region"; + + case FO_light_source: + return out << "light source"; + + case FO_light_definition: + return out << "light source definition"; + + case FO_bounding_sphere: + return out << "bounding sphere"; + + case FO_bounding_cylinder: + return out << "bounding cylinder"; + + case FO_bv_center: + return out << "bounding volume center"; + + case FO_bv_orientation: + return out << "bounding volume orientation"; + + case FO_texture_map_palette: + return out << "texture mapping palette"; + + case FO_15_material: + return out << "material"; + + case FO_color_name_palette: + return out << "color name palette"; + + case FO_cat: + return out << "continuously adaptive terrain"; + + case FO_cat_data: + return out << "CAT Data"; + + case FO_push_attribute: + return out << "push attribute"; + + case FO_pop_attribute: + return out << "pop attribute"; + + default: + return out << "unknown opcode " << (int)opcode; + } +} diff --git a/pandatool/src/flt/fltOpcode.h b/pandatool/src/flt/fltOpcode.h new file mode 100644 index 0000000000..c475367655 --- /dev/null +++ b/pandatool/src/flt/fltOpcode.h @@ -0,0 +1,101 @@ +// Filename: fltOpcode.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTOPCODE_H +#define FLTOPCODE_H + +#include + +// Known opcodes, as of the latest version of flt. +enum FltOpcode { + FO_none = 0, + FO_header = 1, + FO_group = 2, + FO_OB_scale = 3, // obsolete + FO_object = 4, + FO_face = 5, + FO_OB_vertex_i = 6, // obsolete + FO_OB_short_vertex = 7, // obsolete + FO_OB_vertex_c = 8, // obsolete + FO_OB_vertex_cn = 9, // obsolete + FO_push = 10, + FO_pop = 11, + FO_OB_translate = 12, // obsolete + FO_OB_dof = 13, // obsolete + FO_dof = 14, + FO_OB_instance_ref = 16, // obsolete + FO_OB_instance = 17, // obsolete + FO_push_face = 19, + FO_pop_face = 20, + FO_comment = 31, + FO_color_palette = 32, + FO_long_id = 33, + FO_OB_translate2 = 40, // obsolete + FO_OB_rotate_point = 41, // obsolete + FO_OB_rotate_edge = 42, // obsolete + FO_OB_scale2 = 43, // obsolete + FO_OB_translate3 = 44, // obsolete + FO_OB_nu_scale = 45, // obsolete + FO_OB_rotate_point2 = 46, // obsolete + FO_OB_rotate_to_point = 47, // obsolete + FO_OB_put = 48, // obsolete + FO_transform_matrix = 49, + FO_vector = 50, + FO_OB_bounding_box = 51, // obsolete + FO_bsp = 55, + FO_replicate = 60, + FO_instance_ref = 61, + FO_instance = 62, + FO_external_ref = 63, + FO_texture = 64, + FO_OB_eyepoint_palette = 65, // obsolete + FO_14_material_palette = 66, + FO_vertex_palette = 67, + FO_vertex_c = 68, + FO_vertex_cn = 69, + FO_vertex_cnu = 70, + FO_vertex_cu = 71, + FO_vertex_list = 72, + FO_lod = 73, + FO_bounding_box = 74, + FO_rotate_about_edge = 76, + FO_OB_scale3 = 77, // obsolete + FO_translate = 78, + FO_scale = 79, + FO_rotate_about_point = 80, + FO_rotate_and_scale = 81, + FO_put = 82, + FO_eyepoint_palette = 83, + FO_road_segment = 87, + FO_road_zone = 88, + FO_morph_list = 89, + FO_behavior_palette = 90, + FO_sound = 91, + FO_road_path = 92, + FO_sound_palette = 93, + FO_general_matrix = 94, + FO_text = 95, + FO_switch = 96, + FO_line_style = 97, + FO_clip_region = 98, + FO_light_source = 101, + FO_light_definition = 102, + FO_bounding_sphere = 105, + FO_bounding_cylinder = 106, + FO_bv_center = 108, + FO_bv_orientation = 109, + FO_texture_map_palette = 112, + FO_15_material = 113, + FO_color_name_palette = 114, + FO_cat = 115, + FO_cat_data = 116, + FO_push_attribute = 122, + FO_pop_attribute = 123, +}; + +ostream &operator << (ostream &out, FltOpcode opcode); + +#endif + diff --git a/pandatool/src/flt/fltPackedColor.I b/pandatool/src/flt/fltPackedColor.I new file mode 100644 index 0000000000..74f636551a --- /dev/null +++ b/pandatool/src/flt/fltPackedColor.I @@ -0,0 +1,47 @@ +// Filename: fltPackedColor.I +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +INLINE ostream & +operator << (ostream &out, const FltPackedColor &color) { + color.output(out); + return out; +} + + +//////////////////////////////////////////////////////////////////// +// Function: FltPackedColor::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE FltPackedColor:: +FltPackedColor() { + _a = 0; + _b = 0; + _g = 0; + _r = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltPackedColor::get_color +// Access: Public +// Description: Returns the four-component color as a Colorf, where +// each component is in the range [0, 1]. +//////////////////////////////////////////////////////////////////// +INLINE Colorf FltPackedColor:: +get_color() const { + return Colorf(_r / 255.0, _g / 255.0, _b / 255.0, _a / 255.0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltPackedColor::get_rgb +// Access: Public +// Description: Returns the three-component color as an RGBColorf +// (ignoring the alpha component), where each component +// is in the range [0, 1]. +//////////////////////////////////////////////////////////////////// +INLINE RGBColorf FltPackedColor:: +get_rgb() const { + return RGBColorf(_r / 255.0, _g / 255.0, _b / 255.0); +} diff --git a/pandatool/src/flt/fltPackedColor.cxx b/pandatool/src/flt/fltPackedColor.cxx new file mode 100644 index 0000000000..33d1eee1f2 --- /dev/null +++ b/pandatool/src/flt/fltPackedColor.cxx @@ -0,0 +1,52 @@ +// Filename: fltPackedColor.cxx +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltPackedColor.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +//////////////////////////////////////////////////////////////////// +// Function: FltPackedColor::output +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void FltPackedColor:: +output(ostream &out) const { + out << "(" << _r << " " << _g << " " << _b << " " << _a << ")"; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltPackedColor::extract_record +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +bool FltPackedColor:: +extract_record(FltRecordReader &reader) { + DatagramIterator &iterator = reader.get_iterator(); + + _a = iterator.get_uint8(); + _g = iterator.get_uint8(); + _b = iterator.get_uint8(); + _r = iterator.get_uint8(); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltPackedColor::build_record +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +bool FltPackedColor:: +build_record(FltRecordWriter &writer) const { + Datagram &datagram = writer.update_datagram(); + + datagram.add_uint8(_a); + datagram.add_uint8(_g); + datagram.add_uint8(_b); + datagram.add_uint8(_r); + + return true; +} diff --git a/pandatool/src/flt/fltPackedColor.h b/pandatool/src/flt/fltPackedColor.h new file mode 100644 index 0000000000..c28ea783f8 --- /dev/null +++ b/pandatool/src/flt/fltPackedColor.h @@ -0,0 +1,46 @@ +// Filename: fltPackedColor.h +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTPACKEDCOLOR_H +#define FLTPACKEDCOLOR_H + +#include + +#include + +class FltRecordReader; +class FltRecordWriter; + +//////////////////////////////////////////////////////////////////// +// Class : FltPackedColor +// Description : A packed color record, A, B, G, R. This appears, for +// instance, within a face bead. +//////////////////////////////////////////////////////////////////// +class FltPackedColor { +public: + INLINE FltPackedColor(); + + INLINE Colorf get_color() const; + INLINE RGBColorf get_rgb() const; + + void output(ostream &out) const; + bool extract_record(FltRecordReader &reader); + bool build_record(FltRecordWriter &writer) const; + +public: + int _a; + int _b; + int _g; + int _r; +}; + +INLINE ostream &operator << (ostream &out, const FltPackedColor &color); + +#include "fltPackedColor.I" + +#endif + + + diff --git a/pandatool/src/flt/fltRecord.I b/pandatool/src/flt/fltRecord.I new file mode 100644 index 0000000000..c373946f3f --- /dev/null +++ b/pandatool/src/flt/fltRecord.I @@ -0,0 +1,10 @@ +// Filename: fltRecord.I +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +INLINE ostream & +operator << (ostream &out, const FltRecord &record) { + record.output(out); + return out; +} diff --git a/pandatool/src/flt/fltRecord.cxx b/pandatool/src/flt/fltRecord.cxx new file mode 100644 index 0000000000..9c9ddf3762 --- /dev/null +++ b/pandatool/src/flt/fltRecord.cxx @@ -0,0 +1,648 @@ +// Filename: fltRecord.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltRecord.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltGroup.h" +#include "fltObject.h" +#include "fltFace.h" +#include "fltVertexList.h" +#include "fltLOD.h" +#include "fltInstanceDefinition.h" +#include "fltInstanceRef.h" +#include "fltUnsupportedRecord.h" +#include "fltExternalReference.h" + +#include + +TypeHandle FltRecord::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltRecord:: +FltRecord(FltHeader *header) : + _header(header) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +FltRecord:: +~FltRecord() { +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::get_num_children +// Access: Public +// Description: Returns the number of child records of this record. This +// reflects the normal scene graph hierarchy. +//////////////////////////////////////////////////////////////////// +int FltRecord:: +get_num_children() const { + return _children.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::get_child +// Access: Public +// Description: Returns the nth child of this record. +//////////////////////////////////////////////////////////////////// +FltRecord *FltRecord:: +get_child(int n) const { + nassertr(n >= 0 && n < (int)_children.size(), (FltRecord *)NULL); + return _children[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::clear_children +// Access: Public +// Description: Removes all children from this record. +//////////////////////////////////////////////////////////////////// +void FltRecord:: +clear_children() { + _children.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::add_child +// Access: Public +// Description: Adds a new child to the end of the list of children +// for this record. +//////////////////////////////////////////////////////////////////// +void FltRecord:: +add_child(FltRecord *child) { + _children.push_back(child); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::get_num_subfaces +// Access: Public +// Description: Returns the number of subface records of this record. +// Normally, subfaces will only be present on object +// records, although it is logically possible for them to +// appear anywhere. +//////////////////////////////////////////////////////////////////// +int FltRecord:: +get_num_subfaces() const { + return _subfaces.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::get_subface +// Access: Public +// Description: Returns the nth subface of this record. +//////////////////////////////////////////////////////////////////// +FltRecord *FltRecord:: +get_subface(int n) const { + nassertr(n >= 0 && n < (int)_subfaces.size(), (FltRecord *)NULL); + return _subfaces[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::clear_subfaces +// Access: Public +// Description: Removes all subfaces from this record. +//////////////////////////////////////////////////////////////////// +void FltRecord:: +clear_subfaces() { + _subfaces.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::add_subface +// Access: Public +// Description: Adds a new subface to the end of the list of subfaces +// for this record. +//////////////////////////////////////////////////////////////////// +void FltRecord:: +add_subface(FltRecord *subface) { + _subfaces.push_back(subface); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::get_num_ancillary +// Access: Public +// Description: Returns the number of unsupported ancillary records +// of this record. These are ancillary records that +// appeared following this record in the flt file but that +// aren't directly understood by the flt +// loader--normally, an ancillary record is examined and +// decoded on the spot, and no pointer to it is kept. +//////////////////////////////////////////////////////////////////// +int FltRecord:: +get_num_ancillary() const { + return _ancillary.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::get_ancillary +// Access: Public +// Description: Returns the nth unsupported ancillary record of this +// record. See get_num_ancillary(). +//////////////////////////////////////////////////////////////////// +FltRecord *FltRecord:: +get_ancillary(int n) const { + nassertr(n >= 0 && n < (int)_ancillary.size(), (FltRecord *)NULL); + return _ancillary[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::clear_ancillary +// Access: Public +// Description: Removes all unsupported ancillary records from this +// record. See get_num_ancillary(). +//////////////////////////////////////////////////////////////////// +void FltRecord:: +clear_ancillary() { + _ancillary.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::add_ancillary +// Access: Public +// Description: Adds a new unsupported ancillary record to the end of +// the list of ancillary records for this record. This +// record will be written to the flt file following this +// record, without attempting to understand what is in it. +// +// Normally, there is no reason to use this function; if +// the data stored in the FltRecord requires one or more +// ancillary record, the appropriate records will +// automatically be generated when the record is written. +// This function is only required to output a record +// whose type is not supported by the flt loader. But +// it would be better to extend the flt loader to know +// about this new kind of data record. +//////////////////////////////////////////////////////////////////// +void FltRecord:: +add_ancillary(FltRecord *ancillary) { + _ancillary.push_back(ancillary); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::has_comment +// Access: Public +// Description: Returns true if this record has a nonempty comment, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool FltRecord:: +has_comment() const { + return !_comment.empty(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::get_comment +// Access: Public +// Description: Retrieves the comment for this record. +//////////////////////////////////////////////////////////////////// +const string &FltRecord:: +get_comment() const { + return _comment; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::clear_comment +// Access: Public +// Description: Removes the comment for this record. +//////////////////////////////////////////////////////////////////// +void FltRecord:: +clear_comment() { + _comment = ""; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::set_comment +// Access: Public +// Description: Changes the comment for this record. +//////////////////////////////////////////////////////////////////// +void FltRecord:: +set_comment(const string &comment) { + _comment = comment; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::output +// Access: Public +// Description: Writes a quick one-line description of the record, but +// not its children. This is a human-readable +// description, primarily for debugging; to write a flt +// file, use FltHeader::write_flt(). +//////////////////////////////////////////////////////////////////// +void FltRecord:: +output(ostream &out) const { + out << get_type(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::write +// Access: Public +// Description: Writes a multiple-line description of the record and +// all of its children. This is a human-readable +// description, primarily for debugging; to write a flt +// file, use FltHeader::write_flt(). +//////////////////////////////////////////////////////////////////// +void FltRecord:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) << *this; + write_children(out, indent_level); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::write_children +// Access: Protected +// Description: Assuming the current write position has been left at +// the end of the last line of the record description, +// writes out the list of children. +//////////////////////////////////////////////////////////////////// +void FltRecord:: +write_children(ostream &out, int indent_level) const { + if (!_ancillary.empty()) { + out << " + " << _ancillary.size() << " ancillary"; + } + if (!_subfaces.empty()) { + out << " ["; + Records::const_iterator ci; + for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) { + out << " " << *(*ci); + } + out << " ]"; + } + if (!_children.empty()) { + out << " {\n"; + Records::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->write(out, indent_level + 2); + } + indent(out, indent_level) << "}\n"; + } else { + out << "\n"; + } +} + + /* + virtual void write(ostream &out) const; + virtual void build_record(Datagram &datagram) const; + */ + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::is_ancillary +// Access: Protected, Static +// Description: Returns true if the indicated opcode corresponds to +// an ancillary record type, false otherwise. In +// general, this function is used to identify ancillary +// records that are not presently supported by the +// FltReader; these will be ignored. Normally, +// ancillary records will be detected and processed by +// extract_ancillary(). +//////////////////////////////////////////////////////////////////// +bool FltRecord:: +is_ancillary(FltOpcode opcode) { + switch (opcode) { + case FO_comment: + case FO_long_id: + case FO_replicate: + case FO_road_zone: + case FO_transform_matrix: + case FO_rotate_about_edge: + case FO_translate: + case FO_scale: + case FO_rotate_about_point: + case FO_rotate_and_scale: + case FO_put: + case FO_general_matrix: + case FO_vector: + case FO_bounding_box: + case FO_bounding_sphere: + case FO_bounding_cylinder: + case FO_bv_center: + case FO_bv_orientation: + case FO_vertex_palette: + case FO_vertex_c: + case FO_vertex_cn: + case FO_vertex_cnu: + case FO_vertex_cu: + case FO_color_palette: + case FO_color_name_palette: + case FO_15_material: + case FO_texture: + case FO_eyepoint_palette: + case FO_light_definition: + return true; + + case FO_header: + case FO_group: + case FO_object: + case FO_face: + case FO_dof: + case FO_vertex_list: + case FO_morph_list: + case FO_bsp: + case FO_external_ref: + case FO_lod: + case FO_sound: + case FO_light_source: + case FO_road_segment: + case FO_road_path: + case FO_clip_region: + case FO_text: + case FO_switch: + return false; + + case FO_push: + case FO_pop: + case FO_push_face: + case FO_pop_face: + case FO_push_attribute: + case FO_pop_attribute: + case FO_instance: + case FO_instance_ref: + return false; + + default: + nout << "Don't know whether " << opcode << " is ancillary.\n"; + return false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::create_new_record +// Access: Protected +// Description: Creates a new FltRecord corresponding to the opcode. +// If the opcode is unknown, creates a +// FltUnsupportedRecord. +//////////////////////////////////////////////////////////////////// +FltRecord *FltRecord:: +create_new_record(FltOpcode opcode) const { + switch (opcode) { + case FO_group: + return new FltGroup(_header); + + case FO_object: + return new FltObject(_header); + + case FO_face: + return new FltFace(_header); + + case FO_vertex_list: + return new FltVertexList(_header); + + case FO_lod: + return new FltLOD(_header); + + case FO_instance: + return new FltInstanceDefinition(_header); + + case FO_instance_ref: + return new FltInstanceRef(_header); + + case FO_external_ref: + return new FltExternalReference(_header); + + default: + nout << "Unsupported record " << opcode << "\n"; + return new FltUnsupportedRecord(_header); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::read_record_and_children +// Access: Protected +// Description: Extracts this record information from the current +// record presented in the reader, then advances the +// reader and continues to read any children, if +// present. On return, the reader is position on the +// next sibling record to this record. +// +// Returns FE_ok if successful, otherwise on error. +//////////////////////////////////////////////////////////////////// +FltError FltRecord:: +read_record_and_children(FltRecordReader &reader) { + if (!extract_record(reader)) { + nout << "Could not extract record for " << *this << "\n"; + return FE_invalid_record; + } + FltError result = reader.advance(); + if (result == FE_end_of_file) { + return FE_ok; + } else if (result != FE_ok) { + return result; + } + + while (true) { + if (extract_ancillary(reader)) { + // Ok, a known ancillary record. Fine. + + } else if (reader.get_opcode() == FO_push) { + // A push begins a new list of children. + result = reader.advance(); + if (result != FE_ok) { + return result; + } + + while (reader.get_opcode() != FO_pop) { + PT(FltRecord) child = create_new_record(reader.get_opcode()); + FltError result = child->read_record_and_children(reader); + if (result != FE_ok) { + return result; + } + + if (child->is_of_type(FltInstanceDefinition::get_class_type())) { + // A special case for an instance definition. These + // shouldn't appear in the hierarchy, but should instead be + // added directly to the header. + _header->add_instance(DCAST(FltInstanceDefinition, child)); + + } else { + add_child(child); + } + + if (reader.eof() || reader.error()) { + return FE_end_of_file; + } + } + + } else if (reader.get_opcode() == FO_push_face) { + // A push subface begins a new list of subfaces. + result = reader.advance(); + if (result != FE_ok) { + return result; + } + + while (reader.get_opcode() != FO_pop_face) { + PT(FltRecord) subface = create_new_record(reader.get_opcode()); + FltError result = subface->read_record_and_children(reader); + if (result != FE_ok) { + return result; + } + add_subface(subface); + if (reader.eof() || reader.error()) { + return FE_end_of_file; + } + } + + } else if (is_ancillary(reader.get_opcode())) { + // An unsupported ancillary record. Skip it. + PT(FltRecord) ancillary = create_new_record(reader.get_opcode()); + ancillary->extract_record(reader); + _ancillary.push_back(ancillary); + + } else { + // None of the above: we're done. + return FE_ok; + } + + // Skip to the next record. If that's the end, fine. + result = reader.advance(); + if (result == FE_end_of_file) { + return FE_ok; + } else if (result != FE_ok) { + return result; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltRecord:: +extract_record(FltRecordReader &) { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::extract_ancillary +// Access: Protected, Virtual +// Description: Checks whether the given record, which follows this +// record sequentially in the file, is an ancillary record +// of this record. If it is, extracts the relevant +// information and returns true; otherwise, leaves it +// alone and returns false. +//////////////////////////////////////////////////////////////////// +bool FltRecord:: +extract_ancillary(FltRecordReader &reader) { + if (reader.get_opcode() == FO_comment) { + _comment = reader.get_iterator().get_remaining_bytes(); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::write_record_and_children +// Access: Protected, Virtual +// Description: Writes this record out to the flt file, along with all +// of its ancillary records and children records. Returns +// FE_ok on success, or something else on error. +//////////////////////////////////////////////////////////////////// +FltError FltRecord:: +write_record_and_children(FltRecordWriter &writer) const { + // First, write the record. + if (!build_record(writer)) { + return FE_bad_data; + } + + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + + // Then the ancillary data. + result = write_ancillary(writer); + if (result != FE_ok) { + return result; + } + Records::const_iterator ci; + for (ci = _ancillary.begin(); ci != _ancillary.end(); ++ci) { + if (!(*ci)->build_record(writer)) { + return FE_bad_data; + } + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + // Any subfaces? + if (!_subfaces.empty()) { + result = writer.write_record(FO_push_face); + if (result != FE_ok) { + return result; + } + + for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) { + (*ci)->write_record_and_children(writer); + } + + result = writer.write_record(FO_pop_face); + if (result != FE_ok) { + return result; + } + } + + // Finally, write all the children. + if (!_children.empty()) { + result = writer.write_record(FO_push); + if (result != FE_ok) { + return result; + } + + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->write_record_and_children(writer); + } + + result = writer.write_record(FO_pop); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltRecord:: +build_record(FltRecordWriter &) const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecord::write_ancillary +// Access: Protected, Virtual +// Description: Writes whatever ancillary records are required for +// this record. Returns FE_ok on success, or something +// else if there is some error. +//////////////////////////////////////////////////////////////////// +FltError FltRecord:: +write_ancillary(FltRecordWriter &writer) const { + if (!_comment.empty()) { + Datagram dc(_comment); + FltError result = writer.write_record(FO_comment, dc); + if (result != FE_ok) { + return result; + } + } + return FE_ok; +} diff --git a/pandatool/src/flt/fltRecord.h b/pandatool/src/flt/fltRecord.h new file mode 100644 index 0000000000..0f31faf2b5 --- /dev/null +++ b/pandatool/src/flt/fltRecord.h @@ -0,0 +1,108 @@ +// filename: fltRecord.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTRECORD_H +#define FLTRECORD_H + +#include + +#include "fltOpcode.h" +#include "fltError.h" + +#include +#include +#include + +class FltHeader; +class FltRecordReader; +class FltRecordWriter; + +//////////////////////////////////////////////////////////////////// +// Class : FltRecord +// Description : The base class for all kinds of records in a MultiGen +// OpenFlight file. A flt file consists of a hierarchy +// of "beads" of various kinds, each of which may be +// followed by n ancillary records, written sequentially +// to the file. +//////////////////////////////////////////////////////////////////// +class FltRecord : public TypedReferenceCount { +public: + FltRecord(FltHeader *header); + virtual ~FltRecord(); + + int get_num_children() const; + FltRecord *get_child(int n) const; + void clear_children(); + void add_child(FltRecord *child); + + int get_num_subfaces() const; + FltRecord *get_subface(int n) const; + void clear_subfaces(); + void add_subface(FltRecord *subface); + + int get_num_ancillary() const; + FltRecord *get_ancillary(int n) const; + void clear_ancillary(); + void add_ancillary(FltRecord *ancillary); + + bool has_comment() const; + const string &get_comment() const; + void clear_comment(); + void set_comment(const string &comment); + + virtual void output(ostream &out) const; + virtual void write(ostream &out, int indent_level = 0) const; + +protected: + void write_children(ostream &out, int indent_level) const; + + static bool is_ancillary(FltOpcode opcode); + + FltRecord *create_new_record(FltOpcode opcode) const; + FltError read_record_and_children(FltRecordReader &reader); + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual FltError write_record_and_children(FltRecordWriter &writer) const; + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +protected: + FltHeader *_header; + +private: + typedef vector Records; + Records _children; + Records _subfaces; + Records _ancillary; + + string _comment; + + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "FltRecord", + TypedReferenceCount::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +INLINE ostream &operator << (ostream &out, const FltRecord &record); + +#include "fltRecord.I" + +#endif + + diff --git a/pandatool/src/flt/fltRecordReader.cxx b/pandatool/src/flt/fltRecordReader.cxx new file mode 100644 index 0000000000..05b8fa9940 --- /dev/null +++ b/pandatool/src/flt/fltRecordReader.cxx @@ -0,0 +1,176 @@ +// Filename: fltRecordReader.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltRecordReader.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltRecordReader:: +FltRecordReader(istream &in) : + _in(in) +{ + _opcode = FO_none; + _record_length = 0; + _iterator = (DatagramIterator *)NULL; + _state = S_begin; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltRecordReader:: +~FltRecordReader() { + if (_iterator != (DatagramIterator *)NULL) { + delete _iterator; + _iterator = (DatagramIterator *)NULL; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::get_opcode +// Access: Public +// Description: Returns the opcode associated with the current +// record. +//////////////////////////////////////////////////////////////////// +FltOpcode FltRecordReader:: +get_opcode() const { + nassertr(_state == S_normal, FO_none); + return _opcode; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::get_iterator +// Access: Public +// Description: Returns an iterator suitable for extracting data from +// the current record. +//////////////////////////////////////////////////////////////////// +DatagramIterator &FltRecordReader:: +get_iterator() { + nassertr(_state == S_normal, *_iterator); + return *_iterator; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::get_datagram +// Access: Public +// Description: Returns the datagram representing the entire record, +// less the four-byte header. +//////////////////////////////////////////////////////////////////// +const Datagram &FltRecordReader:: +get_datagram() { +#ifndef NDEBUG + static Datagram bogus_datagram; + nassertr(_state == S_normal, bogus_datagram); +#endif + return _iterator->get_datagram(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::get_record_length +// Access: Public +// Description: Returns the entire length of the record, including +// the four-byte header. +//////////////////////////////////////////////////////////////////// +int FltRecordReader:: +get_record_length() const { + return _record_length; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::advance +// Access: Public +// Description: Extracts the next record from the file. Returns true +// if there is another record, or false if the end of +// file has been reached. +//////////////////////////////////////////////////////////////////// +FltError FltRecordReader:: +advance() { + if (_state == S_eof) { + return FE_end_of_file; + } + if (_state == S_error) { + return FE_read_error; + } + if (_iterator != (DatagramIterator *)NULL) { + delete _iterator; + _iterator = (DatagramIterator *)NULL; + } + + // Get the first four bytes of the record. This will be the opcode + // and length. + static const int header_size = 4; + char bytes[header_size]; + _in.read(bytes, header_size); + + if (_in.eof()) { + _state = S_eof; + return FE_end_of_file; + + } else if (_in.fail()) { + _state = S_error; + return FE_read_error; + } + + // Now extract out the opcode and length. + Datagram dg(bytes, header_size); + DatagramIterator dgi(dg); + _opcode = (FltOpcode)dgi.get_be_int16(); + _record_length = dgi.get_be_uint16(); + + // cerr << "Reading " << _opcode << " of length " << _record_length << "\n"; + + // And now read the full record based on the length. + int length = _record_length - header_size; + char *buffer = new char[length]; + _in.read(buffer, length); + _datagram = Datagram(buffer, length); + delete[] buffer; + + if (_in.eof()) { + _state = S_eof; + return FE_end_of_file; + } + + if (_in.fail()) { + _state = S_error; + return FE_read_error; + } + + // Finally, create a new iterator to read this record. + _iterator = new DatagramIterator(_datagram); + _state = S_normal; + + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::eof +// Access: Public +// Description: Returns true if end-of-file has been reached without +// error. +//////////////////////////////////////////////////////////////////// +bool FltRecordReader:: +eof() const { + return _state == S_eof; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordReader::error +// Access: Public +// Description: Returns true if some error has been encountered while +// reading (for instance, a truncated file). +//////////////////////////////////////////////////////////////////// +bool FltRecordReader:: +error() const { + return _state == S_error; +} + diff --git a/pandatool/src/flt/fltRecordReader.h b/pandatool/src/flt/fltRecordReader.h new file mode 100644 index 0000000000..91783b2bcc --- /dev/null +++ b/pandatool/src/flt/fltRecordReader.h @@ -0,0 +1,58 @@ +// Filename: fltRecordReader.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTRECORDREADER_H +#define FLTRECORDREADER_H + +#include + +#include "fltOpcode.h" +#include "fltError.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltRecordReader +// Description : This class turns an istream into a sequence of +// FltRecords by reading a sequence of Datagrams and +// extracting the opcode from each one. It remembers +// where it is in the file and what the current record +// is. +//////////////////////////////////////////////////////////////////// +class FltRecordReader { +public: + FltRecordReader(istream &in); + ~FltRecordReader(); + + FltOpcode get_opcode() const; + DatagramIterator &get_iterator(); + const Datagram &get_datagram(); + int get_record_length() const; + + FltError advance(); + + bool eof() const; + bool error() const; + +private: + istream &_in; + Datagram _datagram; + FltOpcode _opcode; + int _record_length; + DatagramIterator *_iterator; + + enum State { + S_begin, + S_normal, + S_eof, + S_error + }; + State _state; +}; + +#endif + + diff --git a/pandatool/src/flt/fltRecordWriter.cxx b/pandatool/src/flt/fltRecordWriter.cxx new file mode 100644 index 0000000000..bc551fd85f --- /dev/null +++ b/pandatool/src/flt/fltRecordWriter.cxx @@ -0,0 +1,140 @@ +// Filename: fltRecordWriter.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltRecordWriter.h" +#include "fltInstanceDefinition.h" +#include "fltHeader.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordWriter::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltRecordWriter:: +FltRecordWriter(ostream &out) : + _out(out) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordWriter::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltRecordWriter:: +~FltRecordWriter() { +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordWriter::set_opcode +// Access: Public +// Description: Sets the opcode associated with the current record. +//////////////////////////////////////////////////////////////////// +void FltRecordWriter:: +set_opcode(FltOpcode opcode) { + _opcode = opcode; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordWriter::set_datagram +// Access: Public +// Description: Sets the datagram that will be written when advance() +// is called. +//////////////////////////////////////////////////////////////////// +void FltRecordWriter:: +set_datagram(const Datagram &datagram) { + _datagram = datagram; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordWriter::update_datagram +// Access: Public +// Description: Returns a modifiable reference to the datagram +// associated with the current record. This datagram +// should then be stuffed with data corresponding to the +// data in the record, in preparation for calling +// advance() to write the data. +//////////////////////////////////////////////////////////////////// +Datagram &FltRecordWriter:: +update_datagram() { + return _datagram; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordWriter::advance +// Access: Public +// Description: Writes the current record to the flt file, and resets +// the current record to receive new data. Returns +// FE_ok on success, or something else on error. +//////////////////////////////////////////////////////////////////// +FltError FltRecordWriter:: +advance() { + // cerr << "Writing " << _opcode << " of length " << _datagram.get_length() << "\n"; + + // Build a mini-datagram to write the header. + static const int header_size = 4; + + Datagram dg; + dg.add_be_int16(_opcode); + dg.add_be_int16(_datagram.get_length() + header_size); + + nassertr(dg.get_length() == header_size, FE_internal); + + _out.write(dg.get_message().data(), dg.get_length()); + if (_out.fail()) { + return FE_write_error; + } + + // Now write the rest of the record. + _out.write(_datagram.get_message().data(), _datagram.get_length()); + if (_out.fail()) { + return FE_write_error; + } + + _datagram.clear(); + _opcode = FO_none; + + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordWriter::write_record +// Access: Public +// Description: A convenience function to quickly write a simple +// record that consists of an opcode and possibly a +// datagram. +//////////////////////////////////////////////////////////////////// +FltError FltRecordWriter:: +write_record(FltOpcode opcode, const Datagram &datagram) { + _opcode = opcode; + _datagram = datagram; + return advance(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltRecordWriter::write_instance_def +// Access: Public +// Description: Ensures that the given instance definition has +// already been written to the file. If it has not, +// writes it now. +//////////////////////////////////////////////////////////////////// +FltError FltRecordWriter:: +write_instance_def(FltHeader *header, int instance_index) { + bool inserted = _instances_written.insert(instance_index).second; + + if (!inserted) { + // It's already been written. + return FE_ok; + } + + FltInstanceDefinition *instance = header->get_instance(instance_index); + if (instance == (FltInstanceDefinition *)NULL) { + return FE_undefined_instance; + } + + return instance->write_record_and_children(*this); +} diff --git a/pandatool/src/flt/fltRecordWriter.h b/pandatool/src/flt/fltRecordWriter.h new file mode 100644 index 0000000000..4c26d7cc37 --- /dev/null +++ b/pandatool/src/flt/fltRecordWriter.h @@ -0,0 +1,51 @@ +// Filename: fltRecordWriter.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTRECORDWRITER_H +#define FLTRECORDWRITER_H + +#include + +#include "fltOpcode.h" +#include "fltError.h" + +#include + +class FltHeader; + +//////////////////////////////////////////////////////////////////// +// Class : FltRecordWriter +// Description : This class writes a sequence of FltRecords to an +// ostream, handling opcode and size counts properly. +//////////////////////////////////////////////////////////////////// +class FltRecordWriter { +public: + FltRecordWriter(ostream &out); + ~FltRecordWriter(); + + void set_opcode(FltOpcode opcode); + const Datagram &get_datagram() const; + void set_datagram(const Datagram &datagram); + Datagram &update_datagram(); + + FltError advance(); + + FltError write_record(FltOpcode opcode, + const Datagram &datagram = Datagram()); + + FltError write_instance_def(FltHeader *header, int instance_index); + +private: + ostream &_out; + Datagram _datagram; + FltOpcode _opcode; + + typedef set Instances; + Instances _instances_written; +}; + +#endif + + diff --git a/pandatool/src/flt/fltTexture.cxx b/pandatool/src/flt/fltTexture.cxx new file mode 100644 index 0000000000..6575633547 --- /dev/null +++ b/pandatool/src/flt/fltTexture.cxx @@ -0,0 +1,431 @@ +// Filename: fltTexture.cxx +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTexture.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" + +TypeHandle FltTexture::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTexture:: +FltTexture(FltHeader *header) : FltRecord(header) { + _pattern_index = 0; + _x_location = 0; + _y_location = 0; + + _num_texels_u = 0; + _num_texels_v = 0; + _real_world_size_u = 0; + _real_world_size_v = 0; + _up_vector_x = 0; + _up_vector_y = 1; + _file_format = FF_none; + _min_filter = MN_point; + _mag_filter = MG_point; + _repeat = RT_repeat; + _repeat_u = RT_repeat; + _repeat_v = RT_repeat; + _modify_flag = 0; + _x_pivot_point = 0; + _y_pivot_point = 0; + _env_type = ET_modulate; + _intensity_is_alpha = false; + _float_real_world_size_u = 0.0; + _float_real_world_size_v = 0.0; + _imported_origin_code = 0; + _kernel_version = 1520; + _internal_format = IF_default; + _external_format = EF_default; + _use_mipmap_kernel = false; + memset(_mipmap_kernel, 0, sizeof(_mipmap_kernel)); + _use_lod_scale = false; + memset(_lod_scale, 0, sizeof(_lod_scale)); + _clamp = 0.0; + _mag_filter_alpha = MG_point; + _mag_filter_color = MG_point; + _lambert_conic_central_meridian = 0.0; + _lambert_conic_upper_latitude = 0.0; + _lambert_conic_lower_latitude = 0.0; + _use_detail = false; + _detail_j = 0; + _detail_k = 0; + _detail_m = 0; + _detail_n = 0; + _detail_scramble = 0; + _use_tile = false; + _tile_lower_left_u = 0.0; + _tile_lower_left_v = 0.0; + _tile_upper_right_u = 0.0; + _tile_upper_right_v = 0.0; + _projection = PT_flat_earth; + _earth_model = EM_wgs84; + _utm_zone = 0; + _image_origin = IO_lower_left; + _geospecific_points_units = PU_degrees; + _geospecific_hemisphere = H_southern; + _file_version = 1501; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::get_texture_filename +// Access: Public +// Description: Returns the name of the texture image file. If it +// appears to be a relative filename, it will be +// converted to the correct full pathname according to +// the texture_path specified in the header. +//////////////////////////////////////////////////////////////////// +Filename FltTexture:: +get_texture_filename() const { + Filename file(_filename); + file.resolve_filename(_header->get_texture_path()); + return file; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::get_attr_filename +// Access: Public +// Description: Returns the name of the texture's associated .attr +// file. This contains some additional MultiGen +// information about the texture parameters. This is, +// of course, just the name of the texture with .attr +// appended. +// +// Normally, it won't be necessary to access this file +// directly; you can call read_attr_data() or +// write_attr_data() to get at the data stored in this +// file. (And read_attr_data() is called automatically +// when the Flt file is read in.) +//////////////////////////////////////////////////////////////////// +Filename FltTexture:: +get_attr_filename() const { + string texture_filename = get_texture_filename(); + return Filename::binary_filename(texture_filename + ".attr"); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::read_attr_data +// Access: Public +// Description: Opens up the texture's .attr file and reads its data +// into the extra FltTexture fields. This is normally +// performed automatically when the Flt file is read +// from disk. +//////////////////////////////////////////////////////////////////// +FltError FltTexture:: +read_attr_data() { + Filename attr_filename = get_attr_filename(); + + ifstream attr; + if (!attr_filename.open_read(attr)) { + return FE_could_not_open; + } + + // Determine the file's size so we can read it all into one big + // datagram. + attr.seekg(0, ios::end); + if (attr.fail()) { + return FE_read_error; + } + streampos length = attr.tellg(); + + char *buffer = new char[length]; + + attr.seekg(0, ios::beg); + attr.read(buffer, length); + if (attr.fail()) { + return FE_read_error; + } + + Datagram datagram(buffer, length); + delete[] buffer; + + return unpack_attr(datagram); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::write_attr_data +// Access: Public +// Description: Writes the texture's .attr file. This may or may +// not be performed automatically, according to the +// setting of FltHeader::set_auto_attr_update(). +//////////////////////////////////////////////////////////////////// +FltError FltTexture:: +write_attr_data() const { + Datagram datagram; + FltError result = pack_attr(datagram); + if (result != FE_ok) { + return result; + } + + Filename attr_filename = get_attr_filename(); + + ofstream attr; + if (!attr_filename.open_write(attr)) { + return FE_could_not_open; + } + + attr.write((const char *)datagram.get_data(), datagram.get_length()); + if (attr.fail()) { + return FE_write_error; + } + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltTexture:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_texture, false); + DatagramIterator &iterator = reader.get_iterator(); + + _filename = iterator.get_fixed_string(200); + _pattern_index = iterator.get_be_int32(); + _x_location = iterator.get_be_int32(); + _y_location = iterator.get_be_int32(); + + if (read_attr_data() != FE_ok) { + nout << "Unable to read attribute file " << get_attr_filename() << "\n"; + } + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltTexture:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_texture); + Datagram &datagram = writer.update_datagram(); + + datagram.add_fixed_string(_filename, 200); + datagram.add_be_int32(_pattern_index); + datagram.add_be_int32(_x_location); + datagram.add_be_int32(_y_location); + + if (_header->get_auto_attr_update() == FltHeader::AU_always || + (_header->get_auto_attr_update() == FltHeader::AU_if_missing && + !get_attr_filename().exists())) { + if (write_attr_data() != FE_ok) { + nout << "Unable to write attribute file " << get_attr_filename() << "\n"; + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::unpack_attr +// Access: Private +// Description: Reads the data from the attribute file. +//////////////////////////////////////////////////////////////////// +FltError FltTexture:: +unpack_attr(const Datagram &datagram) { + DatagramIterator iterator(datagram); + + _num_texels_u = iterator.get_be_int32(); + _num_texels_v = iterator.get_be_int32(); + _real_world_size_u = iterator.get_be_int32(); + _real_world_size_v = iterator.get_be_int32(); + _up_vector_x = iterator.get_be_int32(); + _up_vector_y = iterator.get_be_int32(); + _file_format = (FileFormat)iterator.get_be_int32(); + _min_filter = (Minification)iterator.get_be_int32(); + _mag_filter = (Magnification)iterator.get_be_int32(); + _repeat = (RepeatType)iterator.get_be_int32(); + _repeat_u = (RepeatType)iterator.get_be_int32(); + _repeat_v = (RepeatType)iterator.get_be_int32(); + _modify_flag = iterator.get_be_int32(); + _x_pivot_point = iterator.get_be_int32(); + _y_pivot_point = iterator.get_be_int32(); + _env_type = (EnvironmentType)iterator.get_be_int32(); + _intensity_is_alpha = (iterator.get_be_int32() != 0); + iterator.skip_bytes(4 * 8); + iterator.skip_bytes(4); // Undocumented padding. + _float_real_world_size_u = iterator.get_be_float64(); + _float_real_world_size_v = iterator.get_be_float64(); + _imported_origin_code = iterator.get_be_int32(); + _kernel_version = iterator.get_be_int32(); + _internal_format = (InternalFormat)iterator.get_be_int32(); + _external_format = (ExternalFormat)iterator.get_be_int32(); + _use_mipmap_kernel = (iterator.get_be_int32() != 0); + int i; + for (i = 0; i < 8; i++) { + _mipmap_kernel[i] = iterator.get_be_float32(); + } + _use_lod_scale = (iterator.get_be_int32() != 0); + for (i = 0; i < 8; i++) { + _lod_scale[i]._lod = iterator.get_be_float32(); + _lod_scale[i]._scale = iterator.get_be_float32(); + } + _clamp = iterator.get_be_float32(); + _mag_filter_alpha = (Magnification)iterator.get_be_int32(); + _mag_filter_color = (Magnification)iterator.get_be_int32(); + iterator.skip_bytes(4 + 4 * 8); + _lambert_conic_central_meridian = iterator.get_be_float64(); + _lambert_conic_upper_latitude = iterator.get_be_float64(); + _lambert_conic_lower_latitude = iterator.get_be_float64(); + iterator.skip_bytes(8 + 4 * 5); + _use_detail = (iterator.get_be_int32() != 0); + _detail_j = iterator.get_be_int32(); + _detail_k = iterator.get_be_int32(); + _detail_m = iterator.get_be_int32(); + _detail_n = iterator.get_be_int32(); + _detail_scramble = iterator.get_be_int32(); + _use_tile = (iterator.get_be_int32() != 0); + _tile_lower_left_u = iterator.get_be_float32(); + _tile_lower_left_v = iterator.get_be_float32(); + _tile_upper_right_u = iterator.get_be_float32(); + _tile_upper_right_v = iterator.get_be_float32(); + _projection = (ProjectionType)iterator.get_be_int32(); + _earth_model = (EarthModel)iterator.get_be_int32(); + iterator.skip_bytes(4); + _utm_zone = iterator.get_be_int32(); + _image_origin = (ImageOrigin)iterator.get_be_int32(); + _geospecific_points_units = (PointsUnits)iterator.get_be_int32(); + _geospecific_hemisphere = (Hemisphere)iterator.get_be_int32(); + iterator.skip_bytes(4 + 4 + 149 * 4); + iterator.skip_bytes(8); // Undocumented padding. + _comment = iterator.get_fixed_string(512); + iterator.skip_bytes(13 * 4); + iterator.skip_bytes(4); // Undocumented padding. + _file_version = iterator.get_be_int32(); + + // Now read the geospecific control points. + _geospecific_control_points.clear(); + int num_points = iterator.get_be_int32(); + if (num_points > 0) { + iterator.skip_bytes(4); + + while (num_points > 0) { + GeospecificControlPoint gcp; + gcp._uv[0] = iterator.get_be_float64(); + gcp._uv[1] = iterator.get_be_float64(); + gcp._real_earth[0] = iterator.get_be_float64(); + gcp._real_earth[1] = iterator.get_be_float64(); + } + } + + nassertr(iterator.get_remaining_size() == 0, FE_ok); + return FE_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTexture::pack_attr +// Access: Private +// Description: Packs the attribute data into a big datagram. +//////////////////////////////////////////////////////////////////// +FltError FltTexture:: +pack_attr(Datagram &datagram) const { + datagram.add_be_int32(_num_texels_u); + datagram.add_be_int32(_num_texels_v); + datagram.add_be_int32(_real_world_size_u); + datagram.add_be_int32(_real_world_size_v); + datagram.add_be_int32(_up_vector_x); + datagram.add_be_int32(_up_vector_y); + datagram.add_be_int32(_file_format); + datagram.add_be_int32(_min_filter); + datagram.add_be_int32(_mag_filter); + datagram.add_be_int32(_repeat); + datagram.add_be_int32(_repeat_u); + datagram.add_be_int32(_repeat_v); + datagram.add_be_int32(_modify_flag); + datagram.add_be_int32(_x_pivot_point); + datagram.add_be_int32(_y_pivot_point); + datagram.add_be_int32(_env_type); + datagram.add_be_int32(_intensity_is_alpha); + datagram.pad_bytes(4 * 8); + datagram.pad_bytes(4); // Undocumented padding. + datagram.add_be_float64(_float_real_world_size_u); + datagram.add_be_float64(_float_real_world_size_v); + datagram.add_be_int32(_imported_origin_code); + datagram.add_be_int32(_kernel_version); + datagram.add_be_int32(_internal_format); + datagram.add_be_int32(_external_format); + datagram.add_be_int32(_use_mipmap_kernel); + int i; + for (i = 0; i < 8; i++) { + datagram.add_be_float32(_mipmap_kernel[i]); + } + datagram.add_be_int32(_use_lod_scale); + for (i = 0; i < 8; i++) { + datagram.add_be_float32(_lod_scale[i]._lod); + datagram.add_be_float32(_lod_scale[i]._scale); + } + datagram.add_be_float32(_clamp); + datagram.add_be_int32(_mag_filter_alpha); + datagram.add_be_int32(_mag_filter_color); + datagram.pad_bytes(4 + 4 * 8); + datagram.add_be_float64(_lambert_conic_central_meridian); + datagram.add_be_float64(_lambert_conic_upper_latitude); + datagram.add_be_float64(_lambert_conic_lower_latitude); + datagram.pad_bytes(8 + 4 * 5); + datagram.add_be_int32(_use_detail); + datagram.add_be_int32(_detail_j); + datagram.add_be_int32(_detail_k); + datagram.add_be_int32(_detail_m); + datagram.add_be_int32(_detail_n); + datagram.add_be_int32(_detail_scramble); + datagram.add_be_int32(_use_tile); + datagram.add_be_float32(_tile_lower_left_u); + datagram.add_be_float32(_tile_lower_left_v); + datagram.add_be_float32(_tile_upper_right_u); + datagram.add_be_float32(_tile_upper_right_v); + datagram.add_be_int32(_projection); + datagram.add_be_int32(_earth_model); + datagram.pad_bytes(4); + datagram.add_be_int32(_utm_zone); + datagram.add_be_int32(_image_origin); + datagram.add_be_int32(_geospecific_points_units); + datagram.add_be_int32(_geospecific_hemisphere); + datagram.pad_bytes(4 + 4 + 149 * 4); + datagram.pad_bytes(8); // Undocumented padding. + datagram.add_fixed_string(_comment, 512); + datagram.pad_bytes(13 * 4); + datagram.pad_bytes(4); // Undocumented padding. + datagram.add_be_int32(_file_version); + + // Now write the geospecific control points. + datagram.add_be_int32(_geospecific_control_points.size()); + if (!_geospecific_control_points.empty()) { + datagram.pad_bytes(4); + GeospecificControlPoints::const_iterator pi; + for (pi = _geospecific_control_points.begin(); + pi != _geospecific_control_points.end(); + ++pi) { + datagram.add_be_float64((*pi)._uv[0]); + datagram.add_be_float64((*pi)._uv[1]); + datagram.add_be_float64((*pi)._real_earth[0]); + datagram.add_be_float64((*pi)._real_earth[1]); + } + } + + return FE_ok; +} diff --git a/pandatool/src/flt/fltTexture.h b/pandatool/src/flt/fltTexture.h new file mode 100644 index 0000000000..f9985e6636 --- /dev/null +++ b/pandatool/src/flt/fltTexture.h @@ -0,0 +1,232 @@ +// Filename: fltTexture.h +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTEXTURE_H +#define FLTTEXTURE_H + +#include + +#include "fltRecord.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltTexture +// Description : Represents a single texture in the texture palette. +//////////////////////////////////////////////////////////////////// +class FltTexture : public FltRecord { +public: + FltTexture(FltHeader *header); + + string _filename; + int _pattern_index; + int _x_location; + int _y_location; + + Filename get_texture_filename() const; + Filename get_attr_filename() const; + FltError read_attr_data(); + FltError write_attr_data() const; + + // The remaining fields are from the attr file. + enum FileFormat { + FF_none = -1, + FF_att_8_pattern = 0, + FF_att_8_template = 1, + FF_sgi_i = 2, + FF_sgi_ia = 3, + FF_sgi_rgb = 4, + FF_sgi_rgba = 5 + }; + + enum Minification { + MN_point = 0, + MN_bilinear = 1, + MN_OB_mipmap = 2, // obsolete + MN_mipmap_point = 3, + MN_mipmap_linear = 4, + MN_mipmap_bilinear = 5, + MN_mipmap_trilinear = 6, + MN_bicubic = 8, + MN_bilinear_gequal = 9, + MN_bilinear_lequal = 10, + MN_bicubic_gequal = 11, + MN_bicubic_lequal = 12 + }; + + enum Magnification { + MG_point = 0, + MG_bilinear = 1, + MG_bicubic = 3, + MG_sharpen = 4, + MG_add_detail = 5, + MG_modulate_detail = 6, + MG_bilinear_gequal = 7, + MG_bilinear_lequal = 8, + MG_bicubic_gequal = 9, + MG_bicubic_lequal = 10 + }; + + enum RepeatType { + RT_repeat = 0, + RT_clamp = 1 + }; + + enum EnvironmentType { + ET_modulate = 0, + ET_blend = 1, + ET_decal = 2, + ET_color = 3 + }; + + enum InternalFormat { + IF_default = 0, + IF_i_12a_4 = 1, + IF_ia_8 = 2, + IF_rgb_5 = 3, + IF_rgba_4 = 4, + IF_ia_12 = 5, + IF_rgba_8 = 6, + IF_rgba_12 = 7, + IF_i_16 = 8, // shadow mode only + IF_rgb_12 = 9 + }; + + enum ExternalFormat { + EF_default = 0, + EF_pack_8 = 1, + EF_pack_16 = 2 + }; + + enum ProjectionType { + PT_flat_earth = 0, + PT_lambert = 3, + PT_utm = 4, + PT_undefined = 7 + }; + + enum EarthModel { + EM_wgs84 = 0, + EM_wgs72 = 1, + EM_bessel = 2, + EM_clarke_1866 = 3, + EM_nad27 = 4 + }; + + enum ImageOrigin { + IO_lower_left = 0, + IO_upper_left = 1 + }; + + enum PointsUnits { + PU_degrees = 0, + PU_meters = 1, + PU_pixels = 2 + }; + + enum Hemisphere { + H_southern = 0, + H_northern = 1, + }; + + struct LODScale { + float _lod; + float _scale; + }; + + struct GeospecificControlPoint { + LPoint2d _uv; + LPoint2d _real_earth; + }; + + typedef vector GeospecificControlPoints; + + int _num_texels_u; + int _num_texels_v; + int _real_world_size_u; + int _real_world_size_v; + int _up_vector_x; + int _up_vector_y; + FileFormat _file_format; + Minification _min_filter; + Magnification _mag_filter; + RepeatType _repeat; + RepeatType _repeat_u; + RepeatType _repeat_v; + int _modify_flag; + int _x_pivot_point; + int _y_pivot_point; + EnvironmentType _env_type; + bool _intensity_is_alpha; // if true, a one-channel image is actually + // an alpha image, not an intensity image. + double _float_real_world_size_u; + double _float_real_world_size_v; + int _imported_origin_code; + int _kernel_version; + InternalFormat _internal_format; + ExternalFormat _external_format; + bool _use_mipmap_kernel; + float _mipmap_kernel[8]; + bool _use_lod_scale; + LODScale _lod_scale[8]; + float _clamp; + Magnification _mag_filter_alpha; + Magnification _mag_filter_color; + double _lambert_conic_central_meridian; + double _lambert_conic_upper_latitude; + double _lambert_conic_lower_latitude; + bool _use_detail; + int _detail_j; + int _detail_k; + int _detail_m; + int _detail_n; + int _detail_scramble; + bool _use_tile; + float _tile_lower_left_u; + float _tile_lower_left_v; + float _tile_upper_right_u; + float _tile_upper_right_v; + ProjectionType _projection; + EarthModel _earth_model; + int _utm_zone; + ImageOrigin _image_origin; + PointsUnits _geospecific_points_units; + Hemisphere _geospecific_hemisphere; + string _comment; + int _file_version; + GeospecificControlPoints _geospecific_control_points; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +private: + FltError unpack_attr(const Datagram &datagram); + FltError pack_attr(Datagram &datagram) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltTexture", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#endif + + diff --git a/pandatool/src/flt/fltTrackplane.cxx b/pandatool/src/flt/fltTrackplane.cxx new file mode 100644 index 0000000000..69d97af02d --- /dev/null +++ b/pandatool/src/flt/fltTrackplane.cxx @@ -0,0 +1,95 @@ +// Filename: fltTrackplane.cxx +// Created by: drose (26Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTrackplane.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +//////////////////////////////////////////////////////////////////// +// Function: FltTrackplane::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTrackplane:: +FltTrackplane() { + _origin.set(0.0, 0.0, 0.0); + _alignment.set(0.0, 0.0, 0.0); + _plane.set(0.0, 0.0, 1.0); + _grid_state = false; + _grid_under = false; + _grid_angle = 0.0; + _grid_spacing_x = 1; + _grid_spacing_y = 1; + _snap_to_grid = false; + _grid_size = 10.0; + _grid_spacing_direction = 0; + _grid_mask = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTrackplane::extract_record +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +bool FltTrackplane:: +extract_record(FltRecordReader &reader) { + DatagramIterator &iterator = reader.get_iterator(); + + _origin[0] = iterator.get_be_float64(); + _origin[1] = iterator.get_be_float64(); + _origin[2] = iterator.get_be_float64(); + _alignment[0] = iterator.get_be_float64(); + _alignment[1] = iterator.get_be_float64(); + _alignment[0] = iterator.get_be_float64(); + _plane[0] = iterator.get_be_float64(); + _plane[1] = iterator.get_be_float64(); + _plane[2] = iterator.get_be_float64(); + _grid_state = (iterator.get_be_int32() != 0); + _grid_under = (iterator.get_be_int32() != 0); + _grid_angle = iterator.get_be_float32(); + iterator.skip_bytes(4); + _grid_spacing_x = iterator.get_be_float64(); + _grid_spacing_y = iterator.get_be_float64(); + _snap_to_grid = (iterator.get_be_int32() != 0); + _grid_size = iterator.get_be_float64(); + _grid_spacing_direction = iterator.get_be_int32(); + _grid_mask = iterator.get_be_int32(); + iterator.skip_bytes(4); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTrackplane::build_record +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +bool FltTrackplane:: +build_record(FltRecordWriter &writer) const { + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_float64(_origin[0]); + datagram.add_be_float64(_origin[1]); + datagram.add_be_float64(_origin[2]); + datagram.add_be_float64(_alignment[0]); + datagram.add_be_float64(_alignment[1]); + datagram.add_be_float64(_alignment[2]); + datagram.add_be_float64(_plane[0]); + datagram.add_be_float64(_plane[1]); + datagram.add_be_float64(_plane[2]); + datagram.add_be_int32(_grid_state); + datagram.add_be_int32(_grid_under); + datagram.add_be_float32(_grid_angle); + datagram.pad_bytes(4); + datagram.add_be_float64(_grid_spacing_x); + datagram.add_be_float64(_grid_spacing_y); + datagram.add_be_int32(_snap_to_grid); + datagram.add_be_float64(_grid_size); + datagram.add_be_int32(_grid_spacing_direction); + datagram.add_be_int32(_grid_mask); + datagram.pad_bytes(4); + + return true; +} diff --git a/pandatool/src/flt/fltTrackplane.h b/pandatool/src/flt/fltTrackplane.h new file mode 100644 index 0000000000..c81fc29b50 --- /dev/null +++ b/pandatool/src/flt/fltTrackplane.h @@ -0,0 +1,46 @@ +// Filename: fltTrackplane.h +// Created by: drose (26Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRACKPLANE_H +#define FLTTRACKPLANE_H + +#include + +#include + +class FltRecordReader; +class FltRecordWriter; + +//////////////////////////////////////////////////////////////////// +// Class : FltTrackplane +// Description : A single trackplane entry in the eyepoint/trackplane +// palette. +//////////////////////////////////////////////////////////////////// +class FltTrackplane { +public: + FltTrackplane(); + + bool extract_record(FltRecordReader &reader); + bool build_record(FltRecordWriter &writer) const; + +public: + LPoint3d _origin; + LPoint3d _alignment; + LVector3d _plane; + bool _grid_state; + bool _grid_under; + float _grid_angle; + double _grid_spacing_x; + double _grid_spacing_y; + bool _snap_to_grid; + double _grid_size; + int _grid_spacing_direction; + int _grid_mask; +}; + +#endif + + + diff --git a/pandatool/src/flt/fltTransformGeneralMatrix.cxx b/pandatool/src/flt/fltTransformGeneralMatrix.cxx new file mode 100644 index 0000000000..7fb7cd8ab4 --- /dev/null +++ b/pandatool/src/flt/fltTransformGeneralMatrix.cxx @@ -0,0 +1,92 @@ +// Filename: fltTransformGeneralMatrix.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTransformGeneralMatrix.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformGeneralMatrix::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformGeneralMatrix::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTransformGeneralMatrix:: +FltTransformGeneralMatrix(FltHeader *header) : FltTransformRecord(header) { +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformGeneralMatrix::set_matrix +// Access: Public +// Description: Directly sets the general matrix. +//////////////////////////////////////////////////////////////////// +void FltTransformGeneralMatrix:: +set_matrix(const LMatrix4d &matrix) { + _matrix = matrix; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformGeneralMatrix::set_matrix +// Access: Public +// Description: Directly sets the general matrix. +//////////////////////////////////////////////////////////////////// +void FltTransformGeneralMatrix:: +set_matrix(const LMatrix4f &matrix) { + _matrix = LCAST(double, matrix); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformGeneralMatrix::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltTransformGeneralMatrix:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_general_matrix, false); + DatagramIterator &iterator = reader.get_iterator(); + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + _matrix(r, c) = iterator.get_be_float32(); + } + } + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformGeneralMatrix::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltTransformGeneralMatrix:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_general_matrix); + Datagram &datagram = writer.update_datagram(); + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + datagram.add_be_float32(_matrix(r, c)); + } + } + + return true; +} diff --git a/pandatool/src/flt/fltTransformGeneralMatrix.h b/pandatool/src/flt/fltTransformGeneralMatrix.h new file mode 100644 index 0000000000..40db71c99e --- /dev/null +++ b/pandatool/src/flt/fltTransformGeneralMatrix.h @@ -0,0 +1,50 @@ +// Filename: fltTransformGeneralMatrix.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRANSFORMGENERALMATRIX_H +#define FLTTRANSFORMGENERALMATRIX_H + +#include + +#include "fltTransformRecord.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltTransformGeneralMatrix +// Description : A general 4x4 matrix. This appears in the flt file +// when there is no record of the composition of the +// transform. +//////////////////////////////////////////////////////////////////// +class FltTransformGeneralMatrix : public FltTransformRecord { +public: + FltTransformGeneralMatrix(FltHeader *header); + + void set_matrix(const LMatrix4d &matrix); + void set_matrix(const LMatrix4f &matrix); + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformGeneralMatrix", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltTransformPut.cxx b/pandatool/src/flt/fltTransformPut.cxx new file mode 100644 index 0000000000..5a50e9a71a --- /dev/null +++ b/pandatool/src/flt/fltTransformPut.cxx @@ -0,0 +1,213 @@ +// Filename: fltTransformPut.cxx +// Created by: drose (29Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTransformPut.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +#include + +TypeHandle FltTransformPut::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTransformPut:: +FltTransformPut(FltHeader *header) : FltTransformRecord(header) { + _from_origin.set(0.0, 0.0, 0.0); + _from_align.set(1.0, 0.0, 0.0); + _from_track.set(1.0, 0.0, 0.0); + _to_origin.set(0.0, 0.0, 0.0); + _to_align.set(1.0, 0.0, 0.0); + _to_track.set(1.0, 0.0, 0.0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::set +// Access: Public +// Description: Defines the put explicitly. The transformation will +// map the three "from" points to the corresponding +// three "to" points. +//////////////////////////////////////////////////////////////////// +void FltTransformPut:: +set(const LPoint3d &from_origin, const LPoint3d &from_align, + const LPoint3d &from_track, + const LPoint3d &to_origin, const LPoint3d &to_align, + const LPoint3d &to_track) { + _from_origin = from_origin; + _from_align = from_align; + _from_track = from_track; + _to_origin = to_origin; + _to_align = to_align; + _to_track = to_track; + + recompute_matrix(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::get_from_origin +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformPut:: +get_from_origin() const { + return _from_origin; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::get_from_align +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformPut:: +get_from_align() const { + return _from_align; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::get_from_track +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformPut:: +get_from_track() const { + return _from_track; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::get_to_origin +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformPut:: +get_to_origin() const { + return _to_origin; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::get_to_align +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformPut:: +get_to_align() const { + return _to_align; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::get_to_track +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformPut:: +get_to_track() const { + return _to_track; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::recompute_matrix +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void FltTransformPut:: +recompute_matrix() { + LMatrix4d r1, r2; + look_at(r1, _from_align - _from_origin, _from_track - _from_origin, CS_zup_right); + look_at(r2, _to_align - _to_origin, _to_track - _to_origin, CS_zup_right); + + _matrix = + LMatrix4d::translate_mat(-_from_origin) * + invert(r1) * + r2 * + LMatrix4d::translate_mat(_to_origin); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltTransformPut:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_put, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _from_origin[0] = iterator.get_be_float64(); + _from_origin[1] = iterator.get_be_float64(); + _from_origin[2] = iterator.get_be_float64(); + _from_align[0] = iterator.get_be_float64(); + _from_align[1] = iterator.get_be_float64(); + _from_align[2] = iterator.get_be_float64(); + _from_track[0] = iterator.get_be_float64(); + _from_track[1] = iterator.get_be_float64(); + _from_track[2] = iterator.get_be_float64(); + _to_origin[0] = iterator.get_be_float64(); + _to_origin[1] = iterator.get_be_float64(); + _to_origin[2] = iterator.get_be_float64(); + _to_align[0] = iterator.get_be_float64(); + _to_align[1] = iterator.get_be_float64(); + _to_align[2] = iterator.get_be_float64(); + _to_track[0] = iterator.get_be_float64(); + _to_track[1] = iterator.get_be_float64(); + _to_track[2] = iterator.get_be_float64(); + + recompute_matrix(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformPut::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltTransformPut:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_put); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_from_origin[0]); + datagram.add_be_float64(_from_origin[1]); + datagram.add_be_float64(_from_origin[2]); + datagram.add_be_float64(_from_align[0]); + datagram.add_be_float64(_from_align[1]); + datagram.add_be_float64(_from_align[2]); + datagram.add_be_float64(_from_track[0]); + datagram.add_be_float64(_from_track[1]); + datagram.add_be_float64(_from_track[2]); + datagram.add_be_float64(_to_origin[0]); + datagram.add_be_float64(_to_origin[1]); + datagram.add_be_float64(_to_origin[2]); + datagram.add_be_float64(_to_align[0]); + datagram.add_be_float64(_to_align[1]); + datagram.add_be_float64(_to_align[2]); + datagram.add_be_float64(_to_track[0]); + datagram.add_be_float64(_to_track[1]); + datagram.add_be_float64(_to_track[2]); + + return true; +} + diff --git a/pandatool/src/flt/fltTransformPut.h b/pandatool/src/flt/fltTransformPut.h new file mode 100644 index 0000000000..88a56d05ce --- /dev/null +++ b/pandatool/src/flt/fltTransformPut.h @@ -0,0 +1,71 @@ +// Filename: fltTransformPut.h +// Created by: drose (29Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRANSFORMPUT_H +#define FLTTRANSFORMPUT_H + +#include + +#include "fltTransformRecord.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltTransformPut +// Description : A "put", which is a MultiGen concept of defining a +// transformation by mapping three arbitrary points to +// three new arbitrary points. +//////////////////////////////////////////////////////////////////// +class FltTransformPut : public FltTransformRecord { +public: + FltTransformPut(FltHeader *header); + + void set(const LPoint3d &from_origin, + const LPoint3d &from_align, + const LPoint3d &from_track, + const LPoint3d &to_origin, + const LPoint3d &to_align, + const LPoint3d &to_track); + + const LPoint3d &get_from_origin() const; + const LPoint3d &get_from_align() const; + const LPoint3d &get_from_track() const; + const LPoint3d &get_to_origin() const; + const LPoint3d &get_to_align() const; + const LPoint3d &get_to_track() const; + +private: + void recompute_matrix(); + + LPoint3d _from_origin; + LPoint3d _from_align; + LPoint3d _from_track; + LPoint3d _to_origin; + LPoint3d _to_align; + LPoint3d _to_track; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformPut", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltTransformRecord.cxx b/pandatool/src/flt/fltTransformRecord.cxx new file mode 100644 index 0000000000..fac22a1cca --- /dev/null +++ b/pandatool/src/flt/fltTransformRecord.cxx @@ -0,0 +1,29 @@ +// Filename: fltTransformRecord.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTransformRecord.h" + +TypeHandle FltTransformRecord::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRecord::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTransformRecord:: +FltTransformRecord(FltHeader *header) : FltRecord(header) { + _matrix = LMatrix4d::ident_mat(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRecord::get_matrix +// Access: Public +// Description: Returns the transform matrix represented by this +// particular component of the transform. +//////////////////////////////////////////////////////////////////// +const LMatrix4d &FltTransformRecord:: +get_matrix() const { + return _matrix; +} diff --git a/pandatool/src/flt/fltTransformRecord.h b/pandatool/src/flt/fltTransformRecord.h new file mode 100644 index 0000000000..65cbc88e43 --- /dev/null +++ b/pandatool/src/flt/fltTransformRecord.h @@ -0,0 +1,53 @@ +// Filename: fltTransformRecord.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRANSFORMRECORD_H +#define FLTTRANSFORMRECORD_H + +#include + +#include "fltRecord.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltTransformRecord +// Description : A base class for a number of types of ancillary +// records that follow beads and indicate some kind of a +// transformation. Pointers of this type are collected +// in the FltTransformation class. +//////////////////////////////////////////////////////////////////// +class FltTransformRecord : public FltRecord { +public: + FltTransformRecord(FltHeader *header); + + const LMatrix4d &get_matrix() const; + +protected: + LMatrix4d _matrix; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltTransformRecord", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltBead; +}; + +#endif + + diff --git a/pandatool/src/flt/fltTransformRotateAboutEdge.cxx b/pandatool/src/flt/fltTransformRotateAboutEdge.cxx new file mode 100644 index 0000000000..545a5b0c6b --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateAboutEdge.cxx @@ -0,0 +1,155 @@ +// Filename: fltTransformRotateAboutEdge.cxx +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTransformRotateAboutEdge.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformRotateAboutEdge::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutEdge::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTransformRotateAboutEdge:: +FltTransformRotateAboutEdge(FltHeader *header) : FltTransformRecord(header) { + _point_a.set(0.0, 0.0, 0.0); + _point_b.set(1.0, 0.0, 0.0); + _angle = 0.0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutEdge::set +// Access: Public +// Description: Defines the rotation. The angle is given in degrees, +// counterclockwise about the axis as seen from point a. +//////////////////////////////////////////////////////////////////// +void FltTransformRotateAboutEdge:: +set(const LPoint3d &point_a, const LPoint3d &point_b, float angle) { + _point_a = point_a; + _point_b = point_b; + _angle = angle; + + recompute_matrix(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutEdge::get_point_a +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformRotateAboutEdge:: +get_point_a() const { + return _point_a; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutEdge::get_point_b +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformRotateAboutEdge:: +get_point_b() const { + return _point_b; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutEdge::get_angle +// Access: Public +// Description: Returns the angle of rotation, in degrees +// counterclockwise about the axis as seen from point a. +//////////////////////////////////////////////////////////////////// +float FltTransformRotateAboutEdge:: +get_angle() const { + return _angle; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutEdge::recompute_matrix +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void FltTransformRotateAboutEdge:: +recompute_matrix() { + if (_point_a == _point_b) { + // Degenerate case. + _matrix = LMatrix4d::ident_mat(); + } else { + LVector3d axis = _point_b - _point_a; + _matrix = + LMatrix4d::translate_mat(-_point_a) * + LMatrix4d::rotate_mat(_angle, normalize(axis), CS_zup_right) * + LMatrix4d::translate_mat(_point_a); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutEdge::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltTransformRotateAboutEdge:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_rotate_about_edge, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _point_a[0] = iterator.get_be_float64(); + _point_a[1] = iterator.get_be_float64(); + _point_a[2] = iterator.get_be_float64(); + _point_b[0] = iterator.get_be_float64(); + _point_b[1] = iterator.get_be_float64(); + _point_b[2] = iterator.get_be_float64(); + _angle = iterator.get_be_float32(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + recompute_matrix(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutEdge::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltTransformRotateAboutEdge:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_rotate_about_edge); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_point_a[0]); + datagram.add_be_float64(_point_a[1]); + datagram.add_be_float64(_point_a[2]); + datagram.add_be_float64(_point_b[0]); + datagram.add_be_float64(_point_b[1]); + datagram.add_be_float64(_point_b[2]); + datagram.add_be_float32(_angle); + + datagram.pad_bytes(4); // Undocumented additional padding. + + return true; +} + diff --git a/pandatool/src/flt/fltTransformRotateAboutEdge.h b/pandatool/src/flt/fltTransformRotateAboutEdge.h new file mode 100644 index 0000000000..6ce466033a --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateAboutEdge.h @@ -0,0 +1,57 @@ +// Filename: fltTransformRotateAboutEdge.h +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRANSFORMROTATEABOUTEDGE_H +#define FLTTRANSFORMROTATEABOUTEDGE_H + +#include + +#include "fltTransformRecord.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltTransformRotateAboutEdge +// Description : A transformation that rotates about a particular axis +// in space, defined by two endpoints. +//////////////////////////////////////////////////////////////////// +class FltTransformRotateAboutEdge : public FltTransformRecord { +public: + FltTransformRotateAboutEdge(FltHeader *header); + + void set(const LPoint3d &point_a, const LPoint3d &point_b, float angle); + + const LPoint3d &get_point_a() const; + const LPoint3d &get_point_b() const; + float get_angle() const; + +private: + void recompute_matrix(); + + LPoint3d _point_a; + LPoint3d _point_b; + float _angle; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformRotateAboutEdge", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformRotateAboutPoint.cxx b/pandatool/src/flt/fltTransformRotateAboutPoint.cxx new file mode 100644 index 0000000000..b5ab313997 --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateAboutPoint.cxx @@ -0,0 +1,152 @@ +// Filename: fltTransformRotateAboutPoint.cxx +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTransformRotateAboutPoint.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformRotateAboutPoint::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutPoint::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTransformRotateAboutPoint:: +FltTransformRotateAboutPoint(FltHeader *header) : FltTransformRecord(header) { + _center.set(0.0, 0.0, 0.0); + _axis.set(1.0, 0.0, 0.0); + _angle = 0.0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutPoint::set +// Access: Public +// Description: Defines the rotation. The angle is given in degrees, +// counterclockwise about the axis as seen from point a. +//////////////////////////////////////////////////////////////////// +void FltTransformRotateAboutPoint:: +set(const LPoint3d ¢er, const LVector3f &axis, float angle) { + _center = center; + _axis = axis; + _angle = angle; + + recompute_matrix(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutPoint::get_center +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformRotateAboutPoint:: +get_center() const { + return _center; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutPoint::get_axis +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LVector3f &FltTransformRotateAboutPoint:: +get_axis() const { + return _axis; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutPoint::get_angle +// Access: Public +// Description: Returns the angle of rotation, in degrees +// counterclockwise about the axis. +//////////////////////////////////////////////////////////////////// +float FltTransformRotateAboutPoint:: +get_angle() const { + return _angle; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutPoint::recompute_matrix +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void FltTransformRotateAboutPoint:: +recompute_matrix() { + if (_axis == LVector3f::zero()) { + // Degenerate case. + _matrix = LMatrix4d::ident_mat(); + } else { + LVector3d axis = LCAST(double, axis); + + _matrix = + LMatrix4d::translate_mat(-_center) * + LMatrix4d::rotate_mat(_angle, normalize(axis), CS_zup_right) * + LMatrix4d::translate_mat(_center); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutPoint::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltTransformRotateAboutPoint:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_rotate_about_point, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _center[0] = iterator.get_be_float64(); + _center[1] = iterator.get_be_float64(); + _center[2] = iterator.get_be_float64(); + _axis[0] = iterator.get_be_float32(); + _axis[1] = iterator.get_be_float32(); + _axis[2] = iterator.get_be_float32(); + _angle = iterator.get_be_float32(); + + recompute_matrix(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateAboutPoint::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltTransformRotateAboutPoint:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_rotate_about_point); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_center[0]); + datagram.add_be_float64(_center[1]); + datagram.add_be_float64(_center[2]); + datagram.add_be_float32(_axis[0]); + datagram.add_be_float32(_axis[1]); + datagram.add_be_float32(_axis[2]); + datagram.add_be_float32(_angle); + + return true; +} + diff --git a/pandatool/src/flt/fltTransformRotateAboutPoint.h b/pandatool/src/flt/fltTransformRotateAboutPoint.h new file mode 100644 index 0000000000..91cda0bad4 --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateAboutPoint.h @@ -0,0 +1,57 @@ +// Filename: fltTransformRotateAboutPoint.h +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRANSFORMROTATEABOUTPOINT_H +#define FLTTRANSFORMROTATEABOUTPOINT_H + +#include + +#include "fltTransformRecord.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltTransformRotateAboutPoint +// Description : A transformation that rotates about a particular axis +// in space, defined by a point and vector. +//////////////////////////////////////////////////////////////////// +class FltTransformRotateAboutPoint : public FltTransformRecord { +public: + FltTransformRotateAboutPoint(FltHeader *header); + + void set(const LPoint3d ¢er, const LVector3f &axis, float angle); + + const LPoint3d &get_center() const; + const LVector3f &get_axis() const; + float get_angle() const; + +private: + void recompute_matrix(); + + LPoint3d _center; + LVector3f _axis; + float _angle; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformRotateAboutPoint", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformRotateScale.cxx b/pandatool/src/flt/fltTransformRotateScale.cxx new file mode 100644 index 0000000000..09d0947bb0 --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateScale.cxx @@ -0,0 +1,230 @@ +// Filename: fltTransformRotateScale.cxx +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTransformRotateScale.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +#include +#include + +TypeHandle FltTransformRotateScale::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTransformRotateScale:: +FltTransformRotateScale(FltHeader *header) : FltTransformRecord(header) { + _center.set(0.0, 0.0, 0.0); + _reference_point.set(0.0, 0.0, 0.0); + _to_point.set(0.0, 0.0, 0.0); + _overall_scale = 1.0; + _axis_scale = 1.0; + _angle = 0.0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::set +// Access: Public +// Description: Defines the transform explicitly. The angle of +// rotation is determined by the angle between the +// reference point and the to point (relative to the +// center), and the scale factor is determined by the +// distance between the reference point and the center +// point. If axis_scale is true, the scale is along +// reference point axis only; otherwise, it is a uniform +// scale. +//////////////////////////////////////////////////////////////////// +void FltTransformRotateScale:: +set(const LPoint3d ¢er, const LPoint3d &reference_point, + const LPoint3d &to_point, bool axis_scale) { + _center = center; + _reference_point = reference_point; + _to_point = to_point; + + LVector3d v1 = _reference_point - _center; + LVector3d v2 = _to_point - _center; + + _angle = + acos(dot(normalize(v1), normalize(v2))) * 180.0 / MathNumbers::pi; + + if (axis_scale) { + _axis_scale = length(v1); + _overall_scale = 1.0; + } else { + _overall_scale = length(v1); + _axis_scale = 1.0; + } + + recompute_matrix(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::get_center +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformRotateScale:: +get_center() const { + return _center; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::get_reference_point +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformRotateScale:: +get_reference_point() const { + return _reference_point; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::get_to_point +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformRotateScale:: +get_to_point() const { + return _to_point; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::get_overall_scale +// Access: Public +// Description: Returns the overall scale factor. +//////////////////////////////////////////////////////////////////// +float FltTransformRotateScale:: +get_overall_scale() const { + return _overall_scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::get_axis_scale +// Access: Public +// Description: Returns the scale factor in the direction of the +// axis. +//////////////////////////////////////////////////////////////////// +float FltTransformRotateScale:: +get_axis_scale() const { + return _axis_scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::get_angle +// Access: Public +// Description: Returns the angle of rotation in degrees. +//////////////////////////////////////////////////////////////////// +float FltTransformRotateScale:: +get_angle() const { + return _angle; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::recompute_matrix +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void FltTransformRotateScale:: +recompute_matrix() { + LVector3d v1 = _reference_point - _center; + LVector3d v2 = _to_point - _center; + LVector3d rotate_axis = normalize(cross(v1, v2)); + + // To scale along an axis, we have to do a bit of work. First + // determine the matrices to rotate and unrotate the rotate axis + // to the y-forward axis. + LMatrix4d r1; + look_at(r1, v1, rotate_axis, CS_zup_right); + + _matrix = + LMatrix4d::translate_mat(-_center) * + r1 * + LMatrix4d::scale_mat(1.0, _axis_scale, 1.0) * + LMatrix4d::scale_mat(_overall_scale) * + invert(r1) * + LMatrix4d::rotate_mat(_angle, rotate_axis, CS_zup_right) * + LMatrix4d::translate_mat(_center); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltTransformRotateScale:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_rotate_and_scale, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _center[0] = iterator.get_be_float64(); + _center[1] = iterator.get_be_float64(); + _center[2] = iterator.get_be_float64(); + _reference_point[0] = iterator.get_be_float64(); + _reference_point[1] = iterator.get_be_float64(); + _reference_point[2] = iterator.get_be_float64(); + _to_point[0] = iterator.get_be_float64(); + _to_point[1] = iterator.get_be_float64(); + _to_point[2] = iterator.get_be_float64(); + _overall_scale = iterator.get_be_float32(); + _axis_scale = iterator.get_be_float32(); + _angle = iterator.get_be_float32(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + recompute_matrix(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformRotateScale::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltTransformRotateScale:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_put); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_center[0]); + datagram.add_be_float64(_center[1]); + datagram.add_be_float64(_center[2]); + datagram.add_be_float64(_reference_point[0]); + datagram.add_be_float64(_reference_point[1]); + datagram.add_be_float64(_reference_point[2]); + datagram.add_be_float64(_to_point[0]); + datagram.add_be_float64(_to_point[1]); + datagram.add_be_float64(_to_point[2]); + datagram.add_be_float32(_overall_scale); + datagram.add_be_float32(_axis_scale); + datagram.add_be_float32(_angle); + + datagram.pad_bytes(4); // Undocumented additional padding. + + return true; +} + diff --git a/pandatool/src/flt/fltTransformRotateScale.h b/pandatool/src/flt/fltTransformRotateScale.h new file mode 100644 index 0000000000..5d11d2a63f --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateScale.h @@ -0,0 +1,66 @@ +// Filename: fltTransformRotateScale.h +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRANSFORMROTATESCALE_H +#define FLTTRANSFORMROTATESCALE_H + +#include + +#include "fltTransformRecord.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltTransformRotateScale +// Description : A combination rotation and scale. This is sometimes +// called "Rotate To Point" within MultiGen. +//////////////////////////////////////////////////////////////////// +class FltTransformRotateScale : public FltTransformRecord { +public: + FltTransformRotateScale(FltHeader *header); + + void set(const LPoint3d ¢er, const LPoint3d &reference_point, + const LPoint3d &to_point, bool axis_scale); + + const LPoint3d &get_center() const; + const LPoint3d &get_reference_point() const; + const LPoint3d &get_to_point() const; + float get_overall_scale() const; + float get_axis_scale() const; + float get_angle() const; + +private: + void recompute_matrix(); + + LPoint3d _center; + LPoint3d _reference_point; + LPoint3d _to_point; + float _overall_scale; + float _axis_scale; + float _angle; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformRotateScale", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltTransformScale.cxx b/pandatool/src/flt/fltTransformScale.cxx new file mode 100644 index 0000000000..fc5b6fce8e --- /dev/null +++ b/pandatool/src/flt/fltTransformScale.cxx @@ -0,0 +1,133 @@ +// Filename: fltTransformScale.cxx +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTransformScale.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformScale::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformScale::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTransformScale:: +FltTransformScale(FltHeader *header) : FltTransformRecord(header) { + _center.set(0.0, 0.0, 0.0); + _scale.set(1.0, 1.0, 1.0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformScale::set +// Access: Public +// Description: Defines the scale. +//////////////////////////////////////////////////////////////////// +void FltTransformScale:: +set(const LPoint3d ¢er, const LVecBase3f &scale) { + _center = center; + _scale = scale; + + recompute_matrix(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformScale::get_center +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformScale:: +get_center() const { + return _center; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformScale::get_scale +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LVecBase3f &FltTransformScale:: +get_scale() const { + return _scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformScale::recompute_matrix +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void FltTransformScale:: +recompute_matrix() { + _matrix = + LMatrix4d::translate_mat(-_center) * + LMatrix4d::scale_mat(LCAST(double, _scale)) * + LMatrix4d::translate_mat(_center); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformScale::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltTransformScale:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_scale, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _center[0] = iterator.get_be_float64(); + _center[1] = iterator.get_be_float64(); + _center[2] = iterator.get_be_float64(); + _scale[0] = iterator.get_be_float32(); + _scale[1] = iterator.get_be_float32(); + _scale[2] = iterator.get_be_float32(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + recompute_matrix(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformScale::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltTransformScale:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_scale); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_center[0]); + datagram.add_be_float64(_center[1]); + datagram.add_be_float64(_center[2]); + datagram.add_be_float32(_scale[0]); + datagram.add_be_float32(_scale[1]); + datagram.add_be_float32(_scale[2]); + + datagram.pad_bytes(4); // Undocumented additional padding. + + return true; +} + diff --git a/pandatool/src/flt/fltTransformScale.h b/pandatool/src/flt/fltTransformScale.h new file mode 100644 index 0000000000..da7aef9f91 --- /dev/null +++ b/pandatool/src/flt/fltTransformScale.h @@ -0,0 +1,55 @@ +// Filename: fltTransformScale.h +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRANSFORMSCALE_H +#define FLTTRANSFORMSCALE_H + +#include + +#include "fltTransformRecord.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltTransformScale +// Description : A transformation that applies a (possibly nonuniform) +// scale. +//////////////////////////////////////////////////////////////////// +class FltTransformScale : public FltTransformRecord { +public: + FltTransformScale(FltHeader *header); + + void set(const LPoint3d ¢er, const LVecBase3f &scale); + + const LPoint3d &get_center() const; + const LVecBase3f &get_scale() const; + +private: + void recompute_matrix(); + + LPoint3d _center; + LVecBase3f _scale; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformScale", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformTranslate.cxx b/pandatool/src/flt/fltTransformTranslate.cxx new file mode 100644 index 0000000000..59ce8c6958 --- /dev/null +++ b/pandatool/src/flt/fltTransformTranslate.cxx @@ -0,0 +1,132 @@ +// Filename: fltTransformTranslate.cxx +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltTransformTranslate.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformTranslate::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformTranslate::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltTransformTranslate:: +FltTransformTranslate(FltHeader *header) : FltTransformRecord(header) { + _from.set(0.0, 0.0, 0.0); + _delta.set(0.0, 0.0, 0.0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformTranslate::set +// Access: Public +// Description: Defines the translation. The "from" point seems to +// be pretty much ignored. +//////////////////////////////////////////////////////////////////// +void FltTransformTranslate:: +set(const LPoint3d &from, const LVector3d &delta) { + _from = from; + _delta = delta; + + recompute_matrix(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformTranslate::get_from +// Access: Public +// Description: Returns the reference point of the translation. This +// is largely meaningless. +//////////////////////////////////////////////////////////////////// +const LPoint3d &FltTransformTranslate:: +get_from() const { + return _from; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformTranslate::get_delta +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +const LVector3d &FltTransformTranslate:: +get_delta() const { + return _delta; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformTranslate::recompute_matrix +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void FltTransformTranslate:: +recompute_matrix() { + _matrix = LMatrix4d::translate_mat(_delta); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformTranslate::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltTransformTranslate:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_translate, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _from[0] = iterator.get_be_float64(); + _from[1] = iterator.get_be_float64(); + _from[2] = iterator.get_be_float64(); + _delta[0] = iterator.get_be_float64(); + _delta[1] = iterator.get_be_float64(); + _delta[2] = iterator.get_be_float64(); + + // iterator.skip_bytes(4); // Undocumented additional padding. + + recompute_matrix(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltTransformTranslate::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltTransformTranslate:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_translate); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_from[0]); + datagram.add_be_float64(_from[1]); + datagram.add_be_float64(_from[2]); + datagram.add_be_float64(_delta[0]); + datagram.add_be_float64(_delta[1]); + datagram.add_be_float64(_delta[2]); + + // datagram.pad_bytes(4); // Undocumented additional padding. + + return true; +} + diff --git a/pandatool/src/flt/fltTransformTranslate.h b/pandatool/src/flt/fltTransformTranslate.h new file mode 100644 index 0000000000..d3d46f1bb5 --- /dev/null +++ b/pandatool/src/flt/fltTransformTranslate.h @@ -0,0 +1,55 @@ +// Filename: fltTransformTranslate.h +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTTRANSFORMTRANSLATE_H +#define FLTTRANSFORMTRANSLATE_H + +#include + +#include "fltTransformRecord.h" + +//////////////////////////////////////////////////////////////////// +// Class : FltTransformTranslate +// Description : A transformation that applies a (possibly nonuniform) +// scale. +//////////////////////////////////////////////////////////////////// +class FltTransformTranslate : public FltTransformRecord { +public: + FltTransformTranslate(FltHeader *header); + + void set(const LPoint3d &from, const LVector3d &delta); + + const LPoint3d &get_from() const; + const LVector3d &get_delta() const; + +private: + void recompute_matrix(); + + LPoint3d _from; + LVector3d _delta; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformTranslate", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltUnsupportedRecord.cxx b/pandatool/src/flt/fltUnsupportedRecord.cxx new file mode 100644 index 0000000000..d1e4d54c22 --- /dev/null +++ b/pandatool/src/flt/fltUnsupportedRecord.cxx @@ -0,0 +1,63 @@ +// Filename: fltUnsupportedRecord.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltUnsupportedRecord.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltUnsupportedRecord::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltUnsupportedRecord::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltUnsupportedRecord:: +FltUnsupportedRecord(FltHeader *header) : FltRecord(header) { + _opcode = FO_none; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltUnsupportedRecord::output +// Access: Public +// Description: Writes a quick one-line description of the bead, but +// not its children. This is a human-readable +// description, primarily for debugging; to write a flt +// file, use FltHeader::write_flt(). +//////////////////////////////////////////////////////////////////// +void FltUnsupportedRecord:: +output(ostream &out) const { + out << "Unsupported(" << _opcode << ")"; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltUnsupportedRecord::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltUnsupportedRecord:: +extract_record(FltRecordReader &reader) { + _opcode = reader.get_opcode(); + _datagram = reader.get_datagram(); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltUnsupportedRecord::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltUnsupportedRecord:: +build_record(FltRecordWriter &writer) const { + writer.set_opcode(_opcode); + writer.set_datagram(_datagram); +} diff --git a/pandatool/src/flt/fltUnsupportedRecord.h b/pandatool/src/flt/fltUnsupportedRecord.h new file mode 100644 index 0000000000..5627f5360d --- /dev/null +++ b/pandatool/src/flt/fltUnsupportedRecord.h @@ -0,0 +1,53 @@ +// Filename: fltUnsupportedRecord.h +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTUNSUPPORTEDRECORD_H +#define FLTUNSUPPORTEDRECORD_H + +#include + +#include "fltRecord.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltUnsupportedRecord +// Description : +//////////////////////////////////////////////////////////////////// +class FltUnsupportedRecord : public FltRecord { +public: + FltUnsupportedRecord(FltHeader *header); + + virtual void output(ostream &out) const; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +private: + FltOpcode _opcode; + Datagram _datagram; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltUnsupportedRecord", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/fltVertex.I b/pandatool/src/flt/fltVertex.I new file mode 100644 index 0000000000..c0ce7b9cca --- /dev/null +++ b/pandatool/src/flt/fltVertex.I @@ -0,0 +1,16 @@ +// Filename: fltVertex.I +// Created by: drose (30Aug00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: FltVertex::has_color +// Access: Public +// Description: Returns true if the vertex has a primary color +// indicated, false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool FltVertex:: +has_color() const { + return (_flags & F_no_color) == 0; +} diff --git a/pandatool/src/flt/fltVertex.cxx b/pandatool/src/flt/fltVertex.cxx new file mode 100644 index 0000000000..998eb5aade --- /dev/null +++ b/pandatool/src/flt/fltVertex.cxx @@ -0,0 +1,221 @@ +// Filename: fltVertex.cxx +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltVertex.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" + +TypeHandle FltVertex::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltVertex::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltVertex:: +FltVertex(FltHeader *header) : FltRecord(header) { + _color_name_index = 0; + _flags = 0; + _pos.set(0.0, 0.0, 0.0); + _normal.set(0.0, 0.0, 0.0); + _uv.set(0.0, 0.0); + _color_index = 0; + + _has_normal = false; + _has_uv = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertex::get_opcode +// Access: Public +// Description: Returns the opcode that this record will be written +// as. +//////////////////////////////////////////////////////////////////// +FltOpcode FltVertex:: +get_opcode() const { + if (_has_normal) { + if (_has_uv) { + return FO_vertex_cnu; + } else { + return FO_vertex_cn; + } + } else { + if (_has_uv) { + return FO_vertex_cu; + } else { + return FO_vertex_c; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertex::get_record_length +// Access: Public +// Description: Returns the length of this record in bytes as it will +// be written to the flt file. +//////////////////////////////////////////////////////////////////// +int FltVertex:: +get_record_length() const { + switch (get_opcode()) { + case FO_vertex_c: + return 40; + + case FO_vertex_cn: + return 52; + + case FO_vertex_cnu: + return 60; + + case FO_vertex_cu: + return 48; + + default: + nassertr(false, 0); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertex::get_color +// Access: Public +// Description: If has_color() indicates true, returns the primary +// color of the face, as a four-component value. In the +// case of a vertex, the alpha channel will always be +// 1.0, as MultiGen does not store transparency +// per-vertex. +//////////////////////////////////////////////////////////////////// +Colorf FltVertex:: +get_color() const { + nassertr(has_color(), Colorf(0.0, 0.0, 0.0, 0.0)); + + return _header->get_color(_color_index, (_flags & F_packed_color) != 0, + _packed_color, 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertex::get_rgb +// Access: Public +// Description: If has_color() indicates true, returns the primary +// color of the face, as a three-component value +// ignoring transparency. +//////////////////////////////////////////////////////////////////// +RGBColorf FltVertex:: +get_rgb() const { + nassertr(has_color(), RGBColorf(0.0, 0.0, 0.0)); + + return _header->get_rgb(_color_index, (_flags & F_packed_color) != 0, + _packed_color); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertex::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this record based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltVertex:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + switch (reader.get_opcode()) { + case FO_vertex_c: + _has_normal = false; + _has_uv = false; + break; + + case FO_vertex_cn: + _has_normal = true; + _has_uv = false; + break; + + case FO_vertex_cnu: + _has_normal = true; + _has_uv = true; + break; + + case FO_vertex_cu: + _has_normal = false; + _has_uv = true; + break; + + default: + nassertr(false, false); + } + + DatagramIterator &iterator = reader.get_iterator(); + + _color_name_index = iterator.get_be_int16(); + _flags = iterator.get_be_uint16(); + _pos[0] = iterator.get_be_float64(); + _pos[1] = iterator.get_be_float64(); + _pos[2] = iterator.get_be_float64(); + + if (_has_normal) { + _normal[0] = iterator.get_be_float32(); + _normal[1] = iterator.get_be_float32(); + _normal[2] = iterator.get_be_float32(); + } + if (_has_uv) { + _uv[0] = iterator.get_be_float32(); + _uv[1] = iterator.get_be_float32(); + } + + if (!_packed_color.extract_record(reader)) { + return false; + } + _color_index = iterator.get_be_uint32(); + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertex::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltVertex:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(get_opcode()); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int16(_color_name_index); + datagram.add_be_uint16(_flags); + datagram.add_be_float64(_pos[0]); + datagram.add_be_float64(_pos[1]); + datagram.add_be_float64(_pos[2]); + + if (_has_normal) { + datagram.add_be_float32(_normal[0]); + datagram.add_be_float32(_normal[1]); + datagram.add_be_float32(_normal[2]); + } + if (_has_uv) { + datagram.add_be_float32(_uv[0]); + datagram.add_be_float32(_uv[1]); + } + + if (!_packed_color.build_record(writer)) { + return false; + } + + datagram.add_be_uint32(_color_index); + + nassertr(datagram.get_length() == get_record_length() - 4, true); + return true; +} diff --git a/pandatool/src/flt/fltVertex.h b/pandatool/src/flt/fltVertex.h new file mode 100644 index 0000000000..fe72e6e862 --- /dev/null +++ b/pandatool/src/flt/fltVertex.h @@ -0,0 +1,85 @@ +// Filename: fltVertex.h +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTVERTEX_H +#define FLTVERTEX_H + +#include + +#include "fltRecord.h" +#include "fltPackedColor.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltVertex +// Description : Represents a single vertex in the vertex palette. +// Flt files index vertices by their byte offset in the +// vertex palette; within this library, we map those +// byte offsets to pointers automatically. +// +// This may represent a vertex with or without a normal +// or texture coordinates. +//////////////////////////////////////////////////////////////////// +class FltVertex : public FltRecord { +public: + FltVertex(FltHeader *header); + + FltOpcode get_opcode() const; + int get_record_length() const; + + enum Flags { + F_hard_edge = 0x80000000, + F_normal_frozen = 0x40000000, + F_no_color = 0x20000000, + F_packed_color = 0x10000000 + }; + + int _color_name_index; + unsigned int _flags; + LPoint3d _pos; + LPoint3f _normal; + LPoint2f _uv; + FltPackedColor _packed_color; + int _color_index; + + bool _has_normal; + bool _has_uv; + +public: + INLINE bool has_color() const; + Colorf get_color() const; + RGBColorf get_rgb() const; + + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltVertex", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#include "fltVertex.I" + +#endif + + diff --git a/pandatool/src/flt/fltVertexList.cxx b/pandatool/src/flt/fltVertexList.cxx new file mode 100644 index 0000000000..2589c7aa60 --- /dev/null +++ b/pandatool/src/flt/fltVertexList.cxx @@ -0,0 +1,128 @@ +// Filename: fltVertexList.cxx +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltVertexList.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" + +TypeHandle FltVertexList::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: FltVertexList::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +FltVertexList:: +FltVertexList(FltHeader *header) : FltRecord(header) { +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertexList::get_num_vertices +// Access: Public +// Description: Returns the number of vertices in this vertex list. +//////////////////////////////////////////////////////////////////// +int FltVertexList:: +get_num_vertices() const { + return _vertices.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertexList::get_vertex +// Access: Public +// Description: Returns the nth vertex of this vertex list. +//////////////////////////////////////////////////////////////////// +FltVertex *FltVertexList:: +get_vertex(int n) const { + nassertr(n >= 0 && n < (int)_vertices.size(), 0); + return _vertices[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertexList::clear_vertices +// Access: Public +// Description: Removes all vertices from this vertex list. +//////////////////////////////////////////////////////////////////// +void FltVertexList:: +clear_vertices() { + _vertices.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertexList::add_vertex +// Access: Public +// Description: Adds a new vertex to the end of the vertex list. +// Care must be taken to ensure the vertex is also added +// to the vertex palette. +//////////////////////////////////////////////////////////////////// +void FltVertexList:: +add_vertex(FltVertex *vertex) { + _vertices.push_back(vertex); +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertexList::output +// Access: Public +// Description: Writes a quick one-line description of the record, but +// not its children. This is a human-readable +// description, primarily for debugging; to write a flt +// file, use FltHeader::write_flt(). +//////////////////////////////////////////////////////////////////// +void FltVertexList:: +output(ostream &out) const { + out << _vertices.size() << " vertices"; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertexList::extract_record +// Access: Protected, Virtual +// Description: Fills in the information in this bead based on the +// information given in the indicated datagram, whose +// opcode has already been read. Returns true on +// success, false if the datagram is invalid. +//////////////////////////////////////////////////////////////////// +bool FltVertexList:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_vertex_list, false); + DatagramIterator &iterator = reader.get_iterator(); + + _vertices.clear(); + while (iterator.get_remaining_size() >= 4) { + int vertex_offset = iterator.get_be_int32(); + _vertices.push_back(_header->get_vertex_by_offset(vertex_offset)); + } + + nassertr(iterator.get_remaining_size() == 0, true); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FltVertexList::build_record +// Access: Protected, Virtual +// Description: Fills up the current record on the FltRecordWriter with +// data for this record, but does not advance the +// writer. Returns true on success, false if there is +// some error. +//////////////////////////////////////////////////////////////////// +bool FltVertexList:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_vertex_list); + Datagram &datagram = writer.update_datagram(); + + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + datagram.add_be_uint32(_header->get_offset_by_vertex(*vi)); + } + + return true; +} diff --git a/pandatool/src/flt/fltVertexList.h b/pandatool/src/flt/fltVertexList.h new file mode 100644 index 0000000000..33ef07747f --- /dev/null +++ b/pandatool/src/flt/fltVertexList.h @@ -0,0 +1,61 @@ +// Filename: fltVertexList.h +// Created by: drose (25Aug00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FLTVERTEXLIST_H +#define FLTVERTEXLIST_H + +#include + +#include "fltRecord.h" +#include "fltPackedColor.h" +#include "fltVertex.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : FltVertexList +// Description : A list of vertices, typically added as a child of a +// face bead. +//////////////////////////////////////////////////////////////////// +class FltVertexList : public FltRecord { +public: + FltVertexList(FltHeader *header); + + int get_num_vertices() const; + FltVertex *get_vertex(int n) const; + void clear_vertices(); + void add_vertex(FltVertex *vertex); + + virtual void output(ostream &out) const; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +private: + typedef vector Vertices; + Vertices _vertices; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltVertexList", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/flt/test_flt.cxx b/pandatool/src/flt/test_flt.cxx new file mode 100644 index 0000000000..2b8a37e5e5 --- /dev/null +++ b/pandatool/src/flt/test_flt.cxx @@ -0,0 +1,61 @@ +// Filename: test_flt.cxx +// Created by: drose (24Aug00) +// +//////////////////////////////////////////////////////////////////// + +#include "fltHeader.h" + +#include + +void +usage() { + cerr << "Usage: test_flt [opts] filename.flt\n"; +} + +int +main(int argc, char *argv[]) { + static const char * const opts = "t:"; + extern char *optarg; + extern int optind; + + DSearchPath texture_path; + + int flag = getopt(argc, argv, opts); + while (flag != EOF) { + switch (flag) { + case 't': + // t: Texture search path. + texture_path.append_directory(optarg); + break; + + default: + usage(); + exit(1); + } + flag = getopt(argc, argv, opts); + } + argc -= (optind - 1); + argv += (optind - 1); + + if (argc != 2) { + usage(); + exit(1); + } + + Filename filename = argv[1]; + + PT(FltHeader) header = new FltHeader; + header->set_texture_path(texture_path); + + FltError result = header->read_flt(filename); + cerr << "Read result is " << result << "\n\n"; + + if (result == FE_ok) { + //header->write(cerr); + + result = header->write_flt("t.flt"); + cerr << "Write result is " << result << "\n\n"; + } + + return (0); +} diff --git a/pandatool/src/gtk-stats/Sources.pp b/pandatool/src/gtk-stats/Sources.pp new file mode 100644 index 0000000000..820689d218 --- /dev/null +++ b/pandatool/src/gtk-stats/Sources.pp @@ -0,0 +1,24 @@ +#define DIRECTORY_IF_GTKMM yes +#define USE_GTKMM yes + +#begin bin_target + #define TARGET gtk-stats + #define LOCAL_LIBS \ + gtkbase progbase pstatserver config compiler + #define OTHER_LIBS \ + pstatclient:c linmath:c putil:c express:c panda:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + gtkStats.cxx gtkStats.h gtkStatsGuide.cxx gtkStatsGuide.h \ + gtkStatsLabel.cxx gtkStatsLabel.h gtkStatsMainWindow.cxx \ + gtkStatsMainWindow.h gtkStatsMonitor.cxx gtkStatsMonitor.h \ + gtkStatsPianoRoll.I gtkStatsPianoRoll.cxx gtkStatsPianoRoll.h \ + gtkStatsPianoWindow.cxx gtkStatsPianoWindow.h gtkStatsServer.cxx \ + gtkStatsServer.h gtkStatsStripChart.I gtkStatsStripChart.cxx \ + gtkStatsStripChart.h gtkStatsStripWindow.cxx gtkStatsStripWindow.h \ + gtkStatsWindow.cxx gtkStatsWindow.h + +#end bin_target + diff --git a/pandatool/src/gtk-stats/gtkStats.cxx b/pandatool/src/gtk-stats/gtkStats.cxx new file mode 100644 index 0000000000..45c286fa59 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStats.cxx @@ -0,0 +1,77 @@ +// Filename: gtkStats.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStats.h" +#include "gtkStatsMainWindow.h" + +#include +#include + +#include + +GtkStatsMainWindow *GtkStats::_main_window = NULL; + +static bool user_interrupted = false; + +// This simple signal handler lets us know when the user has pressed +// control-C, so we can clean up nicely. +static void signal_handler(int) { + user_interrupted = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStats::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStats:: +GtkStats() { + set_program_description + ("This is a fancy GUI PStats server that listens on a TCP port for a " + "connection from a PStatClient in a Panda player. It will then " + "draw strip charts illustrating the performance stats as reported " + "by the player."); + + add_option + ("p", "port", 0, + "Specify the TCP port to listen for connections on. By default, this " + "is taken from the pstats-host Config variable.", + &GtkStats::dispatch_int, NULL, &_port); + + _port = pstats_port; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStats::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStats:: +run() { + new GtkStatsMainWindow(_port); + + main_loop(); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStats::quit +// Access: Public, Static +// Description: Call this to cleanly shut down the program. +//////////////////////////////////////////////////////////////////// +void GtkStats:: +quit() { + if (_main_window != (GtkStatsMainWindow *)NULL) { + _main_window->destruct(); + } + Gtk::Main::quit(); +} + + +int main(int argc, char *argv[]) { + GtkStats prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/gtk-stats/gtkStats.h b/pandatool/src/gtk-stats/gtkStats.h new file mode 100644 index 0000000000..ea7d661667 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStats.h @@ -0,0 +1,32 @@ +// Filename: gtkStats.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATS_H +#define GTKSTATS_H + +#include + +#include + +class GtkStatsMainWindow; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStats +// Description : A fancy graphical pstats server written using gtk+ +// (actually, Gtk--, the C++ layer over gtk+). +//////////////////////////////////////////////////////////////////// +class GtkStats : public GtkBase { +public: + GtkStats(); + + void run(); + static void quit(); + + int _port; + static GtkStatsMainWindow *_main_window; +}; + +#endif + diff --git a/pandatool/src/gtk-stats/gtkStatsGuide.cxx b/pandatool/src/gtk-stats/gtkStatsGuide.cxx new file mode 100644 index 0000000000..152e74162e --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsGuide.cxx @@ -0,0 +1,76 @@ +// Filename: gtkStatsGuide.cxx +// Created by: drose (16Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsGuide.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsGuide::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsGuide:: +GtkStatsGuide(PStatStripChart *chart) : + _chart(chart) +{ + set_events(GDK_EXPOSURE_MASK); + + // Choose a suitable minimum width. This requires knowing what the + // font will be. + Gdk_GC fg_gc = + get_style()->gtkobj()->fg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))]; + + Gdk_Font font = fg_gc.get_font(); + int text_width = font.string_width("000"); + set_usize(text_width, 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsGuide::configure_event_impl +// Access: Private, Virtual +// Description: Creates a new backing pixmap of the appropriate size. +//////////////////////////////////////////////////////////////////// +gint GtkStatsGuide:: +configure_event_impl(GdkEventConfigure *) { + Gdk_Window window = get_window(); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsGuide::expose_event_impl +// Access: Private, Virtual +// Description: Redraw the text. We don't bother with clipping +// regions here, but just draw the whole thing every +// time. +//////////////////////////////////////////////////////////////////// +gint GtkStatsGuide:: +expose_event_impl(GdkEventExpose *event) { + Gdk_GC fg_gc = + get_style()->gtkobj()->fg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))]; + Gdk_GC bg_gc = + get_style()->gtkobj()->bg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))]; + + Gdk_Window window = get_window(); + window.draw_rectangle(bg_gc, true, 0, 0, width(), height()); + + Gdk_Font font = fg_gc.get_font(); + int text_ascent = font.ascent(); + + int num_guide_bars = _chart->get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + const PStatStripChart::GuideBar &bar = _chart->get_guide_bar(i); + int y = _chart->height_to_pixel(bar._height); + + if (y >= 5) { + // Only draw it if it's not too close to the top. + window.draw_string(font, fg_gc, 0, y + text_ascent / 2, + bar._label); + } + } + + return false; +} diff --git a/pandatool/src/gtk-stats/gtkStatsGuide.h b/pandatool/src/gtk-stats/gtkStatsGuide.h new file mode 100644 index 0000000000..cca31f6182 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsGuide.h @@ -0,0 +1,34 @@ +// Filename: gtkStatsGuide.h +// Created by: drose (16Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSGUIDE_H +#define GTKSTATSGUIDE_H + +#include + +#include + +class PStatStripChart; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsGuide +// Description : A widget designed to be drawn next to a +// GtkStatsStripChart that shows the labels associated +// with the strip chart's guide bars. +//////////////////////////////////////////////////////////////////// +class GtkStatsGuide : public Gtk::DrawingArea { +public: + GtkStatsGuide(PStatStripChart *chart); + +private: + virtual gint configure_event_impl(GdkEventConfigure *event); + virtual gint expose_event_impl(GdkEventExpose *event); + +private: + PStatStripChart *_chart; +}; + +#endif + diff --git a/pandatool/src/gtk-stats/gtkStatsLabel.cxx b/pandatool/src/gtk-stats/gtkStatsLabel.cxx new file mode 100644 index 0000000000..66e263749f --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsLabel.cxx @@ -0,0 +1,129 @@ +// Filename: gtkStatsLabel.cxx +// Created by: drose (15Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsLabel.h" +#include "gtkStatsMonitor.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsLabel::Constructor +// Access: Public +// Description: This constructor automatically figures out the +// appropriate name and color for the label. +//////////////////////////////////////////////////////////////////// +GtkStatsLabel:: +GtkStatsLabel(PStatMonitor *monitor, int collector_index, + Gdk_Font font) : + _collector_index(collector_index), + _font(font) +{ + set_events(GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK); + + _text = monitor->get_client_data()->get_collector_name(_collector_index); + RGBColorf rgb = monitor->get_collector_color(_collector_index); + _bg_color.set_rgb_p(rgb[0], rgb[1], rgb[2]); + + // Should our foreground be black or white? + double bright = + rgb[0] * 0.299 + + rgb[1] * 0.587 + + rgb[2] * 0.114; + + if (bright >= 0.5) { + _fg_color.set_rgb_p(0, 0, 0); + } else { + _fg_color.set_rgb_p(1, 1, 1); + } + + Gdk_Colormap::get_system().alloc(_fg_color); + Gdk_Colormap::get_system().alloc(_bg_color); + + int text_width = _font.string_width(_text); + int text_height = _font.height(); + + _height = text_height + 4; + _width = text_width + 4; + + set_usize(_width, _height); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsLabel::get_width +// Access: Public +// Description: Returns the width of the widget as we requested it. +//////////////////////////////////////////////////////////////////// +int GtkStatsLabel:: +get_width() const { + return _width; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsLabel::get_height +// Access: Public +// Description: Returns the height of the widget as we requested it. +//////////////////////////////////////////////////////////////////// +int GtkStatsLabel:: +get_height() const { + return _height; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsLabel::configure_event_impl +// Access: Private, Virtual +// Description: Creates a new backing pixmap of the appropriate size. +//////////////////////////////////////////////////////////////////// +gint GtkStatsLabel:: +configure_event_impl(GdkEventConfigure *) { + Gdk_Window window = get_window(); + + _gc = Gdk_GC(window); + _gc.set_foreground(_fg_color); + _gc.set_background(_bg_color); + _gc.set_font(_font); + + _reverse_gc = Gdk_GC(window); + _reverse_gc.set_foreground(_bg_color); + _reverse_gc.set_background(_fg_color); + _reverse_gc.set_font(_font); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsLabel::expose_event_impl +// Access: Private, Virtual +// Description: Redraw the text. We don't bother with clipping +// regions here, but just draw the whole text every +// time. +//////////////////////////////////////////////////////////////////// +gint GtkStatsLabel:: +expose_event_impl(GdkEventExpose *event) { + int text_width = _font.string_width(_text); + int text_height = _font.height(); + + Gdk_Window window = get_window(); + + window.draw_rectangle(_reverse_gc, true, 0, 0, width(), height()); + window.draw_string(_font, _gc, width() - text_width - 2, + height() - (height() - text_height) / 2 - _font.descent(), + _text); + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsLabel::button_press_event_impl +// Access: Private, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +gint GtkStatsLabel:: +button_press_event_impl(GdkEventButton *button) { + if (button->type == GDK_2BUTTON_PRESS && button->button == 1) { + collector_picked(_collector_index); + return true; + } + return false; +} diff --git a/pandatool/src/gtk-stats/gtkStatsLabel.h b/pandatool/src/gtk-stats/gtkStatsLabel.h new file mode 100644 index 0000000000..5c117df222 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsLabel.h @@ -0,0 +1,55 @@ +// Filename: gtkStatsLabel.h +// Created by: drose (15Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSLABEL_H +#define GTKSTATSLABEL_H + +#include + +#include + +class PStatMonitor; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsLabel +// Description : A text label that will draw in color appropriate for +// a particular collector, instead of referring to some +// dumb Gtk::Style. It also throws a signal when the +// user double-clicks on it, passing in the collector +// index. This is handy for putting colored labels on +// strip charts. +//////////////////////////////////////////////////////////////////// +class GtkStatsLabel : public Gtk::DrawingArea { +public: + GtkStatsLabel(PStatMonitor *monitor, int collector_index, + Gdk_Font font); + + int get_width() const; + int get_height() const; + + SigC::Signal1 collector_picked; + +private: + virtual gint configure_event_impl (GdkEventConfigure *event); + virtual gint expose_event_impl (GdkEventExpose *event); + virtual gint button_press_event_impl(GdkEventButton *button); + +private: + int _collector_index; + + string _text; + Gdk_Font _font; + Gdk_Color _fg_color; + Gdk_Color _bg_color; + + int _width; + int _height; + + Gdk_GC _gc; + Gdk_GC _reverse_gc; +}; + +#endif + diff --git a/pandatool/src/gtk-stats/gtkStatsMainWindow.cxx b/pandatool/src/gtk-stats/gtkStatsMainWindow.cxx new file mode 100644 index 0000000000..ec6770e8f8 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsMainWindow.cxx @@ -0,0 +1,128 @@ +// Filename: gtkStatsMainWindow.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsMainWindow.h" +#include "gtkStats.h" +#include "gtkStatsServer.h" + +#include + +#include + +static bool user_interrupted = false; + +// This simple signal handler lets us know when the user has pressed +// control-C, so we can clean up nicely. +static void signal_handler(int) { + user_interrupted = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMainWindow::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsMainWindow:: +GtkStatsMainWindow(int port) : _port(port) { + nassertv(GtkStats::_main_window == (GtkStatsMainWindow *)NULL); + GtkStats::_main_window = this; + + // Set up a global signal handler to catch Interrupt (Control-C) so + // we can clean up nicely if the user stops us. + signal(SIGINT, &signal_handler); + + _server = new GtkStatsServer; + if (!_server->listen(_port)) { + nout << "Unable to open port.\n"; + exit(1); + } + + layout_window(); + setup(); + + Gtk::Main::timeout. + connect(slot(this, &GtkStatsMainWindow::idle_callback), 200); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMainWindow::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsMainWindow:: +~GtkStatsMainWindow() { + nassertv(GtkStats::_main_window == this); + GtkStats::_main_window = NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMainWindow::destruct +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool GtkStatsMainWindow:: +destruct() { + if (BasicGtkWindow::destruct()) { + nassertr(_server != (GtkStatsServer *)NULL, false); + delete _server; + GtkStats::quit(); + } + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMainWindow::layout_window +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsMainWindow:: +layout_window() { + set_title("Gtk Stats"); + + Gtk::VBox *box1 = new Gtk::VBox; + box1->show(); + box1->set_border_width(8); + add(*manage(box1)); + + Gtk::Label *listening = + new Gtk::Label("Listening on port " + format_string(_port)); + listening->show(); + box1->pack_start(*manage(listening), true, false, 8); + + Gtk::HBox *box2 = new Gtk::HBox; + box2->show(); + box1->pack_start(*manage(box2), false, false, 0); + + Gtk::Button *close = new Gtk::Button("Close"); + close->set_usize(80, 30); + close->show(); + box2->pack_start(*manage(close), true, false, 0); + close->clicked.connect(slot(this, &GtkStatsMainWindow::close_clicked)); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMainWindow::close_clicked +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsMainWindow:: +close_clicked() { + destruct(); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMainWindow::idle_callback +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +gint GtkStatsMainWindow:: +idle_callback() { + if (user_interrupted) { + destruct(); + return false; + } + _server->poll(); + return true; +} diff --git a/pandatool/src/gtk-stats/gtkStatsMainWindow.h b/pandatool/src/gtk-stats/gtkStatsMainWindow.h new file mode 100644 index 0000000000..8a3bfaf2a6 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsMainWindow.h @@ -0,0 +1,38 @@ +// Filename: gtkStatsMainWindow.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSMAINWINDOW_H +#define GTKSTATSMAINWINDOW_H + +#include + +#include + +class GtkStatsServer; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsMainWindow +// Description : This is the main window that's opened up and stays up +// all the time when you run gtk-stats. It just shows +// that it's running. +//////////////////////////////////////////////////////////////////// +class GtkStatsMainWindow : public BasicGtkWindow { +public: + GtkStatsMainWindow(int port); + virtual ~GtkStatsMainWindow(); + virtual bool destruct(); + +private: + void layout_window(); + void close_clicked(); + gint idle_callback(); + + int _port; + GtkStatsServer *_server; +}; + + +#endif + diff --git a/pandatool/src/gtk-stats/gtkStatsMonitor.cxx b/pandatool/src/gtk-stats/gtkStatsMonitor.cxx new file mode 100644 index 0000000000..f8a10ba69a --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsMonitor.cxx @@ -0,0 +1,207 @@ +// Filename: gtkStatsMonitor.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsMonitor.h" +#include "gtkStatsWindow.h" +#include "gtkStatsStripWindow.h" + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsMonitor:: +GtkStatsMonitor() { + _destructing = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsMonitor:: +~GtkStatsMonitor() { + _destructing = true; + + Windows::iterator wi; + for (wi = _windows.begin(); wi != _windows.end(); ++wi) { + (*wi)->destruct(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::close_all_windows +// Access: Public +// Description: Closes all the windows associated with this client. +// This returns a PointerTo itself, just to guarantee +// that the monitor won't destruct until the function +// returns (as it might, if there were no other pointers +// to it). +//////////////////////////////////////////////////////////////////// +PT(PStatMonitor) GtkStatsMonitor:: +close_all_windows() { + PT(PStatMonitor) temp = this; + Windows::iterator wi; + for (wi = _windows.begin(); wi != _windows.end(); ++wi) { + (*wi)->destruct(); + } + return temp; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::get_monitor_name +// Access: Public, Virtual +// Description: Should be redefined to return a descriptive name for +// the type of PStatsMonitor this is. +//////////////////////////////////////////////////////////////////// +string GtkStatsMonitor:: +get_monitor_name() { + return "Gtk Stats"; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::initialized +// Access: Public, Virtual +// Description: Called after the monitor has been fully set up. At +// this time, it will have a valid _client_data pointer, +// and things like is_alive() and close() will be +// meaningful. However, we may not yet know who we're +// connected to (is_client_known() may return false), +// and we may not know anything about the threads or +// collectors we're about to get data on. +//////////////////////////////////////////////////////////////////// +void GtkStatsMonitor:: +initialized() { + // Create a default window: a strip chart for the main thread. + new GtkStatsStripWindow(this, 0, 0, 400, 100); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::got_hello +// Access: Public, Virtual +// Description: Called when the "hello" message has been received +// from the client. At this time, the client's hostname +// and program name will be known. +//////////////////////////////////////////////////////////////////// +void GtkStatsMonitor:: +got_hello() { + Windows::iterator wi; + for (wi = _windows.begin(); wi != _windows.end(); ++wi) { + (*wi)->update_title(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::new_data +// Access: Public, Virtual +// Description: Called as each frame's data is made available. There +// is no gurantee the frames will arrive in order, or +// that all of them will arrive at all. The monitor +// should be prepared to accept frames received +// out-of-order or missing. +//////////////////////////////////////////////////////////////////// +void GtkStatsMonitor:: +new_data(int thread_index, int frame_number) { +} + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::lost_connection +// Access: Public, Virtual +// Description: Called whenever the connection to the client has been +// lost. This is a permanent state change. The monitor +// should update its display to represent this, and may +// choose to close down automatically. +//////////////////////////////////////////////////////////////////// +void GtkStatsMonitor:: +lost_connection() { + Windows::iterator wi; + for (wi = _windows.begin(); wi != _windows.end(); ++wi) { + (*wi)->update_title(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::idle +// Access: Public, Virtual +// Description: If has_idle() returns true, this will be called +// periodically to allow the monitor to update its +// display or whatever it needs to do. +//////////////////////////////////////////////////////////////////// +void GtkStatsMonitor:: +idle() { + Windows::iterator wi; + for (wi = _windows.begin(); wi != _windows.end(); ++wi) { + (*wi)->idle(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::has_idle +// Access: Public, Virtual +// Description: Should be redefined to return true if you want to +// redefine idle() and expect it to be called. +//////////////////////////////////////////////////////////////////// +bool GtkStatsMonitor:: +has_idle() { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::is_thread_safe +// Access: Public, Virtual +// Description: Should be redefined to return true if this monitor +// class can handle running in a sub-thread. +// +// This is not related to the question of whether it can +// handle multiple different PStatThreadDatas; this is +// strictly a question of whether or not the monitor +// itself wants to run in a sub-thread. +//////////////////////////////////////////////////////////////////// +bool GtkStatsMonitor:: +is_thread_safe() { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::add_window +// Access: Public +// Description: Called only from the GtkStatsWindow constructor, this +// indicates a new window that we should track. +//////////////////////////////////////////////////////////////////// +void GtkStatsMonitor:: +add_window(GtkStatsWindow *window) { + nassertv(!_destructing); + bool inserted = _windows.insert(window).second; + nassertv(inserted); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsMonitor::remove_window +// Access: Public +// Description: Called only from the GtkStatsWindow destructor, this +// indicates the end of a window that we should now no +// longer track. +// +// When the last window is deleted, this automatically +// closes the connection. +//////////////////////////////////////////////////////////////////// +void GtkStatsMonitor:: +remove_window(GtkStatsWindow *window) { + if (!_destructing) { + bool removed = (_windows.erase(window) != 0); + nassertv(removed); + + if (_windows.empty()) { + close(); + } + } +} diff --git a/pandatool/src/gtk-stats/gtkStatsMonitor.h b/pandatool/src/gtk-stats/gtkStatsMonitor.h new file mode 100644 index 0000000000..399d27a463 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsMonitor.h @@ -0,0 +1,51 @@ +// Filename: gtkStatsMonitor.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSMONITOR_H +#define GTKSTATSMONITOR_H + +#include + +#include +#include + +#include + + +class GtkStatsWindow; +class Gdk_Color; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsMonitor +// Description : +//////////////////////////////////////////////////////////////////// +class GtkStatsMonitor : public PStatMonitor { +public: + GtkStatsMonitor(); + ~GtkStatsMonitor(); + + PT(PStatMonitor) close_all_windows(); + + virtual string get_monitor_name(); + + virtual void initialized(); + virtual void got_hello(); + virtual void new_data(int thread_index, int frame_number); + virtual void lost_connection(); + virtual void idle(); + virtual bool has_idle(); + virtual bool is_thread_safe(); + +public: + void add_window(GtkStatsWindow *window); + void remove_window(GtkStatsWindow *window); + + typedef set Windows; + Windows _windows; + + bool _destructing; +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsPianoRoll.I b/pandatool/src/gtk-stats/gtkStatsPianoRoll.I new file mode 100644 index 0000000000..167771bdb3 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsPianoRoll.I @@ -0,0 +1,4 @@ +// Filename: gtkStatsPianoRoll.I +// Created by: drose (18Jul00) +// +//////////////////////////////////////////////////////////////////// diff --git a/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx b/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx new file mode 100644 index 0000000000..976c1785b2 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx @@ -0,0 +1,274 @@ +// Filename: gtkStatsPianoRoll.cxx +// Created by: drose (18Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsPianoRoll.h" +#include "gtkStatsLabel.h" +#include "gtkStatsGuide.h" + +#include +#include +#include +#include + +#include + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsPianoRoll:: +GtkStatsPianoRoll(GtkStatsMonitor *monitor, int thread_index, + int xsize, int ysize) : + PStatPianoRoll(monitor, thread_index, xsize, ysize) +{ + set_events(GDK_EXPOSURE_MASK); + + _label_align = manage(new Gtk::Alignment(1.0, 1.0)); + _label_align->show(); + + _label_box = NULL; + pack_labels(); + + request_initial_size(*this, get_xsize(), get_ysize()); +} + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::get_labels +// Access: Public +// Description: Returns an alignment widget that contains all of the +// labels appropriate to this chart, already formatted +// and stacked up bottom-to-top. The window should pack +// this widget suitably near the strip chart. +//////////////////////////////////////////////////////////////////// +Gtk::Alignment *GtkStatsPianoRoll:: +get_labels() { + return _label_align; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::get_collector_gc +// Access: Public +// Description: Returns a graphics context suitable for drawing in +// the indicated collector's color. +//////////////////////////////////////////////////////////////////// +Gdk_GC GtkStatsPianoRoll:: +get_collector_gc(int collector_index) { + GCs::iterator gi; + gi = _gcs.find(collector_index); + if (gi != _gcs.end()) { + return (*gi).second; + } + + // Ask the monitor what color this guy should be. + RGBColorf rgb = get_monitor()->get_collector_color(collector_index); + Gdk_Color color; + color.set_rgb_p(rgb[0], rgb[1], rgb[2]); + + // Now allocate the color from the system colormap. + Gdk_Colormap::get_system().alloc(color); + + // Allocate a new graphics context. + Gdk_GC gc(_pixmap); + gc.set_foreground(color); + + _gcs[collector_index] = gc; + return gc; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::begin_draw +// Access: Protected, Virtual +// Description: Erases the chart area in preparation for drawing it +// full of bars. +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoRoll:: +begin_draw() { + _pixmap.draw_rectangle(_white_gc, true, 0, 0, get_xsize(), get_ysize()); + + Gdk_GC fg_gc = + get_style()->gtkobj()->fg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))]; + Gdk_Font font = fg_gc.get_font(); + int text_height = font.height(); + + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + const GuideBar &bar = get_guide_bar(i); + int x = (int)((double)get_xsize() * bar._height / get_horizontal_scale()); + + if (x >= 5 && x <= get_xsize() - 5) { + // Only draw it if it's not too close to either edge. + if (bar._is_target) { + _pixmap.draw_line(_light_gc, x, text_height + 4, x, get_ysize()); + } else { + _pixmap.draw_line(_dark_gc, x, text_height + 4, x, get_ysize()); + } + } + } + +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::draw_bar +// Access: Protected, Virtual +// Description: Draws a single bar on the chart. +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoRoll:: +draw_bar(int row, int from_x, int to_x) { + if (row >= 0 && row < (int)_y_positions.size()) { + int y = height() - _y_positions[row]; + _pixmap.draw_rectangle(get_collector_gc(get_label_collector(row)), + true, from_x, y - 6, to_x - from_x, 12); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::end_draw +// Access: Protected, Virtual +// Description: Called after all the bars have been drawn, this +// triggers a refresh event to draw it to the window. +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoRoll:: +end_draw() { + // Draw in the labels for the guide bars. We do this in end_draw() + // instead of in begin_draw() so the labels will appear on top of + // any of the color bars. + Gdk_GC fg_gc = + get_style()->gtkobj()->fg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))]; + Gdk_Font font = fg_gc.get_font(); + int text_ascent = font.ascent(); + + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + const GuideBar &bar = get_guide_bar(i); + int x = (int)((double)get_xsize() * bar._height / get_horizontal_scale()); + + if (x >= 5 && x <= get_xsize() - 5) { + // Only draw it if it's not too close to either edge. + int width = font.string_measure(bar._label); + _pixmap.draw_string(font, _black_gc, x - width / 2, text_ascent + 2, + bar._label); + } + } + + GdkRectangle update_rect; + update_rect.x = 0; + update_rect.y = 0; + update_rect.width = get_xsize(); + update_rect.height = get_ysize(); + draw(&update_rect); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::idle +// Access: Protected, Virtual +// Description: Called at the end of the draw cycle. +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoRoll:: +idle() { + if (_labels_changed) { + pack_labels(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::configure_event_impl +// Access: Private, Virtual +// Description: Creates a new backing pixmap of the appropriate size. +//////////////////////////////////////////////////////////////////// +gint GtkStatsPianoRoll:: +configure_event_impl(GdkEventConfigure *) { + if (width() != get_xsize() || height() != get_ysize() || + _pixmap.gdkobj() == (GdkDrawable *)NULL) { + if (_pixmap) { + _pixmap.release(); + } + + _pixmap.create(get_window(), width(), height()); + + Gdk_Colormap system_colormap = Gdk_Colormap::get_system(); + + _white_gc = Gdk_GC(_pixmap); + _white_gc.set_foreground(system_colormap.white()); + _black_gc = Gdk_GC(_pixmap); + _black_gc.set_foreground(system_colormap.black()); + + _dark_gc = Gdk_GC(_pixmap); + Gdk_Color dark; + dark.set_grey_p(0.2); + system_colormap.alloc(dark); + _dark_gc.set_foreground(dark); + + _light_gc = Gdk_GC(_pixmap); + Gdk_Color light; + light.set_grey_p(0.6); + system_colormap.alloc(light); + _light_gc.set_foreground(light); + + _pixmap.draw_rectangle(_white_gc, true, 0, 0, width(), height()); + + changed_size(width(), height()); + } + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::expose_event_impl +// Access: Private, Virtual +// Description: Redraw the screen from the backing pixmap. +//////////////////////////////////////////////////////////////////// +gint GtkStatsPianoRoll:: +expose_event_impl(GdkEventExpose *event) { + get_window().draw_pixmap(_white_gc, _pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoRoll::pack_labels +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoRoll:: +pack_labels() { + // First, remove the old labels. + _label_align->remove(); + + // Now add the new labels back in. + _label_box = manage(new Gtk::VBox); + _label_box->show(); + _label_align->add(*_label_box); + + Gdk_GC window_gc = + get_style()->gtkobj()->fg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))]; + Gdk_Font font = window_gc.get_font(); + + int num_labels = get_num_labels(); + + while (_y_positions.size() < num_labels) { + _y_positions.push_back(0); + } + + int y = 0; + for (int i = 0; i < num_labels; i++) { + int collector_index = get_label_collector(i); + GtkStatsLabel *label = + new GtkStatsLabel(get_monitor(), collector_index, font); + label->show(); + + _label_box->pack_end(*manage(label), false, false); + _y_positions[i] = y + label->get_height() / 2; + + y += label->get_height(); + } + + _labels_changed = false; +} diff --git a/pandatool/src/gtk-stats/gtkStatsPianoRoll.h b/pandatool/src/gtk-stats/gtkStatsPianoRoll.h new file mode 100644 index 0000000000..ff8eacb9ad --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsPianoRoll.h @@ -0,0 +1,75 @@ +// Filename: gtkStatsPianoRoll.h +// Created by: drose (18Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSPIANOROLL_H +#define GTKSTATSPIANOROLL_H + +#include + +#include "gtkStatsMonitor.h" + +#include +#include + +#include +#include + +class PStatView; +class GtkStatsGuide; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsPianoRoll +// Description : A special widget that draws a piano-roll style chart, +// which shows the collectors explicitly stopping and +// starting, one frame at a time. +//////////////////////////////////////////////////////////////////// +class GtkStatsPianoRoll : public Gtk::DrawingArea, public PStatPianoRoll { +public: + GtkStatsPianoRoll(GtkStatsMonitor *monitor, int thread_index, + int xsize, int ysize); + + Gtk::Alignment *get_labels(); + + Gdk_GC get_collector_gc(int collector_index); + +private: + virtual void begin_draw(); + virtual void draw_bar(int row, int from_x, int to_x); + virtual void end_draw(); + virtual void idle(); + + virtual gint configure_event_impl(GdkEventConfigure *event); + virtual gint expose_event_impl(GdkEventExpose *event); + + void pack_labels(); + +private: + // Backing pixmap for drawing area. + Gdk_Pixmap _pixmap; + + // Graphics contexts for fg/bg. We don't use the contexts defined + // in the style, because that would probably interfere with the + // visibility of the chart. + Gdk_GC _white_gc; + Gdk_GC _black_gc; + Gdk_GC _dark_gc; + Gdk_GC _light_gc; + + // Table of graphics contexts for our various collectors. + typedef map GCs; + GCs _gcs; + + // Table of Y-positions for each of our rows, measured from the + // bottom. + vector_int _y_positions; + + Gtk::Alignment *_label_align; + Gtk::VBox *_label_box; +}; + +#include "gtkStatsPianoRoll.I" + +#endif + diff --git a/pandatool/src/gtk-stats/gtkStatsPianoWindow.cxx b/pandatool/src/gtk-stats/gtkStatsPianoWindow.cxx new file mode 100644 index 0000000000..bd31fca42b --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsPianoWindow.cxx @@ -0,0 +1,137 @@ +// Filename: gtkStatsPianoWindow.cxx +// Created by: drose (18Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsPianoWindow.h" +#include "gtkStatsPianoRoll.h" + +using Gtk::Menu_Helpers::MenuElem; +using Gtk::Menu_Helpers::SeparatorElem; + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoWindow::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsPianoWindow:: +GtkStatsPianoWindow(GtkStatsMonitor *monitor, int thread_index, + int chart_xsize, int chart_ysize) : + GtkStatsWindow(monitor), + _thread_index(thread_index) +{ + setup_menu(); + layout_window(chart_xsize, chart_ysize); + show(); +} + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoWindow::idle +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoWindow:: +idle() { + _chart->update(); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoWindow::setup_menu +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoWindow:: +setup_menu() { + GtkStatsWindow::setup_menu(); + + Gtk::Menu *scale_menu = new Gtk::Menu; + + scale_menu->items().push_back + (MenuElem("0.1 Hz", + bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 0.1))); + scale_menu->items().push_back + (MenuElem("1 Hz", + bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 1.0))); + scale_menu->items().push_back + (MenuElem("5 Hz", + bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 5.0))); + scale_menu->items().push_back + (MenuElem("10 Hz", + bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 10.0))); + scale_menu->items().push_back + (MenuElem("20 Hz", + bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 20.0))); + scale_menu->items().push_back + (MenuElem("30 Hz", + bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 30.0))); + scale_menu->items().push_back + (MenuElem("60 Hz", + bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 60.0))); + scale_menu->items().push_back + (MenuElem("120 Hz", + bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 120.0))); + + _menu->items().push_back(MenuElem("Scale", *manage(scale_menu))); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoWindow::menu_new_window +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoWindow:: +menu_new_window() { + new GtkStatsPianoWindow(_monitor, _thread_index, + _chart->get_xsize(), _chart->get_ysize()); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoWindow::menu_hscale +// Access: Protected +// Description: Selects a new horizontal scale for the piano roll. +// This is done from the menu called "Scale". +// +// The units is in Hz. +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoWindow:: +menu_hscale(double hz) { + _chart->set_horizontal_scale(1.0 / hz); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsPianoWindow::layout_window +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsPianoWindow:: +layout_window(int chart_xsize, int chart_ysize) { + Gtk::HBox *hbox = new Gtk::HBox; + hbox = new Gtk::HBox; + hbox->show(); + _main_box->pack_start(*manage(hbox), true, true, 8); + + Gtk::Table *chart_table = new Gtk::Table(1, 2); + chart_table->show(); + hbox->pack_start(*manage(chart_table), true, true, 8); + + Gtk::Frame *frame = new Gtk::Frame; + frame->set_shadow_type(GTK_SHADOW_ETCHED_OUT); + frame->show(); + chart_table->attach(*manage(frame), 1, 2, 0, 1); + + _chart = new GtkStatsPianoRoll(_monitor, _thread_index, + chart_xsize, chart_ysize); + frame->add(*manage(_chart)); + + // We put the labels in a frame, too, so they'll line up vertically. + Gtk::Frame *label_frame = new Gtk::Frame; + label_frame->set_shadow_type(GTK_SHADOW_NONE); + label_frame->show(); + label_frame->add(*manage(_chart->get_labels())); + + chart_table->attach(*manage(label_frame), 0, 1, 0, 1, + 0, (GTK_FILL|GTK_EXPAND), 4, 0); + + _chart->show(); +} diff --git a/pandatool/src/gtk-stats/gtkStatsPianoWindow.h b/pandatool/src/gtk-stats/gtkStatsPianoWindow.h new file mode 100644 index 0000000000..1940253e61 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsPianoWindow.h @@ -0,0 +1,43 @@ +// Filename: gtkStatsPianoWindow.h +// Created by: drose (18Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSPIANOWINDOW_H +#define GTKSTATSPIANOWINDOW_H + +#include + +#include "gtkStatsMonitor.h" +#include "gtkStatsWindow.h" + +class GtkStatsPianoRoll; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsPianoWindow +// Description : A window that contains a GtkStatsPianoRoll. +//////////////////////////////////////////////////////////////////// +class GtkStatsPianoWindow : public GtkStatsWindow { +public: + GtkStatsPianoWindow(GtkStatsMonitor *monitor, int thread_index, + int chart_xsize, int chart_ysize); + + virtual void idle(); + +protected: + virtual void setup_menu(); + virtual void menu_new_window(); + void menu_hscale(double hz); + +private: + void layout_window(int chart_xsize, int chart_ysize); + +private: + int _thread_index; + + GtkStatsPianoRoll *_chart; +}; + + +#endif + diff --git a/pandatool/src/gtk-stats/gtkStatsServer.cxx b/pandatool/src/gtk-stats/gtkStatsServer.cxx new file mode 100644 index 0000000000..0f9f620b4b --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsServer.cxx @@ -0,0 +1,18 @@ +// Filename: gtkStatsServer.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsServer.h" +#include "gtkStatsMonitor.h" + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsServer::make_monitor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatMonitor *GtkStatsServer:: +make_monitor() { + return new GtkStatsMonitor; +} diff --git a/pandatool/src/gtk-stats/gtkStatsServer.h b/pandatool/src/gtk-stats/gtkStatsServer.h new file mode 100644 index 0000000000..9899bd1b72 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsServer.h @@ -0,0 +1,23 @@ +// Filename: gtkStatsServer.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSSERVER_H +#define GTKSTATSSERVER_H + +#include + +#include + + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsServer +// Description : +//////////////////////////////////////////////////////////////////// +class GtkStatsServer : public PStatServer { +public: + virtual PStatMonitor *make_monitor(); +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsStripChart.I b/pandatool/src/gtk-stats/gtkStatsStripChart.I new file mode 100644 index 0000000000..7ef568f109 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsStripChart.I @@ -0,0 +1,4 @@ +// Filename: gtkStatsStripChart.I +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// diff --git a/pandatool/src/gtk-stats/gtkStatsStripChart.cxx b/pandatool/src/gtk-stats/gtkStatsStripChart.cxx new file mode 100644 index 0000000000..89bd0ea7e0 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsStripChart.cxx @@ -0,0 +1,350 @@ +// Filename: gtkStatsStripChart.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsStripChart.h" +#include "gtkStatsLabel.h" +#include "gtkStatsGuide.h" + +#include +#include +#include +#include + +#include + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsStripChart:: +GtkStatsStripChart(GtkStatsMonitor *monitor, PStatView &view, + int collector_index, int xsize, int ysize) : + PStatStripChart(monitor, view, collector_index, xsize, ysize) +{ + set_events(GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK); + + _label_align = manage(new Gtk::Alignment(1.0, 1.0)); + _label_align->show(); + + _label_box = NULL; + pack_labels(); + + _guide = manage(new GtkStatsGuide(this)); + _guide->show(); + + request_initial_size(*this, get_xsize(), get_ysize()); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::get_labels +// Access: Public +// Description: Returns an alignment widget that contains all of the +// labels appropriate to this chart, already formatted +// and stacked up bottom-to-top. The window should pack +// this widget suitably near the strip chart. +//////////////////////////////////////////////////////////////////// +Gtk::Alignment *GtkStatsStripChart:: +get_labels() { + return _label_align; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::get_guide +// Access: Public +// Description: Returns a widget that contains the numeric labels for +// the guide bars. The window should pack this widget +// suitably near the strip chart. +//////////////////////////////////////////////////////////////////// +GtkStatsGuide *GtkStatsStripChart:: +get_guide() { + return _guide; +} + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::get_collector_gc +// Access: Public +// Description: Returns a graphics context suitable for drawing in +// the indicated collector's color. +//////////////////////////////////////////////////////////////////// +Gdk_GC GtkStatsStripChart:: +get_collector_gc(int collector_index) { + GCs::iterator gi; + gi = _gcs.find(collector_index); + if (gi != _gcs.end()) { + return (*gi).second; + } + + // Ask the monitor what color this guy should be. + RGBColorf rgb = get_monitor()->get_collector_color(collector_index); + Gdk_Color color; + color.set_rgb_p(rgb[0], rgb[1], rgb[2]); + + // Now allocate the color from the system colormap. + Gdk_Colormap::get_system().alloc(color); + + // Allocate a new graphics context. + Gdk_GC gc(_pixmap); + gc.set_foreground(color); + + _gcs[collector_index] = gc; + return gc; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::clear_region +// Access: Protected, Virtual +// Description: Erases the chart area. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripChart:: +clear_region() { + _pixmap.draw_rectangle(_white_gc, true, 0, 0, get_xsize(), get_ysize()); + end_draw(0, get_xsize()); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::copy_region +// Access: Protected, Virtual +// Description: Should be overridden by the user class to copy a +// region of the chart from one part of the chart to +// another. This is used to implement scrolling. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripChart:: +copy_region(int start_x, int end_x, int dest_x) { + _pixmap.copy_area(_white_gc, 0, 0, + _pixmap, start_x, 0, + end_x - start_x + 1, get_ysize()); + + // We could make a window-to-window copy to implement scrolling in + // the window. But this leads to trouble if the scrolling window + // isn't on top. Instead, we'll just do the scroll in the pixmap, + // and then blt the pixmap back out--in principle, this ought to be + // just as fast. + /* + Gdk_Window window = get_window(); + window.copy_area(_white_gc, 0, 0, + window, start_x, 0, + end_x - start_x + 1, get_ysize()); + */ + + GdkRectangle update_rect; + update_rect.x = dest_x; + update_rect.y = 0; + update_rect.width = end_x - start_x + 1; + update_rect.height = get_ysize(); + draw(&update_rect); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::draw_slice +// Access: Protected, Virtual +// Description: Draws a single vertical slice of the strip chart, at +// the given pixel position, and corresponding to the +// indicated level data. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripChart:: +draw_slice(int x, int frame_number) { + const FrameData &frame = get_frame_data(frame_number); + + // Start by clearing the band first. + _pixmap.draw_line(_white_gc, x, 0, x, get_ysize()); + + double overall_time = 0.0; + int y = get_ysize(); + + FrameData::const_iterator fi; + for (fi = frame.begin(); fi != frame.end(); ++fi) { + const ColorData &cd = (*fi); + overall_time += cd._net_time; + int top_y = height_to_pixel(overall_time); + _pixmap.draw_line(get_collector_gc(cd._collector_index), x, y, x, top_y); + y = top_y; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::draw_empty +// Access: Protected, Virtual +// Description: Draws a single vertical slice of background color. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripChart:: +draw_empty(int x) { + _pixmap.draw_line(_white_gc, x, 0, x, get_ysize()); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::draw_cursor +// Access: Protected, Virtual +// Description: Draws a single vertical slice of foreground color. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripChart:: +draw_cursor(int x) { + _pixmap.draw_line(_black_gc, x, 0, x, get_ysize()); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::end_draw +// Access: Protected, Virtual +// Description: Should be overridden by the user class. This hook +// will be called after drawing a series of color bars +// in the strip chart; it gives the pixel range that +// was just redrawn. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripChart:: +end_draw(int from_x, int to_x) { + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + const GuideBar &bar = get_guide_bar(i); + int y = height_to_pixel(bar._height); + + if (y >= 5) { + // Only draw it if it's not too close to the top. + if (bar._is_target) { + _pixmap.draw_line(_light_gc, from_x, y, to_x, y); + } else { + _pixmap.draw_line(_dark_gc, from_x, y, to_x, y); + } + } + } + + GdkRectangle update_rect; + update_rect.x = from_x; + update_rect.y = 0; + update_rect.width = to_x - from_x + 1; + update_rect.height = get_ysize(); + draw(&update_rect); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::idle +// Access: Protected, Virtual +// Description: Called at the end of the draw cycle. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripChart:: +idle() { + if (_labels_changed) { + pack_labels(); + } + if (_guide_bars_changed) { + GdkRectangle update_rect; + update_rect.x = 0; + update_rect.y = 0; + update_rect.width = _guide->width(); + update_rect.height = _guide->height(); + _guide->draw(&update_rect); + _guide_bars_changed = false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::configure_event_impl +// Access: Private, Virtual +// Description: Creates a new backing pixmap of the appropriate size. +//////////////////////////////////////////////////////////////////// +gint GtkStatsStripChart:: +configure_event_impl(GdkEventConfigure *) { + if (width() != get_xsize() || height() != get_ysize() || + _pixmap.gdkobj() == (GdkDrawable *)NULL) { + bool is_initial = true; + if (_pixmap) { + is_initial = false; + _pixmap.release(); + } + + _pixmap.create(get_window(), width(), height()); + + Gdk_Colormap system_colormap = Gdk_Colormap::get_system(); + + _white_gc = Gdk_GC(_pixmap); + _white_gc.set_foreground(system_colormap.white()); + _black_gc = Gdk_GC(_pixmap); + _black_gc.set_foreground(system_colormap.black()); + + _dark_gc = Gdk_GC(_pixmap); + Gdk_Color dark; + dark.set_grey_p(0.2); + system_colormap.alloc(dark); + _dark_gc.set_foreground(dark); + + _light_gc = Gdk_GC(_pixmap); + Gdk_Color light; + light.set_grey_p(0.6); + system_colormap.alloc(light); + _light_gc.set_foreground(light); + + _pixmap.draw_rectangle(_white_gc, true, 0, 0, width(), height()); + + changed_size(width(), height()); + } + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::expose_event_impl +// Access: Private, Virtual +// Description: Redraw the screen from the backing pixmap. +//////////////////////////////////////////////////////////////////// +gint GtkStatsStripChart:: +expose_event_impl(GdkEventExpose *event) { + get_window().draw_pixmap(_white_gc, _pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::button_press_event_impl +// Access: Private, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +gint GtkStatsStripChart:: +button_press_event_impl(GdkEventButton *button) { + if (button->type == GDK_2BUTTON_PRESS && button->button == 1) { + int collector_index = get_collector_under_pixel(button->x, button->y); + collector_picked(collector_index); + return true; + } + return false; +} + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripChart::pack_labels +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsStripChart:: +pack_labels() { + // First, remove the old labels. + _label_align->remove(); + + // Now add the new labels back in. + _label_box = manage(new Gtk::VBox); + _label_box->show(); + _label_align->add(*_label_box); + + Gdk_GC window_gc = + get_style()->gtkobj()->fg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))]; + Gdk_Font font = window_gc.get_font(); + + int num_labels = get_num_labels(); + for (int i = 0; i < num_labels; i++) { + int collector_index = get_label_collector(i); + GtkStatsLabel *label = + new GtkStatsLabel(get_monitor(), collector_index, font); + label->show(); + + label->collector_picked.connect(collector_picked.slot()); + + _label_box->pack_end(*manage(label), false, false); + } + + _labels_changed = false; +} diff --git a/pandatool/src/gtk-stats/gtkStatsStripChart.h b/pandatool/src/gtk-stats/gtkStatsStripChart.h new file mode 100644 index 0000000000..791505101f --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsStripChart.h @@ -0,0 +1,81 @@ +// Filename: gtkStatsStripChart.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSSTRIPCHART_H +#define GTKSTATSSTRIPCHART_H + +#include + +#include "gtkStatsMonitor.h" + +#include +#include + +#include +#include + +class PStatView; +class GtkStatsGuide; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsStripChart +// Description : A special widget that draws a strip chart, given a +// view. +//////////////////////////////////////////////////////////////////// +class GtkStatsStripChart : public Gtk::DrawingArea, public PStatStripChart { +public: + GtkStatsStripChart(GtkStatsMonitor *monitor, + PStatView &view, int collector_index, + int xsize, int ysize); + + Gtk::Alignment *get_labels(); + GtkStatsGuide *get_guide(); + + Gdk_GC get_collector_gc(int collector_index); + + // This signal is thrown when the user double-clicks on a label or + // on a band of color. + SigC::Signal1 collector_picked; + +private: + virtual void clear_region(); + virtual void copy_region(int start_x, int end_x, int dest_x); + virtual void draw_slice(int x, int frame_number); + virtual void draw_empty(int x); + virtual void draw_cursor(int x); + virtual void end_draw(int from_x, int to_x); + virtual void idle(); + + virtual gint configure_event_impl(GdkEventConfigure *event); + virtual gint expose_event_impl(GdkEventExpose *event); + virtual gint button_press_event_impl(GdkEventButton *button); + + void pack_labels(); + +private: + // Backing pixmap for drawing area. + Gdk_Pixmap _pixmap; + + // Graphics contexts for fg/bg. We don't use the contexts defined + // in the style, because that would probably interfere with the + // visibility of the strip chart. + Gdk_GC _white_gc; + Gdk_GC _black_gc; + Gdk_GC _dark_gc; + Gdk_GC _light_gc; + + // Table of graphics contexts for our various collectors. + typedef map GCs; + GCs _gcs; + + Gtk::Alignment *_label_align; + Gtk::VBox *_label_box; + GtkStatsGuide *_guide; +}; + +#include "gtkStatsStripChart.I" + +#endif + diff --git a/pandatool/src/gtk-stats/gtkStatsStripWindow.cxx b/pandatool/src/gtk-stats/gtkStatsStripWindow.cxx new file mode 100644 index 0000000000..e6dd717023 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsStripWindow.cxx @@ -0,0 +1,259 @@ +// Filename: gtkStatsStripWindow.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsStripWindow.h" +#include "gtkStatsStripChart.h" +#include "gtkStatsGuide.h" + +#include +#include // for sprintf + + +using Gtk::Menu_Helpers::MenuElem; +using Gtk::Menu_Helpers::SeparatorElem; + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsStripWindow:: +GtkStatsStripWindow(GtkStatsMonitor *monitor, int thread_index, + int collector_index, int chart_xsize, int chart_ysize) : + GtkStatsWindow(monitor), + _thread_index(thread_index), + _collector_index(collector_index) +{ + _title_unknown = false; + + setup_menu(); + layout_window(chart_xsize, chart_ysize); + show(); +} + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::idle +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsStripWindow:: +idle() { + _chart->update(); + + const PStatThreadData *thread_data = _chart->get_view().get_thread_data(); + if (!thread_data->is_empty()) { + double frame_rate = thread_data->get_frame_rate(); + char buffer[128]; + sprintf(buffer, "Frame rate: %0.1f Hz", frame_rate); + _frame_rate_label->set_text(buffer); + } + + if (_title_unknown) { + _title_label->set_text(get_title_text()); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::setup_menu +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsStripWindow:: +setup_menu() { + GtkStatsWindow::setup_menu(); + + Gtk::Menu *speed_menu = new Gtk::Menu; + + speed_menu->items().push_back + (MenuElem("1", // 1 chart width scrolls by per minute. + bind(slot(this, &GtkStatsStripWindow::menu_hscale), 1.0))); + speed_menu->items().push_back + (MenuElem("2", // 2 chart widths scroll by per minute. + bind(slot(this, &GtkStatsStripWindow::menu_hscale), 2.0))); + speed_menu->items().push_back + (MenuElem("3", + bind(slot(this, &GtkStatsStripWindow::menu_hscale), 3.0))); + speed_menu->items().push_back + (MenuElem("6", + bind(slot(this, &GtkStatsStripWindow::menu_hscale), 6.0))); + speed_menu->items().push_back + (MenuElem("12", + bind(slot(this, &GtkStatsStripWindow::menu_hscale), 12.0))); + + _menu->items().push_back(MenuElem("Speed", *manage(speed_menu))); + + + Gtk::Menu *scale_menu = new Gtk::Menu; + + scale_menu->items().push_back + (MenuElem("0.1 Hz", + bind(slot(this, &GtkStatsStripWindow::menu_vscale), 0.1))); + scale_menu->items().push_back + (MenuElem("1 Hz", + bind(slot(this, &GtkStatsStripWindow::menu_vscale), 1.0))); + scale_menu->items().push_back + (MenuElem("5 Hz", + bind(slot(this, &GtkStatsStripWindow::menu_vscale), 5.0))); + scale_menu->items().push_back + (MenuElem("10 Hz", + bind(slot(this, &GtkStatsStripWindow::menu_vscale), 10.0))); + scale_menu->items().push_back + (MenuElem("20 Hz", + bind(slot(this, &GtkStatsStripWindow::menu_vscale), 20.0))); + scale_menu->items().push_back + (MenuElem("30 Hz", + bind(slot(this, &GtkStatsStripWindow::menu_vscale), 30.0))); + scale_menu->items().push_back + (MenuElem("60 Hz", + bind(slot(this, &GtkStatsStripWindow::menu_vscale), 60.0))); + scale_menu->items().push_back + (MenuElem("120 Hz", + bind(slot(this, &GtkStatsStripWindow::menu_vscale), 120.0))); + + _menu->items().push_back(MenuElem("Scale", *manage(scale_menu))); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::menu_new_window +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsStripWindow:: +menu_new_window() { + new GtkStatsStripWindow(_monitor, _thread_index, _collector_index, + _chart->get_xsize(), _chart->get_ysize()); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::menu_hscale +// Access: Protected +// Description: Selects a new horizontal scale for the strip chart. +// This is done from the menu called "Speed", since +// changing the horizontal scale most obviously affects +// the scrolling speed. +// +// The units is in chart width per minute. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripWindow:: +menu_hscale(double wpm) { + _chart->set_horizontal_scale(60.0 / wpm); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::menu_vscale +// Access: Protected +// Description: Selects a new vertical scale for the strip chart. +// This is done from the menu called "Scale". +// +// The units is in Hz. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripWindow:: +menu_vscale(double hz) { + _chart->set_vertical_scale(1.0 / hz); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::open_subchart +// Access: Protected +// Description: This is called in response to the collector_picked +// signal from the strip chart, which is generated when +// the user double-clicks on a band of color or a label. +// +// This opens up a new window focusing just on the +// indicated collector. +//////////////////////////////////////////////////////////////////// +void GtkStatsStripWindow:: +open_subchart(int collector_index) { + new GtkStatsStripWindow(_monitor, _thread_index, collector_index, + _chart->get_xsize(), _chart->get_ysize()); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::layout_window +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsStripWindow:: +layout_window(int chart_xsize, int chart_ysize) { + Gtk::HBox *hbox = new Gtk::HBox; + hbox = new Gtk::HBox; + hbox->show(); + _main_box->pack_start(*manage(hbox), true, true, 8); + + Gtk::Table *chart_table = new Gtk::Table(3, 2); + chart_table->show(); + hbox->pack_start(*manage(chart_table), true, true, 8); + + Gtk::HBox *title_hbox = new Gtk::HBox; + title_hbox->show(); + chart_table->attach(*manage(title_hbox), 1, 2, 0, 1, + (GTK_FILL|GTK_EXPAND), 0); + + _title_label = new Gtk::Label(get_title_text()); + if (_collector_index != 0 || _thread_index != 0) { + _title_label->show(); + _title_label->set_alignment(0.0, 0.5); + title_hbox->pack_start(*manage(_title_label), true, true); + } + + _frame_rate_label = new Gtk::Label; + if (_collector_index == 0) { + _frame_rate_label->show(); + _frame_rate_label->set_alignment(1.0, 0.5); + title_hbox->pack_start(*manage(_frame_rate_label), true, true); + } + + Gtk::Frame *frame = new Gtk::Frame; + frame->set_shadow_type(GTK_SHADOW_ETCHED_OUT); + frame->show(); + chart_table->attach(*manage(frame), 1, 2, 1, 2); + + _chart = new GtkStatsStripChart(_monitor, + _monitor->get_view(_thread_index), + _collector_index, + chart_xsize, chart_ysize); + _chart->collector_picked. + connect(slot(this, &GtkStatsStripWindow::open_subchart)); + frame->add(*manage(_chart)); + + chart_table->attach(*_chart->get_labels(), 0, 1, 1, 2, + 0, (GTK_FILL|GTK_EXPAND), 4, 0); + chart_table->attach(*_chart->get_guide(), 2, 3, 1, 2, + 0, (GTK_FILL|GTK_EXPAND), 4, 0); + _chart->show(); +} + + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsStripWindow::get_title_text +// Access: Private +// Description: Returns the text suitable for the title label on the +// top line. +//////////////////////////////////////////////////////////////////// +string GtkStatsStripWindow:: +get_title_text() { + string text; + + _title_unknown = false; + + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data->has_collector(_collector_index)) { + text = client_data->get_collector_name(_collector_index) + " time"; + } else { + _title_unknown = true; + } + + if (_thread_index != 0) { + if (client_data->has_thread(_thread_index)) { + text += "(" + client_data->get_thread_name(_thread_index) + " thread)"; + } else { + _title_unknown = true; + } + } + + return text; +} + diff --git a/pandatool/src/gtk-stats/gtkStatsStripWindow.h b/pandatool/src/gtk-stats/gtkStatsStripWindow.h new file mode 100644 index 0000000000..4e2985bfd8 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsStripWindow.h @@ -0,0 +1,51 @@ +// Filename: gtkStatsStripWindow.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSSTRIPWINDOW_H +#define GTKSTATSSTRIPWINDOW_H + +#include + +#include "gtkStatsMonitor.h" +#include "gtkStatsWindow.h" + +class GtkStatsStripChart; + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsStripWindow +// Description : A window that contains your basic one-thread, +// one-level strip chart. +//////////////////////////////////////////////////////////////////// +class GtkStatsStripWindow : public GtkStatsWindow { +public: + GtkStatsStripWindow(GtkStatsMonitor *monitor, int thread_index, + int collector_index, int chart_xsize, int chart_ysize); + + virtual void idle(); + +protected: + virtual void setup_menu(); + virtual void menu_new_window(); + void menu_hscale(double wpm); + void menu_vscale(double hz); + void open_subchart(int collector_index); + +private: + void layout_window(int chart_xsize, int chart_ysize); + string get_title_text(); + +private: + int _thread_index; + int _collector_index; + bool _title_unknown; + + Gtk::Label *_title_label; + Gtk::Label *_frame_rate_label; + GtkStatsStripChart *_chart; +}; + + +#endif + diff --git a/pandatool/src/gtk-stats/gtkStatsWindow.cxx b/pandatool/src/gtk-stats/gtkStatsWindow.cxx new file mode 100644 index 0000000000..4c1ac90fe7 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsWindow.cxx @@ -0,0 +1,191 @@ +// Filename: gtkStatsWindow.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkStatsWindow.h" +#include "gtkStatsMonitor.h" +#include "gtkStatsStripWindow.h" +#include "gtkStatsPianoWindow.h" + +using Gtk::Menu_Helpers::MenuElem; +using Gtk::Menu_Helpers::SeparatorElem; + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkStatsWindow:: +GtkStatsWindow(GtkStatsMonitor *monitor) : _monitor(monitor) { + _monitor->add_window(this); + update_title(); + setup(); + + _main_box = new Gtk::VBox; + _main_box->show(); + add(*manage(_main_box)); + + _menu = manage(new Gtk::MenuBar); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::destruct +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool GtkStatsWindow:: +destruct() { + if (BasicGtkWindow::destruct()) { + _monitor->remove_window(this); + _monitor.clear(); + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::update_title +// Access: Public, Virtual +// Description: Sets the title bar appropriately, once the client's +// information is known. +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +update_title() { + if (_monitor->is_client_known()) { + string title = + _monitor->get_client_progname() + " from " + _monitor->get_client_hostname(); + if (!_monitor->is_alive()) { + title += " (closed)"; + } + set_title(title); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::new_collector +// Access: Public, Virtual +// Description: Called when a new collector has become known, in case +// the window cares. +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +new_collector(int) { +} +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::new_thread +// Access: Public, Virtual +// Description: Called when a new thread has become known, in case +// the window cares. +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +new_thread(int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::idle +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +idle() { +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::setup_menu +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +setup_menu() { + _file_menu = new Gtk::Menu; + + _file_menu->items().push_back + (MenuElem("New strip chart", + slot(this, &GtkStatsWindow::menu_open_strip_chart))); + _file_menu->items().push_back + (MenuElem("New piano roll", + slot(this, &GtkStatsWindow::menu_open_piano_roll))); + + /* + _file_menu->items().push_back + (MenuElem("New window", + slot(this, &GtkStatsWindow::menu_new_window))); + */ + + _file_menu->items().push_back(SeparatorElem()); + + _file_menu->items().push_back + (MenuElem("Disconnect from client", + slot(this, &GtkStatsWindow::menu_disconnect))); + _file_menu->items().push_back + (MenuElem("Close window", + slot(this, &GtkStatsWindow::menu_close_window))); + _file_menu->items().push_back + (MenuElem("Close all windows this client", + slot(this, &GtkStatsWindow::menu_close_all_windows))); + + _menu->items().push_back(MenuElem("File", *manage(_file_menu))); + _menu->show(); + _main_box->pack_start(*_menu, false, false); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::menu_open_strip_chart +// Access: Protected +// Description: Open up a new strip-chart style window for the main +// thread. +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +menu_open_strip_chart() { + new GtkStatsStripWindow(_monitor, 0, 0, 400, 100); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::menu_open_piano_roll +// Access: Protected +// Description: Open up a new piano-roll style window for the main +// thread. +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +menu_open_piano_roll() { + new GtkStatsPianoWindow(_monitor, 0, 400, 100); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::menu_new_window +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +menu_new_window() { +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::menu_close_window +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +menu_close_window() { + destruct(); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::menu_close_all_windows +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +menu_close_all_windows() { + _monitor->close_all_windows(); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkStatsWindow::menu_disconnect +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +void GtkStatsWindow:: +menu_disconnect() { + _monitor->close(); +} diff --git a/pandatool/src/gtk-stats/gtkStatsWindow.h b/pandatool/src/gtk-stats/gtkStatsWindow.h new file mode 100644 index 0000000000..531f098648 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsWindow.h @@ -0,0 +1,56 @@ +// Filename: gtkStatsWindow.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKSTATSWINDOW_H +#define GTKSTATSWINDOW_H + +#include + +#include "gtkStatsMonitor.h" + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : GtkStatsWindow +// Description : This is the base class for a family of windows that +// are associated with one particular stats client. +// Each window keeps a pointer back to the +// GtkStatsMonitor object, which in turn knows about all +// of the windows; when the last window is closed, the +// monitor object goes away and ends the session. +//////////////////////////////////////////////////////////////////// +class GtkStatsWindow : public BasicGtkWindow { +public: + GtkStatsWindow(GtkStatsMonitor *monitor); + virtual bool destruct(); + + virtual void update_title(); + virtual void new_collector(int collector_index); + virtual void new_thread(int thread_index); + virtual void idle(); + +protected: + virtual void setup_menu(); + + void menu_open_strip_chart(); + void menu_open_piano_roll(); + virtual void menu_new_window(); + void menu_close_window(); + void menu_close_all_windows(); + void menu_disconnect(); + +protected: + PT(GtkStatsMonitor) _monitor; + + Gtk::VBox *_main_box; + Gtk::MenuBar *_menu; + Gtk::Menu *_file_menu; +}; + + +#endif + diff --git a/pandatool/src/gtk-stats/scribble.cc b/pandatool/src/gtk-stats/scribble.cc new file mode 100644 index 0000000000..b517a72d99 --- /dev/null +++ b/pandatool/src/gtk-stats/scribble.cc @@ -0,0 +1,201 @@ + + + +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +/* Modifed for gtk-- by sac@transmeta.com */ +/* Modified (slightly) for gdk-- by freyd@uni-muenster.de */ +#include +#include +#include +#include +#include +#include +#include + + +class ScribbleDrawingArea : public Gtk::DrawingArea +{ + /* Backing pixmap for drawing area */ + + Gdk_Pixmap pixmap; + Gdk_GC gc; + Gdk_Window win; + Gdk_Visual visual; + + virtual gint configure_event_impl (GdkEventConfigure *event); + virtual gint expose_event_impl (GdkEventExpose *event); + virtual gint button_press_event_impl (GdkEventButton *event); + virtual gint motion_notify_event_impl (GdkEventMotion *event); + void draw_brush (gdouble x, gdouble y); + +public: + ScribbleDrawingArea (); + +}; + +ScribbleDrawingArea::ScribbleDrawingArea() + : Gtk::DrawingArea(), pixmap (0) + { + set_events (GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); + } + + +/* Create a new backing pixmap of the appropriate size */ +int ScribbleDrawingArea::configure_event_impl (GdkEventConfigure * /* event */) + { + win = get_window(); + visual = win.get_visual(); + + if (pixmap) + pixmap.release(); + gc = get_style()->gtkobj()->white_gc; + // Gtk::Style has no access to its data members, so use gtk objekt. + // Some access functions would be nice like GtkStyle::get_white_gc() etc. + pixmap.create(get_window(), width(), height()); + + pixmap.draw_rectangle (gc, + TRUE, + 0, 0, + width(), + height()); + + return TRUE; + } + +/* Redraw the screen from the backing pixmap */ +int ScribbleDrawingArea::expose_event_impl (GdkEventExpose *event) + { + + gc = get_style()->gtkobj()->fg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))]; + // Same like above, + Gtk::Widget has set_state function but no get_state + // function. + win.draw_pixmap(gc , + pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return FALSE; + } + +/* Draw a rectangle on the screen */ +void ScribbleDrawingArea::draw_brush (gdouble x, gdouble y) + { + GdkRectangle update_rect; + update_rect.x = (int)x - 5; + update_rect.y = (int)y - 5; + update_rect.width = 10; + update_rect.height = 10; + gc = get_style()->gtkobj()->black_gc; + pixmap.draw_rectangle( + gc, + TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + draw(&update_rect); + //draw (&update_rect); + } + +gint ScribbleDrawingArea::button_press_event_impl (GdkEventButton *event) + { + if (event->button == 1 && pixmap) + draw_brush (event->x, event->y); + + return TRUE; + } + +gint ScribbleDrawingArea::motion_notify_event_impl (GdkEventMotion *event) + { + int x, y; + GdkModifierType state; + if (event->is_hint) + gdk_window_get_pointer (event->window, &x, &y, &state); + else + { + x = (int)event->x; + y = (int)event->y; + state = (GdkModifierType) event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap) + draw_brush (x, y); + + return TRUE; + } + + +class ScribbleWindow : public Gtk::Window +{ + + Gtk::VBox vbox; + ScribbleDrawingArea drawing_area; + Gtk::Button button; + void quit (); +public: + ScribbleWindow (); +}; + +void ScribbleWindow::quit () + { + Gtk::Main::quit(); + } + +ScribbleWindow::ScribbleWindow () + : Gtk::Window(GTK_WINDOW_TOPLEVEL), + vbox (FALSE, 0), + button ("quit") + { + add (vbox); + + /* Create the drawing area */ + drawing_area.size (400, 400); + vbox.pack_start (drawing_area, TRUE, TRUE, 0); + + + /* Add the button */ + vbox.pack_start (button, FALSE, FALSE, 0); + + button.clicked.connect(slot(*this, &ScribbleWindow::quit)); + destroy.connect(slot(*this, &ScribbleWindow::quit)); + + drawing_area.show(); + button.show(); + vbox.show(); + } + +int +main (int argc, char *argv[]) +{ + ScribbleWindow *window; + Gtk::Main myapp(argc, argv); + + window = new ScribbleWindow; + window->show(); + + myapp.run(); + + return 0; +} diff --git a/pandatool/src/gtkbase/Sources.pp b/pandatool/src/gtkbase/Sources.pp new file mode 100644 index 0000000000..6b28460c08 --- /dev/null +++ b/pandatool/src/gtkbase/Sources.pp @@ -0,0 +1,18 @@ +#define DIRECTORY_IF_GTKMM yes +#define USE_GTKMM yes + +#begin lib_target + #define TARGET gtkbase + #define LOCAL_LIBS \ + progbase + + #define SOURCES \ + basicGtkDialog.cxx basicGtkDialog.h basicGtkWindow.cxx \ + basicGtkWindow.h gtkBase.cxx gtkBase.h request_initial_size.cxx \ + request_initial_size.h + + #define INSTALL_HEADERS \ + basicGtkDialog.h basicGtkWindow.h gtkBase.h request_initial_size.h + +#end lib_target + diff --git a/pandatool/src/gtkbase/basicGtkDialog.cxx b/pandatool/src/gtkbase/basicGtkDialog.cxx new file mode 100644 index 0000000000..d875f6693b --- /dev/null +++ b/pandatool/src/gtkbase/basicGtkDialog.cxx @@ -0,0 +1,56 @@ +// Filename: basicGtkDialog.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "basicGtkDialog.h" + + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkDialog::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +BasicGtkDialog:: +BasicGtkDialog(bool free_store) : BasicGtkWindow(free_store) { + _vbox = manage(new Gtk::VBox); + _action_area = manage(new Gtk::HBox); + + Gtk::VBox *box0 = manage(new Gtk::VBox); + Gtk::HSeparator *hsep = manage(new Gtk::HSeparator); + + add(*box0); + box0->show(); + box0->pack_start(*_vbox); + _vbox->show(); + + box0->pack_start(*hsep); + hsep->show(); + + _action_area->set_border_width(10); + box0->pack_start(*_action_area, false); + _action_area->show(); +} + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkDialog::get_vbox +// Access: Public +// Description: Returns a pointer to the main part of the dialog +// window. +//////////////////////////////////////////////////////////////////// +Gtk::VBox *BasicGtkDialog:: +get_vbox() const { + return _vbox; +} + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkDialog::get_action_area +// Access: Public +// Description: Returns a pointer to part of the dialog reserved for +// action buttons. +//////////////////////////////////////////////////////////////////// +Gtk::HBox *BasicGtkDialog:: +get_action_area() const { + return _action_area; +} + diff --git a/pandatool/src/gtkbase/basicGtkDialog.h b/pandatool/src/gtkbase/basicGtkDialog.h new file mode 100644 index 0000000000..0976645553 --- /dev/null +++ b/pandatool/src/gtkbase/basicGtkDialog.h @@ -0,0 +1,35 @@ +// Filename: basicGtkDialog.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef BASICGTKDIALOG_H +#define BASICGTKDIALOG_H + +#include "basicGtkWindow.h" + +#include + + +//////////////////////////////////////////////////////////////////// +// Class : BasicGtkDialog +// Description : This looks like a wrapper around Gtk::Dialog. +// Actually, it doesn't inherit from Gtk::Dialog at all, +// but instead (indirectly) from Gtk::Window; it just +// duplicates the default functionality of Gtk::Dialog +// by defining get_vbox() and a get_action_area(). +//////////////////////////////////////////////////////////////////// +class BasicGtkDialog : public BasicGtkWindow { +public: + BasicGtkDialog(bool free_store = true); + + Gtk::VBox *get_vbox() const; + Gtk::HBox *get_action_area() const; + +private: + Gtk::VBox *_vbox; + Gtk::HBox *_action_area; +}; + + +#endif diff --git a/pandatool/src/gtkbase/basicGtkWindow.cxx b/pandatool/src/gtkbase/basicGtkWindow.cxx new file mode 100644 index 0000000000..a04a7fcdc1 --- /dev/null +++ b/pandatool/src/gtkbase/basicGtkWindow.cxx @@ -0,0 +1,127 @@ +// Filename: basicGtkWindow.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "basicGtkWindow.h" +#include "gtkBase.h" + + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkWindow::Constructor +// Access: Public +// Description: The free_store parameter should be true if the window +// object has been allocated from the free store (using +// new) and can be safely deleted using delete when the +// window is destroyed by the user, or false if this is +// not the case. +//////////////////////////////////////////////////////////////////// +BasicGtkWindow:: +BasicGtkWindow(bool free_store) : _free_store(free_store) { + _destroyed = false; + _state = S_virgin; +} + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkWindow::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +BasicGtkWindow:: +~BasicGtkWindow() { + destruct(); +} + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkWindow::setup +// Access: Public +// Description: Call this after initializing the window. +//////////////////////////////////////////////////////////////////// +void BasicGtkWindow:: +setup() { + _state = S_setup; + _destroy_connection = + destroy.connect(slot(this, &BasicGtkWindow::window_destroyed)); + show(); + + // Calling show() sets in motion some X events that must flow + // completely through the queue before we can safely hide() the + // thing again. To measure when this has happened, we'll drop our + // own event into the queue. When this event makes it through the + // queue, we'll assume all relevant X events have also, and it will + // be safe to hide the window. + Gtk::Main::idle.connect(slot(this, &BasicGtkWindow::idle_event)); +} + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkWindow::destruct +// Access: Public, Virtual +// Description: Call this to remove the window, etc. It's not tied +// directly to the real destructor because that seems to +// just lead to trouble. This returns true if it +// actually destructed, or false if it had already +// destructed previously and did nothing this time. +//////////////////////////////////////////////////////////////////// +bool BasicGtkWindow:: +destruct() { + if (_state != S_gone) { + // We must hide the window before we destruct, or it won't disappear + // from the screen. Strange. But we also don't want to try to hide + // the window if we're destructing because of a window_destroyed + // event, so we check our little flag. + if (!_destroyed && _state != S_virgin) { + // Now, in case the window was never completely shown, we must + // wait for that to happen before we can hide it. + while (!_destroyed && _state == S_setup) { + GtkBase::_gtk->iteration(); + } + + if (!_destroyed) { + hide(); + } + } + _state = S_gone; + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkWindow::delete_self +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +void BasicGtkWindow:: +delete_self() { + destruct(); +} + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkWindow::window_destroyed +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void BasicGtkWindow:: +window_destroyed() { + _destroyed = true; + destruct(); + + // We should probably also delete the pointer here. But maybe not. + // Gtk-- is very mysterious about this, so we'll just let it maybe + // leak. +} + +//////////////////////////////////////////////////////////////////// +// Function: BasicGtkWindow::idle_event +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +gint BasicGtkWindow:: +idle_event() { + // Now, we're finally a bona fide window with all rights thereunto + // appertaining. + _state = S_ready; + return false; +} + diff --git a/pandatool/src/gtkbase/basicGtkWindow.h b/pandatool/src/gtkbase/basicGtkWindow.h new file mode 100644 index 0000000000..5a3652e63c --- /dev/null +++ b/pandatool/src/gtkbase/basicGtkWindow.h @@ -0,0 +1,47 @@ +// Filename: basicGtkWindow.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef BASICGTKWINDOW_H +#define BASICGTKWINDOW_H + +#include + + +//////////////////////////////////////////////////////////////////// +// Class : BasicGtkWindow +// Description : This is just a handy wrapper around Gtk::Window that +// provides some convenient setup functions. +//////////////////////////////////////////////////////////////////// +class BasicGtkWindow : public Gtk::Window { +public: + BasicGtkWindow(bool free_store = true); + virtual ~BasicGtkWindow(); + void setup(); + virtual bool destruct(); + +protected: + void delete_self(); + static gint static_delete(BasicGtkWindow *window); + +private: + void window_destroyed(); + gint idle_event(); + + enum State { + S_virgin, + S_setup, + S_ready, + S_gone, + }; + + bool _destroyed; + bool _free_store; + State _state; + SigC::Connection _destroy_connection; +}; + + +#endif + diff --git a/pandatool/src/gtkbase/gtkBase.cxx b/pandatool/src/gtkbase/gtkBase.cxx new file mode 100644 index 0000000000..3541a5d87d --- /dev/null +++ b/pandatool/src/gtkbase/gtkBase.cxx @@ -0,0 +1,63 @@ +// Filename: gtkBase.cxx +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "gtkBase.h" + +#include + +Gtk::Main *GtkBase::_gtk = NULL; + +//////////////////////////////////////////////////////////////////// +// Function: GtkBase::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkBase:: +GtkBase() { + if (_gtk != (Gtk::Main *)NULL) { + nout << "Invalid attempt to create multiple instances of GtkBase!\n"; + abort(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkBase::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GtkBase:: +~GtkBase() { + nassertv(_gtk != (Gtk::Main *)NULL); + delete _gtk; + _gtk = NULL; +} + + +//////////////////////////////////////////////////////////////////// +// Function: GtkBase::parse_command_line +// Access: Public, Virtual +// Description: This is overridden for GtkBase to give Gtk a chance +// to pull out its X-related parameters. +//////////////////////////////////////////////////////////////////// +void GtkBase:: +parse_command_line(int argc, char *argv[]) { + nassertv(_gtk == (Gtk::Main *)NULL); + _gtk = new Gtk::Main(argc, argv); + ProgramBase::parse_command_line(argc, argv); +} + +//////////////////////////////////////////////////////////////////// +// Function: GtkBase::main_loop +// Access: Public +// Description: Call this after all is set up to yield control of the +// main loop to Gtk. This normally doesn't return. +//////////////////////////////////////////////////////////////////// +void GtkBase:: +main_loop() { + nassertv(_gtk != NULL); + + _gtk->run(); +} + diff --git a/pandatool/src/gtkbase/gtkBase.h b/pandatool/src/gtkbase/gtkBase.h new file mode 100644 index 0000000000..1aae9fda63 --- /dev/null +++ b/pandatool/src/gtkbase/gtkBase.h @@ -0,0 +1,34 @@ +// Filename: gtkBase.h +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GTKBASE_H +#define GTKBASE_H + +#include + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : GtkBase +// Description : This is a specialization of ProgramBase for programs +// that use the Gtk-- GUI toolkit. +//////////////////////////////////////////////////////////////////// +class GtkBase : public ProgramBase { +public: + GtkBase(); + ~GtkBase(); + + virtual void parse_command_line(int argc, char *argv[]); + void main_loop(); + +public: + static Gtk::Main *_gtk; +}; + +#endif + + diff --git a/pandatool/src/gtkbase/request_initial_size.cxx b/pandatool/src/gtkbase/request_initial_size.cxx new file mode 100644 index 0000000000..99f2908d88 --- /dev/null +++ b/pandatool/src/gtkbase/request_initial_size.cxx @@ -0,0 +1,33 @@ +// Filename: request_initial_size.cxx +// Created by: drose (15Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "request_initial_size.h" + +static int +restore_usize(Gtk::Widget *widget) { + widget->set_usize(0, 0); + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: request_initial_size +// Description: Gtk-- hack to request an initial size for a widget, +// while still allowing the user to resize it smaller. +//////////////////////////////////////////////////////////////////// +void +request_initial_size(Gtk::Widget &widget, int xsize, int ysize) { + if (xsize != 0 || ysize != 0) { + // I can't find a way to request an initial size for a general + // widget. The best I can do is specify its minimum size. + widget.set_usize(xsize, ysize); + + // However, I don't want the minimum size to be enforced forever; + // the user should be able to resize the window smaller if he wants + // to. Thus, this ugly hack: at the first idle signal, we return + // the usize to 0. + Gtk::Main::idle.connect(bind(slot(&restore_usize), &widget)); + } +} + diff --git a/pandatool/src/gtkbase/request_initial_size.h b/pandatool/src/gtkbase/request_initial_size.h new file mode 100644 index 0000000000..21cc2b4a5a --- /dev/null +++ b/pandatool/src/gtkbase/request_initial_size.h @@ -0,0 +1,15 @@ +// Filename: request_initial_size.h +// Created by: drose (15Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef REQUEST_INITIAL_SIZE_H +#define REQUEST_INITIAL_SIZE_H + +#include + +#include + +void request_initial_size(Gtk::Widget &widget, int xsize, int ysize); + +#endif diff --git a/pandatool/src/imagebase/Sources.pp b/pandatool/src/imagebase/Sources.pp new file mode 100644 index 0000000000..f3e0903acc --- /dev/null +++ b/pandatool/src/imagebase/Sources.pp @@ -0,0 +1,17 @@ +#begin lib_target + #define TARGET imagebase + #define LOCAL_LIBS \ + progbase + #define OTHER_LIBS \ + pnmimage:c panda:m + + #define SOURCES \ + imageBase.cxx imageBase.h imageFilter.cxx imageFilter.h \ + imageReader.cxx imageReader.h imageWriter.I imageWriter.cxx \ + imageWriter.h + + #define INSTALL_HEADERS \ + imageBase.h imageFilter.h imageReader.h imageWriter.I imageWriter.h + +#end lib_target + diff --git a/pandatool/src/imagebase/imageBase.cxx b/pandatool/src/imagebase/imageBase.cxx new file mode 100644 index 0000000000..2aadb85a45 --- /dev/null +++ b/pandatool/src/imagebase/imageBase.cxx @@ -0,0 +1,27 @@ +// Filename: imageBase.cxx +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#include "imageBase.h" + +//////////////////////////////////////////////////////////////////// +// Function: ImageBase::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +ImageBase:: +ImageBase() { +} + + +//////////////////////////////////////////////////////////////////// +// Function: ImageBase::post_command_line +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool ImageBase:: +post_command_line() { + return ProgramBase::post_command_line(); +} + diff --git a/pandatool/src/imagebase/imageBase.h b/pandatool/src/imagebase/imageBase.h new file mode 100644 index 0000000000..493a98b198 --- /dev/null +++ b/pandatool/src/imagebase/imageBase.h @@ -0,0 +1,38 @@ +// Filename: imageBase.h +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef IMAGEBASE_H +#define IMAGEBASE_H + +#include + +#include +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : ImageBase +// Description : This specialization of ProgramBase is intended for +// programs that read and/or write a single image file. +// (See ImageMultiBase for programs that operate on +// multiple image files at once.) +// +// This is just a base class; see ImageReader, ImageWriter, +// or ImageFilter according to your particular I/O needs. +//////////////////////////////////////////////////////////////////// +class ImageBase : public ProgramBase { +public: + ImageBase(); + +protected: + virtual bool post_command_line(); + +protected: + PNMImage _image; +}; + +#endif + + diff --git a/pandatool/src/imagebase/imageFilter.cxx b/pandatool/src/imagebase/imageFilter.cxx new file mode 100644 index 0000000000..5a40855058 --- /dev/null +++ b/pandatool/src/imagebase/imageFilter.cxx @@ -0,0 +1,48 @@ +// Filename: imageFilter.cxx +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#include "imageFilter.h" + +//////////////////////////////////////////////////////////////////// +// Function: ImageFilter::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +ImageFilter:: +ImageFilter() { + clear_runlines(); + add_runline("[opts] inputimage outputimage"); + add_runline("[opts] -o outputimage inputimage"); +} + +//////////////////////////////////////////////////////////////////// +// Function: ImageFilter::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool ImageFilter:: +handle_args(ProgramBase::Args &args) { + if (!_got_output_filename) { + if (args.size() != 2) { + nout << "You must specify the input and output filenames on the " + << "command line, or use -o to specify the output filename.\n"; + return false; + } + + if (!_image.read(args[0])) { + nout << "Unable to read image file.\n"; + return false; + } + + _output_filename = args[1]; + _got_output_filename = true; + return true; + } + + return ImageReader::handle_args(args); +} diff --git a/pandatool/src/imagebase/imageFilter.h b/pandatool/src/imagebase/imageFilter.h new file mode 100644 index 0000000000..37bb6d0451 --- /dev/null +++ b/pandatool/src/imagebase/imageFilter.h @@ -0,0 +1,30 @@ +// Filename: imageFilter.h +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef IMAGEFILTER_H +#define IMAGEFILTER_H + +#include + +#include "imageReader.h" +#include "imageWriter.h" + +//////////////////////////////////////////////////////////////////// +// Class : ImageFilter +// Description : This is the base class for a program that reads an +// image file, operates on it, and writes another image +// file out. +//////////////////////////////////////////////////////////////////// +class ImageFilter : public ImageReader, public ImageWriter { +public: + ImageFilter(); + +protected: + virtual bool handle_args(Args &args); +}; + +#endif + + diff --git a/pandatool/src/imagebase/imageReader.cxx b/pandatool/src/imagebase/imageReader.cxx new file mode 100644 index 0000000000..743806fb12 --- /dev/null +++ b/pandatool/src/imagebase/imageReader.cxx @@ -0,0 +1,42 @@ +// Filename: imageReader.cxx +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#include "imageReader.h" + +//////////////////////////////////////////////////////////////////// +// Function: ImageReader::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +ImageReader:: +ImageReader() { + clear_runlines(); + add_runline("[opts] imagename"); +} + +//////////////////////////////////////////////////////////////////// +// Function: ImageReader::handle_args +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool ImageReader:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the image file to read on the command line.\n"; + return false; + } + + if (args.size() > 1) { + nout << "Specify only one image on the command line.\n"; + return false; + } + + if (!_image.read(args[0])) { + nout << "Unable to read image file.\n"; + return false; + } + + return true; +} diff --git a/pandatool/src/imagebase/imageReader.h b/pandatool/src/imagebase/imageReader.h new file mode 100644 index 0000000000..5f0d97da8e --- /dev/null +++ b/pandatool/src/imagebase/imageReader.h @@ -0,0 +1,29 @@ +// Filename: imageReader.h +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef IMAGEREADER_H +#define IMAGEREADER_H + +#include + +#include "imageBase.h" + +//////////////////////////////////////////////////////////////////// +// Class : ImageReader +// Description : This is the base class for a program that reads an +// image file, but doesn't write an image file. +//////////////////////////////////////////////////////////////////// +class ImageReader : virtual public ImageBase { +public: + ImageReader(); + +protected: + virtual bool handle_args(Args &args); + +}; + +#endif + + diff --git a/pandatool/src/imagebase/imageWriter.I b/pandatool/src/imagebase/imageWriter.I new file mode 100644 index 0000000000..48bb13a2e8 --- /dev/null +++ b/pandatool/src/imagebase/imageWriter.I @@ -0,0 +1,16 @@ +// Filename: imageWriter.I +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: ImageWriter::write_image +// Access: Public +// Description: Writes the generated to the user's specified output +// filename. +//////////////////////////////////////////////////////////////////// +INLINE void ImageWriter:: +write_image() { + write_image(_image); +} diff --git a/pandatool/src/imagebase/imageWriter.cxx b/pandatool/src/imagebase/imageWriter.cxx new file mode 100644 index 0000000000..b9edeaae92 --- /dev/null +++ b/pandatool/src/imagebase/imageWriter.cxx @@ -0,0 +1,73 @@ +// Filename: imageWriter.cxx +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#include "imageWriter.h" + +//////////////////////////////////////////////////////////////////// +// Function: ImageWriter::Constructor +// Access: Public +// Description: Image-writing type programs *must* specify their +// output file using -o. +//////////////////////////////////////////////////////////////////// +ImageWriter:: +ImageWriter() { + clear_runlines(); + add_runline("[opts] outputimage"); + add_runline("[opts] -o outputimage"); + + add_option + ("o", "filename", 50, + "Specify the filename to which the resulting image file will be written. " + "If this is omitted, the last parameter on the command line is taken as " + "the output filename.", + &ImageWriter::dispatch_filename, &_got_output_filename, &_output_filename); +} + + +//////////////////////////////////////////////////////////////////// +// Function: ImageWriter::write_image +// Access: Public +// Description: Writes the generated to the user's specified output +// filename. +//////////////////////////////////////////////////////////////////// +void ImageWriter:: +write_image(const PNMImage &image) { + if (!image.write(_output_filename)) { + nout << "Unable to write output image to " << _output_filename << "\n"; + exit(1); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: ImageWriter::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool ImageWriter:: +handle_args(ProgramBase::Args &args) { + if (!_got_output_filename) { + if (args.size() != 1) { + nout << "You must specify the filename to write with -o, or as " + << "the last parameter on the command line.\n"; + return false; + } + _output_filename = args[0]; + _got_output_filename = true; + + } else { + if (!args.empty()) { + nout << "Unexpected arguments on command line:\n"; + copy(args.begin(), args.end(), ostream_iterator(nout, " ")); + nout << "\r"; + return false; + } + } + + return true; +} + diff --git a/pandatool/src/imagebase/imageWriter.h b/pandatool/src/imagebase/imageWriter.h new file mode 100644 index 0000000000..01eeed0c85 --- /dev/null +++ b/pandatool/src/imagebase/imageWriter.h @@ -0,0 +1,41 @@ +// Filename: imageWriter.h +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef IMAGEWRITER_H +#define IMAGEWRITER_H + +#include + +#include "imageBase.h" + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : ImageWriter +// Description : This is the base class for a program that generates +// an image file output, but doesn't read any for input. +//////////////////////////////////////////////////////////////////// +class ImageWriter : virtual public ImageBase { +public: + ImageWriter(); + + INLINE void write_image(); + void write_image(const PNMImage &image); + +protected: + virtual bool handle_args(Args &args); + +protected: + bool _got_output_filename; + Filename _output_filename; +}; + +#include "imageWriter.I" + +#endif + + diff --git a/pandatool/src/imageprogs/Sources.pp b/pandatool/src/imageprogs/Sources.pp new file mode 100644 index 0000000000..2ab6eae7d5 --- /dev/null +++ b/pandatool/src/imageprogs/Sources.pp @@ -0,0 +1,16 @@ +#begin bin_target + #define TARGET image-trans + #define LOCAL_LIBS \ + imagebase progbase config compiler + #define OTHER_LIBS \ + pnmimagetypes:c pnmimage:c putil:c express:c panda:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + imageTrans.cxx imageTrans.h + + #define INSTALL_HEADERS \ + +#end bin_target + diff --git a/pandatool/src/imageprogs/imageTrans.cxx b/pandatool/src/imageprogs/imageTrans.cxx new file mode 100644 index 0000000000..04ac0c7988 --- /dev/null +++ b/pandatool/src/imageprogs/imageTrans.cxx @@ -0,0 +1,36 @@ +// Filename: imageTrans.cxx +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#include "imageTrans.h" + +//////////////////////////////////////////////////////////////////// +// Function: ImageTrans::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +ImageTrans:: +ImageTrans() { + set_program_description + ("This program reads an image file and writes an essentially equivalent " + "image file to the file specified with -o."); +} + +//////////////////////////////////////////////////////////////////// +// Function: ImageTrans::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void ImageTrans:: +run() { + write_image(); +} + + +int main(int argc, char *argv[]) { + ImageTrans prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/imageprogs/imageTrans.h b/pandatool/src/imageprogs/imageTrans.h new file mode 100644 index 0000000000..c22bb704c9 --- /dev/null +++ b/pandatool/src/imageprogs/imageTrans.h @@ -0,0 +1,27 @@ +// Filename: imageTrans.h +// Created by: drose (19Jun00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef IMAGETRANS_H +#define IMAGETRANS_H + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : ImageTrans +// Description : A program to read an image file and write an +// equivalent image file, possibly performing some minor +// operations along the way. +//////////////////////////////////////////////////////////////////// +class ImageTrans : public ImageFilter { +public: + ImageTrans(); + + void run(); +}; + +#endif + diff --git a/pandatool/src/maya/Sources.pp b/pandatool/src/maya/Sources.pp new file mode 100644 index 0000000000..6409f827e4 --- /dev/null +++ b/pandatool/src/maya/Sources.pp @@ -0,0 +1,27 @@ +#define DIRECTORY_IF_MAYA yes + +#begin sed_bin_target + #define TARGET maya2egg + + #define SOURCE maya2egg_script + #define COMMAND 's:xxx:$[MAYA_LOCATION]:g' + +#end sed_bin_target + +#begin bin_target + #define USE_MAYA yes + #define TARGET maya2egg_bin + #define LOCAL_LIBS \ + eggbase progbase config compiler + #define OTHER_LIBS \ + egg:c linmath:c putil:c express:c panda:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + global_parameters.cxx global_parameters.h mayaFile.cxx mayaFile.h \ + mayaShader.cxx mayaShader.h mayaShaders.cxx mayaShaders.h \ + mayaToEgg.cxx mayaToEgg.h maya_funcs.I maya_funcs.cxx maya_funcs.h + +#end bin_target + diff --git a/pandatool/src/maya/global_parameters.cxx b/pandatool/src/maya/global_parameters.cxx new file mode 100644 index 0000000000..5b1965c8ed --- /dev/null +++ b/pandatool/src/maya/global_parameters.cxx @@ -0,0 +1,11 @@ +// Filename: global_parameters.cxx +// Created by: drose (16Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "global_parameters.h" + +int verbose = 0; +bool polygon_output = false; +double polygon_tolerance = 0.01; +bool ignore_transforms = false; diff --git a/pandatool/src/maya/global_parameters.d b/pandatool/src/maya/global_parameters.d new file mode 100644 index 0000000000..6aae1d02a5 --- /dev/null +++ b/pandatool/src/maya/global_parameters.d @@ -0,0 +1,32 @@ +global_parameters.o global_parameters.d : global_parameters.cxx global_parameters.h \ + ../../inc/pandatoolbase.h ../../../panda/inc/pandabase.h \ + ../../../dtool/inc/dtoolbase.h \ + /home/drose/player/dtool/dtool_config.h \ + ../../../dtool/inc/dtoolsymbols.h /usr/include/malloc.h \ + /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h \ + /usr/include/alloca.h /usr/include/features.h \ + /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \ + /usr/include/unistd.h /usr/include/bits/posix_opt.h \ + /usr/include/bits/types.h /usr/include/bits/confname.h \ + /usr/include/getopt.h ../../../dtool/inc/dtoolbase_cc.h \ + /usr/include/g++-2/iostream.h /usr/include/g++-2/streambuf.h \ + /usr/include/libio.h /usr/include/_G_config.h \ + /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stdarg.h \ + /usr/include/g++-2/fstream.h /usr/include/g++-2/iomanip.h \ + ../../../dtool/inc/fakestringstream.h /usr/include/g++-2/strstream.h \ + /usr/include/g++-2/strfile.h /usr/include/string.h \ + /usr/include/g++-2/string /usr/include/g++-2/std/bastring.h \ + /usr/include/g++-2/cstddef /usr/include/g++-2/std/straits.h \ + /usr/include/g++-2/cctype /usr/include/ctype.h /usr/include/endian.h \ + /usr/include/bits/endian.h /usr/include/g++-2/cstring \ + /usr/include/g++-2/alloc.h /usr/include/g++-2/stl_config.h \ + /usr/include/g++-2/stl_alloc.h /usr/include/stdlib.h \ + /usr/include/sys/types.h /usr/include/time.h \ + /usr/include/sys/select.h /usr/include/bits/select.h \ + /usr/include/bits/sigset.h /usr/include/sys/sysmacros.h \ + /usr/include/assert.h /usr/include/pthread.h /usr/include/sched.h \ + /usr/include/bits/sched.h /usr/include/bits/time.h \ + /usr/include/signal.h /usr/include/bits/pthreadtypes.h \ + /usr/include/bits/sigthread.h /usr/include/g++-2/iterator \ + /usr/include/g++-2/stl_relops.h /usr/include/g++-2/stl_iterator.h \ + /usr/include/g++-2/std/bastring.cc ../../../panda/inc/pandasymbols.h diff --git a/pandatool/src/maya/global_parameters.h b/pandatool/src/maya/global_parameters.h new file mode 100644 index 0000000000..35ab9a1cd6 --- /dev/null +++ b/pandatool/src/maya/global_parameters.h @@ -0,0 +1,17 @@ +// Filename: global_parameters.h +// Created by: drose (16Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef GLOBAL_PARAMETERS_H +#define GLOBAL_PARAMETERS_H + +#include + +extern int verbose; +extern bool polygon_output; +extern double polygon_tolerance; +extern bool ignore_transforms; + +#endif + diff --git a/pandatool/src/maya/maya2egg_script b/pandatool/src/maya/maya2egg_script new file mode 100644 index 0000000000..12fc1c5f8d --- /dev/null +++ b/pandatool/src/maya/maya2egg_script @@ -0,0 +1,4 @@ +#! /bin/sh +MAYA_LOCATION=xxx +export MAYA_LOCATION +exec `dirname $0`/maya2egg_bin $* diff --git a/pandatool/src/maya/mayaFile.cxx b/pandatool/src/maya/mayaFile.cxx new file mode 100644 index 0000000000..4c6c1e02cf --- /dev/null +++ b/pandatool/src/maya/mayaFile.cxx @@ -0,0 +1,771 @@ +// Filename: mayaFile.cxx +// Created by: drose (10Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "mayaFile.h" +#include "mayaShader.h" +#include "global_parameters.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MayaFile:: +MayaFile() { + verbose = 0; + _scale_units = 1.0; +} + +MayaFile:: +~MayaFile() { + MLibrary::cleanup(); +} + +bool MayaFile:: +init(const string &program) { + MStatus stat = MLibrary::initialize((char *)program.c_str()); + if (!stat) { + stat.perror("MLibrary::initialize"); + return false; + } + return true; +} + + +bool MayaFile:: +read(const string &filename) { + MFileIO::newFile(true); + + nout << "Loading \"" << filename << "\" ... " << flush; + // Load the file into Maya + MStatus stat = MFileIO::open(filename.c_str()); + if (!stat) { + stat.perror(filename.c_str()); + return false; + } + nout << " done.\n"; + return true; +} + + +void MayaFile:: +make_egg(EggData &data) { + traverse(data); +} + + +bool MayaFile:: +traverse(EggData &data) { + MStatus status; + + MItDag dag_iterator(MItDag::kDepthFirst, MFn::kTransform, &status); + if (!status) { + status.perror("MItDag constructor"); + return false; + } + + if (verbose >= 1) { + nout << "Traversing scene graph.\n"; + } + + // Scan the entire DAG and output the name and depth of each node + while (!dag_iterator.isDone()) { + MDagPath dag_path; + status = dag_iterator.getPath(dag_path); + if (!status) { + status.perror("MItDag::getPath"); + } else { + process_node(dag_path, data); + } + + dag_iterator.next(); + } + + if (verbose == 1) { + nout << "\nDone.\n"; + } + + return true; +} + + +bool MayaFile:: +process_node(const MDagPath &dag_path, EggData &data) { + MStatus status; + MFnDagNode dag_node(dag_path, &status); + if (!status) { + status.perror("MFnDagNode constructor"); + return false; + } + + if (verbose == 1) { + nout << "." << flush; + } else if (verbose >= 2) { + nout << dag_node.name() << ": " << dag_node.typeName() << "\n" + << " dag_path: " << dag_path.fullPathName() << "\n"; + } + + if (dag_path.hasFn(MFn::kCamera)) { + if (verbose >= 2) { + nout << "Ignoring camera node " << dag_path.fullPathName() << "\n"; + } + + } else if (dag_path.hasFn(MFn::kLight)) { + if (verbose >= 2) { + nout << "Ignoring light node " << dag_path.fullPathName() << "\n"; + } + + } else if (dag_path.hasFn(MFn::kNurbsSurface)) { + EggGroup *egg_group = + get_egg_group(dag_path.fullPathName().asChar(), data); + + if (egg_group == (EggGroup *)NULL) { + nout << "Cannot determine group node.\n"; + + } else { + get_transform(dag_path, egg_group); + + MFnNurbsSurface surface(dag_path, &status); + if (!status) { + if (verbose >= 2) { + nout << "Error in node " << dag_path.fullPathName() << ":\n" + << " it appears to have a NURBS surface, but does not.\n"; + } + } else { + make_nurbs_surface(dag_path, surface, egg_group); + } + } + + } else if (dag_path.hasFn(MFn::kNurbsCurve)) { + EggGroup *egg_group = + get_egg_group(dag_path.fullPathName().asChar(), data); + + if (egg_group == (EggGroup *)NULL) { + nout << "Cannot determine group node.\n"; + + } else { + get_transform(dag_path, egg_group); + + MFnNurbsCurve curve(dag_path, &status); + if (!status) { + if (verbose >= 2) { + nout << "Error in node " << dag_path.fullPathName() << ":\n" + << " it appears to have a NURBS curve, but does not.\n"; + } + } else { + make_nurbs_curve(dag_path, curve, egg_group); + } + } + + } else if (dag_path.hasFn(MFn::kMesh)) { + EggGroup *egg_group = + get_egg_group(dag_path.fullPathName().asChar(), data); + + if (egg_group == (EggGroup *)NULL) { + nout << "Cannot determine group node.\n"; + + } else { + get_transform(dag_path, egg_group); + + MFnMesh mesh(dag_path, &status); + if (!status) { + if (verbose >= 2) { + nout << "Error in node " << dag_path.fullPathName() << ":\n" + << " it appears to have a polygon mesh, but does not.\n"; + } + } else { + make_polyset(dag_path, mesh, egg_group); + } + } + + } else { + // Get the translation/rotation/scale data + EggGroup *egg_group = + get_egg_group(dag_path.fullPathName().asChar(), data); + + if (egg_group != (EggGroup *)NULL) { + get_transform(dag_path, egg_group); + } + } + + return true; +} + +void MayaFile:: +get_transform(const MDagPath &dag_path, EggGroup *egg_group) { + if (ignore_transforms) { + return; + } + + MStatus status; + MObject transformNode = dag_path.transform(&status); + // This node has no transform - i.e., it's the world node + if (!status && status.statusCode() == MStatus::kInvalidParameter) + return; + + MFnDagNode transform(transformNode, &status); + if (!status) { + status.perror("MFnDagNode constructor"); + return; + } + + MTransformationMatrix matrix(transform.transformationMatrix()); + + if (verbose >= 3) { + nout << " translation: " << matrix.translation(MSpace::kWorld) + << "\n"; + double d[3]; + MTransformationMatrix::RotationOrder rOrder; + + matrix.getRotation(d, rOrder, MSpace::kWorld); + nout << " rotation: [" + << d[0] << ", " + << d[1] << ", " + << d[2] << "]\n"; + matrix.getScale(d, MSpace::kWorld); + nout << " scale: [" + << d[0] << ", " + << d[1] << ", " + << d[2] << "]\n"; + } + + MMatrix mat = matrix.asMatrix(); + MMatrix ident_mat; + ident_mat.setToIdentity(); + + if (!mat.isEquivalent(ident_mat, 0.0001)) { + egg_group->set_transform + (LMatrix4d(mat[0][0], mat[0][1], mat[0][2], mat[0][3], + mat[1][0], mat[1][1], mat[1][2], mat[1][3], + mat[2][0], mat[2][1], mat[2][2], mat[2][3], + mat[3][0], mat[3][1], mat[3][2], mat[3][3])); + } +} + +void MayaFile:: +make_nurbs_surface(const MDagPath &dag_path, MFnNurbsSurface surface, + EggGroup *egg_group) { + MStatus status; + string name = surface.name().asChar(); + + if (verbose >= 3) { + nout << " numCVs: " + << surface.numCVsInU() + << " * " + << surface.numCVsInV() + << "\n"; + nout << " numKnots: " + << surface.numKnotsInU() + << " * " + << surface.numKnotsInV() + << "\n"; + nout << " numSpans: " + << surface.numSpansInU() + << " * " + << surface.numSpansInV() + << "\n"; + } + + MayaShader *shader = _shaders.find_shader_for_node(surface.object()); + + if (polygon_output) { + // If we want polygon output only, tesselate the NURBS and output + // that. + MTesselationParams params; + params.setFormatType(MTesselationParams::kStandardFitFormat); + params.setOutputType(MTesselationParams::kQuads); + params.setStdFractionalTolerance(polygon_tolerance); + + // We'll create the tesselation as a sibling of the NURBS surface. + // That way we inherit all of the transformations. + MDagPath polyset_path = dag_path; + MObject polyset_parent = polyset_path.node(); + MObject polyset = + surface.tesselate(params, polyset_parent, &status); + if (!status) { + status.perror("MFnNurbsSurface::tesselate"); + return; + } + + status = polyset_path.push(polyset); + if (!status) { + status.perror("MDagPath::push"); + } + + MFnMesh polyset_fn(polyset, &status); + if (!status) { + status.perror("MFnMesh constructor"); + return; + } + make_polyset(polyset_path, polyset_fn, egg_group, shader); + + return; + } + + MPointArray cv_array; + status = surface.getCVs(cv_array, MSpace::kWorld); + if (!status) { + status.perror("MFnNurbsSurface::getCVs"); + return; + } + MDoubleArray u_knot_array, v_knot_array; + status = surface.getKnotsInU(u_knot_array); + if (!status) { + status.perror("MFnNurbsSurface::getKnotsInU"); + return; + } + status = surface.getKnotsInV(v_knot_array); + if (!status) { + status.perror("MFnNurbsSurface::getKnotsInV"); + return; + } + + MFnNurbsSurface::Form u_form = surface.formInU(); + MFnNurbsSurface::Form v_form = surface.formInV(); + + int u_degree = surface.degreeU(); + int v_degree = surface.degreeV(); + + int u_cvs = surface.numCVsInU(); + int v_cvs = surface.numCVsInV(); + + int u_knots = surface.numKnotsInU(); + int v_knots = surface.numKnotsInV(); + + assert(u_knots == u_cvs + u_degree - 1); + assert(v_knots == v_cvs + v_degree - 1); + + string vpool_name = name + ".cvs"; + EggVertexPool *vpool = new EggVertexPool(vpool_name); + egg_group->add_child(vpool); + + EggNurbsSurface *egg_nurbs = new EggNurbsSurface(name); + egg_nurbs->setup(u_degree + 1, v_degree + 1, + u_knots + 2, v_knots + 2); + + int i; + + egg_nurbs->set_u_knot(0, u_knot_array[0]); + for (i = 0; i < u_knots; i++) { + egg_nurbs->set_u_knot(i + 1, u_knot_array[i]); + } + egg_nurbs->set_u_knot(u_knots + 1, u_knot_array[u_knots - 1]); + + egg_nurbs->set_v_knot(0, v_knot_array[0]); + for (i = 0; i < v_knots; i++) { + egg_nurbs->set_v_knot(i + 1, v_knot_array[i]); + } + egg_nurbs->set_v_knot(v_knots + 1, v_knot_array[v_knots - 1]); + + for (i = 0; i < egg_nurbs->get_num_cvs(); i++) { + int ui = egg_nurbs->get_u_index(i); + int vi = egg_nurbs->get_v_index(i); + + double v[4]; + MStatus status = cv_array[v_cvs * ui + vi].get(v); + if (!status) { + status.perror("MPoint::get"); + } else { + EggVertex vert; + vert.set_pos(LPoint4d(v[0], v[1], v[2], v[3])); + egg_nurbs->add_vertex(vpool->create_unique_vertex(vert)); + } + } + + // Now consider the trim curves, if any. + unsigned num_trims = surface.numRegions(); + int trim_curve_index = 0; + for (unsigned ti = 0; ti < num_trims; ti++) { + egg_nurbs->_trims.push_back(EggNurbsSurface::Trim()); + EggNurbsSurface::Trim &egg_trim = egg_nurbs->_trims.back(); + + unsigned num_loops = surface.numBoundaries(ti); + for (unsigned li = 0; li < num_loops; li++) { + egg_trim.push_back(EggNurbsSurface::Loop()); + EggNurbsSurface::Loop &egg_loop = egg_trim.back(); + + MFnNurbsSurface::BoundaryType type = + surface.boundaryType(ti, li, &status); + bool keep_loop = false; + + if (!status) { + status.perror("MFnNurbsSurface::BoundaryType"); + } else { + keep_loop = (type == MFnNurbsSurface::kInner || + type == MFnNurbsSurface::kOuter); + } + + if (keep_loop) { + unsigned num_edges = surface.numEdges(ti, li); + for (unsigned ei = 0; ei < num_edges; ei++) { + MObjectArray edge = surface.edge(ti, li, ei, true, &status); + if (!status) { + status.perror("MFnNurbsSurface::edge"); + } else { + unsigned num_segs = edge.length(); + for (unsigned si = 0; si < num_segs; si++) { + MObject segment = edge[si]; + if (segment.hasFn(MFn::kNurbsCurve)) { + MFnNurbsCurve curve(segment, &status); + if (!status) { + nout << "Trim curve appears to be a nurbs curve, but isn't.\n"; + } else { + // Finally, we have a valid curve! + EggNurbsCurve *egg_curve = + make_trim_curve(curve, name, egg_group, trim_curve_index); + trim_curve_index++; + if (egg_curve != (EggNurbsCurve *)NULL) { + egg_loop.push_back(egg_curve); + } + } + } else { + nout << "Trim curve segment is not a nurbs curve.\n"; + } + } + } + } + } + } + } + + // We add the NURBS to the group down here, after all of the vpools + // for the trim curves have been added. + egg_group->add_child(egg_nurbs); + + if (shader != (MayaShader *)NULL) { + shader->set_attributes(*egg_nurbs, *this); + } +} + +EggNurbsCurve *MayaFile:: +make_trim_curve(MFnNurbsCurve curve, const string &nurbs_name, + EggGroupNode *egg_group, int trim_curve_index) { + if (verbose >= 3) { + nout << "Trim curve:\n"; + nout << " numCVs: " + << curve.numCVs() + << "\n"; + nout << " numKnots: " + << curve.numKnots() + << "\n"; + nout << " numSpans: " + << curve.numSpans() + << "\n"; + } + + MStatus status; + + MPointArray cv_array; + status = curve.getCVs(cv_array, MSpace::kWorld); + if (!status) { + status.perror("MFnNurbsCurve::getCVs"); + return (EggNurbsCurve *)NULL; + } + MDoubleArray knot_array; + status = curve.getKnots(knot_array); + if (!status) { + status.perror("MFnNurbsCurve::getKnots"); + return (EggNurbsCurve *)NULL; + } + + MFnNurbsCurve::Form form = curve.form(); + + int degree = curve.degree(); + int cvs = curve.numCVs(); + int knots = curve.numKnots(); + + assert(knots == cvs + degree - 1); + + char trim_str[20]; + sprintf(trim_str, "trim%d", trim_curve_index); + assert(strlen(trim_str) < 20); + string trim_name = trim_str; + + string vpool_name = nurbs_name + "." + trim_name; + EggVertexPool *vpool = new EggVertexPool(vpool_name); + egg_group->add_child(vpool); + + EggNurbsCurve *egg_curve = new EggNurbsCurve(trim_name); + egg_curve->setup(degree + 1, knots + 2); + + int i; + + egg_curve->set_knot(0, knot_array[0]); + for (i = 0; i < knots; i++) { + egg_curve->set_knot(i + 1, knot_array[i]); + } + egg_curve->set_knot(knots + 1, knot_array[knots - 1]); + + for (i = 0; i < egg_curve->get_num_cvs(); i++) { + double v[4]; + MStatus status = cv_array[i].get(v); + if (!status) { + status.perror("MPoint::get"); + } else { + EggVertex vert; + vert.set_pos(LPoint3d(v[0], v[1], v[3])); + egg_curve->add_vertex(vpool->create_unique_vertex(vert)); + } + } + + return egg_curve; +} + +void MayaFile:: +make_nurbs_curve(const MDagPath &, MFnNurbsCurve curve, + EggGroup *egg_group) { + MStatus status; + string name = curve.name().asChar(); + + if (verbose >= 3) { + nout << " numCVs: " + << curve.numCVs() + << "\n"; + nout << " numKnots: " + << curve.numKnots() + << "\n"; + nout << " numSpans: " + << curve.numSpans() + << "\n"; + } + + MPointArray cv_array; + status = curve.getCVs(cv_array, MSpace::kWorld); + if (!status) { + status.perror("MFnNurbsCurve::getCVs"); + return; + } + MDoubleArray knot_array; + status = curve.getKnots(knot_array); + if (!status) { + status.perror("MFnNurbsCurve::getKnots"); + return; + } + + MFnNurbsCurve::Form form = curve.form(); + + int degree = curve.degree(); + int cvs = curve.numCVs(); + int knots = curve.numKnots(); + + assert(knots == cvs + degree - 1); + + string vpool_name = name + ".cvs"; + EggVertexPool *vpool = new EggVertexPool(vpool_name); + egg_group->add_child(vpool); + + EggNurbsCurve *egg_curve = new EggNurbsCurve(name); + egg_group->add_child(egg_curve); + egg_curve->setup(degree + 1, knots + 2); + + int i; + + egg_curve->set_knot(0, knot_array[0]); + for (i = 0; i < knots; i++) { + egg_curve->set_knot(i + 1, knot_array[i]); + } + egg_curve->set_knot(knots + 1, knot_array[knots - 1]); + + for (i = 0; i < egg_curve->get_num_cvs(); i++) { + double v[4]; + MStatus status = cv_array[i].get(v); + if (!status) { + status.perror("MPoint::get"); + } else { + EggVertex vert; + vert.set_pos(LPoint4d(v[0], v[1], v[2], v[3])); + egg_curve->add_vertex(vpool->create_unique_vertex(vert)); + } + } + + MayaShader *shader = _shaders.find_shader_for_node(curve.object()); + if (shader != (MayaShader *)NULL) { + shader->set_attributes(*egg_curve, *this); + } +} + +void MayaFile:: +make_polyset(const MDagPath &dag_path, MFnMesh mesh, + EggGroup *egg_group, MayaShader *default_shader) { + MStatus status; + string name = mesh.name().asChar(); + + if (verbose >= 3) { + nout << " numPolygons: " + << mesh.numPolygons() + << "\n"; + nout << " numVertices: " + << mesh.numVertices() + << "\n"; + } + + if (mesh.numPolygons() == 0) { + if (verbose >= 2) { + nout << "Ignoring empty mesh " << name << "\n"; + } + return; + } + + string vpool_name = name + ".verts"; + EggVertexPool *vpool = new EggVertexPool(vpool_name); + egg_group->add_child(vpool); + + /* + MDagPath mesh_path; + status = mesh.getPath(mesh_path); + if (!status) { + status.perror("MFnMesh::dagPath"); + return; + } + */ + MObject component_obj; + MItMeshPolygon pi(dag_path, component_obj, &status); + if (!status) { + status.perror("MItMeshPolygon constructor"); + return; + } + + MObjectArray shaders; + MIntArray poly_shader_indices; + + status = mesh.getConnectedShaders(dag_path.instanceNumber(), + shaders, poly_shader_indices); + if (!status) { + status.perror("MFnMesh::getConnectedShaders"); + } + + while (!pi.isDone()) { + EggPolygon *egg_poly = new EggPolygon; + egg_group->add_child(egg_poly); + + long num_verts = pi.polygonVertexCount(); + for (long i = 0; i < num_verts; i++) { + EggVertex vert; + + MPoint p = pi.point(i, MSpace::kWorld); + vert.set_pos(LPoint3d(p[0], p[1], p[2])); + + MVector n; + status = pi.getNormal(i, n, MSpace::kWorld); + if (!status) { + status.perror("MItMeshPolygon::getNormal"); + } else { + vert.set_normal(LVector3d(n[0], n[1], n[2])); + } + + if (pi.hasUVs()) { + float2 uvs; + status = pi.getUV(i, uvs); + if (!status) { + status.perror("MItMeshPolygon::getUV"); + } else { + vert.set_uv(TexCoordd(uvs[0], uvs[1])); + } + } + + if (pi.hasColor()) { + MColor c; + status = pi.getColor(c, i); + if (!status) { + status.perror("MItMeshPolygon::getColor"); + } else { + vert.set_color(Colorf(c.r, c.g, c.b, 1.0)); + } + } + + egg_poly->add_vertex(vpool->create_unique_vertex(vert)); + } + + // Determine the shader for this particular polygon. + int index = pi.index(); + assert(index >= 0 && index < poly_shader_indices.length()); + int shader_index = poly_shader_indices[index]; + if (shader_index != -1) { + assert(shader_index >= 0 && shader_index < shaders.length()); + MObject engine = shaders[shader_index]; + MayaShader *shader = + _shaders.find_shader_for_shading_engine(engine); + if (shader != (MayaShader *)NULL) { + shader->set_attributes(*egg_poly, *this); + } + + } else if (default_shader != (MayaShader *)NULL) { + default_shader->set_attributes(*egg_poly, *this); + } + + pi.next(); + } +} + + +EggGroup *MayaFile:: +get_egg_group(const string &name, EggData &data) { + Groups::const_iterator gi = _groups.find(name); + if (gi != _groups.end()) { + return (*gi).second; + } + + EggGroup *egg_group; + + if (name.empty()) { + // This is the top. + egg_group = (EggGroup *)NULL; + + } else { + size_t bar = name.rfind("|"); + string parent_name, local_name; + if (bar != NPOS) { + parent_name = name.substr(0, bar); + local_name = name.substr(bar + 1); + } else { + local_name = name; + } + + EggGroup *parent_egg_group = get_egg_group(parent_name, data); + egg_group = new EggGroup(local_name); + + if (parent_egg_group != (EggGroup *)NULL) { + parent_egg_group->add_child(egg_group); + } else { + data.add_child(egg_group); + } + } + + _groups.insert(Groups::value_type(name, egg_group)); + return egg_group; +} diff --git a/pandatool/src/maya/mayaFile.h b/pandatool/src/maya/mayaFile.h new file mode 100644 index 0000000000..8fde42dcf0 --- /dev/null +++ b/pandatool/src/maya/mayaFile.h @@ -0,0 +1,63 @@ +// Filename: mayaFile.h +// Created by: drose (10Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef MAYAFILE_H +#define MAYAFILE_H + +#include + +#include "mayaShaders.h" + +#include + +class EggData; +class EggGroup; +class EggVertexPool; +class EggNurbsCurve; + +class MDagPath; +class MFnNurbsSurface; +class MFnNurbsCurve; +class MFnMesh; +class MPointArray; + +class MayaFile { +public: + MayaFile(); + ~MayaFile(); + + bool init(const string &program); + bool read(const string &filename); + void make_egg(EggData &data); + +private: + bool traverse(EggData &data); + bool process_node(const MDagPath &dag_path, EggData &data); + void get_transform(const MDagPath &dag_path, EggGroup *egg_group); + void make_nurbs_surface(const MDagPath &dag_path, MFnNurbsSurface surface, + EggGroup *group); + EggNurbsCurve *make_trim_curve(MFnNurbsCurve curve, + const string &nurbs_name, + EggGroupNode *egg_group, + int trim_curve_index); + void make_nurbs_curve(const MDagPath &dag_path, MFnNurbsCurve curve, + EggGroup *group); + void make_polyset(const MDagPath &dag_path, MFnMesh mesh, + EggGroup *egg_group, + MayaShader *default_shader = NULL); + + EggGroup *get_egg_group(const string &name, EggData &data); + + typedef map Groups; + Groups _groups; + +public: + double _scale_units; + MayaShaders _shaders; + EggTextureCollection _textures; +}; + + +#endif diff --git a/pandatool/src/maya/mayaShader.cxx b/pandatool/src/maya/mayaShader.cxx new file mode 100644 index 0000000000..22d18736fd --- /dev/null +++ b/pandatool/src/maya/mayaShader.cxx @@ -0,0 +1,201 @@ +// Filename: mayaShader.cxx +// Created by: drose (01Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "mayaShader.h" +#include "maya_funcs.h" +#include "mayaFile.h" +#include "global_parameters.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +MayaShader:: +MayaShader(MObject engine) { + _has_color = false; + _transparency = 0.0; + + _has_texture = false; + + _coverage.set(1.0, 1.0); + _translate_frame.set(0.0, 0.0); + _rotate_frame = 0.0; + + _mirror = false; + _stagger = false; + _wrap_u = true; + _wrap_v = true; + + _repeat_uv.set(1.0, 1.0); + _offset.set(0.0, 0.0); + _rotate_uv = 0.0; + + MFnDependencyNode engine_fn(engine); + + _name = engine_fn.name().asChar(); + + if (verbose >= 2) { + nout << "Reading shading engine " << _name << "\n"; + } + + bool found_shader = false; + MPlug shader_plug = engine_fn.findPlug("surfaceShader"); + if (!shader_plug.isNull()) { + MPlugArray shader_pa; + shader_plug.connectedTo(shader_pa, true, false); + + for (size_t i = 0; i < shader_pa.length() && !found_shader; i++) { + MObject shader = shader_pa[0].node(); + found_shader = read_surface_shader(shader); + } + } +} + +void MayaShader:: +set_attributes(EggPrimitive &primitive, MayaFile &file) { + if (_has_texture) { + EggTextureCollection &textures = file._textures; + EggTexture tex(_name, _texture); + tex.set_wrap_u(_wrap_u ? EggTexture::WM_repeat : EggTexture::WM_clamp); + tex.set_wrap_v(_wrap_v ? EggTexture::WM_repeat : EggTexture::WM_clamp); + + LMatrix3d mat = compute_texture_matrix(); + if (!mat.almost_equal(LMatrix3d::ident_mat())) { + tex.set_transform(mat); + } + + EggTexture *new_tex = + textures.create_unique_texture(tex, ~EggTexture::E_tref_name); + + primitive.set_texture(new_tex); + + } else if (_has_color) { + primitive.set_color(Colorf(_color[0], _color[1], _color[2], 1.0)); + } +} + +LMatrix3d MayaShader:: +compute_texture_matrix() { + LVector2d scale(_repeat_uv[0] / _coverage[0], + _repeat_uv[1] / _coverage[1]); + LVector2d trans(_offset[0] - _translate_frame[0] / _coverage[0], + _offset[1] - _translate_frame[1] / _coverage[1]); + + return + (LMatrix3d::translate_mat(LVector2d(-0.5, -0.5)) * + LMatrix3d::rotate_mat(_rotate_frame) * + LMatrix3d::translate_mat(LVector2d(0.5, 0.5))) * + LMatrix3d::scale_mat(scale) * + LMatrix3d::translate_mat(trans); +} + + +void MayaShader:: +output(ostream &out) const { + out << "Shader " << _name << ":\n"; + if (_has_texture) { + out << " texture is " << _texture << "\n" + << " coverage is " << _coverage << "\n" + << " translate_frame is " << _translate_frame << "\n" + << " rotate_frame is " << _rotate_frame << "\n" + << " mirror is " << _mirror << "\n" + << " stagger is " << _stagger << "\n" + << " wrap_u is " << _wrap_u << "\n" + << " wrap_v is " << _wrap_v << "\n" + << " repeat_uv is " << _repeat_uv << "\n" + << " offset is " << _offset << "\n" + << " rotate_uv is " << _rotate_uv << "\n"; + + } else if (_has_color) { + out << " color is " << _color << "\n"; + } +} + +bool MayaShader:: +read_surface_shader(MObject shader) { + MStatus status; + MFnDependencyNode shader_fn(shader); + + if (verbose >= 3) { + nout << " Reading surface shader " << shader_fn.name() << "\n"; + } + + // First, check for a connection to the color attribute. This could + // be a texture map or something, and will override whatever the + // shader says for color. + + MPlug color_plug = shader_fn.findPlug("color"); + if (!color_plug.isNull()) { + MPlugArray color_pa; + color_plug.connectedTo(color_pa, true, false); + + for (size_t i = 0; i < color_pa.length(); i++) { + read_surface_color(color_pa[0].node()); + } + } + + // Also try to get the ordinary color directly from the surface + // shader. + if (shader.hasFn(MFn::kLambert)) { + MFnLambertShader lambert_fn(shader); + MColor color = lambert_fn.color(&status); + if (status) { + _color.set(color.r, color.g, color.b, color.a); + _has_color = true; + } + } + + if (!_has_color && !_has_texture) { + if (verbose >= 2) { + nout << " Color definition not found.\n"; + } + } + return true; +} + +void MayaShader:: +read_surface_color(MObject color) { + if (color.hasFn(MFn::kFileTexture)) { + _has_texture = get_string_attribute(color, "fileTextureName", _texture); + + get_vec2f_attribute(color, "coverage", _coverage); + get_vec2f_attribute(color, "translateFrame", _translate_frame); + get_angle_attribute(color, "rotateFrame", _rotate_frame); + + get_bool_attribute(color, "mirror", _mirror); + get_bool_attribute(color, "stagger", _stagger); + get_bool_attribute(color, "wrapU", _wrap_u); + get_bool_attribute(color, "wrapV", _wrap_v); + + get_vec2f_attribute(color, "repeatUV", _repeat_uv); + get_vec2f_attribute(color, "offset", _offset); + get_angle_attribute(color, "rotateUV", _rotate_uv); + } else { + // This shader wasn't understood. + if (verbose >= 2) { + nout << "**Don't know how to interpret color attribute type " + << color.apiTypeStr() << "\n"; + } else { + // If we don't have a heavy verbose count, only report each type + // of unsupportted shader once. + static set bad_types; + if (bad_types.insert(color.apiType()).second) { + if (verbose == 1) { + nout << "\n"; + } + nout << "Don't know how to interpret color attribute type " + << color.apiTypeStr() << "\n"; + } + } + } +} diff --git a/pandatool/src/maya/mayaShader.h b/pandatool/src/maya/mayaShader.h new file mode 100644 index 0000000000..b38bdf50d6 --- /dev/null +++ b/pandatool/src/maya/mayaShader.h @@ -0,0 +1,62 @@ +// Filename: mayaShader.h +// Created by: drose (01Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef MAYASHADER_H +#define MAYASHADER_H + +#include + +#include +#include + +#include + +class MObject; +class MayaFile; +class EggPrimitive; + +class MayaShader { +public: + MayaShader(MObject engine); + + void set_attributes(EggPrimitive &primitive, MayaFile &file); + LMatrix3d compute_texture_matrix(); + + void output(ostream &out) const; + + string _name; + + bool _has_color; + Colord _color; + double _transparency; + + bool _has_texture; + string _texture; + + LVector2f _coverage; + LVector2f _translate_frame; + double _rotate_frame; + + bool _mirror; + bool _stagger; + bool _wrap_u; + bool _wrap_v; + + LVector2f _repeat_uv; + LVector2f _offset; + double _rotate_uv; + +protected: + bool read_surface_shader(MObject shader); + void read_surface_color(MObject color); +}; + +inline ostream &operator << (ostream &out, const MayaShader &shader) { + shader.output(out); + return out; +} + +#endif + diff --git a/pandatool/src/maya/mayaShaders.cxx b/pandatool/src/maya/mayaShaders.cxx new file mode 100644 index 0000000000..8e3f73cfac --- /dev/null +++ b/pandatool/src/maya/mayaShaders.cxx @@ -0,0 +1,82 @@ +// Filename: mayaShaders.cxx +// Created by: drose (11Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "mayaShaders.h" +#include "mayaShader.h" +#include "global_parameters.h" + +#include +#include +#include +#include +#include +#include + +MayaShader *MayaShaders:: +find_shader_for_node(MObject node) { + MStatus status; + MFnDependencyNode node_fn(node); + + // Look on the instObjGroups attribute for shading engines. + MObject iog_attr = node_fn.attribute("instObjGroups", &status); + if (!status) { + // The node is not renderable. What are you thinking? + nout << node_fn.name() << " : not a renderable object.\n"; + return (MayaShader *)NULL; + } + + // instObjGroups is a multi attribute, whatever that means. For + // now, we'll just get the first connection, since that's what the + // example code did. Is there any reason to search deeper? + + MPlug iog_plug(node, iog_attr); + MPlugArray iog_pa; + iog_plug.elementByLogicalIndex(0).connectedTo(iog_pa, false, true, &status); + if (!status) { + // No shading group defined for this object. + nout << node_fn.name() << " : no shading group defined.\n"; + return (MayaShader *)NULL; + } + + // Now we have a number of ShadingEngines defined, one for each of + // these connections we just turned up. Usually there will only be + // one. In fact, we'll just take the first one we find. + + size_t i; + for (i = 0; i < iog_pa.length(); i++) { + MObject engine = iog_pa[i].node(); + if (engine.hasFn(MFn::kShadingEngine)) { + return find_shader_for_shading_engine(engine); + } + } + + // Well, we didn't find a ShadingEngine after all. Huh. + if (verbose >= 2) { + nout << node_fn.name() << " : no shading engine found.\n"; + } + return (MayaShader *)NULL; +} + +MayaShader *MayaShaders:: +find_shader_for_shading_engine(MObject engine) { + MFnDependencyNode engine_fn(engine); + + // See if we have already decoded this engine. + string engine_name = engine_fn.name().asChar(); + Shaders::const_iterator si = _shaders.find(engine_name); + if (si != _shaders.end()) { + return (*si).second; + } + + // All right, this is a newly encountered shading engine. Create a + // new MayaShader object to represent it. + MayaShader *shader = new MayaShader(engine); + + // Record this for the future. + _shaders.insert(Shaders::value_type(engine_name, shader)); + return shader; +} + + diff --git a/pandatool/src/maya/mayaShaders.h b/pandatool/src/maya/mayaShaders.h new file mode 100644 index 0000000000..5e0a37ceb6 --- /dev/null +++ b/pandatool/src/maya/mayaShaders.h @@ -0,0 +1,29 @@ +// Filename: mayaShaders.h +// Created by: drose (11Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef MAYASHADERS_H +#define MAYASHADERS_H + +#include + +#include +#include + +class MayaShader; +class MObject; + +class MayaShaders { +public: + MayaShader *find_shader_for_node(MObject node); + MayaShader *find_shader_for_shading_engine(MObject engine); + +protected: + + typedef map Shaders; + Shaders _shaders; +}; + +#endif + diff --git a/pandatool/src/maya/mayaToEgg.cxx b/pandatool/src/maya/mayaToEgg.cxx new file mode 100644 index 0000000000..488d7a0efe --- /dev/null +++ b/pandatool/src/maya/mayaToEgg.cxx @@ -0,0 +1,96 @@ +// Filename: mayaToEgg.cxx +// Created by: drose (15Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "mayaToEgg.h" +#include "global_parameters.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: MayaToEgg::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +MayaToEgg:: +MayaToEgg() : + SomethingToEgg("Maya", ".mb") +{ + set_program_description + ("This program converts Maya model files to egg. Nothing fancy yet."); + + add_option + ("p", "", 0, + "Generate polygon output only. Tesselate all NURBS surfaces to " + "polygons via the built-in Maya tesselator. The tesselation will " + "be based on the tolerance factor given by -ptol.", + &MayaToEgg::dispatch_none, &polygon_output); + + add_option + ("ptol", "tolerance", 0, + "Specify the fit tolerance for Maya polygon tesselation. The smaller " + "the number, the more polygons will be generated. The default is " + "0.01.", + &MayaToEgg::dispatch_double, NULL, &polygon_tolerance); + + add_option + ("notrans", "", 0, + "Don't convert explicit DAG transformations given in the Maya file. " + "Instead, convert all vertices to world space and write the file as " + "one big transform space. Using this option doesn't change the " + "position of objects in the scene, just the number of explicit " + "transforms appearing in the resulting egg file.", + &MayaToEgg::dispatch_none, &ignore_transforms); + + add_option + ("v", "", 0, + "Increase verbosity. More v's means more verbose.", + &MayaToEgg::dispatch_count, NULL, &verbose); + verbose = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: MayaToEgg::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void MayaToEgg:: +run() { + nout << "Initializing Maya.\n"; + if (!_maya.init(_program_name)) { + nout << "Unable to initialize Maya.\n"; + exit(1); + } + + if (!_maya.read(_input_filename.c_str())) { + nout << "Error reading " << _input_filename << ".\n"; + exit(1); + } + + // First, we build the egg file in the same coordinate system as + // Maya. + if (MGlobal::isYAxisUp()) { + _data.set_coordinate_system(CS_yup_right); + } else { + _data.set_coordinate_system(CS_zup_right); + } + + _maya.make_egg(_data); + + // Then, if the user so requested, we convert the egg file to the + // desired output coordinate system. + if (_got_coordinate_system) { + _data.set_coordinate_system(_coordinate_system); + } + + _data.write_egg(get_output()); +} + + +int main(int argc, char *argv[]) { + MayaToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/maya/mayaToEgg.h b/pandatool/src/maya/mayaToEgg.h new file mode 100644 index 0000000000..6cdb023278 --- /dev/null +++ b/pandatool/src/maya/mayaToEgg.h @@ -0,0 +1,28 @@ +// Filename: mayaToEgg.h +// Created by: drose (15Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef MAYATOEGG_H +#define MAYATOEGG_H + +#include + +#include "mayaFile.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : MayaToEgg +// Description : +//////////////////////////////////////////////////////////////////// +class MayaToEgg : public SomethingToEgg { +public: + MayaToEgg(); + + void run(); + + MayaFile _maya; +}; + +#endif diff --git a/pandatool/src/maya/maya_funcs.I b/pandatool/src/maya/maya_funcs.I new file mode 100644 index 0000000000..05e19e02fc --- /dev/null +++ b/pandatool/src/maya/maya_funcs.I @@ -0,0 +1,41 @@ +// Filename: maya_funcs.I +// Created by: drose (16Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +template +bool +get_maya_attribute(MObject &node, const string &attribute_name, + ValueType &value) { + MStatus status; + MFnDependencyNode node_fn(node, &status); + if (!status) { + nout << "Object is a " << node.apiTypeStr() << ", not a DependencyNode.\n"; + return false; + } + + MObject attr = node_fn.attribute(attribute_name.c_str(), &status); + if (!status) { + nout << "Object " << node_fn.name() << " does not support attribute " + << attribute_name << "\n"; + return false; + } + + MFnAttribute attr_fn(attr, &status); + if (!status) { + nout << "Attribute " << attribute_name << " on " << node_fn.name() + << " is a " << attr.apiTypeStr() << ", not an Attribute.\n"; + return false; + } + + MPlug plug(node, attr); + status = plug.getValue(value); + + return status; +} diff --git a/pandatool/src/maya/maya_funcs.cxx b/pandatool/src/maya/maya_funcs.cxx new file mode 100644 index 0000000000..8a80e5ae53 --- /dev/null +++ b/pandatool/src/maya/maya_funcs.cxx @@ -0,0 +1,141 @@ +// Filename: maya_funcs.cxx +// Created by: drose (16Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "maya_funcs.h" + +#include +#include +#include +#include +#include +#include + +bool +get_bool_attribute(MObject &node, const string &attribute_name, + bool &value) { + if (!get_maya_attribute(node, attribute_name, value)) { + nout << "Attribute " << attribute_name + << " does not have an bool value.\n"; + describe_maya_attribute(node, attribute_name); + return false; + } + return true; +} + +bool +get_angle_attribute(MObject &node, const string &attribute_name, + double &value) { + MAngle maya_value; + if (!get_maya_attribute(node, attribute_name, maya_value)) { + nout << "Attribute " << attribute_name + << " does not have an angle value.\n"; + describe_maya_attribute(node, attribute_name); + return false; + } + value = maya_value.asDegrees(); + return true; +} + +bool +get_vec2f_attribute(MObject &node, const string &attribute_name, + LVecBase2f &value) { + MStatus status; + + MObject vec2f_object; + if (!get_maya_attribute(node, attribute_name, vec2f_object)) { + nout << "Attribute " << attribute_name + << " does not have a vec2f object value.\n"; + describe_maya_attribute(node, attribute_name); + return false; + } + + MFnNumericData data(vec2f_object, &status); + if (!status) { + nout << "Attribute " << attribute_name << " is of type " + << vec2f_object.apiTypeStr() << ", not a NumericData.\n"; + return false; + } + + status = data.getData(value[0], value[1]); + if (!status) { + nout << "Unable to extract 2 floats from " << attribute_name + << ", of type " << vec2f_object.apiTypeStr() << "\n"; + } + + return true; +} + +bool +get_vec2d_attribute(MObject &node, const string &attribute_name, + LVecBase2d &value) { + MStatus status; + + MObject vec2d_object; + if (!get_maya_attribute(node, attribute_name, vec2d_object)) { + nout << "Attribute " << attribute_name + << " does not have a vec2d object value.\n"; + describe_maya_attribute(node, attribute_name); + return false; + } + + MFnNumericData data(vec2d_object, &status); + if (!status) { + nout << "Attribute " << attribute_name << " is of type " + << vec2d_object.apiTypeStr() << ", not a NumericData.\n"; + return false; + } + + status = data.getData(value[0], value[1]); + if (!status) { + nout << "Unable to extract 2 doubles from " << attribute_name + << ", of type " << vec2d_object.apiTypeStr() << "\n"; + } + + return true; +} + +bool +get_string_attribute(MObject &node, const string &attribute_name, + string &value) { + MStatus status; + + MObject string_object; + if (!get_maya_attribute(node, attribute_name, string_object)) { + nout << "Attribute " << attribute_name + << " does not have an string object value.\n"; + describe_maya_attribute(node, attribute_name); + return false; + } + + MFnStringData data(string_object, &status); + if (!status) { + nout << "Attribute " << attribute_name << " is of type " + << string_object.apiTypeStr() << ", not a StringData.\n"; + return false; + } + + value = data.string().asChar(); + return true; +} + +void +describe_maya_attribute(MObject &node, const string &attribute_name) { + MStatus status; + MFnDependencyNode node_fn(node, &status); + if (!status) { + nout << "Object is a " << node.apiTypeStr() << ", not a DependencyNode.\n"; + return; + } + + MObject attr = node_fn.attribute(attribute_name.c_str(), &status); + if (!status) { + nout << "Object " << node_fn.name() << " does not support attribute " + << attribute_name << "\n"; + return; + } + + nout << "Attribute " << attribute_name << " on object " + << node_fn.name() << " has type " << attr.apiTypeStr() << "\n"; +} diff --git a/pandatool/src/maya/maya_funcs.h b/pandatool/src/maya/maya_funcs.h new file mode 100644 index 0000000000..1c39d50ebe --- /dev/null +++ b/pandatool/src/maya/maya_funcs.h @@ -0,0 +1,47 @@ +// Filename: maya_funcs.h +// Created by: drose (16Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef MAYA_FUNCS_H +#define MAYA_FUNCS_H + +#include + +#include + +#include + +class MObject; + +template +bool +get_maya_attribute(MObject &node, const string &attribute_name, + ValueType &value); + +bool +get_bool_attribute(MObject &node, const string &attribute_name, + bool &value); + +bool +get_angle_attribute(MObject &node, const string &attribute_name, + double &value); + +bool +get_vec2f_attribute(MObject &node, const string &attribute_name, + LVecBase2f &value); + +bool +get_vec2d_attribute(MObject &node, const string &attribute_name, + LVecBase2d &value); + +bool +get_string_attribute(MObject &node, const string &attribute_name, + string &value); + +void +describe_maya_attribute(MObject &node, const string &attribute_name); + +#include "maya_funcs.I" + +#endif diff --git a/pandatool/src/pandatoolbase/Sources.pp b/pandatool/src/pandatoolbase/Sources.pp new file mode 100644 index 0000000000..57430b4543 --- /dev/null +++ b/pandatool/src/pandatoolbase/Sources.pp @@ -0,0 +1,12 @@ +#define OTHER_LIBS panda dtool + +#begin lib_target + #define TARGET pandatoolbase + + #define SOURCES \ + pandatoolbase.cxx pandatoolbase.h + + #define INSTALL_HEADERS \ + pandatoolbase.h + +#end lib_target diff --git a/pandatool/src/pandatoolbase/pandatoolbase.cxx b/pandatool/src/pandatoolbase/pandatoolbase.cxx new file mode 100644 index 0000000000..fdab5447f7 --- /dev/null +++ b/pandatool/src/pandatoolbase/pandatoolbase.cxx @@ -0,0 +1,6 @@ +// Filename: pandatoolbase.cc +// Created by: drose (15Sep00) +// +//////////////////////////////////////////////////////////////////// + +#include "pandatoolbase.h" diff --git a/pandatool/src/pandatoolbase/pandatoolbase.h b/pandatool/src/pandatoolbase/pandatoolbase.h new file mode 100644 index 0000000000..2329429296 --- /dev/null +++ b/pandatool/src/pandatoolbase/pandatoolbase.h @@ -0,0 +1,17 @@ +/* + * Filename: pandatoolbase.h + * Created by: drose (12Sep00) + * + */ + +/* This file is included at the beginning of every header file and/or + C or C++ file. It must be compilable for C as well as C++ files, + so no C++-specific code or syntax can be put here. */ + +#ifndef PANDATOOLBASE_H +#define PANDATOOLBASE_H + +#include + +#endif + diff --git a/pandatool/src/progbase/Sources.pp b/pandatool/src/progbase/Sources.pp new file mode 100644 index 0000000000..aa5a220eee --- /dev/null +++ b/pandatool/src/progbase/Sources.pp @@ -0,0 +1,28 @@ +#begin lib_target + #define TARGET progbase + #define LOCAL_LIBS \ + config compiler + #define OTHER_LIBS \ + linmath:c putil:c express:c panda:m pystub + + #define SOURCES \ + programBase.I programBase.cxx programBase.h wordWrapStream.cxx \ + wordWrapStream.h wordWrapStreamBuf.I wordWrapStreamBuf.cxx \ + wordWrapStreamBuf.h + + #define INSTALL_HEADERS \ + programBase.I programBase.h wordWrapStream.h wordWrapStreamBuf.I \ + wordWrapStreamBuf.h + +#end lib_target + +#begin test_bin_target + #define TARGET test_prog + #define LOCAL_LIBS \ + progbase + + #define SOURCES \ + test_prog.cxx + +#end test_bin_target + diff --git a/pandatool/src/progbase/programBase.I b/pandatool/src/progbase/programBase.I new file mode 100644 index 0000000000..141aee592d --- /dev/null +++ b/pandatool/src/progbase/programBase.I @@ -0,0 +1,16 @@ +// Filename: programBase.I +// Created by: drose (28Jun00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::show_text +// Access: Public +// Description: Formats the indicated text to stderr with the known +// _terminal_width. +//////////////////////////////////////////////////////////////////// +INLINE void ProgramBase:: +show_text(const string &text) { + show_text("", 0, text); +} diff --git a/pandatool/src/progbase/programBase.cxx b/pandatool/src/progbase/programBase.cxx new file mode 100644 index 0000000000..2cd57c1365 --- /dev/null +++ b/pandatool/src/progbase/programBase.cxx @@ -0,0 +1,836 @@ +// Filename: programBase.cxx +// Created by: drose (13Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "programBase.h" +#include "wordWrapStream.h" + +#include +// Since programBase.cxx includes pystub.h, no program that links with +// progbase needs to do so. No Python code should attempt to link +// with libprogbase.so. + +#include +#include +#include +#include + +#include +#include +#include + +// If our system getopt() doesn't come with getopt_long_only(), then use +// the GNU flavor that we've got in tool for this purpose. +#ifndef HAVE_GETOPT_LONG_ONLY +#include +#else +#include +#endif + +// This manifest is defined if we are running on a system (e.g. most +// any Unix) that allows us to determine the width of the terminal +// screen via an ioctl() call. It's just handy to know for formatting +// output nicely for the user. +#ifdef IOCTL_TERMINAL_WIDTH +#include +#ifndef TIOCGWINSZ +#include +#endif // TIOCGWINSZ +#endif // IOCTL_TERMINAL_WIDTH + +bool ProgramBase::SortOptionsByIndex:: +operator () (const Option *a, const Option *b) const { + if (a->_index_group != b->_index_group) { + return a->_index_group < b->_index_group; + } + return a->_sequence < b->_sequence; +} + +// This should be called at program termination just to make sure +// Notify gets properly flushed before we exit, if someone calls +// exit(). It's probably not necessary, but why not be phobic about +// it? +static void flush_nout() { + nout << flush; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +ProgramBase:: +ProgramBase() { + // A call to pystub() to force libpystub.so to be linked in. + pystub(); + + // Set up Notify to write output to our own formatted stream. + Notify::ptr()->set_ostream_ptr(new WordWrapStream(this), true); + + // And we'll want to be sure to flush that in all normal exit cases. + atexit(&flush_nout); + + _next_sequence = 0; + _sorted_options = false; + _got_terminal_width = false; + _got_option_indent = false; + + add_option("h", "", 100, + "Display this help page.", + &ProgramBase::handle_help_option); + + // Should we report DConfig's debugging information? + if (dconfig_cat.is_debug()) { + dconfig_cat.debug() + << "DConfig took " << Config::get_total_time_config_init() + << " CPU seconds initializing, and " + << Config::get_total_time_external_init() + << " CPU seconds calling external initialization routines.\n"; + dconfig_cat.debug() + << "ConfigTable::GetSym() was called " + << Config::get_total_num_get() << " times.\n"; + } + + // It's nice to start with a blank line. + nout << "\r"; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +ProgramBase:: +~ProgramBase() { + // Reset Notify in case any messages get sent after our + // destruction--our stream is no longer valid. + Notify::ptr()->set_ostream_ptr(NULL, false); +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::show_description +// Access: Public +// Description: Writes the program description to stderr. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +show_description() { + nout << _description << "\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::show_usage +// Access: Public +// Description: Writes the usage line(s) to stderr. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +show_usage() { + nout << "\rUsage:\n"; + Runlines::const_iterator ri; + string prog = " " +_program_name.get_basename(); + + for (ri = _runlines.begin(); ri != _runlines.end(); ++ri) { + show_text(prog, prog.length() + 1, *ri); + } + nout << "\r"; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::show_options +// Access: Public +// Description: Describes each of the available options to stderr. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +show_options() { + sort_options(); + if (!_got_option_indent) { + get_terminal_width(); + _option_indent = min(15, (int)(_terminal_width * 0.25)); + _got_option_indent = true; + } + + nout << "Options:\n"; + OptionsByIndex::const_iterator oi; + for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) { + const Option &opt = *(*oi); + string prefix = " -" + opt._option + " " + opt._parm_name; + show_text(prefix, _option_indent, opt._description + "\r"); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::show_text +// Access: Public +// Description: Formats the indicated text and its prefix for output +// to stderr with the known _terminal_width. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +show_text(const string &prefix, int indent_width, string text) { + get_terminal_width(); + + // This is correct! It goes go to cerr, not to nout. Sending it to + // nout would be cyclic, since nout is redefined to map back through + // this function. + format_text(cerr, prefix, indent_width, text, _terminal_width); +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::parse_command_line +// Access: Public, Virtual +// Description: Dispatches on each of the options on the command +// line, and passes the remaining parameters to +// handle_args(). If an error on the command line is +// detected, will automatically call show_usage() and +// exit(1). +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +parse_command_line(int argc, char *argv[]) { + _program_name = argv[0]; + int i; + for (i = 1; i < argc; i++) { + _program_args.push_back(argv[i]); + } + + // Build up the long options list and the short options string for + // getopt_long_only(). + vector long_options; + string short_options; + + // We also need to build a temporary map of int index numbers to + // Option pointers. We'll pass these index numbers to GNU's + // getopt_long() so we can tell one option from another. + typedef map Options; + Options options; + + OptionsByName::const_iterator oi; + int next_index = 256; + + // Let's prefix the option string with "-" to tell GNU getopt that + // we want it to tell us the post-option arguments, instead of + // trying to meddle with ARGC and ARGV (which we aren't using + // directly). + short_options = "-"; + + for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) { + const Option &opt = (*oi).second; + + int index; + if (opt._option.length() == 1) { + // This is a "short" option; its option string consists of only + // one letter. Its index is the letter itself. + index = (int)opt._option[0]; + + short_options += opt._option; + if (!opt._parm_name.empty()) { + // This option takes an argument. + short_options += ':'; + } + } else { + // This is a "long" option; we'll assign it the next available + // index. + index = ++next_index; + } + + // Now add it to the GNU data structures. + struct option gopt; + gopt.name = opt._option.c_str(); + gopt.has_arg = (opt._parm_name.empty()) ? + no_argument : required_argument; + gopt.flag = (int *)NULL; + + // Return an index into the _options_by_index array, offset by 256 + // so we don't confuse it with '?'. + gopt.val = index; + + long_options.push_back(gopt); + + options[index] = &opt; + } + + // Finally, add one more structure, all zeroes, to indicate the end + // of the options. + struct option gopt; + memset(&gopt, 0, sizeof(gopt)); + long_options.push_back(gopt); + + // We'll use this vector to save the non-option arguments. + // Generally, these will all be at the end, but with the GNU + // extensions, they need not be. + Args remaining_args; + + // Now call getopt_long() to actually parse the arguments. + extern char *optarg; + const struct option *long_opts = &long_options[0]; + + int flag = + getopt_long_only(argc, argv, short_options.c_str(), long_opts, NULL); + while (flag != EOF) { + string arg; + if (optarg != NULL) { + arg = optarg; + } + + switch (flag) { + case '?': + // Invalid option or parameter. + show_usage(); + exit(1); + + case '\x1': + // A special return value from getopt() indicating a non-option + // argument. + remaining_args.push_back(arg); + break; + + default: + { + // A normal option. Figure out which one it is. + Options::const_iterator ii; + ii = options.find(flag); + if (ii == options.end()) { + nout << "Internal error! Invalid option index returned.\n"; + abort(); + } + + const Option &opt = *(*ii).second; + bool okflag = true; + if (opt._option_function != (OptionDispatch)NULL) { + okflag = (this->*opt._option_function)(opt._option, arg, + opt._option_data); + } + if (opt._bool_var != (bool *)NULL) { + (*opt._bool_var) = true; + } + + if (!okflag) { + show_usage(); + exit(1); + } + } + } + + flag = + getopt_long_only(argc, argv, short_options.c_str(), long_opts, NULL); + } + + if (!handle_args(remaining_args)) { + show_usage(); + exit(1); + } + + if (!post_command_line()) { + show_usage(); + exit(1); + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +handle_args(ProgramBase::Args &args) { + if (!args.empty()) { + nout << "Unexpected arguments on command line:\n"; + copy(args.begin(), args.end(), ostream_iterator(nout, " ")); + nout << "\r"; + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::post_command_line +// Access: Protected, Virtual +// Description: This is called after the command line has been +// completely processed, and it gives the program a +// chance to do some last-minute processing and +// validation of the options and arguments. It should +// return true if everything is fine, false if there is +// an error. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +post_command_line() { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::set_program_description +// Access: Protected +// Description: Sets the description of the program that will be +// reported by show_usage(). The description should be +// one long string of text. Embedded newline characters +// are interpreted as paragraph breaks and printed as +// blank lines. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +set_program_description(const string &description) { + _description = description; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::clear_runlines +// Access: Protected +// Description: Removes all of the runlines that were previously +// added, presumably before adding some new ones. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +clear_runlines() { + _runlines.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::add_runline +// Access: Protected +// Description: Adds an additional line to the list of lines that +// will be displayed to describe briefly how the program +// is to be run. Each line should be something like +// "[opts] arg1 arg2", that is, it does *not* include +// the name of the program, but it includes everything +// that should be printed after the name of the program. +// +// Normally there is only one runline for a given +// program, but it is possible to define more than one. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +add_runline(const string &runline) { + _runlines.push_back(runline); +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::clear_options +// Access: Protected +// Description: Removes all of the options that were previously +// added, presumably before adding some new ones. +// Normally you wouldn't want to do this unless you want +// to completely replace all of the options defined by +// base classes. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +clear_options() { + _options_by_name.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::add_option +// Access: Protected +// Description: Adds (or redefines) a command line option. When +// parse_command_line() is executed it will look for +// these options (followed by a hyphen) on the command +// line; when a particular option is found it will call +// the indicated option_function, supplying the provided +// option_data. This allows the user to define a +// function that does some special behavior for any +// given option, or to use any of a number of generic +// pre-defined functions to fill in data for each +// option. +// +// Each option may or may not take a parameter. If +// parm_name is nonempty, it is assumed that the option +// does take a parameter (and parm_name contains the +// name that will be printed by show_options()). This +// parameter will be supplied as the second parameter to +// the dispatch function. If parm_name is empty, it is +// assumed that the option does not take a parameter. +// There is no provision for optional parameters. +// +// The options are listed first in order by their +// index_group number, and then in the order that +// add_option() was called. This provides a mechanism +// for listing the options defined in derived classes +// before those of the base classes. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +add_option(const string &option, const string &parm_name, + int index_group, const string &description, + OptionDispatch option_function, + bool *bool_var, void *option_data) { + Option opt; + opt._option = option; + opt._parm_name = parm_name; + opt._index_group = index_group; + opt._sequence = ++_next_sequence; + opt._description = description; + opt._option_function = option_function; + opt._bool_var = bool_var; + opt._option_data = option_data; + + _options_by_name[option] = opt; + _sorted_options = false; + + if (bool_var != (bool *)NULL) { + (*bool_var) = false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::redescribe_option +// Access: Protected +// Description: Changes the description associated with a +// previously-defined option. Returns true if the +// option was changed, false if it hadn't been defined. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +redescribe_option(const string &option, const string &description) { + OptionsByName::iterator oi = _options_by_name.find(option); + if (oi == _options_by_name.end()) { + return false; + } + (*oi).second._description = description; + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::remove_option +// Access: Protected +// Description: Removes a previously-defined option. Returns true if +// the option was removed, false if it hadn't existed. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +remove_option(const string &option) { + OptionsByName::iterator oi = _options_by_name.find(option); + if (oi == _options_by_name.end()) { + return false; + } + _options_by_name.erase(oi); + _sorted_options = false; + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::dispatch_none +// Access: Protected +// Description: Standard dispatch function for an option that takes +// no parameters, and does nothing special. Typically +// this would be used for a boolean flag, whose presence +// means something and whose absence means something +// else. Use the bool_var parameter to add_option() to +// determine whether the option appears on the command +// line or not. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +dispatch_none(const string &, const string &, void *) { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::dispatch_count +// Access: Protected +// Description: Standard dispatch function for an option that takes +// no parameters, but whose presence on the command line +// increments an integer counter for each time it +// appears. -v is often an option that works this way. +// The data pointer is to an int counter variable. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +dispatch_count(const string &, const string &, void *var) { + int *ip = (int *)var; + (*ip)++; + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::dispatch_int +// Access: Protected +// Description: Standard dispatch function for an option that takes +// one parameter, which is to be interpreted as an +// integer. The data pointer is to an int variable. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +dispatch_int(const string &opt, const string &arg, void *var) { + if (arg.empty()) { + nout << "-" << opt << " requires an integer parameter.\n"; + return false; + } + + int *ip = (int *)var; + const char *arg_str = arg.c_str(); + char *endptr; + (*ip) = strtol(arg_str, &endptr, 0); + + if (*endptr != '\0') { + nout << "Invalid integer parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::dispatch_double +// Access: Protected +// Description: Standard dispatch function for an option that takes +// one parameter, which is to be interpreted as a +// double. The data pointer is to an double variable. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +dispatch_double(const string &opt, const string &arg, void *var) { + if (arg.empty()) { + nout << "-" << opt << " requires a floating-point parameter.\n"; + return false; + } + + double *ip = (double *)var; + const char *arg_str = arg.c_str(); + char *endptr; + (*ip) = strtod(arg_str, &endptr); + + if (*endptr != '\0') { + nout << "Invalid floating-point parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::dispatch_string +// Access: Protected +// Description: Standard dispatch function for an option that takes +// one parameter, which is to be interpreted as a +// string. The data pointer is to a string variable. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +dispatch_string(const string &, const string &arg, void *var) { + string *ip = (string *)var; + (*ip) = arg; + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::dispatch_filename +// Access: Protected +// Description: Standard dispatch function for an option that takes +// one parameter, which is to be interpreted as a +// filename. The data pointer is to a Filename variable. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +dispatch_filename(const string &opt, const string &arg, void *var) { + if (arg.empty()) { + nout << "-" << opt << " requires a filename parameter.\n"; + return false; + } + + Filename *ip = (Filename *)var; + (*ip) = arg; + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::dispatch_coordinate_system +// Access: Protected +// Description: Standard dispatch function for an option that takes +// one parameter, which is to be interpreted as a +// coordinate system string. The data pointer is to a +// CoordinateSystem variable. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +dispatch_coordinate_system(const string &opt, const string &arg, void *var) { + CoordinateSystem *ip = (CoordinateSystem *)var; + (*ip) = parse_coordinate_system_string(arg); + + if ((*ip) == CS_invalid) { + nout << "Invalid coordinate system for -" << opt << ": " << arg << "\n" + << "Valid coordinate system strings are any of 'y-up', 'z-up', " + "'y-up-left', or 'z-up-left'.\n"; + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::handle_help_option +// Access: Protected +// Description: Called when the user enters '-h', this describes how +// to use the program and then exits. +//////////////////////////////////////////////////////////////////// +bool ProgramBase:: +handle_help_option(const string &, const string &, void *) { + show_description(); + show_usage(); + show_options(); + exit(0); + + return false; +} + + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::format_text +// Access: Protected, Static +// Description: Word-wraps the indicated text to the indicated output +// stream. The first line is prefixed with the +// indicated prefix, then tabbed over to indent_width +// where the text actually begins. A newline is +// inserted at or before column line_width. Each +// subsequent line begins with indent_width spaces. +// +// An embedded newline character ('\n') forces a line +// break, while an embedded carriage-return character +// ('\r') marks a paragraph break, which is usually +// printed as a blank line. Redundant newline and +// carriage-return characters are generally ignored. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +format_text(ostream &out, + const string &prefix, int indent_width, + const string &text, int line_width) { + indent_width = min(indent_width, line_width - 20); + int indent_amount = indent_width; + bool initial_break = false; + + if (!prefix.empty()) { + out << prefix; + indent_amount = indent_width - prefix.length(); + if (prefix.length() + 1 > indent_width) { + out << "\n"; + initial_break = true; + indent_amount = indent_width; + } + } + + size_t p = 0; + + // Skip any initial whitespace and newlines. + while (p < text.length() && isspace(text[p])) { + if (text[p] == '\r') { + if (!initial_break) { + // Here's an initial paragraph break, however. + out << "\n"; + initial_break = true; + } + indent_amount = indent_width; + + } else if (text[p] == '\n') { + // Largely ignore an initial newline. + indent_amount = indent_width; + + } else if (text[p] == ' ') { + // Do count up leading spaces. + indent_amount++; + } + p++; + } + + while (p < text.length()) { + // Look for the paragraph or line break--the next newline + // character, if any. + size_t par = text.find_first_of("\n\r", p); + bool is_paragraph_break = false; + if (par == string::npos) { + par = text.length(); + } else { + is_paragraph_break = (text[par] == '\r'); + } + + indent(out, indent_amount); + + size_t eol = p + (line_width - indent_width); + if (eol >= par) { + // The rest of the paragraph fits completely on the line. + eol = par; + + } else { + // The paragraph doesn't fit completely on the line. Determine + // the best place to break the line. Look for the last space + // before the ideal eol. + size_t min_eol = max((int)p, (int)eol - 25); + size_t q = eol; + while (q > min_eol && !isspace(text[q])) { + q--; + } + // Now roll back to the last non-space before this one. + while (q > min_eol && isspace(text[q])) { + q--; + } + + if (q != min_eol) { + // Here's a good place to stop! + eol = q + 1; + } + } + out << text.substr(p, eol - p) << "\n"; + p = eol; + + // Skip additional whitespace between the lines. + while (p < text.length() && isspace(text[p])) { + if (text[p] == '\r') { + is_paragraph_break = true; + } + p++; + } + + if (eol == par && is_paragraph_break) { + // Print the paragraph break as a blank line. + out << "\n"; + } + + indent_amount = indent_width; + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::sort_options +// Access: Private +// Description: Puts all the options in order by index number +// (e.g. in the order they were added, within +// index_groups), for output by show_options(). +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +sort_options() { + if (!_sorted_options) { + _options_by_index.clear(); + + OptionsByName::const_iterator oi; + for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) { + _options_by_index.push_back(&(*oi).second); + } + + sort(_options_by_index.begin(), _options_by_index.end(), + SortOptionsByIndex()); + _sorted_options = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: ProgramBase::get_terminal_width +// Access: Private +// Description: Attempts to determine the ideal terminal width for +// formatting output. +//////////////////////////////////////////////////////////////////// +void ProgramBase:: +get_terminal_width() { + if (!_got_terminal_width) { +#ifdef IOCTL_TERMINAL_WIDTH + struct winsize size; + int result = ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&size); + if (result < 0) { + // Couldn't determine the width for some reason. Instead of + // complaining, just punt. + _terminal_width = 72; + } else { + + // Subtract 10% for the comfort margin at the edge. + _terminal_width = size.ws_col - min(8, (int)(size.ws_col * 0.1)); + } +#else // IOCTL_TERMINAL_WIDTH + _terminal_width = 72; +#endif // IOCTL_TERMINAL_WIDTH + _got_terminal_width = true; + _got_option_indent = false; + } +} + diff --git a/pandatool/src/progbase/programBase.h b/pandatool/src/progbase/programBase.h new file mode 100644 index 0000000000..c78e441355 --- /dev/null +++ b/pandatool/src/progbase/programBase.h @@ -0,0 +1,120 @@ +// Filename: programBase.h +// Created by: drose (13Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PROGRAMBASE_H +#define PROGRAMBASE_H + +#include + +#include + +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : ProgramBase +// Description : This is intended to be the base class for most +// general-purpose utility programs in the PANDATOOL +// tree. It automatically handles things like +// command-line arguments in a portable way. +//////////////////////////////////////////////////////////////////// +class ProgramBase { +public: + ProgramBase(); + virtual ~ProgramBase(); + + void show_description(); + void show_usage(); + void show_options(); + + INLINE void show_text(const string &text); + void show_text(const string &prefix, int indent_width, string text); + + virtual void parse_command_line(int argc, char *argv[]); + + typedef vector Args; + Filename _program_name; + Args _program_args; + +protected: + typedef bool (ProgramBase::*OptionDispatch)(const string &opt, const string &parm, void *data); + + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + + void set_program_description(const string &description); + void clear_runlines(); + void add_runline(const string &runline); + void clear_options(); + void add_option(const string &option, const string &parm_name, + int index_group, const string &description, + OptionDispatch option_function, + bool *bool_var = (bool *)NULL, + void *option_data = (void *)NULL); + bool redescribe_option(const string &option, const string &description); + bool remove_option(const string &option); + + bool dispatch_none(const string &opt, const string &arg, void *); + bool dispatch_count(const string &opt, const string &arg, void *var); + bool dispatch_int(const string &opt, const string &arg, void *var); + bool dispatch_double(const string &opt, const string &arg, void *var); + bool dispatch_string(const string &opt, const string &arg, void *var); + bool dispatch_filename(const string &opt, const string &arg, void *var); + bool dispatch_coordinate_system(const string &opt, const string &arg, void *var); + + bool handle_help_option(const string &opt, const string &arg, void *); + + static void format_text(ostream &out, + const string &prefix, int indent_width, + const string &text, int line_width); + +private: + void sort_options(); + void get_terminal_width(); + + class Option { + public: + string _option; + string _parm_name; + int _index_group; + int _sequence; + string _description; + OptionDispatch _option_function; + bool *_bool_var; + void *_option_data; + }; + + class SortOptionsByIndex { + public: + bool operator () (const Option *a, const Option *b) const; + }; + + string _description; + typedef vector Runlines; + Runlines _runlines; + + typedef map OptionsByName; + typedef vector OptionsByIndex; + OptionsByName _options_by_name; + OptionsByIndex _options_by_index; + int _next_sequence; + bool _sorted_options; + + typedef map GotOptions; + GotOptions _got_options; + + int _terminal_width; + bool _got_terminal_width; + int _option_indent; + bool _got_option_indent; +}; + +#include "programBase.I" + +#endif + + diff --git a/pandatool/src/progbase/test_prog.cxx b/pandatool/src/progbase/test_prog.cxx new file mode 100644 index 0000000000..1fc171b903 --- /dev/null +++ b/pandatool/src/progbase/test_prog.cxx @@ -0,0 +1,58 @@ +// Filename: test_prog.cxx +// Created by: drose (14Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "programBase.h" + +class TestProgram : public ProgramBase { +public: + TestProgram(); + + bool _bool_a; + int _count_b; + int _int_c; +}; + +TestProgram:: +TestProgram() { + set_program_description + ("This is a simple test program to verify the effectiveness of the " + "ProgramBase base class as a base class for simple programs. It " + "includes some simple options and some description strings that are " + "long enough to require word-wrapping.\r" + "Don't expect anything fancy, though."); + add_runline("[opts]"); + + add_option + ("bog", "", 90, + "This is test option 'bog'. It is a simple boolean toggle; if it appears " + "at all, it sets a boolean flag to indicate that. If it does not " + "appear, it leaves the boolean flag alone.\r" + "There's not a whole lot of point to this option, when you come down " + "to it.", + &TestProgram::dispatch_none, &_bool_a); + + add_option + ("b", "", 90, "Test option b", + &TestProgram::dispatch_count, NULL, &_count_b); + _count_b = 0; + + add_option + ("c", "integer_parameter", 90, + "This is test option 'c'. It takes an integer parameter.", + &TestProgram::dispatch_int, NULL, &_int_c); + _int_c = 0; +} + + +int main(int argc, char *argv[]) { + TestProgram t; + t.parse_command_line(argc, argv); + + nout << "Executed successfully.\n" + << " _bool_a = " << t._bool_a << "\n" + << " _count_b = " << t._count_b << "\n" + << " _int_c = " << t._int_c << "\n"; + return 0; +} diff --git a/pandatool/src/progbase/wordWrapStream.cxx b/pandatool/src/progbase/wordWrapStream.cxx new file mode 100644 index 0000000000..5dd9b75f02 --- /dev/null +++ b/pandatool/src/progbase/wordWrapStream.cxx @@ -0,0 +1,19 @@ +// Filename: wordWrapStream.cxx +// Created by: drose (28Jun00) +// +//////////////////////////////////////////////////////////////////// + +#include "wordWrapStream.h" + + +//////////////////////////////////////////////////////////////////// +// Function: WordWrapStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +WordWrapStream:: +WordWrapStream(ProgramBase *program) : + ostream(&_lsb), + _lsb(this, program) +{ +} diff --git a/pandatool/src/progbase/wordWrapStream.h b/pandatool/src/progbase/wordWrapStream.h new file mode 100644 index 0000000000..f303aa93c9 --- /dev/null +++ b/pandatool/src/progbase/wordWrapStream.h @@ -0,0 +1,34 @@ +// Filename: wordWrapStream.h +// Created by: drose (28Jun00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef WORDWRAPSTREAM_H +#define WORDWRAPSTREAM_H + +#include + +#include "wordWrapStreamBuf.h" + +//////////////////////////////////////////////////////////////////// +// Class : WordWrapStream +// Description : A special ostream that formats all of its output +// through ProgramBase::show_text(). This allows the +// program to easily word-wrap its output messages to +// fit the terminal width. +// +// By convention (inherited from show_text), a newline +// written to the WordWrapStream indicates a paragraph +// break, and is generally printed as a blank line. To +// force a line break without a paragraph break, use +// '\r'. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA WordWrapStream : public ostream { +public: + WordWrapStream(ProgramBase *program); + +private: + WordWrapStreamBuf _lsb; +}; + +#endif diff --git a/pandatool/src/progbase/wordWrapStreamBuf.I b/pandatool/src/progbase/wordWrapStreamBuf.I new file mode 100644 index 0000000000..4c853a8343 --- /dev/null +++ b/pandatool/src/progbase/wordWrapStreamBuf.I @@ -0,0 +1,24 @@ +// Filename: wordWrapStreamBuf.I +// Created by: drose (01Jul00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: WordWrapStreamBuf::set_literal_mode +// Access: Private +// Description: An internal function called to update the internal +// state according to the current value of the +// Notify::literal flag, which might or might not be set +// of the ostream at any time. When the literal flag is +// true, we should not word-wrap, so toggling this flag +// means we need to flush the current buffer. +//////////////////////////////////////////////////////////////////// +INLINE void WordWrapStreamBuf:: +set_literal_mode(bool mode) { + if (mode != _literal_mode) { + flush_data(); + _literal_mode = mode; + } +} + diff --git a/pandatool/src/progbase/wordWrapStreamBuf.cxx b/pandatool/src/progbase/wordWrapStreamBuf.cxx new file mode 100644 index 0000000000..d917c73ab4 --- /dev/null +++ b/pandatool/src/progbase/wordWrapStreamBuf.cxx @@ -0,0 +1,124 @@ +// Filename: wordWrapStreamBuf.cxx +// Created by: drose (28Jun00) +// +//////////////////////////////////////////////////////////////////// + +#include "wordWrapStreamBuf.h" +#include "wordWrapStream.h" +#include "programBase.h" + +#include + +#ifdef PENV_SGI +// SGI compiler doesn't seem to define this yet. +typedef int streamsize; +#endif + +//////////////////////////////////////////////////////////////////// +// Function: WordWrapStreamBuf::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +WordWrapStreamBuf:: +WordWrapStreamBuf(WordWrapStream *owner, ProgramBase *program) : + _owner(owner), + _program(program) +{ + _literal_mode = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: WordWrapStreamBuf::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +WordWrapStreamBuf:: +~WordWrapStreamBuf() { + sync(); +} + +//////////////////////////////////////////////////////////////////// +// Function: WordWrapStreamBuf::sync +// Access: Public, Virtual +// Description: Called by the system ostream implementation when the +// buffer should be flushed to output (for instance, on +// destruction). +//////////////////////////////////////////////////////////////////// +int WordWrapStreamBuf:: +sync() { + streamsize n = pptr() - pbase(); + write_chars(pbase(), n); + + // Send all the data out now. + flush_data(); + + return 0; // EOF to indicate write full. +} + +//////////////////////////////////////////////////////////////////// +// Function: WordWrapStreamBuf::overflow +// Access: Public, Virtual +// Description: Called by the system ostream implementation when its +// internal buffer is filled, plus one character. +//////////////////////////////////////////////////////////////////// +int WordWrapStreamBuf:: +overflow(int ch) { + streamsize n = pptr() - pbase(); + + if (n != 0 && sync() != 0) { + return EOF; + } + + if (ch != EOF) { + // Write one more character. + char c = ch; + write_chars(&c, 1); + } + + pbump(-n); // Reset pptr(). + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: WordWrapStreamBuf::write_chars +// Access: Public +// Description: An internal function called by sync() and overflow() +// to store one or more characters written to the stream +// into the memory buffer. +//////////////////////////////////////////////////////////////////// +void WordWrapStreamBuf:: +write_chars(const char *start, int length) { + set_literal_mode((_owner->flags() & Notify::get_literal_flag()) != 0); + string new_data(start, length); + size_t newline = new_data.find_first_of("\n\r"); + size_t p = 0; + while (newline != string::npos) { + // The new data contains a newline; flush our data to that point. + _data += new_data.substr(p, newline - p + 1); + flush_data(); + p = newline + 1; + newline = new_data.find_first_of("\n\r", p); + } + + // Save the rest for the next write. + _data += new_data.substr(p); +} + +//////////////////////////////////////////////////////////////////// +// Function: WordWrapStreamBuf::flush_data +// Access: Private +// Description: Writes the contents of _data to the actual output +// stream, either word-wrapped or not as appropriate, +// and empties the contents of _data. +//////////////////////////////////////////////////////////////////// +void WordWrapStreamBuf:: +flush_data() { + if (!_data.empty()) { + if (_literal_mode) { + cerr << _data; + } else { + _program->show_text(_data); + } + _data = ""; + } +} diff --git a/pandatool/src/progbase/wordWrapStreamBuf.h b/pandatool/src/progbase/wordWrapStreamBuf.h new file mode 100644 index 0000000000..e50007653f --- /dev/null +++ b/pandatool/src/progbase/wordWrapStreamBuf.h @@ -0,0 +1,43 @@ +// Filename: wordWrapStreamBuf.h +// Created by: drose (28Jun00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef WORDWRAPSTREAMBUF_H +#define WORDWRAPSTREAMBUF_H + +#include + +#include + +class ProgramBase; +class WordWrapStream; + +//////////////////////////////////////////////////////////////////// +// Class : WordWrapStreamBuf +// Description : Used by WordWrapStream to implement an ostream that +// flushes its output to ProgramBase::show_text(). +//////////////////////////////////////////////////////////////////// +class WordWrapStreamBuf : public streambuf { +public: + WordWrapStreamBuf(WordWrapStream *owner, ProgramBase *program); + virtual ~WordWrapStreamBuf(); + +protected: + virtual int overflow(int c); + virtual int sync(); + +private: + void write_chars(const char *start, int length); + INLINE void set_literal_mode(bool mode); + void flush_data(); + + string _data; + WordWrapStream *_owner; + ProgramBase *_program; + bool _literal_mode; +}; + +#include "wordWrapStreamBuf.I" + +#endif diff --git a/pandatool/src/pstatserver/Sources.pp b/pandatool/src/pstatserver/Sources.pp new file mode 100644 index 0000000000..1ef5d6666c --- /dev/null +++ b/pandatool/src/pstatserver/Sources.pp @@ -0,0 +1,28 @@ +#begin lib_target + #define TARGET pstatserver + #define LOCAL_LIBS \ + compiler + #define OTHER_LIBS \ + pstatclient:c net:c putil:c express:c panda:m dtool:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + pStatClientData.cxx pStatClientData.h pStatGraph.I pStatGraph.cxx \ + pStatGraph.h pStatListener.cxx pStatListener.h pStatMonitor.I \ + pStatMonitor.cxx pStatMonitor.h pStatPianoRoll.I pStatPianoRoll.cxx \ + pStatPianoRoll.h pStatReader.cxx pStatReader.h pStatServer.cxx \ + pStatServer.h pStatStripChart.I pStatStripChart.cxx \ + pStatStripChart.h pStatThreadData.I pStatThreadData.cxx \ + pStatThreadData.h pStatView.I pStatView.cxx pStatView.h \ + pStatViewLevel.I pStatViewLevel.cxx pStatViewLevel.h + + #define INSTALL_HEADERS \ + pStatClientData.h pStatGraph.I pStatGraph.h pStatListener.h \ + pStatMonitor.I pStatMonitor.h pStatPianoRoll.I pStatPianoRoll.h \ + pStatReader.h pStatServer.h pStatStripChart.I pStatStripChart.h \ + pStatThreadData.I pStatThreadData.h pStatView.I pStatView.h \ + pStatViewLevel.I pStatViewLevel.h + +#end lib_target + diff --git a/pandatool/src/pstatserver/pStatClientData.cxx b/pandatool/src/pstatserver/pStatClientData.cxx new file mode 100644 index 0000000000..e3bdf92855 --- /dev/null +++ b/pandatool/src/pstatserver/pStatClientData.cxx @@ -0,0 +1,290 @@ +// Filename: pStatClientData.cxx +// Created by: drose (11Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatClientData.h" +#include "pStatReader.h" + +#include + +PStatCollectorDef PStatClientData::_null_collector(-1, "Unknown"); + + + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatClientData:: +PStatClientData(PStatReader *reader) : + _reader(reader) +{ + _is_alive = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatClientData:: +~PStatClientData() { + Collectors::const_iterator ci; + for (ci = _collectors.begin(); ci != _collectors.end(); ++ci) { + delete (*ci); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::is_alive +// Access: Public +// Description: Returns true if the data is actively getting filled +// by a connected client, or false if the client has +// terminated. +//////////////////////////////////////////////////////////////////// +bool PStatClientData:: +is_alive() const { + return _is_alive; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::close +// Access: Public +// Description: Closes the client connection if it is open. +//////////////////////////////////////////////////////////////////// +void PStatClientData:: +close() { + if (_is_alive && _reader != (PStatReader *)NULL) { + _reader->close(); + _reader = (PStatReader *)NULL; + _is_alive = false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::get_num_collectors +// Access: Public +// Description: Returns the total number of collectors the Data +// knows about. +//////////////////////////////////////////////////////////////////// +int PStatClientData:: +get_num_collectors() const { + return _collectors.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::has_collector +// Access: Public +// Description: Returns true if the indicated collector has been +// defined by the client already, false otherwise. It +// is possible for the client to start streaming data +// before all of the collectors have been defined. +//////////////////////////////////////////////////////////////////// +bool PStatClientData:: +has_collector(int index) const { + return (index >= 0 && index < (int)_collectors.size() && + _collectors[index] != (PStatCollectorDef *)NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::get_collector_def +// Access: Public +// Description: Returns the nth collector definition. +//////////////////////////////////////////////////////////////////// +const PStatCollectorDef &PStatClientData:: +get_collector_def(int index) const { + if (!has_collector(index)) { + return _null_collector; + } + return *_collectors[index]; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::get_collector_name +// Access: Public +// Description: Returns the name of the indicated collector. +//////////////////////////////////////////////////////////////////// +string PStatClientData:: +get_collector_name(int index) const { + if (!has_collector(index)) { + return "Unknown"; + } + const PStatCollectorDef *def = _collectors[index]; + return def->_name; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::get_collector_fullname +// Access: Public +// Description: Returns the "full name" of the indicated collector. +// This will be the concatenation of all of the +// collector's parents' names (except Frame) and the +// collector's own name. +//////////////////////////////////////////////////////////////////// +string PStatClientData:: +get_collector_fullname(int index) const { + if (!has_collector(index)) { + return "Unknown"; + } + + const PStatCollectorDef *def = _collectors[index]; + if (def->_parent_index == 0) { + return def->_name; + } else { + return get_collector_fullname(def->_parent_index) + ":" + def->_name; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::get_num_threads +// Access: Public +// Description: Returns the total number of threads the Data +// knows about. +//////////////////////////////////////////////////////////////////// +int PStatClientData:: +get_num_threads() const { + return _threads.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::has_thread +// Access: Public +// Description: Returns true if the indicated thread has been +// defined by the client already, false otherwise. It +// is possible for the client to start streaming data +// before all of the threads have been defined. +//////////////////////////////////////////////////////////////////// +bool PStatClientData:: +has_thread(int index) const { + return (index >= 0 && index < (int)_threads.size() && + !_threads[index]._name.empty()); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::get_thread_name +// Access: Public +// Description: Returns the name of the indicated thread. +//////////////////////////////////////////////////////////////////// +string PStatClientData:: +get_thread_name(int index) const { + if (!has_thread(index)) { + return "Unknown"; + } + return _threads[index]._name; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::get_thread_data +// Access: Public +// Description: Returns the data associated with the indicated +// thread. This will create a thread definition if it +// does not already exist. +//////////////////////////////////////////////////////////////////// +const PStatThreadData *PStatClientData:: +get_thread_data(int index) const { + ((PStatClientData *)this)->define_thread(index); + nassertr(index >= 0 && index < (int)_threads.size(), NULL); + return _threads[index]._data; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::get_child_distance +// Access: Public +// Description: Returns the number of Collectors between the +// indicated parent and the child Collector in the +// relationship graph. If child is the same as parent, +// returns zero. If child is an immediate child of +// parent, returns 1. If child is a grandchild of +// parent, returns 2, and so on. If child is not a +// descendant of parent at all, returns -1. +//////////////////////////////////////////////////////////////////// +int PStatClientData:: +get_child_distance(int parent, int child) const { + if (parent == child) { + return 0; + } + if (!has_collector(child) || child == 0) { + return -1; + } + int dist = get_child_distance(parent, get_collector_def(child)._parent_index); + if (dist == -1) { + return -1; + } else { + return dist + 1; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::add_collector +// Access: Public +// Description: Adds a new collector definition to the dataset. +// Presumably this is information just arrived from the +// client. +// +// The pointer will become owned by the PStatClientData +// object and will be freed on destruction. +//////////////////////////////////////////////////////////////////// +void PStatClientData:: +add_collector(PStatCollectorDef *def) { + // A sanity check on the index number. + nassertv(def->_index < 1000); + + // Make sure we have enough slots allocated. + while (_collectors.size() <= def->_index) { + _collectors.push_back(NULL); + } + + if (_collectors[def->_index] != (PStatCollectorDef *)NULL) { + // Free any old definition. + delete _collectors[def->_index]; + } + + _collectors[def->_index] = def; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::define_thread +// Access: Public +// Description: Adds a new thread definition to the dataset. +// Presumably this is information just arrived from the +// client. +//////////////////////////////////////////////////////////////////// +void PStatClientData:: +define_thread(int thread_index, const string &name) { + // A sanity check on the index number. + nassertv(thread_index < 1000); + + // Make sure we have enough slots allocated. + while (_threads.size() <= thread_index) { + _threads.push_back(Thread()); + } + + if (!name.empty()) { + _threads[thread_index]._name = name; + } + + if (_threads[thread_index]._data.is_null()) { + _threads[thread_index]._data = new PStatThreadData(this); + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatClientData::record_new_frame +// Access: Public +// Description: Makes room for and stores a new frame's worth of +// data associated with some particular thread (which +// may or may not have already been defined). +// +// The pointer will become owned by the PStatThreadData +// object and will be freed on destruction. +//////////////////////////////////////////////////////////////////// +void PStatClientData:: +record_new_frame(int thread_index, int frame_number, + PStatFrameData *frame_data) { + define_thread(thread_index); + nassertv(thread_index >= 0 && thread_index < (int)_threads.size()); + _threads[thread_index]._data->record_new_frame(frame_number, frame_data); +} diff --git a/pandatool/src/pstatserver/pStatClientData.h b/pandatool/src/pstatserver/pStatClientData.h new file mode 100644 index 0000000000..64a95703d0 --- /dev/null +++ b/pandatool/src/pstatserver/pStatClientData.h @@ -0,0 +1,74 @@ +// Filename: pStatClientData.h +// Created by: drose (11Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATCLIENTDATA_H +#define PSTATCLIENTDATA_H + +#include + +#include "pStatThreadData.h" + +#include +#include + +#include + +class PStatReader; + +//////////////////////////////////////////////////////////////////// +// Class : PStatClientData +// Description : The data associated with a particular client, but not +// with any one particular frame or thread: the list of +// collectors and threads, for instance. +//////////////////////////////////////////////////////////////////// +class PStatClientData : public ReferenceCount { +public: + PStatClientData(PStatReader *reader); + ~PStatClientData(); + + bool is_alive() const; + void close(); + + int get_num_collectors() const; + bool has_collector(int index) const; + const PStatCollectorDef &get_collector_def(int index) const; + string get_collector_name(int index) const; + string get_collector_fullname(int index) const; + + int get_num_threads() const; + bool has_thread(int index) const; + string get_thread_name(int index) const; + const PStatThreadData *get_thread_data(int index) const; + + int get_child_distance(int parent, int child) const; + + + void add_collector(PStatCollectorDef *def); + void define_thread(int thread_index, const string &name = string()); + + void record_new_frame(int thread_index, int frame_number, + PStatFrameData *frame_data); + +private: + bool _is_alive; + PStatReader *_reader; + + typedef vector Collectors; + Collectors _collectors; + + class Thread { + public: + string _name; + PT(PStatThreadData) _data; + }; + typedef vector Threads; + Threads _threads; + + static PStatCollectorDef _null_collector; + friend class PStatReader; +}; + +#endif + diff --git a/pandatool/src/pstatserver/pStatGraph.I b/pandatool/src/pstatserver/pStatGraph.I new file mode 100644 index 0000000000..2f3b45499a --- /dev/null +++ b/pandatool/src/pstatserver/pStatGraph.I @@ -0,0 +1,133 @@ +// Filename: pStatGraph.I +// Created by: drose (19Jul00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_monitor +// Access: Public +// Description: Returns the monitor associated with this chart. +//////////////////////////////////////////////////////////////////// +INLINE PStatMonitor *PStatGraph:: +get_monitor() const { + return _monitor; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_num_labels +// Access: Public +// Description: Returns the number of labels to be drawn for this +// chart. +//////////////////////////////////////////////////////////////////// +INLINE int PStatGraph:: +get_num_labels() const { + return _labels.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_label_collector +// Access: Public +// Description: Returns the collector index associated with the nth +// label. +//////////////////////////////////////////////////////////////////// +INLINE int PStatGraph:: +get_label_collector(int n) const { + nassertr(n >= 0 && n < _labels.size(), 0); + return _labels[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_label_name +// Access: Public +// Description: Returns the text associated with the nth label. +//////////////////////////////////////////////////////////////////// +INLINE string PStatGraph:: +get_label_name(int n) const { + nassertr(n >= 0 && n < _labels.size(), string()); + return _monitor->get_client_data()->get_collector_name(_labels[n]); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_label_color +// Access: Public +// Description: Returns the color associated with the nth label. +//////////////////////////////////////////////////////////////////// +INLINE RGBColorf PStatGraph:: +get_label_color(int n) const { + nassertr(n >= 0 && n < _labels.size(), RGBColorf(0.0, 0.0, 0.0)); + return _monitor->get_collector_color(_labels[n]); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::set_target_frame_rate +// Access: Public +// Description: Sets the target frame rate of the application in Hz. +// This only affects the choice of initial scale and the +// placement of guide bars. +//////////////////////////////////////////////////////////////////// +INLINE void PStatGraph:: +set_target_frame_rate(double frame_rate) { + if (_target_frame_rate != frame_rate) { + _target_frame_rate = frame_rate; + normal_guide_bars(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_target_frame_rate +// Access: Public +// Description: Returns the indicated target frame rate in Hz. See +// set_target_frame_rate(). +//////////////////////////////////////////////////////////////////// +INLINE double PStatGraph:: +get_target_frame_rate() const { + return _target_frame_rate; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_xsize +// Access: Public +// Description: Returns the width of the chart in pixels. +//////////////////////////////////////////////////////////////////// +INLINE int PStatGraph:: +get_xsize() const { + return _xsize; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_ysize +// Access: Public +// Description: Returns the height of the chart in pixels. +//////////////////////////////////////////////////////////////////// +INLINE int PStatGraph:: +get_ysize() const { + return _ysize; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::set_guide_bar_units +// Access: Public +// Description: Sets the units that are displayed for the guide bar +// labels. This may be a union of one or more members +// of the GuideBarUnits enum. +//////////////////////////////////////////////////////////////////// +INLINE void PStatGraph:: +set_guide_bar_units(int guide_bar_units) { + if (_guide_bar_units != guide_bar_units) { + _guide_bar_units = guide_bar_units; + normal_guide_bars(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_guide_bar_units +// Access: Public +// Description: Returns the units that are displayed for the guide bar +// labels. This may be a union of one or more members +// of the GuideBarUnits enum. +//////////////////////////////////////////////////////////////////// +INLINE int PStatGraph:: +get_guide_bar_units() const { + return _guide_bar_units; +} diff --git a/pandatool/src/pstatserver/pStatGraph.cxx b/pandatool/src/pstatserver/pStatGraph.cxx new file mode 100644 index 0000000000..0a42bc8228 --- /dev/null +++ b/pandatool/src/pstatserver/pStatGraph.cxx @@ -0,0 +1,179 @@ +// Filename: pStatGraph.cxx +// Created by: drose (19Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatGraph.h" + +#include +#include +#include +#include + +#include // for sprintf + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatGraph:: +PStatGraph(PStatMonitor *monitor, int xsize, int ysize) : + _monitor(monitor), + _xsize(xsize), + _ysize(ysize) +{ + _target_frame_rate = pstats_target_frame_rate; + _labels_changed = false; + _guide_bars_changed = false; + _guide_bar_units = GBU_hz; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +PStatGraph:: +~PStatGraph() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_num_guide_bars +// Access: Public +// Description: Returns the number of horizontal guide bars that +// should be drawn, based on the indicated target frame +// rate. Not all of these may be visible; some may be +// off the top of the chart because of the vertical +// scale. +//////////////////////////////////////////////////////////////////// +int PStatGraph:: +get_num_guide_bars() const { + return _guide_bars.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::get_guide_bar +// Access: Public +// Description: Returns the nth horizontal guide bar. This should be +// drawn as a horizontal line across the chart at the y +// pixel location determined by height_to_pixel(bar._height). +// +// It is possible that this bar will be off the top of +// the chart. +//////////////////////////////////////////////////////////////////// +const PStatGraph::GuideBar &PStatGraph:: +get_guide_bar(int n) const { +#ifndef NDEBUG + static GuideBar bogus_bar = { 0.0, "bogus", false }; + nassertr(n >= 0 && n < _guide_bars.size(), bogus_bar); +#endif + return _guide_bars[n]; +} + + +// STL function object for sorting labels in order by the collector's +// sort index, used in update_labels(), below. +class SortCollectorLabels { +public: + SortCollectorLabels(const PStatClientData *client_data) : + _client_data(client_data) { + } + bool operator () (int a, int b) const { + // By casting the sort numbers to unsigned ints, we cheat and make + // -1 appear to be a very large positive integer, thus placing + // collectors with a -1 sort value at the very end. + return + (unsigned int)_client_data->get_collector_def(a)._sort < + (unsigned int)_client_data->get_collector_def(b)._sort; + } + const PStatClientData *_client_data; +}; + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::update_guide_bars +// Access: Protected +// Description: Resets the list of guide bars. +//////////////////////////////////////////////////////////////////// +void PStatGraph:: +update_guide_bars(int num_bars, double scale) { + _guide_bars.clear(); + + // We'd like to draw about num_bars bars on the chart. But we also + // want the bars to be harmonics of the target frame rate, so that + // the bottom bar is at tfr/n or n * tfr, where n is an integer, and + // the upper bars are even multiples of that. + + // Choose a suitable harmonic of the target frame rate near the + // bottom part of the chart. + + double bottom = (double)num_bars / scale; + + double harmonic; + if (_target_frame_rate < bottom) { + // n * tfr + harmonic = floor(bottom / _target_frame_rate + 0.5) * _target_frame_rate; + + } else { + // tfr / n + harmonic = _target_frame_rate / floor(_target_frame_rate / bottom + 0.5); + } + + // Now, make a few bars at k / harmonic. + for (int k = 1; k / harmonic <= scale; k++) { + _guide_bars.push_back(make_guide_bar(k / harmonic)); + } + + _guide_bars_changed = true; +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatGraph::make_guide_bar +// Access: Protected +// Description: Makes a guide bar for the indicated frame rate. +//////////////////////////////////////////////////////////////////// +PStatGraph::GuideBar PStatGraph:: +make_guide_bar(double time) const { + string label; + + char buffer[128]; + + if ((_guide_bar_units & GBU_ms) != 0) { + double ms = time * 1000.0; + if (ms < 10.0) { + sprintf(buffer, "%0.1f", ms); + } else { + sprintf(buffer, "%0.0f", ms); + } + label += buffer; + if ((_guide_bar_units & GBU_show_units) != 0) { + label += " ms"; + } + } + + if ((_guide_bar_units & GBU_hz) != 0) { + double frame_rate = 1.0 / time; + if (frame_rate < 10.0) { + sprintf(buffer, "%0.1f", frame_rate); + } else { + sprintf(buffer, "%0.0f", frame_rate); + } + if ((_guide_bar_units & GBU_ms) != 0) { + label += " ("; + } + label += buffer; + if ((_guide_bar_units & GBU_show_units) != 0) { + label += " Hz"; + } + if ((_guide_bar_units & GBU_ms) != 0) { + label += ")"; + } + } + + GuideBar bar; + bar._height = time; + bar._label = label; + bar._is_target = (IS_NEARLY_EQUAL(1.0 / time, _target_frame_rate)); + return bar; +} diff --git a/pandatool/src/pstatserver/pStatGraph.h b/pandatool/src/pstatserver/pStatGraph.h new file mode 100644 index 0000000000..5aae1e440a --- /dev/null +++ b/pandatool/src/pstatserver/pStatGraph.h @@ -0,0 +1,91 @@ +// Filename: pStatGraph.h +// Created by: drose (19Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATGRAPH_H +#define PSTATGRAPH_H + +#include + +#include "pStatMonitor.h" +#include "pStatClientData.h" + +#include +#include + +#include + +class PStatView; + +//////////////////////////////////////////////////////////////////// +// Class : PStatGraph +// Description : This is an abstract base class for several different +// kinds of graphs that have a few things in common, +// like labels and guide bars. +//////////////////////////////////////////////////////////////////// +class PStatGraph { +public: + PStatGraph(PStatMonitor *monitor, int xsize, int ysize); + virtual ~PStatGraph(); + + INLINE PStatMonitor *get_monitor() const; + + INLINE int get_num_labels() const; + INLINE int get_label_collector(int n) const; + INLINE string get_label_name(int n) const; + INLINE RGBColorf get_label_color(int n) const; + + INLINE void set_target_frame_rate(double frame_rate); + INLINE double get_target_frame_rate() const; + + INLINE int get_xsize() const; + INLINE int get_ysize() const; + + class GuideBar { + public: + double _height; + string _label; + bool _is_target; + }; + + enum GuideBarUnits { + GBU_hz = 0x0001, + GBU_ms = 0x0002, + GBU_show_units = 0x0004, + }; + + int get_num_guide_bars() const; + const GuideBar &get_guide_bar(int n) const; + + INLINE void set_guide_bar_units(int unit_mask); + INLINE int get_guide_bar_units() const; + +protected: + virtual void normal_guide_bars()=0; + void update_guide_bars(int num_bars, double scale); + GuideBar make_guide_bar(double time) const; + + bool _labels_changed; + bool _guide_bars_changed; + + PT(PStatMonitor) _monitor; + + double _target_frame_rate; + + int _xsize; + int _ysize; + + // Table of the collectors that should be drawn as labels, in order + // from bottom to top. + typedef vector_int Labels; + Labels _labels; + + typedef vector GuideBars; + GuideBars _guide_bars; + int _guide_bar_units; +}; + +#include "pStatGraph.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatListener.cxx b/pandatool/src/pstatserver/pStatListener.cxx new file mode 100644 index 0000000000..fd5349b1cc --- /dev/null +++ b/pandatool/src/pstatserver/pStatListener.cxx @@ -0,0 +1,43 @@ +// Filename: pStatListener.cxx +// Created by: drose (09Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatListener.h" +#include "pStatServer.h" +#include "pStatReader.h" + +//////////////////////////////////////////////////////////////////// +// Function: PStatListener::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatListener:: +PStatListener(PStatServer *manager) : + ConnectionListener(manager, manager->is_thread_safe() ? 1 : 0), + _manager(manager) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatListener::connection_opened +// Access: Protected, Virtual +// Description: An internal function called by ConnectionListener() +// when a new TCP connection has been established. +//////////////////////////////////////////////////////////////////// +void PStatListener:: +connection_opened(const PT(Connection) &, + const NetAddress &address, + const PT(Connection) &new_connection) { + PStatMonitor *monitor = _manager->make_monitor(); + if (monitor == (PStatMonitor *)NULL) { + nout << "Couldn't create monitor!\n"; + return; + } + + nout << "Got new connection from " << address.get_ip() << "\n"; + + PStatReader *reader = new PStatReader(_manager, monitor); + _manager->add_reader(new_connection, reader); + reader->set_tcp_connection(new_connection); +} diff --git a/pandatool/src/pstatserver/pStatListener.h b/pandatool/src/pstatserver/pStatListener.h new file mode 100644 index 0000000000..22b2a92bcb --- /dev/null +++ b/pandatool/src/pstatserver/pStatListener.h @@ -0,0 +1,36 @@ +// Filename: pStatListener.h +// Created by: drose (09Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATLISTENER_H +#define PSTATLISTENER_H + +#include + +#include +#include + +class PStatServer; +class PStatMonitor; + +//////////////////////////////////////////////////////////////////// +// Class : PStatListener +// Description : This is the TCP rendezvous socket listener. We need +// one of these to listen for new connections on the +// socket(s) added to the PStatServer. +//////////////////////////////////////////////////////////////////// +class PStatListener : public ConnectionListener { +public: + PStatListener(PStatServer *manager); + +protected: + virtual void connection_opened(const PT(Connection) &rendezvous, + const NetAddress &address, + const PT(Connection) &new_connection); + +private: + PStatServer *_manager; +}; + +#endif diff --git a/pandatool/src/pstatserver/pStatMonitor.I b/pandatool/src/pstatserver/pStatMonitor.I new file mode 100644 index 0000000000..842d4c6d42 --- /dev/null +++ b/pandatool/src/pstatserver/pStatMonitor.I @@ -0,0 +1,69 @@ +// Filename: pStatMonitor.I +// Created by: drose (14Jul00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::get_client_data +// Access: Public +// Description: Returns the client data associated with this monitor. +//////////////////////////////////////////////////////////////////// +INLINE const PStatClientData *PStatMonitor:: +get_client_data() const { + return _client_data; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::get_collector_name +// Access: Public +// Description: Returns the name of the indicated collector, if it is +// known. +//////////////////////////////////////////////////////////////////// +INLINE string PStatMonitor:: +get_collector_name(int collector_index) { + if (!_client_data.is_null() && + _client_data->has_collector(collector_index)) { + return _client_data->get_collector_name(collector_index); + } + return "Unknown"; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::is_client_known +// Access: Public +// Description: Returns true if we've yet received the "hello" +// message from the client indicating its name, etc. +//////////////////////////////////////////////////////////////////// +INLINE bool PStatMonitor:: +is_client_known() const { + return _client_known; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::get_client_hostname +// Access: Public +// Description: Returns the hostname of the client we're connected +// to, if known. This may not be known immediately at +// creation time, but should be learned shortly +// thereafter when we receive the client's "hello" +// message. See is_client_known(). +//////////////////////////////////////////////////////////////////// +INLINE string PStatMonitor:: +get_client_hostname() const { + return _client_hostname; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::get_client_progname +// Access: Public +// Description: Returns the program name of the client we're +// connected to, if known. This may not be known +// immediately at creation time, but should be learned +// shortly thereafter when we receive the client's +// "hello" message. See is_client_known(). +//////////////////////////////////////////////////////////////////// +INLINE string PStatMonitor:: +get_client_progname() const { + return _client_progname; +} diff --git a/pandatool/src/pstatserver/pStatMonitor.cxx b/pandatool/src/pstatserver/pStatMonitor.cxx new file mode 100644 index 0000000000..4b1d11a5aa --- /dev/null +++ b/pandatool/src/pstatserver/pStatMonitor.cxx @@ -0,0 +1,261 @@ +// Filename: pStatMonitor.cxx +// Created by: drose (09Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatMonitor.h" + +#include + + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatMonitor:: +PStatMonitor() { + _client_known = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +PStatMonitor:: +~PStatMonitor() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::hello_from +// Access: Public +// Description: Called shortly after startup time with the greeting +// from the client. This indicates the client's +// reported hostname and program name. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +hello_from(const string &hostname, const string &progname) { + _client_known = true; + _client_hostname = hostname; + _client_progname = progname; + got_hello(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::set_client_data +// Access: Public +// Description: Called by the PStatServer at setup time to set the +// new data pointer for the first time. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +set_client_data(PStatClientData *client_data) { + _client_data = client_data; + initialized(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::is_alive +// Access: Public +// Description: Returns true if the client is alive and connected, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool PStatMonitor:: +is_alive() const { + if (_client_data.is_null()) { + // Not yet, but in a second probably. + return false; + } + return _client_data->is_alive(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::close +// Access: Public +// Description: Closes the client connection if it is active. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +close() { + if (!_client_data.is_null()) { + _client_data->close(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::get_collector_color +// Access: Public +// Description: Returns the color associated with the indicated +// collector. If the collector has no associated color, +// or is unknown, a new color will be made up on the +// spot and associated with this collector for the rest +// of the session. +//////////////////////////////////////////////////////////////////// +const RGBColorf &PStatMonitor:: +get_collector_color(int collector_index) { + Colors::iterator ci; + ci = _colors.find(collector_index); + if (ci != _colors.end()) { + return (*ci).second; + } + + // Ask the client data about the color. + if (!_client_data.is_null() && + _client_data->has_collector(collector_index)) { + const PStatCollectorDef &def = + _client_data->get_collector_def(collector_index); + + if (def._suggested_color != RGBColorf::zero()) { + ci = _colors.insert(Colors::value_type(collector_index, def._suggested_color)).first; + return (*ci).second; + } + } + + // We didn't have a color for the collector; make one up. + RGBColorf random_color; + random_color[0] = (double)rand() / (double)RAND_MAX; + random_color[1] = (double)rand() / (double)RAND_MAX; + random_color[2] = (double)rand() / (double)RAND_MAX; + + ci = _colors.insert(Colors::value_type(collector_index, random_color)).first; + return (*ci).second; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::get_view +// Access: Public +// Description: Returns a view on the given thread index. If there +// is no such view already for the indicated thread, +// this will create one. This view can be used to +// examine the accumulated data for the given thread. +//////////////////////////////////////////////////////////////////// +PStatView &PStatMonitor:: +get_view(int thread_index) { + Views::iterator vi; + vi = _views.find(thread_index); + if (vi == _views.end()) { + vi = _views.insert(Views::value_type(thread_index, PStatView())).first; + (*vi).second.set_thread_data(_client_data->get_thread_data(thread_index)); + } + return (*vi).second; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::initialized +// Access: Public, Virtual +// Description: Called after the monitor has been fully set up. At +// this time, it will have a valid _client_data pointer, +// and things like is_alive() and close() will be +// meaningful. However, we may not yet know who we're +// connected to (is_client_known() may return false), +// and we may not know anything about the threads or +// collectors we're about to get data on. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +initialized() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::got_hello +// Access: Public, Virtual +// Description: Called when the "hello" message has been received +// from the client. At this time, the client's hostname +// and program name will be known. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +got_hello() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::new_collector +// Access: Public, Virtual +// Description: Called whenever a new Collector definition is +// received from the client. Generally, the client will +// send all of its collectors over shortly after +// connecting, but there's no guarantee that they will +// all be received before the first frames are received. +// The monitor should be prepared to accept new Collector +// definitions midstream. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +new_collector(int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::new_thread +// Access: Public, Virtual +// Description: Called whenever a new Thread definition is +// received from the client. Generally, the client will +// send all of its threads over shortly after +// connecting, but there's no guarantee that they will +// all be received before the first frames are received. +// The monitor should be prepared to accept new Thread +// definitions midstream. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +new_thread(int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::new_data +// Access: Public, Virtual +// Description: Called as each frame's data is made available. There +// is no gurantee the frames will arrive in order, or +// that all of them will arrive at all. The monitor +// should be prepared to accept frames received +// out-of-order or missing. The use of the +// PStatFrameData / PStatView objects to report the data +// will facilitate this. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +new_data(int, int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::lost_connection +// Access: Public, Virtual +// Description: Called whenever the connection to the client has been +// lost. This is a permanent state change. The monitor +// should update its display to represent this, and may +// choose to close down automatically. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +lost_connection() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::idle +// Access: Public, Virtual +// Description: If has_idle() returns true, this will be called +// periodically to allow the monitor to update its +// display or whatever it needs to do. +//////////////////////////////////////////////////////////////////// +void PStatMonitor:: +idle() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::has_idle +// Access: Public, Virtual +// Description: Should be redefined to return true if you want to +// redefine idle() and expect it to be called. +//////////////////////////////////////////////////////////////////// +bool PStatMonitor:: +has_idle() { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatMonitor::is_thread_safe +// Access: Public, Virtual +// Description: Should be redefined to return true if this monitor +// class can handle running in a sub-thread. +// +// This is not related to the question of whether it can +// handle multiple different PStatThreadDatas; this is +// strictly a question of whether or not the monitor +// itself wants to run in a sub-thread. +//////////////////////////////////////////////////////////////////// +bool PStatMonitor:: +is_thread_safe() { + return false; +} diff --git a/pandatool/src/pstatserver/pStatMonitor.h b/pandatool/src/pstatserver/pStatMonitor.h new file mode 100644 index 0000000000..cd5d2615be --- /dev/null +++ b/pandatool/src/pstatserver/pStatMonitor.h @@ -0,0 +1,94 @@ +// Filename: pStatMonitor.h +// Created by: drose (08Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATMONITOR_H +#define PSTATMONITOR_H + +#include + +#include "pStatClientData.h" +#include "pStatView.h" + +#include +#include +#include + +#include + +class PStatCollectorDef; + +//////////////////////////////////////////////////////////////////// +// Class : PStatMonitor +// Description : This is an abstract class that presents the interface +// to any number of different front-ends for the stats +// monitor. One of these will be created by the +// PStatMonitor as each client is connected; this class +// is responsible for opening up a new strip-chart graph +// or whatever is appropriate. It defines a number of +// empty virtual functions that will be called as new +// data becomes available. +//////////////////////////////////////////////////////////////////// +class PStatMonitor : public ReferenceCount { +public: + // The following functions are primarily for use by internal classes + // to set up the monitor. + PStatMonitor(); + virtual ~PStatMonitor(); + + void hello_from(const string &hostname, const string &progname); + void set_client_data(PStatClientData *client_data); + + + // The following functions are for use by user code to determine + // information about the client data available. + bool is_alive() const; + void close(); + + INLINE const PStatClientData *get_client_data() const; + INLINE string get_collector_name(int collector_index); + const RGBColorf &get_collector_color(int collector_index); + + INLINE bool is_client_known() const; + INLINE string get_client_hostname() const; + INLINE string get_client_progname() const; + + PStatView &get_view(int thread_index); + + + // The following virtual methods may be overridden by a derived + // monitor class to customize behavior. + + virtual string get_monitor_name()=0; + + virtual void initialized(); + virtual void got_hello(); + virtual void new_collector(int collector_index); + virtual void new_thread(int thread_index); + virtual void new_data(int thread_index, int frame_number); + + virtual void lost_connection(); + virtual void idle(); + virtual bool has_idle(); + + virtual bool is_thread_safe(); + + +private: + PT(PStatClientData) _client_data; + + bool _client_known; + string _client_hostname; + string _client_progname; + + typedef map Views; + Views _views; + + typedef map Colors; + Colors _colors; +}; + +#include "pStatMonitor.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatPianoRoll.I b/pandatool/src/pstatserver/pStatPianoRoll.I new file mode 100644 index 0000000000..b4aa138f5f --- /dev/null +++ b/pandatool/src/pstatserver/pStatPianoRoll.I @@ -0,0 +1,50 @@ +// Filename: pStatPianoRoll.I +// Created by: drose (18Jul00) +// +//////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::set_horizontal_scale +// Access: Public +// Description: Changes the amount of time the width of the +// horizontal axis represents. This may force a redraw. +//////////////////////////////////////////////////////////////////// +INLINE void PStatPianoRoll:: +set_horizontal_scale(double time_width) { + if (_time_width != time_width) { + _time_width = time_width; + normal_guide_bars(); + force_redraw(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::get_horizontal_scale +// Access: Public +// Description: Returns the amount of total time the width of the +// horizontal axis represents. +//////////////////////////////////////////////////////////////////// +INLINE double PStatPianoRoll:: +get_horizontal_scale() const { + return _time_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::timestamp_to_pixel +// Access: Public +// Description: Converts a timestamp to a horizontal pixel offset. +//////////////////////////////////////////////////////////////////// +INLINE int PStatPianoRoll:: +timestamp_to_pixel(double time) const { + return (int)((double)_xsize * (time - _start_time) / _time_width); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::pixel_to_timestamp +// Access: Public +// Description: Converts a horizontal pixel offset to a timestamp. +//////////////////////////////////////////////////////////////////// +INLINE double PStatPianoRoll:: +pixel_to_timestamp(int x) const { + return _time_width * (double)x / (double)_xsize + _start_time; +} diff --git a/pandatool/src/pstatserver/pStatPianoRoll.cxx b/pandatool/src/pstatserver/pStatPianoRoll.cxx new file mode 100644 index 0000000000..32f52812c5 --- /dev/null +++ b/pandatool/src/pstatserver/pStatPianoRoll.cxx @@ -0,0 +1,319 @@ +// Filename: pStatPianoRoll.cxx +// Created by: drose (18Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatPianoRoll.h" + +#include +#include +#include +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::BarBuilder::Constructor +// Access: Public +// Description: This class is used internally to build up the set of +// color bars defined by a frame's worth of data. +//////////////////////////////////////////////////////////////////// +PStatPianoRoll::BarBuilder:: +BarBuilder() { + _is_new = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::BarBuilder::clear +// Access: Public +// Description: Resets the data in the BarBuilder for a new frame. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll::BarBuilder:: +clear() { + _is_new = false; + _color_bars.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::BarBuilder::add_data_point +// Access: Public +// Description: Adds a new data point. The first data point for a +// given collector turns in on (starts the bar), the +// second data point turns it off (ends the bar). +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll::BarBuilder:: +add_data_point(double time) { + if (_color_bars.empty() || _color_bars.back()._end >= 0.0) { + // This is an odd-numbered data point: start the bar. + ColorBar bar; + bar._start = time; + bar._end = -1.0; + _color_bars.push_back(bar); + + } else { + // This is an even-numbered data point: end the bar. + _color_bars.back()._end = time; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::BarBuilder::finish +// Access: Public +// Description: Makes sure that each start-bar data point was matched +// by a corresponding end-bar data point. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll::BarBuilder:: +finish(double time) { + if (!_color_bars.empty() && _color_bars.back()._end < 0.0) { + nout << "Warning: collector was left on at the end of the frame.\n"; + _color_bars.back()._end = time; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatPianoRoll:: +PStatPianoRoll(PStatMonitor *monitor, int thread_index, int xsize, int ysize) : + PStatGraph(monitor, xsize, ysize), + _thread_index(thread_index) +{ + _time_width = 1.0 / pstats_target_frame_rate; + _start_time = 0.0; + + _current_frame = -1; + _guide_bar_units = GBU_ms | GBU_hz | GBU_show_units; + normal_guide_bars(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +PStatPianoRoll:: +~PStatPianoRoll() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::update +// Access: Public +// Description: Updates the chart with the latest data. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +update() { + const PStatClientData *client_data = _monitor->get_client_data(); + + // Don't bother to update the thread data until we know at least + // something about the collectors and threads. + if (client_data->get_num_collectors() != 0 && + client_data->get_num_threads() != 0) { + const PStatThreadData *thread_data = + client_data->get_thread_data(_thread_index); + if (!thread_data->is_empty()) { + int frame_number = thread_data->get_latest_frame_number(); + if (frame_number != _current_frame) { + compute_page(thread_data->get_frame(frame_number)); + _current_frame = frame_number; + force_redraw(); + } + } + } + + idle(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::changed_size +// Access: Protected +// Description: To be called by the user class when the widget size +// has changed. This updates the chart's internal data +// and causes it to issue redraw commands to reflect the +// new size. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +changed_size(int xsize, int ysize) { + if (xsize != _xsize || ysize != _ysize) { + _xsize = xsize; + _ysize = ysize; + + normal_guide_bars(); + force_redraw(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::force_redraw +// Access: Protected +// Description: To be called by the user class when the whole thing +// needs to be redrawn for some reason. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +force_redraw() { + if (!_labels.empty()) { + begin_draw(); + for (int i = 0; i < (int)_labels.size(); i++) { + int collector_index = _labels[i]; + const ColorBars &bars = _page_data[collector_index]._color_bars; + + begin_row(i); + ColorBars::const_iterator bi; + for (bi = bars.begin(); bi != bars.end(); ++bi) { + const ColorBar &bar = (*bi); + draw_bar(i, timestamp_to_pixel(bar._start), timestamp_to_pixel(bar._end)); + } + end_row(i); + } + end_draw(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::normal_guide_bars +// Access: Protected, Virtual +// Description: Calls update_guide_bars with parameters suitable to +// this kind of graph. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +normal_guide_bars() { + // We want vaguely 100 pixels between guide bars. + update_guide_bars(get_xsize() / 100, _time_width); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::begin_draw +// Access: Protected, Virtual +// Description: Should be overridden by the user class. This hook +// will be called before drawing any bars in the chart. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +begin_draw() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::begin_row +// Access: Protected, Virtual +// Description: Should be overridden by the user class. This hook +// will be called before drawing any one row of bars. +// These bars correspond to the collector whose index is +// get_row_collector(row), and in the color +// get_row_color(row). +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +begin_row(int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::draw_bar +// Access: Protected, Virtual +// Description: Draws a single bar in the chart for the indicated +// row, in the color get_row_color(row), for the +// indicated horizontal pixel range. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +draw_bar(int, int, int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::end_row +// Access: Protected, Virtual +// Description: Should be overridden by the user class. This hook +// will be called after drawing a series of color bars +// for a single row. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +end_row(int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::end_draw +// Access: Protected, Virtual +// Description: Should be overridden by the user class. This hook +// will be called after drawing a series of color bars +// in the chart. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +end_draw() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::idle +// Access: Protected, Virtual +// Description: Should be overridden by the user class to perform any +// other updates might be necessary after the bars have +// been redrawn. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +idle() { +} + + +// STL function object for sorting labels in order by the collector's +// sort index, used in compute_page(), below. +class SortCollectorLabels { +public: + SortCollectorLabels(const PStatClientData *client_data) : + _client_data(client_data) { + } + bool operator () (int a, int b) const { + // By casting the sort numbers to unsigned ints, we cheat and make + // -1 appear to be a very large positive integer, thus placing + // collectors with a -1 sort value at the very end. + return + (unsigned int)_client_data->get_collector_def(a)._sort < + (unsigned int)_client_data->get_collector_def(b)._sort; + } + const PStatClientData *_client_data; +}; + +//////////////////////////////////////////////////////////////////// +// Function: PStatPianoRoll::compute_page +// Access: Private +// Description: Examines the given frame data and rebuilds the +// _page_data to match it. +//////////////////////////////////////////////////////////////////// +void PStatPianoRoll:: +compute_page(const PStatFrameData &frame_data) { + _start_time = frame_data.get_start(); + + PageData::iterator pi; + for (pi = _page_data.begin(); pi != _page_data.end(); ++pi) { + (*pi).second.clear(); + } + + size_t num_bars = _page_data.size(); + + int num_events = frame_data.get_num_events(); + for (int i = 0; i < num_events; i++) { + int collector_index = (frame_data.get_collector(i) & 0x7fff); + double time = frame_data.get_time(i); + _page_data[collector_index].add_data_point(time); + } + + if (_page_data.size() != num_bars) { + // If we added some new bars this time, we'll have to update our + // list. + const PStatClientData *client_data = _monitor->get_client_data(); + + _labels.clear(); + for (pi = _page_data.begin(); pi != _page_data.end(); ++pi) { + int collector_index = (*pi).first; + if (client_data->has_collector(collector_index)) { + _labels.push_back(collector_index); + } + } + + SortCollectorLabels sort_labels(client_data); + sort(_labels.begin(), _labels.end(), sort_labels); + + _labels_changed = true; + } + + // Finally, make sure all of the bars are closed. + double time = frame_data.get_end(); + for (pi = _page_data.begin(); pi != _page_data.end(); ++pi) { + (*pi).second.finish(time); + } +} diff --git a/pandatool/src/pstatserver/pStatPianoRoll.h b/pandatool/src/pstatserver/pStatPianoRoll.h new file mode 100644 index 0000000000..d95f2e15f2 --- /dev/null +++ b/pandatool/src/pstatserver/pStatPianoRoll.h @@ -0,0 +1,93 @@ +// Filename: pStatPianoRoll.h +// Created by: drose (18Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATPIANOROLL_H +#define PSTATPIANOROLL_H + +#include + +#include "pStatGraph.h" +#include "pStatMonitor.h" +#include "pStatClientData.h" + +#include +#include + +#include + +class PStatFrameData; + +//////////////////////////////////////////////////////////////////// +// Class : PStatPianoRoll +// Description : This is an abstract class that presents the interface +// for drawing a piano-roll type chart: it shows the +// time spent in each of a number of collectors as a +// horizontal bar of color, with time as the horizontal +// axis. +// +// This class just manages all the piano-roll logic; the +// actual nuts and bolts of drawing pixels is left to a +// user-derived class. +//////////////////////////////////////////////////////////////////// +class PStatPianoRoll : public PStatGraph { +public: + PStatPianoRoll(PStatMonitor *monitor, int thread_index, + int xsize, int ysize); + virtual ~PStatPianoRoll(); + + void update(); + + INLINE void set_horizontal_scale(double time_width); + INLINE double get_horizontal_scale() const; + + INLINE int timestamp_to_pixel(double time) const; + INLINE double pixel_to_timestamp(int x) const; + +protected: + void changed_size(int xsize, int ysize); + void force_redraw(); + virtual void normal_guide_bars(); + + virtual void begin_draw(); + virtual void begin_row(int row); + virtual void draw_bar(int row, int from_x, int to_x); + virtual void end_row(int row); + virtual void end_draw(); + virtual void idle(); + +private: + void compute_page(const PStatFrameData &frame_data); + + int _thread_index; + + double _time_width; + double _start_time; + + class ColorBar { + public: + double _start; + double _end; + }; + typedef vector ColorBars; + + class BarBuilder { + public: + BarBuilder(); + void clear(); + void add_data_point(double time); + void finish(double time); + + bool _is_new; + ColorBars _color_bars; + }; + + typedef map PageData; + PageData _page_data; + int _current_frame; +}; + +#include "pStatPianoRoll.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatReader.cxx b/pandatool/src/pstatserver/pStatReader.cxx new file mode 100644 index 0000000000..6abbacdf65 --- /dev/null +++ b/pandatool/src/pstatserver/pStatReader.cxx @@ -0,0 +1,232 @@ +// Filename: pStatReader.cxx +// Created by: drose (09Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatReader.h" +#include "pStatServer.h" +#include "pStatMonitor.h" + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatReader:: +PStatReader(PStatServer *manager, PStatMonitor *monitor) : + ConnectionReader(manager, monitor->is_thread_safe() ? 1 : 0), + _manager(manager), + _monitor(monitor), + _writer(manager, 0) +{ + _udp_port = 0; + _client_data = new PStatClientData(this); + _monitor->set_client_data(_client_data); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatReader:: +~PStatReader() { + _manager->release_udp_port(_udp_port); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::close +// Access: Public +// Description: This will be called by the PStatClientData in +// response to its close() call. It will tell the +// server to let go of the reader so it can shut down +// its connection. +//////////////////////////////////////////////////////////////////// +void PStatReader:: +close() { + _manager->remove_reader(_tcp_connection, this); + lost_connection(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::set_tcp_connection +// Access: Public +// Description: This is intended to be called only once, immediately +// after construction, by the PStatListener that created +// it. It tells the reader about the newly-established +// TCP connection to a client. +//////////////////////////////////////////////////////////////////// +void PStatReader:: +set_tcp_connection(Connection *tcp_connection) { + _tcp_connection = tcp_connection; + add_connection(_tcp_connection); + + _udp_port = _manager->get_udp_port(); + _udp_connection = _manager->open_UDP_connection(_udp_port); + while (_udp_connection.is_null()) { + // That UDP port was no good. Try another. + _udp_port = _manager->get_udp_port(); + _udp_connection = _manager->open_UDP_connection(_udp_port); + } + + add_connection(_udp_connection); + + send_hello(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::lost_connection +// Access: Public +// Description: This is called by the PStatServer when it detects +// that the connection has been lost. It should clean +// itself up and shut down nicely. +//////////////////////////////////////////////////////////////////// +void PStatReader:: +lost_connection() { + _client_data->_is_alive = false; + _monitor->lost_connection(); + _client_data.clear(); + + _manager->close_connection(_tcp_connection); + _manager->close_connection(_udp_connection); + _tcp_connection.clear(); + _udp_connection.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::idle +// Access: Public +// Description: Called each frame to do what needs to be done for the +// monitor's user-defined idle routines. +//////////////////////////////////////////////////////////////////// +void PStatReader:: +idle() { + _monitor->idle(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::get_hostname +// Access: Private +// Description: Returns the current machine's hostname. +//////////////////////////////////////////////////////////////////// +string PStatReader:: +get_hostname() { + if (_hostname.empty()) { + char temp_buff[1024]; + if (gethostname(temp_buff, 1024) == 0) { + _hostname = temp_buff; + } else { + _hostname = "unknown"; + } + } + return _hostname; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::send_hello +// Access: Private +// Description: Sends the initial greeting message to the client. +//////////////////////////////////////////////////////////////////// +void PStatReader:: +send_hello() { + PStatServerControlMessage message; + message._type = PStatServerControlMessage::T_hello; + message._server_hostname = get_hostname(); + message._server_progname = _monitor->get_monitor_name(); + message._udp_port = _udp_port; + + Datagram datagram; + message.encode(datagram); + _writer.send(datagram, _tcp_connection); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::receive_datagram +// Access: Private, Virtual +// Description: Called by the net code whenever a new datagram is +// detected on a either the TCP or UDP connection. +//////////////////////////////////////////////////////////////////// +void PStatReader:: +receive_datagram(const NetDatagram &datagram) { + Connection *connection = datagram.get_connection(); + + if (connection == _tcp_connection) { + PStatClientControlMessage message; + if (message.decode(datagram)) { + handle_client_control_message(message); + } else { + nout << "Got unexpected message from client.\n"; + } + + } else if (connection == _udp_connection) { + handle_client_udp_data(datagram); + + } else { + nout << "Got datagram from unexpected socket.\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::handle_client_control_message +// Access: Private +// Description: Called when a control message has been received by +// the client over the TCP connection. +//////////////////////////////////////////////////////////////////// +void PStatReader:: +handle_client_control_message(const PStatClientControlMessage &message) { + switch (message._type) { + case PStatClientControlMessage::T_hello: + _monitor->hello_from(message._client_hostname, message._client_progname); + break; + + case PStatClientControlMessage::T_define_collectors: + { + for (int i = 0; i < (int)message._collectors.size(); i++) { + _client_data->add_collector(message._collectors[i]); + _monitor->new_collector(message._collectors[i]->_index); + } + } + break; + + case PStatClientControlMessage::T_define_threads: + { + for (int i = 0; i < (int)message._names.size(); i++) { + int thread_index = message._first_thread_index + i; + string name = message._names[i]; + _client_data->define_thread(thread_index, name); + _monitor->new_thread(thread_index); + } + } + break; + + default: + nout << "Invalid control message received from client.\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatReader::handle_client_udp_data +// Access: Private +// Description: Called when a UDP datagram has been received by the +// client. This should be a single frame's worth of +// data. +//////////////////////////////////////////////////////////////////// +void PStatReader:: +handle_client_udp_data(const Datagram &datagram) { + DatagramIterator source(datagram); + + int thread_index = source.get_uint16(); + int frame_number = source.get_uint32(); + PStatFrameData *frame_data = new PStatFrameData; + frame_data->read_datagram(source); + + _client_data->record_new_frame(thread_index, frame_number, frame_data); + _monitor->new_data(thread_index, frame_number); +} + diff --git a/pandatool/src/pstatserver/pStatReader.h b/pandatool/src/pstatserver/pStatReader.h new file mode 100644 index 0000000000..938118e8a2 --- /dev/null +++ b/pandatool/src/pstatserver/pStatReader.h @@ -0,0 +1,63 @@ +// Filename: pStatReader.h +// Created by: drose (09Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATREADER_H +#define PSTATREADER_H + +#include + +#include "pStatClientData.h" +#include "pStatMonitor.h" + +#include +#include +#include + +class PStatServer; +class PStatMonitor; +class PStatClientControlMessage; + +//////////////////////////////////////////////////////////////////// +// Class : PStatReader +// Description : This is the class that does all the work for handling +// communications from a single Panda client. It reads +// sockets received from the client and boils them down +// into PStatData. +//////////////////////////////////////////////////////////////////// +class PStatReader : public ConnectionReader { +public: + PStatReader(PStatServer *manager, PStatMonitor *monitor); + ~PStatReader(); + + void close(); + + void set_tcp_connection(Connection *tcp_connection); + void lost_connection(); + void idle(); + +private: + string get_hostname(); + void send_hello(); + + virtual void receive_datagram(const NetDatagram &datagram); + + void handle_client_control_message(const PStatClientControlMessage &message); + void handle_client_udp_data(const Datagram &datagram); + +private: + PStatServer *_manager; + PT(PStatMonitor) _monitor; + ConnectionWriter _writer; + + PT(Connection) _tcp_connection; + PT(Connection) _udp_connection; + int _udp_port; + + PT(PStatClientData) _client_data; + + string _hostname; +}; + +#endif diff --git a/pandatool/src/pstatserver/pStatServer.cxx b/pandatool/src/pstatserver/pStatServer.cxx new file mode 100644 index 0000000000..1c1747bfba --- /dev/null +++ b/pandatool/src/pstatserver/pStatServer.cxx @@ -0,0 +1,229 @@ +// Filename: pStatServer.cxx +// Created by: drose (09Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatServer.h" +#include "pStatReader.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatServer:: +PStatServer() { + _listener = new PStatListener(this); + _next_udp_port = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatServer:: +~PStatServer() { + delete _listener; +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::listen +// Access: Public +// Description: Establishes a port number that the manager will +// listen on for TCP connections. This may be called +// more than once to listen simulataneously on multiple +// connections, as if that were at all useful. +// +// The default parameter, -1, indicates the use of +// whatever port number has been indicated in the Config +// file. +// +// This function returns true if the port was +// successfully opened, or false if it could not open +// the port. +//////////////////////////////////////////////////////////////////// +bool PStatServer:: +listen(int port) { + if (port < 0) { + port = pstats_port; + } + + // Now try to listen to the port. + PT(Connection) rendezvous = open_TCP_server_rendezvous(port, 5); + + if (rendezvous.is_null()) { + // Couldn't get it. + return false; + } + + // Tell the listener about the new port. + _listener->add_connection(rendezvous); + + if (_next_udp_port == 0) { + _next_udp_port = port + 1; + } + return true; +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::poll +// Access: Public +// Description: Checks for any network activity and handles it, if +// appropriate, and then returns. This must be called +// periodically unless is_thread_safe() is redefined to +// return true on this class and also on all +// PStatMonitors in use. +// +// Alternatively, a program may call main_loop() and +// yield control of the program entirely to the +// PStatServer. +//////////////////////////////////////////////////////////////////// +void PStatServer:: +poll() { + // Delete all the readers that we couldn't delete before. + while (!_lost_readers.empty()) { + delete _lost_readers.back(); + _lost_readers.pop_back(); + } + + _listener->poll(); + + Readers::const_iterator ri; + for (ri = _readers.begin(); ri != _readers.end(); ++ri) { + (*ri).second->poll(); + (*ri).second->idle(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::main_loop +// Access: Public +// Description: An alternative to repeatedly calling poll(), this +// function yields control of the program to the +// PStatServer. It does not return until the program +// is done. +// +// If interrupt_flag is non-NULL, it is the address of a +// bool variable that is initially false, and may be +// asynchronously set true to indicate the loop should +// terminate. +//////////////////////////////////////////////////////////////////// +void PStatServer:: +main_loop(bool *interrupt_flag) { + while (interrupt_flag == (bool *)NULL || !*interrupt_flag) { + poll(); + // Not great. This will totally blow in a threaded environment. + // We need a portable way to sleep or block. + PRIntervalTime sleep_timeout = PR_MillisecondsToInterval(100); + PR_Sleep(sleep_timeout); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::add_reader +// Access: Public +// Description: Adds the newly-created PStatReader to the list of +// currently active readers. +//////////////////////////////////////////////////////////////////// +void PStatServer:: +add_reader(Connection *connection, PStatReader *reader) { + _readers[connection] = reader; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::remove_reader +// Access: Public +// Description: Removes the indicated reader. +//////////////////////////////////////////////////////////////////// +void PStatServer:: +remove_reader(Connection *connection, PStatReader *reader) { + Readers::iterator ri; + ri = _readers.find(connection); + if (ri == _readers.end() || (*ri).second != reader) { + nout << "Attempt to remove undefined reader.\n"; + } else { + _readers.erase(ri); + _lost_readers.push_back(reader); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::get_udp_port +// Access: Public +// Description: Returns a new port number that will probably be free +// to use as a UDP port. The caller should be prepared +// to accept the possibility that it will be already in +// use by another process, however. +//////////////////////////////////////////////////////////////////// +int PStatServer:: +get_udp_port() { + if (_available_udp_ports.empty()) { + return _next_udp_port++; + } + int udp_port = _available_udp_ports.front(); + _available_udp_ports.pop_front(); + return udp_port; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::release_udp_port +// Access: Public +// Description: Indicates that the given UDP port is once again free +// for use. +//////////////////////////////////////////////////////////////////// +void PStatServer:: +release_udp_port(int port) { + _available_udp_ports.push_back(port); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::is_thread_safe +// Access: Public +// Description: This should be redefined to return true in derived +// classes that want to deal with multithreaded readers +// and such. If this returns true, the manager will +// create the listener in its own thread, and thus the +// PStatReader constructors at least will run in a +// different thread. +// +// This is not related to the question of whether the +// reader can handle multiple different +// PStatThreadDatas; it's strictly a question of whether +// the readers themselves can run in a separate thread. +//////////////////////////////////////////////////////////////////// +bool PStatServer:: +is_thread_safe() { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatServer::connection_reset +// Access: Private +// Description: Called when a lost connection is detected by the net +// code, this should pass the word on to the interested +// parties and clean up gracefully. +//////////////////////////////////////////////////////////////////// +void PStatServer:: +connection_reset(const PT(Connection) &connection) { + // Was this a client connection? Tell the reader about it if it + // was. + close_connection(connection); + + Readers::iterator ri; + ri = _readers.find(connection); + if (ri != _readers.end()) { + PStatReader *reader = (*ri).second; + reader->lost_connection(); + _readers.erase(ri); + + // Unfortunately, we can't delete the reader right away, because + // we might have been called from a method on the reader! We'll + // have to safe the reader pointer and delete it some time later. + _lost_readers.push_back(reader); + } +} diff --git a/pandatool/src/pstatserver/pStatServer.h b/pandatool/src/pstatserver/pStatServer.h new file mode 100644 index 0000000000..bac135b5a9 --- /dev/null +++ b/pandatool/src/pstatserver/pStatServer.h @@ -0,0 +1,68 @@ +// Filename: pStatServer.h +// Created by: drose (09Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATSERVER_H +#define PSTATSERVER_H + +#include + +#include "pStatListener.h" + +#include + +#include +#include + +class PStatReader; + +//////////////////////////////////////////////////////////////////// +// Class : PStatServer +// Description : The overall manager of the network connections. This +// class gets the ball rolling; to use this package, you +// need to derive from this and define make_monitor() to +// allocate and return a PStatMonitor of the suitable +// type. +// +// Then create just one PStatServer object and call +// listen() with the port(s) you would like to listen +// on. It will automatically create PStatMonitors as +// connections are established and mark the connections +// closed as they are lost. +//////////////////////////////////////////////////////////////////// +class PStatServer : public ConnectionManager { +public: + PStatServer(); + ~PStatServer(); + + bool listen(int port = -1); + + void poll(); + void main_loop(bool *interrupt_flag = NULL); + + virtual PStatMonitor *make_monitor()=0; + void add_reader(Connection *connection, PStatReader *reader); + void remove_reader(Connection *connection, PStatReader *reader); + + int get_udp_port(); + void release_udp_port(int port); + + virtual bool is_thread_safe(); + +private: + virtual void connection_reset(const PT(Connection) &connection); + + PStatListener *_listener; + + typedef map Readers; + Readers _readers; + typedef vector LostReaders; + LostReaders _lost_readers; + + typedef deque Ports; + Ports _available_udp_ports; + int _next_udp_port; +}; + +#endif diff --git a/pandatool/src/pstatserver/pStatStripChart.I b/pandatool/src/pstatserver/pStatStripChart.I new file mode 100644 index 0000000000..9c8e2ab836 --- /dev/null +++ b/pandatool/src/pstatserver/pStatStripChart.I @@ -0,0 +1,164 @@ +// Filename: pStatStripChart.I +// Created by: drose (15Jul00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::get_view +// Access: Public +// Description: Returns the View this chart represents. +//////////////////////////////////////////////////////////////////// +INLINE PStatView &PStatStripChart:: +get_view() const { + return _view; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::get_collector_index +// Access: Public +// Description: Returns the particular collector whose data this +// strip chart reflects. +//////////////////////////////////////////////////////////////////// +INLINE int PStatStripChart:: +get_collector_index() const { + return _collector_index; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::set_horizontal_scale +// Access: Public +// Description: Changes the amount of time the width of the +// horizontal axis represents. This may force a redraw. +//////////////////////////////////////////////////////////////////// +INLINE void PStatStripChart:: +set_horizontal_scale(double time_width) { + if (_time_width != time_width) { + if (_scroll_mode) { + _start_time += _time_width - time_width; + } else { + force_reset(); + } + _time_width = time_width; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::get_horizontal_scale +// Access: Public +// Description: Returns the amount of total time the width of the +// horizontal axis represents. +//////////////////////////////////////////////////////////////////// +INLINE double PStatStripChart:: +get_horizontal_scale() const { + return _time_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::set_vertical_scale +// Access: Public +// Description: Changes the amount of time the height of the +// vertical axis represents. This may force a redraw. +//////////////////////////////////////////////////////////////////// +INLINE void PStatStripChart:: +set_vertical_scale(double time_height) { + if (_time_height != time_height) { + _time_height = time_height; + normal_guide_bars(); + force_redraw(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::set_default_vertical_scale +// Access: Public +// Description: Sets the vertical scale to center the target frame +// rate bar. +//////////////////////////////////////////////////////////////////// +INLINE void PStatStripChart:: +set_default_vertical_scale() { + set_vertical_scale(2.0 / get_target_frame_rate()); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::get_vertical_scale +// Access: Public +// Description: Returns the amount of total time the height of the +// vertical axis represents. +//////////////////////////////////////////////////////////////////// +INLINE double PStatStripChart:: +get_vertical_scale() const { + return _time_height; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::set_scroll_mode +// Access: Public +// Description: Changes the scroll_mode flag. When true, the strip +// chart will update itself by scrolling to the left; +// when false, the strip chart will wrap around at the +// right and restart at the left end without scrolling. +//////////////////////////////////////////////////////////////////// +INLINE void PStatStripChart:: +set_scroll_mode(bool scroll_mode) { + if (_scroll_mode != scroll_mode) { + _scroll_mode = scroll_mode; + _first_data = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::get_scroll_mode +// Access: Public +// Description: Returns the current state of the scroll_mode flag. +// When true, the strip chart will update itself by +// scrolling to the left; when false, the strip chart +// will wrap around at the right and restart at the left +// end without scrolling. +//////////////////////////////////////////////////////////////////// +INLINE bool PStatStripChart:: +get_scroll_mode() const { + return _scroll_mode; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::timestamp_to_pixel +// Access: Public +// Description: Converts a timestamp to a horizontal pixel offset. +//////////////////////////////////////////////////////////////////// +INLINE int PStatStripChart:: +timestamp_to_pixel(double time) const { + return (int)((double)get_xsize() * (time - _start_time) / _time_width); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::pixel_to_timestamp +// Access: Public +// Description: Converts a horizontal pixel offset to a timestamp. +//////////////////////////////////////////////////////////////////// +INLINE double PStatStripChart:: +pixel_to_timestamp(int x) const { + return _time_width * (double)x / (double)get_xsize() + _start_time; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::height_to_pixel +// Access: Public +// Description: Converts an elapsed time (e.g. a "height" in the +// strip chart) to a vertical pixel offset. +//////////////////////////////////////////////////////////////////// +INLINE int PStatStripChart:: +height_to_pixel(double elapsed_time) const { + return get_ysize() - (int)((double)get_ysize() * elapsed_time / _time_height); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::pixel_to_height +// Access: Public +// Description: Converts a vertical pixel offset to an elapsed time +// (a "height" in the strip chart). +//////////////////////////////////////////////////////////////////// +INLINE double PStatStripChart:: +pixel_to_height(int x) const { + return _time_height * (double)(get_ysize() - x) / (double)get_ysize(); +} diff --git a/pandatool/src/pstatserver/pStatStripChart.cxx b/pandatool/src/pstatserver/pStatStripChart.cxx new file mode 100644 index 0000000000..86c4da5723 --- /dev/null +++ b/pandatool/src/pstatserver/pStatStripChart.cxx @@ -0,0 +1,502 @@ +// Filename: pStatStripChart.cxx +// Created by: drose (15Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatStripChart.h" + +#include +#include +#include +#include + +#include // for sprintf +#include + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatStripChart:: +PStatStripChart(PStatMonitor *monitor, PStatView &view, + int collector_index, int xsize, int ysize) : + PStatGraph(monitor, xsize, ysize), + _view(view), + _collector_index(collector_index) +{ + _scroll_mode = pstats_scroll_mode; + + _next_frame = 0; + _first_data = true; + _cursor_pixel = 0; + + _time_width = 20.0; + _time_height = 1.0/10.0; + _start_time = 0.0; + + _level_index = 0; + + set_default_vertical_scale(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +PStatStripChart:: +~PStatStripChart() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::new_data +// Access: Public +// Description: Indicates that new data has become available. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +new_data(int frame_number) { + // If the new frame is older than the last one we've drawn, we'll + // need to back up and redraw it. This can happen when frames + // arrive out of order from the client. + _next_frame = min(frame_number, _next_frame); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::update +// Access: Public +// Description: Updates the chart with the latest data. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +update() { + const PStatClientData *client_data = get_monitor()->get_client_data(); + + // Don't bother to update the thread data until we know at least + // something about the collectors and threads. + if (client_data->get_num_collectors() != 0 && + client_data->get_num_threads() != 0) { + const PStatThreadData *thread_data = _view.get_thread_data(); + if (!thread_data->is_empty()) { + int latest = thread_data->get_latest_frame_number(); + + if (latest > _next_frame) { + draw_frames(_next_frame, latest); + } + _next_frame = latest; + + // Clean out the old data. + double oldest_time = + thread_data->get_frame(latest).get_start() - _time_width; + + Data::iterator di; + di = _data.begin(); + while (di != _data.end() && + thread_data->get_frame((*di).first).get_start() < oldest_time) { + _data.erase(di); + di = _data.begin(); + } + } + } + + if (_level_index != _view.get_level_index()) { + update_labels(); + } + + idle(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::get_collector_under_pixel +// Access: Public +// Description: Return the collector index associated with the +// particular band of color at the indicated pixel +// location, or -1 if no band of color was at the pixel. +//////////////////////////////////////////////////////////////////// +int PStatStripChart:: +get_collector_under_pixel(int xpoint, int ypoint) { + // First, we need to know what frame it was; to know that, we need + // to determine the time corresponding to the x pixel. + double time = pixel_to_timestamp(xpoint); + + // Now use that time to determine the frame. + const PStatThreadData *thread_data = _view.get_thread_data(); + int frame_number = thread_data->get_frame_number_at_time(time); + + // And now we can determine which collector within the frame, + // based on the time height. + const FrameData &frame = get_frame_data(frame_number); + double overall_time = 0.0; + int y = get_ysize(); + + FrameData::const_iterator fi; + for (fi = frame.begin(); fi != frame.end(); ++fi) { + const ColorData &cd = (*fi); + overall_time += cd._net_time; + y = height_to_pixel(overall_time); + if (y <= ypoint) { + return cd._collector_index; + } + } + + return -1; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::get_frame_data +// Access: Protected +// Description: Returns the cached FrameData associated with the +// given frame number. This describes the lengths of +// the color bands for a single vertical stripe in the +// chart. +//////////////////////////////////////////////////////////////////// +const PStatStripChart::FrameData &PStatStripChart:: +get_frame_data(int frame_number) { + Data::const_iterator di; + di = _data.find(frame_number); + if (di != _data.end()) { + return (*di).second; + } + + const PStatThreadData *thread_data = _view.get_thread_data(); + _view.set_to_frame(thread_data->get_frame(frame_number)); + + FrameData &data = _data[frame_number]; + + const PStatViewLevel *level = _view.get_level(_collector_index); + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + const PStatViewLevel *child = level->get_child(i); + ColorData cd; + cd._collector_index = child->get_collector(); + cd._net_time = child->get_net_time(); + if (cd._net_time != 0.0) { + data.push_back(cd); + } + } + + // Also, there might be some time in the overall Collector that + // wasn't included in all of the children. + ColorData cd; + cd._collector_index = level->get_collector(); + cd._net_time = level->get_time_alone(); + if (cd._net_time != 0.0) { + data.push_back(cd); + } + + return data; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::changed_size +// Access: Protected +// Description: To be called by the user class when the widget size +// has changed. This updates the chart's internal data +// and causes it to issue redraw commands to reflect the +// new size. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +changed_size(int xsize, int ysize) { + if (xsize != _xsize || ysize != _ysize) { + _cursor_pixel = xsize * _cursor_pixel / _xsize; + _xsize = xsize; + _ysize = ysize; + + if (!_first_data) { + if (_scroll_mode) { + draw_pixels(0, _xsize); + + } else { + // Redraw the stats that were there before. + double old_start_time = _start_time; + + // Back up a bit to draw the stuff to the right of the cursor. + _start_time -= _time_width; + draw_pixels(_cursor_pixel, _xsize); + + // Now draw the stuff to the left of the cursor. + _start_time = old_start_time; + draw_pixels(0, _cursor_pixel); + } + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::force_redraw +// Access: Protected +// Description: To be called by the user class when the whole thing +// needs to be redrawn for some reason. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +force_redraw() { + if (!_first_data) { + draw_pixels(0, _xsize); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::force_reset +// Access: Protected +// Description: To be called by the user class to cause the chart to +// reset to empty and start filling again. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +force_reset() { + clear_region(); + _first_data = true; +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::clear_region +// Access: Protected, Virtual +// Description: Should be overridden by the user class to wipe out +// the entire strip chart region. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +clear_region() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::copy_region +// Access: Protected, Virtual +// Description: Should be overridden by the user class to copy a +// region of the chart from one part of the chart to +// another. This is used to implement scrolling. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +copy_region(int, int, int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::begin_draw +// Access: Protected, Virtual +// Description: Should be overridden by the user class. This hook +// will be called before drawing any color bars in the +// strip chart; it gives the pixel range that's about to +// be redrawn. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +begin_draw(int, int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::draw_slice +// Access: Protected, Virtual +// Description: Should be overridden by the user class to draw a +// single vertical slice in the strip chart at the +// indicated pixel, with the data for the indicated +// frame. Call get_frame_data() to get the actual color +// data for the given frame_number. This call will only +// be made between a corresponding call to begin_draw() +// and end_draw(). +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +draw_slice(int, int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::draw_empty +// Access: Protected, Virtual +// Description: This is similar to draw_slice(), except it should +// draw a vertical line of the background color to +// represent a portion of the chart that has no data. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +draw_empty(int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::draw_cursor +// Access: Protected, Virtual +// Description: This is similar to draw_slice(), except that it +// should draw the black vertical stripe that represents +// the current position when not in scrolling mode. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +draw_cursor(int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::end_draw +// Access: Protected, Virtual +// Description: Should be overridden by the user class. This hook +// will be called after drawing a series of color bars +// in the strip chart; it gives the pixel range that +// was just redrawn. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +end_draw(int, int) { +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::idle +// Access: Protected, Virtual +// Description: Should be overridden by the user class to perform any +// other updates might be necessary after the color bars +// have been redrawn. For instance, it could check the +// state of _labels_changed, and redraw the labels if it +// is true. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +idle() { +} + + +// STL function object for sorting labels in order by the collector's +// sort index, used in update_labels(), below. +class SortCollectorLabels { +public: + SortCollectorLabels(const PStatClientData *client_data) : + _client_data(client_data) { + } + bool operator () (int a, int b) const { + // By casting the sort numbers to unsigned ints, we cheat and make + // -1 appear to be a very large positive integer, thus placing + // collectors with a -1 sort value at the very end. + return + (unsigned int)_client_data->get_collector_def(a)._sort < + (unsigned int)_client_data->get_collector_def(b)._sort; + } + const PStatClientData *_client_data; +}; + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::update_labels +// Access: Protected +// Description: Resets the list of labels. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +update_labels() { + const PStatViewLevel *level = _view.get_level(_collector_index); + _labels.clear(); + + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + const PStatViewLevel *child = level->get_child(i); + int collector = child->get_collector(); + _labels.push_back(collector); + } + + SortCollectorLabels sort_labels(get_monitor()->get_client_data()); + sort(_labels.begin(), _labels.end(), sort_labels); + + int collector = level->get_collector(); + _labels.push_back(collector); + + _labels_changed = true; + _level_index = _view.get_level_index(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::normal_guide_bars +// Access: Protected, Virtual +// Description: Calls update_guide_bars with parameters suitable to +// this kind of graph. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +normal_guide_bars() { + update_guide_bars(4, _time_height); +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::draw_frames +// Access: Private +// Description: Draws the levels for the indicated frame range. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +draw_frames(int first_frame, int last_frame) { + const PStatThreadData *thread_data = _view.get_thread_data(); + + last_frame = min(last_frame, thread_data->get_latest_frame_number()); + + if (_first_data) { + if (_scroll_mode) { + _start_time = + thread_data->get_frame(last_frame).get_start() - _time_width; + } else { + _start_time = thread_data->get_frame(first_frame).get_start(); + _cursor_pixel = 0; + } + } + + int first_pixel; + if (thread_data->has_frame(first_frame)) { + first_pixel = + timestamp_to_pixel(thread_data->get_frame(first_frame).get_start()); + } else { + first_pixel = 0; + } + + int last_pixel = + timestamp_to_pixel(thread_data->get_frame(last_frame).get_start()); + + if (_first_data && !_scroll_mode) { + first_pixel = min(_cursor_pixel, first_pixel); + } + _first_data = false; + + if (last_pixel - first_pixel >= _xsize) { + // If we're drawing the whole thing all in this one swoop, just + // start over. + _start_time = thread_data->get_frame(last_frame).get_start() - _time_width; + first_pixel = 0; + last_pixel = _xsize; + } + + if (last_pixel <= _xsize) { + // It all fits in one block. + _cursor_pixel = last_pixel; + draw_pixels(first_pixel, last_pixel); + + } else { + if (_scroll_mode) { + // In scrolling mode, slide the world back. + int slide_pixels = last_pixel - _xsize; + copy_region(slide_pixels, first_pixel, 0); + first_pixel -= slide_pixels; + last_pixel -= slide_pixels; + _start_time += (double)slide_pixels / (double)_xsize * _time_width; + draw_pixels(first_pixel, last_pixel); + + } else { + // In wrapping mode, do it in two blocks. + _cursor_pixel = -1; + draw_pixels(first_pixel, _xsize); + _start_time = pixel_to_timestamp(_xsize); + last_pixel -= _xsize; + _cursor_pixel = last_pixel; + draw_pixels(0, last_pixel); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatStripChart::draw_pixels +// Access: Private +// Description: Draws the levels for the indicated pixel range. +//////////////////////////////////////////////////////////////////// +void PStatStripChart:: +draw_pixels(int first_pixel, int last_pixel) { + begin_draw(first_pixel, last_pixel); + const PStatThreadData *thread_data = _view.get_thread_data(); + + int frame_number = -1; + for (int x = first_pixel; x <= last_pixel; x++) { + if (x == _cursor_pixel && !_scroll_mode) { + draw_cursor(x); + + } else { + double time = pixel_to_timestamp(x); + frame_number = thread_data->get_frame_number_at_time(time, frame_number); + + if (thread_data->has_frame(frame_number)) { + draw_slice(x, frame_number); + } else { + draw_empty(x); + } + } + } + end_draw(first_pixel, last_pixel); +} diff --git a/pandatool/src/pstatserver/pStatStripChart.h b/pandatool/src/pstatserver/pStatStripChart.h new file mode 100644 index 0000000000..95a563fbaa --- /dev/null +++ b/pandatool/src/pstatserver/pStatStripChart.h @@ -0,0 +1,110 @@ +// Filename: pStatStripChart.h +// Created by: drose (15Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATSTRIPCHART_H +#define PSTATSTRIPCHART_H + +#include + +#include "pStatGraph.h" +#include "pStatMonitor.h" +#include "pStatClientData.h" + +#include +#include + +#include + +class PStatView; + +//////////////////////////////////////////////////////////////////// +// Class : PStatStripChart +// Description : This is an abstract class that presents the interface +// for drawing a basic strip-chart, showing the relative +// time elapsed over an interval of time for several +// different collectors, differentiated by bands of +// color. +// +// This class just manages all the strip-chart logic; +// the actual nuts and bolts of drawing pixels is left +// to a user-derived class. +//////////////////////////////////////////////////////////////////// +class PStatStripChart : public PStatGraph { +public: + PStatStripChart(PStatMonitor *monitor, PStatView &view, + int collector_index, int xsize, int ysize); + virtual ~PStatStripChart(); + + void new_data(int frame_number); + void update(); + + INLINE PStatView &get_view() const; + INLINE int get_collector_index() const; + + INLINE void set_horizontal_scale(double time_width); + INLINE double get_horizontal_scale() const; + INLINE void set_vertical_scale(double time_height); + INLINE void set_default_vertical_scale(); + INLINE double get_vertical_scale() const; + + INLINE void set_scroll_mode(bool scroll_mode); + INLINE bool get_scroll_mode() const; + + int get_collector_under_pixel(int xpoint, int ypoint); + INLINE int timestamp_to_pixel(double time) const; + INLINE double pixel_to_timestamp(int x) const; + INLINE int height_to_pixel(double elapsed_time) const; + INLINE double pixel_to_height(int y) const; + +protected: + class ColorData { + public: + int _collector_index; + double _net_time; + }; + typedef vector FrameData; + typedef map Data; + + const FrameData &get_frame_data(int frame_number); + + void changed_size(int xsize, int ysize); + void force_redraw(); + void force_reset(); + void update_labels(); + virtual void normal_guide_bars(); + + virtual void clear_region(); + virtual void copy_region(int start_x, int end_x, int dest_x); + virtual void begin_draw(int from_x, int to_x); + virtual void draw_slice(int x, int frame_number); + virtual void draw_empty(int x); + virtual void draw_cursor(int x); + virtual void end_draw(int from_x, int to_x); + virtual void idle(); + +private: + void draw_frames(int first_frame, int last_frame); + void draw_pixels(int first_pixel, int last_pixel); + + PStatView &_view; + int _collector_index; + bool _scroll_mode; + + Data _data; + + int _next_frame; + bool _first_data; + int _cursor_pixel; + + int _level_index; + + double _time_width; + double _start_time; + double _time_height; +}; + +#include "pStatStripChart.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatThreadData.I b/pandatool/src/pstatserver/pStatThreadData.I new file mode 100644 index 0000000000..c164933b1e --- /dev/null +++ b/pandatool/src/pstatserver/pStatThreadData.I @@ -0,0 +1,16 @@ +// Filename: pStatThreadData.I +// Created by: drose (10Jul00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_client_data +// Access: Public +// Description: Returns a pointer to the ClientData structure +// associated with this data. +//////////////////////////////////////////////////////////////////// +INLINE const PStatClientData *PStatThreadData:: +get_client_data() const { + return _client_data; +} diff --git a/pandatool/src/pstatserver/pStatThreadData.cxx b/pandatool/src/pstatserver/pStatThreadData.cxx new file mode 100644 index 0000000000..6d63376538 --- /dev/null +++ b/pandatool/src/pstatserver/pStatThreadData.cxx @@ -0,0 +1,323 @@ +// Filename: pStatThreadData.cxx +// Created by: drose (09Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatThreadData.h" + +#include +#include +#include + + +PStatFrameData PStatThreadData::_null_frame; + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatThreadData:: +PStatThreadData(const PStatClientData *client_data) : + _client_data(client_data) +{ + _first_frame_number = 0; + _history = pstats_history; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatThreadData:: +~PStatThreadData() { +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::is_empty +// Access: Public +// Description: Returns true if the structure contains no frames, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool PStatThreadData:: +is_empty() const { + return _frames.empty(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_latest_frame_number +// Access: Public +// Description: Returns the frame number of the most recent frame +// stored in the data. +//////////////////////////////////////////////////////////////////// +int PStatThreadData:: +get_latest_frame_number() const { + nassertr(!_frames.empty(), 0); + return _first_frame_number + _frames.size() - 1; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_oldest_frame_number +// Access: Public +// Description: Returns the frame number of the oldest frame still +// stored in the data. +//////////////////////////////////////////////////////////////////// +int PStatThreadData:: +get_oldest_frame_number() const { + nassertr(!_frames.empty(), 0); + return _first_frame_number; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::has_frame +// Access: Public +// Description: Returns true if we have received data for the +// indicated frame number from the client and we still +// have it stored, or false otherwise. +//////////////////////////////////////////////////////////////////// +bool PStatThreadData:: +has_frame(int frame_number) const { + int rel_frame = frame_number - _first_frame_number; + + return (rel_frame >= 0 && rel_frame < _frames.size() && + _frames[rel_frame] != (PStatFrameData *)NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_frame +// Access: Public +// Description: Returns a FrameData structure associated with the +// indicated frame number. If the frame data has not +// yet been received from the client, returns the newest +// frame older than the requested frame. +//////////////////////////////////////////////////////////////////// +const PStatFrameData &PStatThreadData:: +get_frame(int frame_number) const { + int rel_frame = frame_number - _first_frame_number; + if (rel_frame >= _frames.size()) { + rel_frame = _frames.size() - 1; + } + + while (rel_frame >= 0 && _frames[rel_frame] == (PStatFrameData *)NULL) { + rel_frame--; + } + if (rel_frame >= 0) { + return *_frames[rel_frame]; + } else { + // No frame data that old. Return the oldest frame we've got. + rel_frame = 0; + while (rel_frame < _frames.size() && + _frames[rel_frame] == (PStatFrameData *)NULL) { + rel_frame++; + } + return (rel_frame < _frames.size()) ? *_frames[rel_frame] : _null_frame; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_latest_time +// Access: Public +// Description: Returns the timestamp (in seconds elapsed since +// connection) of the latest available frame. +//////////////////////////////////////////////////////////////////// +double PStatThreadData:: +get_latest_time() const { + nassertr(!_frames.empty(), 0.0); + return _frames.back()->get_start(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_oldest_time +// Access: Public +// Description: Returns the timestamp (in seconds elapsed since +// connection) of the oldest available frame. +//////////////////////////////////////////////////////////////////// +double PStatThreadData:: +get_oldest_time() const { + nassertr(!_frames.empty(), 0.0); + return _frames.front()->get_start(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_frame_at_time +// Access: Public +// Description: Returns the FrameData structure associated with the +// latest frame not later than the indicated time. +//////////////////////////////////////////////////////////////////// +const PStatFrameData &PStatThreadData:: +get_frame_at_time(double time) const { + return get_frame(get_frame_number_at_time(time)); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_frame_number_at_time +// Access: Public +// Description: Returns the frame number of the latest frame not +// later than the indicated time. +// +// If the hint is nonnegative, it represents a frame +// number that we believe the correct answer to be near, +// which may speed the search for the frame. +//////////////////////////////////////////////////////////////////// +int PStatThreadData:: +get_frame_number_at_time(double time, int hint) const { + hint -= _first_frame_number; + if (hint >= 0 && hint < _frames.size()) { + if (_frames[hint] != (PStatFrameData *)NULL && + _frames[hint]->get_start() <= time) { + // The hint might be right. Scan forward from there. + int i = hint + 1; + while (i < _frames.size() && + (_frames[i] == (PStatFrameData *)NULL || + _frames[i]->get_start() <= time)) { + if (_frames[i] != (PStatFrameData *)NULL) { + hint = i; + } + ++i; + } + return _first_frame_number + hint; + } + } + + // The hint is totally wrong. Start from the end and work + // backwards. + + int i = _frames.size() - 1; + while (i >= 0 && (_frames[i] == (PStatFrameData *)NULL || + _frames[i]->get_start() > time)) { + --i; + } + return _first_frame_number + i; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_latest_frame +// Access: Public +// Description: Returns the FrameData associated with the most recent +// frame. +//////////////////////////////////////////////////////////////////// +const PStatFrameData &PStatThreadData:: +get_latest_frame() const { + nassertr(!_frames.empty(), _null_frame); + return *_frames.back(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_frame_rate +// Access: Public +// Description: Computes the average frame rate over the past number +// of seconds, by counting up the number of frames +// elapsed in that time interval. +//////////////////////////////////////////////////////////////////// +double PStatThreadData:: +get_frame_rate(double time) const { + nassertr(!_frames.empty(), 0.0); + + int now_i = _frames.size() - 1; + double now = _frames[now_i]->get_end(); + double then = now - time; + + int then_i = now_i; + int last_good_i = now_i; + + while (then_i > 0 && _frames[then_i] == (PStatFrameData *)NULL) { + then_i--; + } + + while (then_i > 0 && _frames[then_i]->get_start() > then) { + last_good_i = now_i; + then_i--; + while (then_i > 0 && _frames[then_i] == (PStatFrameData *)NULL) { + then_i--; + } + } + + int num_frames = now_i - last_good_i + 1; + return (double)num_frames / (now - _frames[last_good_i]->get_start()); +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::set_history +// Access: Public +// Description: Sets the number of seconds worth of frames that will +// be retained by the ThreadData structure as each new +// frame is added. This affects how old the oldest +// frame that may be queried is. +//////////////////////////////////////////////////////////////////// +void PStatThreadData:: +set_history(double time) { + _history = time; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::get_history +// Access: Public +// Description: Returns the number of seconds worth of frames that +// will be retained by the ThreadData structure as each +// new frame is added. This affects how old the oldest +// frame that may be queried is. +//////////////////////////////////////////////////////////////////// +double PStatThreadData:: +get_history() const { + return _history; +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatThreadData::record_new_frame +// Access: Public +// Description: Makes room for and stores a new frame's worth of +// data. Calling this function may cause old frame data +// to be discarded to make room, according to the amount +// of time set up via set_history(). +// +// The pointer will become owned by the PStatThreadData +// object and will be freed on destruction. +//////////////////////////////////////////////////////////////////// +void PStatThreadData:: +record_new_frame(int frame_number, PStatFrameData *frame_data) { + nassertv(frame_data != (PStatFrameData *)NULL); + nassertv(!frame_data->is_empty()); + double time = frame_data->get_start(); + + // First, remove all the old frames that fall outside of our + // history window. + double oldest_allowable_time = time - _history; + while (!_frames.empty() && + (_frames.front() == (PStatFrameData *)NULL || + _frames.front()->is_empty() || + _frames.front()->get_start() < oldest_allowable_time)) { + if (_frames.front() != (PStatFrameData *)NULL) { + delete _frames.front(); + } + _frames.pop_front(); + _first_frame_number++; + } + + // Now, add enough empty frame definitions to account for the latest + // frame number. This might involve some skips, since we don't + // guarantee that we get all the frames in order or even at all. + if (_frames.empty()) { + _first_frame_number = frame_number; + _frames.push_back(NULL); + + } else { + while (_first_frame_number + _frames.size() <= frame_number) { + _frames.push_back(NULL); + } + } + + int index = frame_number - _first_frame_number; + nassertv(index >= 0 && index < _frames.size()); + + if (_frames[index] != (PStatFrameData *)NULL) { + nout << "Got repeated frame data for frame " << frame_number << "\n"; + delete _frames[index]; + } + + _frames[index] = frame_data; +} + diff --git a/pandatool/src/pstatserver/pStatThreadData.h b/pandatool/src/pstatserver/pStatThreadData.h new file mode 100644 index 0000000000..aa232d3f37 --- /dev/null +++ b/pandatool/src/pstatserver/pStatThreadData.h @@ -0,0 +1,72 @@ +// Filename: pStatThreadData.h +// Created by: drose (08Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATTHREADDATA_H +#define PSTATTHREADDATA_H + +#include + +#include + +#include + +class PStatCollectorDef; +class PStatFrameData; +class PStatClientData; + +//////////////////////////////////////////////////////////////////// +// Class : PStatThreadData +// Description : A collection of FrameData structures for +// recently-received frames within a particular thread. +// This holds the raw data as reported by the client, +// and it automatically handles frames received +// out-of-order or skipped. You can ask for a +// particular frame by frame number or time and receive +// the data for the nearest frame. +//////////////////////////////////////////////////////////////////// +class PStatThreadData : public ReferenceCount { +public: + PStatThreadData(const PStatClientData *client_data); + ~PStatThreadData(); + + INLINE const PStatClientData *get_client_data() const; + + bool is_empty() const; + + int get_latest_frame_number() const; + int get_oldest_frame_number() const; + bool has_frame(int frame_number) const; + const PStatFrameData &get_frame(int frame_number) const; + + double get_latest_time() const; + double get_oldest_time() const; + const PStatFrameData &get_frame_at_time(double time) const; + int get_frame_number_at_time(double time, int hint = -1) const; + + const PStatFrameData &get_latest_frame() const; + + double get_frame_rate(double time = 3.0) const; + + + void set_history(double time); + double get_history() const; + + void record_new_frame(int frame_number, PStatFrameData *frame_data); + +private: + const PStatClientData *_client_data; + + typedef deque Frames; + Frames _frames; + int _first_frame_number; + double _history; + + static PStatFrameData _null_frame; +}; + +#include "pStatThreadData.I" + +#endif + diff --git a/pandatool/src/pstatserver/pStatView.I b/pandatool/src/pstatserver/pStatView.I new file mode 100644 index 0000000000..bfc4652038 --- /dev/null +++ b/pandatool/src/pstatserver/pStatView.I @@ -0,0 +1,71 @@ +// Filename: pStatView.I +// Created by: drose (12Jul00) +// +//////////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::get_thread_data +// Access: Public +// Description: Returns the current PStatThreadData associated with +// the view. This was set by a previous call to +// set_thread_data(). +//////////////////////////////////////////////////////////////////// +INLINE const PStatThreadData *PStatView:: +get_thread_data() { + return _thread_data; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::get_client_data +// Access: Public +// Description: Returns the current PStatClientData associated with +// the view. This was also set by a previous call to +// set_thread_data(). +//////////////////////////////////////////////////////////////////// +INLINE const PStatClientData *PStatView:: +get_client_data() { + return _client_data; +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::set_to_frame +// Access: Public +// Description: Sets to a particular frame number (or the nearest +// available), extracted from the View's PStatThreadData +// pointer. See the comments in the other flavor of +// set_to_frame(). +//////////////////////////////////////////////////////////////////// +INLINE void PStatView:: +set_to_frame(int frame_number) { + set_to_frame(_thread_data->get_frame(frame_number)); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::set_to_time +// Access: Public +// Description: Sets to the frame that occurred at the indicated time +// (or the nearest available frame), extracted from the +// View's PStatThreadData pointer. See the comments in +// set_to_frame. +//////////////////////////////////////////////////////////////////// +INLINE void PStatView:: +set_to_time(double time) { + set_to_frame(_thread_data->get_frame_at_time(time)); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::get_level_index +// Access: Public +// Description: Returns an index number that can be used to determine +// when the set of known levels has changed. Each time +// the set of levels in the view changes (because of new +// data arriving from the client, for instance), this +// number is incremented. +//////////////////////////////////////////////////////////////////// +INLINE int PStatView:: +get_level_index() const { + return _level_index; +} diff --git a/pandatool/src/pstatserver/pStatView.cxx b/pandatool/src/pstatserver/pStatView.cxx new file mode 100644 index 0000000000..bcd9b86a4f --- /dev/null +++ b/pandatool/src/pstatserver/pStatView.cxx @@ -0,0 +1,445 @@ +// Filename: pStatView.cxx +// Created by: drose (10Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatView.h" + +#include +#include +#include + +#include +#include + + + +//////////////////////////////////////////////////////////////////// +// Class : FrameSample +// Description : This class is used within this module only--in fact, +// within PStatView::set_to_frame() only--to help +// collect data out of the PStatFrameData object and +// boil it down to a list of elapsed times. +//////////////////////////////////////////////////////////////////// +class FrameSample { +public: + typedef list Started; + + FrameSample() { + _touched = false; + _is_started = false; + _pushed = false; + _net_time = 0.0; + } + void data_point(double time, Started &started) { + _touched = true; + _is_started = !_is_started; + + if (_pushed) { + nassertv(!_is_started); + Started::iterator si = find(started.begin(), started.end(), this); + nassertv(si != started.end()); + started.erase(si); + + } else { + if (_is_started) { + _net_time -= time; + push_all(time, started); + started.push_back(this); + } else { + _net_time += time; + Started::iterator si = find(started.begin(), started.end(), this); + nassertv(si != started.end()); + started.erase(si); + pop_one(time, started); + } + } + } + void push(double time) { + if (!_pushed) { + _pushed = true; + if (_is_started) { + _net_time += time; + } + } + } + void pop(double time) { + if (_pushed) { + _pushed = false; + if (_is_started) { + _net_time -= time; + } + } + } + + void push_all(double time, Started &started) { + Started::iterator si; + for (si = started.begin(); si != started.end(); ++si) { + (*si)->push(time); + } + } + + void pop_one(double time, Started &started) { + Started::reverse_iterator si; + for (si = started.rbegin(); si != started.rend(); ++si) { + if ((*si)->_pushed) { + (*si)->pop(time); + return; + } + } + } + + bool _touched; + bool _is_started; + bool _pushed; + double _net_time; +}; + + + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatView:: +PStatView() { + _constraint = 0; + _all_collectors_known = false; + _level_index = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatView:: +~PStatView() { + clear_levels(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::constrain +// Access: Public +// Description: Changes the focus of the View. By default, the View +// reports the entire time for the frame, and all of the +// Collectors that are directly parented to "Frame". By +// constraining the view to a particular collector, you +// cause the View to zoom in on that collector's data, +// reporting only the collector and its immediate +// parents. +// +// Changing the constraint causes the current frame's +// data to become invalidated; you must then call +// set_to_frame() again to get any useful data out. +//////////////////////////////////////////////////////////////////// +void PStatView:: +constrain(int collector) { + _constraint = collector; + clear_levels(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::unconstrain +// Access: Public +// Description: Restores the view to the full frame. This is +// equivalent to calling constrain(0). +//////////////////////////////////////////////////////////////////// +void PStatView:: +unconstrain() { + constrain(0); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::set_thread_data +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void PStatView:: +set_thread_data(const PStatThreadData *thread_data) { + _thread_data = thread_data; + _client_data = thread_data->get_client_data(); + clear_levels(); + _all_collectors_known = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::set_to_frame +// Access: Public +// Description: Supplies the View with the data for the current +// frame. This causes the View to update all of its +// internal data to reflect the frame's data, subject to +// the current constraint. +// +// It is possible that calling this will increase the +// total number of reported levels (for instance, if +// this frame introduced a new collector that hadn't +// been active previously). In this case, the caller +// must update its display or whatever to account for +// the new level. +//////////////////////////////////////////////////////////////////// +void PStatView:: +set_to_frame(const PStatFrameData &frame_data) { + nassertv(!_thread_data.is_null()); + nassertv(!_client_data.is_null()); + + int num_events = frame_data.get_num_events(); + + typedef vector Samples; + Samples samples(_client_data->get_num_collectors()); + + FrameSample::Started started; + + _all_collectors_known = true; + + + // This tracks the set of samples we actually care about. + typedef set GotSamples; + GotSamples got_samples; + + int i; + for (i = 0; i < num_events; i++) { + int collector_index = (frame_data.get_collector(i) & 0x7fff); + + if (!_client_data->has_collector(collector_index)) { + _all_collectors_known = false; + + } else { + nassertv(collector_index >= 0 && collector_index < (int)samples.size()); + + if (_client_data->get_child_distance(_constraint, collector_index) >= 0) { + // Here's a data point we care about: anything at constraint + // level or below. + samples[collector_index].data_point(frame_data.get_time(i), started); + got_samples.insert(collector_index); + } + } + } + + // Make sure everything is stopped. + + Samples::iterator si; + for (i = 0, si = samples.begin(); si != samples.end(); ++i, ++si) { + if ((*si)._is_started) { + nout << _client_data->get_collector_fullname(i) + << " was not stopped at frame end!\n"; + (*si).data_point(frame_data.get_end(), started); + } + } + + nassertv(started.empty()); + + bool any_new_levels = false; + + // Now match these samples we got up with those we already had in + // the levels. + Levels::iterator li, lnext; + li = _levels.begin(); + while (li != _levels.end()) { + // Be careful while traversing a container and calling functions + // that could modify that container. + lnext = li; + ++lnext; + + PStatViewLevel *level = (*li).second; + if (reset_level(level)) { + any_new_levels = true; + } + + int collector_index = level->_collector; + GotSamples::iterator gi; + gi = got_samples.find(collector_index); + if (gi != got_samples.end()) { + level->_time_alone = samples[collector_index]._net_time; + got_samples.erase(gi); + } + + li = lnext; + } + + // Finally, any samples left over in the got_samples set are new + // collectors that we need to add to the Levels list. + if (!got_samples.empty()) { + any_new_levels = true; + + GotSamples::const_iterator gi; + for (gi = got_samples.begin(); gi != got_samples.end(); ++gi) { + int collector_index = (*gi); + PStatViewLevel *level = get_level(collector_index); + level->_time_alone = samples[*gi]._net_time; + } + } + + if (any_new_levels) { + _level_index++; + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::all_collectors_known +// Access: Public +// Description: After a call to set_to_frame(), this returns true if +// all collectors in the FrameData are known by the +// PStatsData object, or false if some are still unknown +// (even those that do not appear in the view). +//////////////////////////////////////////////////////////////////// +bool PStatView:: +all_collectors_known() const { + return _all_collectors_known; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::get_net_time +// Access: Public +// Description: Returns the total time accounted for by the frame (or +// by whatever Collector we are constrained to). This +// is the sum of all of the individual levels' +// get_net_time() value. +//////////////////////////////////////////////////////////////////// +double PStatView:: +get_net_time() const { + double net = 0.0; + Levels::const_iterator li; + for (li = _levels.begin(); li != _levels.end(); ++li) { + net += (*li).second->_time_alone; + } + + return net; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::get_top_level +// Access: Public +// Description: Returns a pointer to the level that corresponds to +// the Collector we've constrained to. This is the top +// of a graph of levels; typically the next level +// down--the children of this level--will be the levels +// you want to display to the user. +//////////////////////////////////////////////////////////////////// +const PStatViewLevel *PStatView:: +get_top_level() { + return get_level(_constraint); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::has_level +// Access: Public +// Description: Returns true if there is a level defined for the +// particular collector, false otherwise. +//////////////////////////////////////////////////////////////////// +bool PStatView:: +has_level(int collector) const { + Levels::const_iterator li; + li = _levels.find(collector); + return (li != _levels.end()); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::get_level +// Access: Public +// Description: Returns a pointer to the level that corresponds to +// the indicated Collector. If there is no such level +// in the view, one will be created--use with caution. +// Check has_level() first if you don't want this +// behavior. +//////////////////////////////////////////////////////////////////// +PStatViewLevel *PStatView:: +get_level(int collector) { + Levels::const_iterator li; + li = _levels.find(collector); + if (li != _levels.end()) { + return (*li).second; + } + + PStatViewLevel *level = new PStatViewLevel; + level->_collector = collector; + level->_parent = NULL; + _levels[collector] = level; + + reset_level(level); + return level; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::clear_levels +// Access: Private +// Description: Resets all the levels that have been defined so far. +//////////////////////////////////////////////////////////////////// +void PStatView:: +clear_levels() { + Levels::iterator li; + for (li = _levels.begin(); li != _levels.end(); ++li) { + delete (*li).second; + } + _levels.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatView::reset_level +// Access: Private +// Description: Resets the total time of the Level to zero, and also +// makes sure it is parented to the right Level +// corresponding to its Collector's parent. Since the +// client might change its mind from time to time about +// who the Collector is parented to, we have to update +// this dynamically. +// +// Returns true if any change was made to the level's +// hierarchy, false otherwise. +//////////////////////////////////////////////////////////////////// +bool PStatView:: +reset_level(PStatViewLevel *level) { + bool any_changed = false; + level->_time_alone = 0.0; + + if (level->_collector == _constraint) { + return false; + } + + if (_client_data->has_collector(level->_collector)) { + int parent_index = + _client_data->get_collector_def(level->_collector)._parent_index; + + if (level->_parent == (PStatViewLevel *)NULL) { + // This level didn't know its parent before, but now it does. + PStatViewLevel *parent_level = get_level(parent_index); + nassertr(parent_level != level, true); + + level->_parent = parent_level; + parent_level->_children.push_back(level); + parent_level->sort_children(_client_data); + any_changed = true; + + } else if (level->_parent->_collector != parent_index) { + // This level knew about its parent, but now it's something + // different. + PStatViewLevel *old_parent_level = level->_parent; + nassertr(old_parent_level != level, true); + + if (parent_index != 0) { + PStatViewLevel *new_parent_level = get_level(parent_index); + nassertr(new_parent_level != level, true); + level->_parent = new_parent_level; + new_parent_level->_children.push_back(level); + new_parent_level->sort_children(_client_data); + } else { + level->_parent = NULL; + } + + PStatViewLevel::Children::iterator ci = + find(old_parent_level->_children.begin(), + old_parent_level->_children.end(), + level); + + nassertr(ci != old_parent_level->_children.end(), true); + old_parent_level->_children.erase(ci); + any_changed = true; + } + } + + return any_changed; +} + + diff --git a/pandatool/src/pstatserver/pStatView.h b/pandatool/src/pstatserver/pStatView.h new file mode 100644 index 0000000000..c9413aceb2 --- /dev/null +++ b/pandatool/src/pstatserver/pStatView.h @@ -0,0 +1,70 @@ +// Filename: pStatView.h +// Created by: drose (10Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATVIEW_H +#define PSTATVIEW_H + +#include + +#include "pStatClientData.h" +#include "pStatThreadData.h" +#include "pStatViewLevel.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : PStatView +// Description : A View boils down the frame data to a linear list of +// times spent in a number of different Collectors, +// within a particular thread. This automatically +// accounts for overlapping start/stop times and nested +// Collectors in a sensible way. +//////////////////////////////////////////////////////////////////// +class PStatView { +public: + PStatView(); + ~PStatView(); + + void constrain(int collector); + void unconstrain(); + + void set_thread_data(const PStatThreadData *thread_data); + INLINE const PStatThreadData *get_thread_data(); + INLINE const PStatClientData *get_client_data(); + + void set_to_frame(const PStatFrameData &frame_data); + INLINE void set_to_frame(int frame_number); + INLINE void set_to_time(double time); + + bool all_collectors_known() const; + double get_net_time() const; + + const PStatViewLevel *get_top_level(); + + bool has_level(int collector) const; + PStatViewLevel *get_level(int collector); + + INLINE int get_level_index() const; + +private: + void clear_levels(); + bool reset_level(PStatViewLevel *level); + + int _constraint; + bool _all_collectors_known; + + typedef map Levels; + Levels _levels; + + int _level_index; + + CPT(PStatClientData) _client_data; + CPT(PStatThreadData) _thread_data; +}; + +#include "pStatView.I" + +#endif + diff --git a/pandatool/src/pstatserver/pStatViewLevel.I b/pandatool/src/pstatserver/pStatViewLevel.I new file mode 100644 index 0000000000..e68eb57429 --- /dev/null +++ b/pandatool/src/pstatserver/pStatViewLevel.I @@ -0,0 +1,28 @@ +// Filename: pStatViewLevel.I +// Created by: drose (19Jul00) +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: PStatViewLevel::get_collector +// Access: Public +// Description: Returns the Collector index associated with this +// level. +//////////////////////////////////////////////////////////////////// +INLINE int PStatViewLevel:: +get_collector() const { + return _collector; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatViewLevel::get_time_alone +// Access: Public +// Description: Returns the total elapsed time spent by this +// Collector, not including any time spent in its child +// Collectors. +//////////////////////////////////////////////////////////////////// +INLINE double PStatViewLevel:: +get_time_alone() const { + return _time_alone; +} diff --git a/pandatool/src/pstatserver/pStatViewLevel.cxx b/pandatool/src/pstatserver/pStatViewLevel.cxx new file mode 100644 index 0000000000..fd66996cd5 --- /dev/null +++ b/pandatool/src/pstatserver/pStatViewLevel.cxx @@ -0,0 +1,87 @@ +// Filename: pStatViewLevel.cxx +// Created by: drose (11Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "pStatViewLevel.h" +#include "pStatClientData.h" + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Function: PStatViewLevel::get_net_time +// Access: Public +// Description: Returns the total elapsed time spent by this +// Collector, including all time spent in its child +// Collectors. +//////////////////////////////////////////////////////////////////// +double PStatViewLevel:: +get_net_time() const { + double net = _time_alone; + + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + net += (*ci)->get_net_time(); + } + + return net; +} + + +// STL function object for sorting children in order by the +// collector's sort index, used in sort_children(), below. +class SortCollectorLevels { +public: + SortCollectorLevels(const PStatClientData *client_data) : + _client_data(client_data) { + } + bool operator () (const PStatViewLevel *a, const PStatViewLevel *b) const { + // By casting the sort numbers to unsigned ints, we cheat and make + // -1 appear to be a very large positive integer, thus placing + // collectors with a -1 sort value at the very end. + return + (unsigned int)_client_data->get_collector_def(a->get_collector())._sort < + (unsigned int)_client_data->get_collector_def(b->get_collector())._sort; + } + const PStatClientData *_client_data; +}; + +//////////////////////////////////////////////////////////////////// +// Function: PStatViewLevel::sort_children +// Access: Public +// Description: Sorts the children of this view level into order as +// specified by the client's sort index. +//////////////////////////////////////////////////////////////////// +void PStatViewLevel:: +sort_children(const PStatClientData *client_data) { + SortCollectorLevels sort_levels(client_data); + + sort(_children.begin(), _children.end(), sort_levels); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatViewLevel::get_num_children +// Access: Public +// Description: Returns the number of children of this +// Level/Collector. These are the Collectors whose time +// is considered to be part of the total time of this +// level's Collector. +//////////////////////////////////////////////////////////////////// +int PStatViewLevel:: +get_num_children() const { + return _children.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatViewLevel::get_child +// Access: Public +// Description: Returns the nth child of this Level/Collector. +//////////////////////////////////////////////////////////////////// +const PStatViewLevel *PStatViewLevel:: +get_child(int n) const { + nassertr(n >= 0 && n < (int)_children.size(), NULL); + return _children[n]; +} diff --git a/pandatool/src/pstatserver/pStatViewLevel.h b/pandatool/src/pstatserver/pStatViewLevel.h new file mode 100644 index 0000000000..aecf845062 --- /dev/null +++ b/pandatool/src/pstatserver/pStatViewLevel.h @@ -0,0 +1,46 @@ +// Filename: pStatViewLevel.h +// Created by: drose (11Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PSTATVIEWLEVEL_H +#define PSTATVIEWLEVEL_H + +#include + +#include + +class PStatClientData; + +//////////////////////////////////////////////////////////////////// +// Class : PStatViewLevel +// Description : This is a single level value, or band of color, +// within a View. It generally indicates the elapsed +// time for a particular Collector within a given frame +// for a particular thread. +//////////////////////////////////////////////////////////////////// +class PStatViewLevel { +public: + INLINE int get_collector() const; + INLINE double get_time_alone() const; + double get_net_time() const; + + void sort_children(const PStatClientData *client_data); + + int get_num_children() const; + const PStatViewLevel *get_child(int n) const; + +private: + int _collector; + double _time_alone; + PStatViewLevel *_parent; + + typedef vector Children; + Children _children; + + friend class PStatView; +}; + +#include "pStatViewLevel.I" + +#endif diff --git a/pandatool/src/stitch/Sources.pp b/pandatool/src/stitch/Sources.pp new file mode 100644 index 0000000000..7ad2190fd4 --- /dev/null +++ b/pandatool/src/stitch/Sources.pp @@ -0,0 +1,36 @@ +#begin bin_target + #define TARGET stitch-command + #define LOCAL_LIBS \ + stitchbase progbase + + #define SOURCES \ + stitchCommandProgram.cxx stitchCommandProgram.h + + #define INSTALL_HEADERS \ + +#end bin_target + +#begin bin_target + #define TARGET stitch-image + #define LOCAL_LIBS \ + stitchbase progbase + + #define SOURCES \ + stitchImageProgram.cxx stitchImageProgram.h + +#end bin_target + +#begin bin_target + #define TARGET stitch-viewer + #define LOCAL_LIBS \ + stitchviewer stitchbase progbase config compiler + #define OTHER_LIBS \ + device:c tform:c graph:c dgraph:c sgraph:c gobj:c sgattrib:c \ + event:c chancfg:c display:c sgraphutil:c light:c putil:c express:c \ + panda:m + + #define SOURCES \ + stitchViewerProgram.cxx stitchViewerProgram.h + +#end bin_target + diff --git a/pandatool/src/stitch/stitchCommandProgram.cxx b/pandatool/src/stitch/stitchCommandProgram.cxx new file mode 100644 index 0000000000..7c8b0e4856 --- /dev/null +++ b/pandatool/src/stitch/stitchCommandProgram.cxx @@ -0,0 +1,45 @@ +// Filename: stitchCommandProgram.cxx +// Created by: drose (16Mar00) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchCommandProgram.h" +#include "stitchImageCommandOutput.h" + +//////////////////////////////////////////////////////////////////// +// Function: StitchCommandProgram::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +StitchCommandProgram:: +StitchCommandProgram() { + set_program_description + ("This program reads a stitch command file, performs processing on the " + "file (such as alignment of images according to points marked within a " + "stitch region), and writes the resulting command file to standard " + "output. It does not actually operate on any images.\n" + + "The primary function of this program is to test the syntax of a " + "command file, or to preprocess a command file so that a series of " + "images (for instance, frames of a movie) may be easily transformed " + "by the exact same operation."); +} + +//////////////////////////////////////////////////////////////////// +// Function: StitchCommandProgram::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void StitchCommandProgram:: +run() { + StitchImageCommandOutput outputter; + _command_file.process(outputter); +} + + +int main(int argc, char *argv[]) { + StitchCommandProgram prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/stitch/stitchCommandProgram.h b/pandatool/src/stitch/stitchCommandProgram.h new file mode 100644 index 0000000000..4272052b6a --- /dev/null +++ b/pandatool/src/stitch/stitchCommandProgram.h @@ -0,0 +1,27 @@ +// Filename: stitchCommandProgram.h +// Created by: drose (16Mar00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHCOMMANDPROGRAM_H +#define STITCHCOMMANDPROGRAM_H + +#include + +#include "stitchCommandReader.h" + +//////////////////////////////////////////////////////////////////// +// Class : StitchCommandProgram +// Description : A program to read a stitch command file, process it +// without actually manipulating any images, and write +// the processed command file out. +//////////////////////////////////////////////////////////////////// +class StitchCommandProgram : public StitchCommandReader { +public: + StitchCommandProgram(); + + void run(); +}; + +#endif + diff --git a/pandatool/src/stitch/stitchImageProgram.cxx b/pandatool/src/stitch/stitchImageProgram.cxx new file mode 100644 index 0000000000..da3830cf8b --- /dev/null +++ b/pandatool/src/stitch/stitchImageProgram.cxx @@ -0,0 +1,42 @@ +// Filename: stitchImageProgram.cxx +// Created by: drose (16Mar00) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchImageProgram.h" +#include "stitchImageRasterizer.h" + +//////////////////////////////////////////////////////////////////// +// Function: StitchImageProgram::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +StitchImageProgram:: +StitchImageProgram() { + set_program_description + ("This program reads a stitch command file, performs whatever processing " + "is indicated by the command file, and generates an output image for " + "each image listed in an output_image section.\n" + + "The images are generated internally using a CPU-based rasterization " + "algorithm (no graphics hardware is used)."); +} + +//////////////////////////////////////////////////////////////////// +// Function: StitchImageProgram::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void StitchImageProgram:: +run() { + StitchImageRasterizer outputter; + _command_file.process(outputter); +} + + +int main(int argc, char *argv[]) { + StitchImageProgram prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/stitch/stitchImageProgram.h b/pandatool/src/stitch/stitchImageProgram.h new file mode 100644 index 0000000000..f6cf853764 --- /dev/null +++ b/pandatool/src/stitch/stitchImageProgram.h @@ -0,0 +1,27 @@ +// Filename: stitchImageProgram.h +// Created by: drose (16Mar00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHIMAGEPROGRAM_H +#define STITCHIMAGEPROGRAM_H + +#include + +#include "stitchCommandReader.h" + +//////////////////////////////////////////////////////////////////// +// Class : StitchImageProgram +// Description : A program to read a stitch command file, perform the +// image manipulations in the CPU, and write output +// images for each processed image. +//////////////////////////////////////////////////////////////////// +class StitchImageProgram : public StitchCommandReader { +public: + StitchImageProgram(); + + void run(); +}; + +#endif + diff --git a/pandatool/src/stitch/stitchViewerProgram.cxx b/pandatool/src/stitch/stitchViewerProgram.cxx new file mode 100644 index 0000000000..5830ad47ac --- /dev/null +++ b/pandatool/src/stitch/stitchViewerProgram.cxx @@ -0,0 +1,43 @@ +// Filename: stitchViewerProgram.cxx +// Created by: drose (16Mar00) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchViewerProgram.h" +#include "stitchImageConverter.h" + +//////////////////////////////////////////////////////////////////// +// Function: StitchViewerProgram::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +StitchViewerProgram:: +StitchViewerProgram() { + set_program_description + ("This program reads a stitch command file, performs whatever processing " + "is indicated by the command file, and draws a 3-d representation of " + "all of the input images described in the command file. The output " + "images are ignored.\n" + + "This program is primarily useful for showing the 3-d relationship " + "between images that has been inferred from the stitch command file."); +} + +//////////////////////////////////////////////////////////////////// +// Function: StitchViewerProgram::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void StitchViewerProgram:: +run() { + StitchImageVisualizer outputter; + _command_file.process(outputter); +} + + +int main(int argc, char *argv[]) { + StitchViewerProgram prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/stitch/stitchViewerProgram.h b/pandatool/src/stitch/stitchViewerProgram.h new file mode 100644 index 0000000000..2754aec34a --- /dev/null +++ b/pandatool/src/stitch/stitchViewerProgram.h @@ -0,0 +1,26 @@ +// Filename: stitchViewerProgram.h +// Created by: drose (16Mar00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHVIEWERPROGRAM_H +#define STITCHVIEWERPROGRAM_H + +#include + +#include "stitchCommandReader.h" + +//////////////////////////////////////////////////////////////////// +// Class : StitchViewerProgram +// Description : A program to read a stitch command file, and draw a +// 3-d representation of all of the input images. +//////////////////////////////////////////////////////////////////// +class StitchViewerProgram : public StitchCommandReader { +public: + StitchViewerProgram(); + + void run(); +}; + +#endif + diff --git a/pandatool/src/stitchbase/Sources.pp b/pandatool/src/stitchbase/Sources.pp new file mode 100644 index 0000000000..e944364824 --- /dev/null +++ b/pandatool/src/stitchbase/Sources.pp @@ -0,0 +1,38 @@ +#define YACC_PREFIX stitchyy +#define LFLAGS -i + +#begin lib_target + #define TARGET stitchbase + #define LOCAL_LIBS \ + progbase + #define OTHER_LIBS \ + putil:c express:c mathutil:c linmath:c pnmimage:c pnm:c panda:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + config_stitch.cxx config_stitch.h layeredImage.cxx layeredImage.h \ + morphGrid.cxx morphGrid.h stitchCommand.cxx stitchCommand.h \ + stitchCommandReader.cxx stitchCommandReader.h \ + stitchCylindricalLens.cxx stitchCylindricalLens.h stitchFile.cxx \ + stitchFile.h stitchFisheyeLens.cxx stitchFisheyeLens.h \ + stitchImage.cxx stitchImage.h stitchImageCommandOutput.cxx \ + stitchImageCommandOutput.h stitchImageOutputter.cxx \ + stitchImageOutputter.h stitchImageRasterizer.cxx \ + stitchImageRasterizer.h stitchLens.cxx stitchLens.h \ + stitchPSphereLens.cxx stitchPSphereLens.h stitchPerspectiveLens.cxx \ + stitchPerspectiveLens.h stitchPoint.cxx stitchPoint.h stitcher.cxx \ + stitcher.h triangle.cxx triangle.h triangleRasterizer.cxx \ + triangleRasterizer.h \ + stitchParserDefs.h stitchParser.yxx stitchLexerDefs.h stitchLexer.lxx + + #define INSTALL_HEADERS \ + config_stitch.h fixedPoint.h layeredImage.h morphGrid.h stitchCommand.h \ + stitchCommandReader.h stitchCylindricalLens.h stitchFile.h \ + stitchFisheyeLens.h stitchImage.h stitchImageCommandOutput.h \ + stitchImageOutputter.h stitchImageRasterizer.h stitchLens.h \ + stitchLexerDefs.h stitchPSphereLens.h stitchParser.h \ + stitchParserDefs.h stitchPerspectiveLens.h stitchPoint.h \ + stitcher.h triangle.h triangleRasterizer.h + +#end lib_target diff --git a/pandatool/src/stitchbase/config_stitch.cxx b/pandatool/src/stitchbase/config_stitch.cxx new file mode 100644 index 0000000000..63d9b56122 --- /dev/null +++ b/pandatool/src/stitchbase/config_stitch.cxx @@ -0,0 +1,16 @@ +// Filename: config_stitch.cxx +// Created by: drose (05Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "config_stitch.h" + +#include + +Configure(config_stitch); + +ConfigureFn(config_stitch) { +} + +string chan_cfg = config_stitch.GetString("chan-config", "single"); + diff --git a/pandatool/src/stitchbase/config_stitch.h b/pandatool/src/stitchbase/config_stitch.h new file mode 100644 index 0000000000..4901ed8bcd --- /dev/null +++ b/pandatool/src/stitchbase/config_stitch.h @@ -0,0 +1,15 @@ +// Filename: config_stitch.h +// Created by: drose (05Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef CONFIG_STITCH_H +#define CONFIG_STITCH_H + +#include + +using namespace std; + +extern string chan_cfg; + +#endif diff --git a/pandatool/src/stitchbase/fixedPoint.h b/pandatool/src/stitchbase/fixedPoint.h new file mode 100644 index 0000000000..287e45aadc --- /dev/null +++ b/pandatool/src/stitchbase/fixedPoint.h @@ -0,0 +1,32 @@ +// Filename: fixedPoint.h +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FIXEDPOINT_H +#define FIXEDPOINT_H + +// Simple fixed-point arithmetic definitions, for support of +// TriangleRasterizer. Totally ripped off from Mesa. + +typedef int FixedPoint; + +#define FIXED_ONE 0x00000800 +#define FIXED_HALF 0x00000400 +#define FIXED_FRAC_MASK 0x000007FF +#define FIXED_INT_MASK (~FIXED_FRAC_MASK) +#define FIXED_EPSILON 1 +#define FIXED_SCALE 2048.0 +#define FIXED_SHIFT 11 +#define FloatToFixed(X) ((FixedPoint) ((X) * FIXED_SCALE)) +#define IntToFixed(I) ((I) << FIXED_SHIFT) +#define FixedToInt(X) ((X) >> FIXED_SHIFT) +#define FixedToUns(X) (((unsigned int)(X)) >> 11) +#define FixedCeil(X) (((X) + FIXED_ONE - FIXED_EPSILON) & FIXED_INT_MASK) +#define FixedFloor(X) ((X) & FIXED_INT_MASK) +/* 0.00048828125 = 1/FIXED_SCALE */ +#define FixedToFloat(X) ((X) * 0.00048828125) +#define PosFloatToFixed(X) FloatToFixed(X) +#define SignedFloatToFixed(X) FloatToFixed(X) + +#endif diff --git a/pandatool/src/stitchbase/layeredImage.cxx b/pandatool/src/stitchbase/layeredImage.cxx new file mode 100644 index 0000000000..b90dd19b71 --- /dev/null +++ b/pandatool/src/stitchbase/layeredImage.cxx @@ -0,0 +1,750 @@ +// Filename: layeredImage.cxx +// Created by: drose (29Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "layeredImage.h" + +#include +#include +#include +#include + +// Constants taken from various header files in Gimp. +#define TILE_WIDTH 64 +#define TILE_HEIGHT 64 + +#define RGB_GIMAGE 0 +#define RGBA_GIMAGE 1 + +LayeredImage::TileManager:: +TileManager(const PNMImage *image, int channel) : + _data(image), _channel(channel) +{ + int width = image->get_x_size(); + int height = image->get_y_size(); + + while (width > TILE_WIDTH || height > TILE_WIDTH) { + _levels.push_back(Level()); + Level &l = _levels.back(); + + l._width = width; + l._height = height; + l._ntile_rows = (height + TILE_HEIGHT - 1) / TILE_HEIGHT; + l._ntile_cols = (width + TILE_WIDTH - 1) / TILE_WIDTH; + + width /= 2; + height /= 2; + } + + _levels.push_back(Level()); + Level &l = _levels.back(); + + l._width = width; + l._height = height; + l._ntile_rows = (height + TILE_HEIGHT - 1) / TILE_HEIGHT; + l._ntile_cols = (width + TILE_WIDTH - 1) / TILE_WIDTH; +} + +int LayeredImage::TileManager:: +get_nlevels() const { + return _levels.size(); +} + +int LayeredImage::TileManager:: +get_level_width(int level) const { + assert(level >= 0 && level < _levels.size()); + return _levels[level]._width; +} + +int LayeredImage::TileManager:: +get_level_height(int level) const { + assert(level >= 0 && level < _levels.size()); + return _levels[level]._height; +} + +int LayeredImage::TileManager:: +get_ntiles(int level) const { + assert(level >= 0 && level < _levels.size()); + return _levels[level]._ntile_rows * _levels[level]._ntile_cols; +} + +int LayeredImage::TileManager:: +get_tile_left(int level, int tile) const { + int ntile_rows = _levels[level]._ntile_rows; + int ntile_cols = _levels[level]._ntile_cols; + + int r = tile / ntile_cols; + int c = tile % ntile_cols; + return c * TILE_WIDTH; +} + +int LayeredImage::TileManager:: +get_tile_top(int level, int tile) const { + int ntile_rows = _levels[level]._ntile_rows; + int ntile_cols = _levels[level]._ntile_cols; + + int r = tile / ntile_cols; + int c = tile % ntile_cols; + return r * TILE_HEIGHT; +} + +int LayeredImage::TileManager:: +get_tile_width(int level, int tile) const { + return min(TILE_WIDTH, _data->get_x_size() - get_tile_left(level, tile)); +} + +int LayeredImage::TileManager:: +get_tile_height(int level, int tile) const { + return min(TILE_HEIGHT, _data->get_y_size() - get_tile_top(level, tile)); +} + +// Trims off the invisible (alpha-0) border around the layer. Returns +// true if there is anything left, false if the layer would be empty. +bool LayeredImage::Layer:: +trim() { + assert(_data != NULL); + if (_data->has_alpha()) { + int xsize = _data->get_x_size(); + int ysize = _data->get_y_size(); + + int top = xsize - 1; + int left = ysize - 1; + int bottom = 0; + int right = 0; + + for (int y = 0; y < ysize; y++) { + for (int x = 0; x < xsize; x++) { + if (_data->get_alpha_val(x, y) != 0) { + top = min(top, y); + left = min(left, x); + bottom = max(bottom, y); + right = max(right, x); + } + } + } + + if (top > bottom || left > right) { + // The layer is completely empty. + return false; + } + + if (top > 0 || left > 0 || bottom < ysize - 1 || right < xsize - 1) { + xsize = right - left + 1; + ysize = bottom - top + 1; + PNMImage *sub = new PNMImage(xsize, ysize, 4); + sub->copy_sub_image(*_data, 0, 0, left, top); + delete _data; + _data = sub; + _offset[0] += left; + _offset[1] += top; + } + } + + return true; +} + +LayeredImage:: +LayeredImage(int xsize, int ysize) : + _xsize(xsize), _ysize(ysize) { +} + +LayeredImage:: +~LayeredImage() { + Layers::const_iterator li; + for (li = _layers.begin(); li != _layers.end(); ++li) { + delete (*li)._data; + } +} + +void LayeredImage:: +add_layer(const string &name, const LVector2d &offset, + PNMImage *data) { + _layers.push_back(Layer()); + Layer &l = _layers.back(); + + l._name = name; + l._offset = offset; + l._data = data; + + if (!l.trim()) { + // If trimming the layer reveals that it is empty, delete it. + delete l._data; + _layers.pop_back(); + } +} + +bool LayeredImage:: +write_file(const Filename &filename) { + ofstream out(filename.c_str()); + + // Maybe in the future, if we support more than one kind of file + // here, we'll decide based on the filename extension which kind to + // write out. + + return write_xcf(out); +} + +bool LayeredImage:: +write_xcf(ostream &out) { + _out = &out; + _pos = 0; + + // Write out the version tag + static const int version_tag_len = 14; + int8_t version_tag[version_tag_len]; + memset(version_tag, 0, version_tag_len); + strcpy((char *)version_tag, "gimp xcf file"); + xcf_write_int8(version_tag, version_tag_len); + + // Write out the width, height, and type. + int32_t width = _xsize; + int32_t height = _ysize; + int32_t base_type = RGB_GIMAGE; + xcf_write_int32(&width, 1); + xcf_write_int32(&height, 1); + xcf_write_int32(&base_type, 1); + + xcf_save_image_props(); + + // Save the current file position; we'll return here to place the + // layer offset information. + int saved_pos = _pos; + + int nlayers = _layers.size(); + int nchannels = 0; + + // Seek to after the offset lists. + xcf_seek_pos(_pos + (nlayers + nchannels + 2) * 4); + + // Write out each layer. Since the layers were added to the + // LayeredImage object from the bottom up (to me, the intuitive + // order), and since they are stored in the XCF file from the top + // down, we must reverse the order here. + Layers::reverse_iterator li; + for (li = _layers.rbegin(); li != _layers.rend(); ++li) { + int32_t offset = _pos; + xcf_save_layer(*li); + + // Go back to write this layer offset. + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); + saved_pos = _pos; + + xcf_seek_end(); + } + + // Write out '0' offset to indicate the end of the layer offsets. + int32_t offset = 0; + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); + saved_pos = _pos; + xcf_seek_end(); + + /* + No need to explicitly write out the channels. + + // Write out each channel. + static const char *channel_name[3] = { "red", "green", "blue" }; + for (int i = 0; i < 3; i++) { + // save the start offset of where we are writing + // out the next channel. + int32_t offset = _pos; + + // write out the channel. + xcf_save_channel(channel_name[i], _layers.front()._data, i); + + // seek back to where we are to write out the next + // channel offset and write it out. + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); + + // increment the location we are to write out the + // next offset. + saved_pos = _pos; + + // seek to the end of the file which is where + // we will write out the next channel. + xcf_seek_end(); + } + */ + + // Write out '0' offset to indicate the end of the channel offsets. + offset = 0; + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); + saved_pos = _pos; + xcf_seek_end(); + + return !_out->fail(); +} + +int LayeredImage:: +xcf_write_int8(const int8_t *data, int num) { + _out->write((const char *)data, num); + return _pos += num; +} + +int LayeredImage:: +xcf_write_int32(const int32_t *data, int num) { + int32_t *tmp = new int32_t[num]; + for (int i = 0; i < num; i++) { + tmp[i] = htonl(data[i]); + } + _out->write((const char *)tmp, num * sizeof(int32_t)); + delete[] tmp; + return _pos += num * sizeof(int32_t); +} + +int LayeredImage:: +xcf_write_string(const string &str) { + int32_t size = (int32_t)str.size() + 1; + if (str.empty()) { + size = 0; + } + xcf_write_int32(&size, 1); + return xcf_write_int8((const int8_t *)str.c_str(), size); +} + +void LayeredImage:: +xcf_save_image_props() { + xcf_save_prop(PROP_END); +} + +void LayeredImage:: +xcf_save_layer_props(const LayeredImage::Layer &layer) { + if (&layer == &_layers.front()) { + xcf_save_prop(PROP_ACTIVE_LAYER); + } + xcf_save_prop(PROP_OPACITY, 255); + xcf_save_prop(PROP_VISIBLE, 1); + xcf_save_prop(PROP_LINKED, 0); + xcf_save_prop(PROP_PRESERVE_TRANSPARENCY, 0); + xcf_save_prop(PROP_APPLY_MASK, 1); + xcf_save_prop(PROP_EDIT_MASK, 0); + xcf_save_prop(PROP_SHOW_MASK, 0); + xcf_save_prop(PROP_MODE, 0); + + xcf_save_prop(PROP_OFFSETS, + (int32_t)layer._offset[0], + (int32_t)layer._offset[1]); + xcf_save_prop(PROP_END); +} + +void LayeredImage:: +xcf_save_channel_props() { + xcf_save_prop(PROP_OPACITY, 255); + xcf_save_prop(PROP_VISIBLE, 1); + xcf_save_prop(PROP_SHOW_MASKED, 0); + // xcf_save_prop(PROP_COLOR, channel->col); + + xcf_save_prop(PROP_END); +} + +// This odd function is lifted from Gimp's xcf.c. +void LayeredImage:: +xcf_save_prop(LayeredImage::PropType prop_type, ...) { + int32_t size; + va_list args; + + va_start(args, prop_type); + + switch (prop_type) { + case PROP_END: + size = 0; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + break; + case PROP_COLORMAP: + { + int32_t ncolors; + int8_t *colors; + + ncolors = va_arg(args, int32_t); + colors = va_arg(args, int8_t*); + size = 4 + ncolors; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32(&ncolors, 1); + xcf_write_int8(colors, ncolors * 3); + } + break; + case PROP_ACTIVE_LAYER: + case PROP_ACTIVE_CHANNEL: + case PROP_SELECTION: + size = 0; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + break; + case PROP_FLOATING_SELECTION: + assert(false); + break; + case PROP_OPACITY: + { + int32_t opacity; + + opacity = va_arg(args, int32_t); + + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32((int32_t*)&opacity, 1); + } + break; + case PROP_MODE: + { + int32_t mode; + + mode = va_arg(args, int32_t); + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32((int32_t*)&mode, 1); + } + break; + case PROP_VISIBLE: + { + int32_t visible; + + visible = va_arg(args, int32_t); + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32(&visible, 1); + } + break; + case PROP_LINKED: + { + int32_t linked; + + linked = va_arg(args, int32_t); + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32(&linked, 1); + } + break; + case PROP_PRESERVE_TRANSPARENCY: + { + int32_t preserve_trans; + + preserve_trans = va_arg(args, int32_t); + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32(&preserve_trans, 1); + } + break; + case PROP_APPLY_MASK: + { + int32_t apply_mask; + + apply_mask = va_arg(args, int32_t); + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32(&apply_mask, 1); + } + break; + case PROP_EDIT_MASK: + { + int32_t edit_mask; + + edit_mask = va_arg(args, int32_t); + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32(&edit_mask, 1); + } + break; + case PROP_SHOW_MASK: + { + int32_t show_mask; + + show_mask = va_arg(args, int32_t); + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32(&show_mask, 1); + } + break; + case PROP_SHOW_MASKED: + { + int32_t show_masked; + + show_masked = va_arg(args, int32_t); + size = 4; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32(&show_masked, 1); + } + break; + case PROP_OFFSETS: + { + int32_t offsets[2]; + + offsets[0] = va_arg(args, int32_t); + offsets[1] = va_arg(args, int32_t); + size = 8; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int32((int32_t*) offsets, 2); + } + break; + case PROP_COLOR: + { + int8_t *color; + + color = va_arg(args, int8_t*); + size = 3; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int8(color, 3); + } + break; + case PROP_COMPRESSION: + { + int8_t compression; + + compression =(int8_t) va_arg(args, int32_t); + size = 1; + + xcf_write_int32((int32_t*)&prop_type, 1); + xcf_write_int32(&size, 1); + xcf_write_int8(&compression, 1); + } + break; + case PROP_GUIDES: + assert(false); + break; + } + + va_end(args); +} + +void LayeredImage:: +xcf_save_layer(const LayeredImage::Layer &layer) { + // write out the width, height and image type information for the layer + int32_t width = layer._data->get_x_size(); + int32_t height = layer._data->get_y_size(); + int32_t type = RGBA_GIMAGE; + xcf_write_int32((int32_t*)&width, 1); + xcf_write_int32((int32_t*)&height, 1); + xcf_write_int32((int32_t*)&type, 1); + + // write out the layer's name + xcf_write_string(layer._name); + + // write out the layer properties + xcf_save_layer_props(layer); + + // save the current position which is where the hierarchy offset + // will be stored. + int saved_pos = _pos; + + // write out the layer tile hierarchy + xcf_seek_pos(_pos + 8); + int32_t offset = _pos; + + xcf_save_hierarchy(layer._data, -1); + + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); + saved_pos = _pos; + + // write out the layer mask. We write out the alpha channel here + // instead of as a proper alpha channel, since it's more convenient + // in The Gimp to edit the alpha channel in the layer mask. + + if (layer._data->has_alpha()) { + xcf_seek_end(); + offset = _pos; + xcf_save_channel("mask", layer._data, 3); + } else { + offset = 0; + } + + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); +} + +void LayeredImage:: +xcf_save_channel(const string &name, const PNMImage *image, int channel) { + int32_t saved_pos; + int32_t offset; + + // write out the width and height information for the channel + int32_t width = image->get_x_size(); + int32_t height = image->get_y_size(); + xcf_write_int32(&width, 1); + xcf_write_int32(&height, 1); + + // write out the channels name + xcf_write_string(name); + + // write out the channel properties + xcf_save_channel_props(); + + // save the current position which is where the hierarchy offset + // will be stored. + saved_pos = _pos; + + /* write out the channel tile hierarchy */ + xcf_seek_pos(_pos + 4); + offset = _pos; + + xcf_save_hierarchy(image, channel); + + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); + saved_pos = _pos; +} + +void LayeredImage:: +xcf_save_hierarchy(const PNMImage *image, int channel) { + int32_t width = image->get_x_size(); + int32_t height = image->get_y_size(); + int32_t bpp = (channel < 0) ? 4 : 1; + xcf_write_int32(&width, 1); + xcf_write_int32(&height, 1); + xcf_write_int32(&bpp, 1); + + int saved_pos = _pos; + + TileManager tm(image, channel); + int nlevels = tm.get_nlevels(); + + xcf_seek_pos(_pos + (nlevels + 1) * 4); + + for (int i = 0; i < nlevels; i++) { + // save the start offset of where we are writing + // out the next level. + int32_t offset = _pos; + + // write out the level. + xcf_save_level(tm, i); + + // seek back to where we are to write out the next + // level offset and write it out. + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); + + // increment the location we are to write out the + // next offset. + saved_pos = _pos; + + // seek to the end of the file which is where + // we will write out the next level. + xcf_seek_end(); + } + + // write out a '0' offset position to indicate the end + // of the level offsets. + int32_t offset = 0; + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); +} + +void LayeredImage:: +xcf_save_level(const LayeredImage::TileManager &tm, int level) { + // write out the width and height information for the channel + int32_t width = tm.get_level_width(level); + int32_t height = tm.get_level_height(level); + xcf_write_int32(&width, 1); + xcf_write_int32(&height, 1); + + int saved_pos = _pos; + + int ntiles = tm.get_ntiles(level); + xcf_seek_pos(_pos + (ntiles + 1) * 4); + + for (int i = 0; i < ntiles; i++) { + // save the start offset of where we are writing + // out the next tile. + int32_t offset = _pos; + + // write out the tile. + xcf_save_tile(tm, level, i); + + // seek back to where we are to write out the next + // tile offset and write it out. + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); + + // increment the location we are to write out the + // next offset. + saved_pos = _pos; + + xcf_seek_end(); + } + + // write out a '0' offset position to indicate the end + // of the level offsets. + int32_t offset = 0; + xcf_seek_pos(saved_pos); + xcf_write_int32(&offset, 1); +} + +void LayeredImage:: +xcf_save_tile(const LayeredImage::TileManager &tm, int level, int tile) { + int xoff = tm.get_tile_left(level, tile); + int yoff = tm.get_tile_top(level, tile); + int xsize = tm.get_tile_width(level, tile); + int ysize = tm.get_tile_height(level, tile); + + if (tm._channel < 0) { + int size = xsize * ysize * 4; + int8_t *array = new int8_t[size]; + int i = 0; + for (int y = yoff; y < yoff + ysize; y++) { + for (int x = xoff; x < xoff + xsize; x++) { + array[i++] = tm._data->get_red_val(x, y); + array[i++] = tm._data->get_green_val(x, y); + array[i++] = tm._data->get_blue_val(x, y); + array[i++] = 255; + } + } + assert(i == size); + xcf_write_int8(array, size); + delete[] array; + + } else { + int size = xsize * ysize; + int8_t *array = new int8_t[size]; + int i = 0; + for (int y = yoff; y < yoff + ysize; y++) { + for (int x = xoff; x < xoff + xsize; x++) { + array[i++] = tm._data->get_channel_val(tm._channel, x, y); + } + } + assert(i == size); + xcf_write_int8(array, size); + delete[] array; + } +} + +void LayeredImage:: +xcf_seek_pos(int to_pos) { + _out->seekp(to_pos); + _pos = to_pos; +} + +void LayeredImage:: +xcf_seek_end() { + _out->seekp(0, ios::end); + _pos = _out->tellp(); +} diff --git a/pandatool/src/stitchbase/layeredImage.h b/pandatool/src/stitchbase/layeredImage.h new file mode 100644 index 0000000000..732b48ac3f --- /dev/null +++ b/pandatool/src/stitchbase/layeredImage.h @@ -0,0 +1,120 @@ +// Filename: layeredImage.h +// Created by: drose (29Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef LAYEREDIMAGE_H +#define LAYEREDIMAGE_H + +#include + +#include +#include + +#include + +//#include + +class PNMImage; + +class LayeredImage { +public: + typedef char int8_t; + typedef long int32_t; + + LayeredImage(int xsize, int ysize); + ~LayeredImage(); + + void add_layer(const string &name, const LVector2d &offset, + PNMImage *data); + + bool write_file(const Filename &filename); + + bool write_xcf(ostream &out); + +private: + // XCF property types. From Gimp's xcf.c. + enum PropType { + PROP_END = 0, + PROP_COLORMAP = 1, + PROP_ACTIVE_LAYER = 2, + PROP_ACTIVE_CHANNEL = 3, + PROP_SELECTION = 4, + PROP_FLOATING_SELECTION = 5, + PROP_OPACITY = 6, + PROP_MODE = 7, + PROP_VISIBLE = 8, + PROP_LINKED = 9, + PROP_PRESERVE_TRANSPARENCY = 10, + PROP_APPLY_MASK = 11, + PROP_EDIT_MASK = 12, + PROP_SHOW_MASK = 13, + PROP_SHOW_MASKED = 14, + PROP_OFFSETS = 15, + PROP_COLOR = 16, + PROP_COMPRESSION = 17, + PROP_GUIDES = 18 + }; + + class Layer { + public: + bool trim(); + + string _name; + LVector2d _offset; + PNMImage *_data; + }; + + class TileManager { + public: + TileManager(const PNMImage *image, int channel); + int get_nlevels() const; + int get_level_width(int level) const; + int get_level_height(int level) const; + int get_ntiles(int level) const; + int get_tile_left(int level, int tile) const; + int get_tile_top(int level, int tile) const; + int get_tile_width(int level, int tile) const; + int get_tile_height(int level, int tile) const; + + const PNMImage *_data; + int _channel; + + private: + class Level { + public: + int _width; + int _height; + int _ntile_rows; + int _ntile_cols; + }; + typedef vector Levels; + Levels _levels; + }; + + int xcf_write_int8(const int8_t *data, int num); + int xcf_write_int32(const int32_t *data, int num); + int xcf_write_string(const string &str); + void xcf_save_image_props(); + void xcf_save_layer_props(const Layer &layer); + void xcf_save_channel_props(); + void xcf_save_prop(PropType prop_type, ...); + void xcf_save_layer(const Layer &layer); + void xcf_save_channel(const string &name, const PNMImage *image, + int channel); + void xcf_save_hierarchy(const PNMImage *image, int channel); + void xcf_save_level(const TileManager &tm, int level); + void xcf_save_tile(const TileManager &tm, int level, int tile); + void xcf_seek_pos(int to_pos); + void xcf_seek_end(); + + typedef vector Layers; + Layers _layers; + int _xsize; + int _ysize; + + ostream *_out; + int _pos; +}; + +#endif diff --git a/pandatool/src/stitchbase/morphGrid.cxx b/pandatool/src/stitchbase/morphGrid.cxx new file mode 100644 index 0000000000..4d920950cf --- /dev/null +++ b/pandatool/src/stitchbase/morphGrid.cxx @@ -0,0 +1,471 @@ +// Filename: morphGrid.cxx +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "morphGrid.h" +#include "triangle.h" + +#include + +#include + +MorphGrid::Vertex:: +Vertex(const LPoint2d &p) { + for (int i = 0; i < (int)TT_num; i++) { + _p[i] = p; + } + _alpha = 1.0; + _over_another = false; + + // -1 on the distance counter is a flag that the value hasn't yet + // been computed. + _dist_from_interior = -1; +} + +MorphGrid::Triangle:: +Triangle(Vertex *v0, Vertex *v1, Vertex *v2) { + _v[0] = v0; + _v[1] = v1; + _v[2] = v2; +} + +bool MorphGrid::Triangle:: +contains_point(const LPoint2d &p, TableType from) const { + if ((p[0] < _min_p[from][0] || p[0] > _max_p[from][0]) || + (p[1] < _min_p[from][1] || p[1] > _max_p[from][1])) { + // Doesn't pass the minmax test. + return false; + } + + return triangle_contains_point(p, _v[0]->_p[from], _v[1]->_p[from], + _v[2]->_p[from]); +} + +LPoint2d MorphGrid::Triangle:: +morph_point(const LPoint2d &p, TableType from, TableType to) const { + return (p * _inv[from]) * _mat[to]; +} + +double MorphGrid::Triangle:: +get_alpha(const LPoint2d &p, TableType from) const { + LPoint2d q = p * _inv[from]; + + // Now q is a point in a right triangle, where (0,1) is v0, (0,0) is + // v1, and (1,0) is v2. Interpolate the appropriate alpha value + // based on this coordinate system. + + double alpha01 = (_v[0]->_alpha + q[1] * (_v[1]->_alpha - _v[0]->_alpha)); + return (alpha01 + q[0] * (_v[2]->_alpha - alpha01)); +} + +void MorphGrid::Triangle:: +recompute() { + for (int i = 0; i < (int)TT_num; i++) { + for (int a = 0; a < 2; a++) { + _min_p[i][a] = min(min(_v[0]->_p[i][a], _v[1]->_p[i][a]), + _v[2]->_p[i][a]); + _max_p[i][a] = max(max(_v[0]->_p[i][a], _v[1]->_p[i][a]), + _v[2]->_p[i][a]); + } + + LPoint2d origin = _v[1]->_p[i]; + LVector2d yaxis = _v[0]->_p[i] - origin; + LVector2d xaxis = _v[2]->_p[i] - origin; + + _mat[i] = LMatrix3d(xaxis[0], xaxis[1], 0.0, + yaxis[0], yaxis[1], 0.0, + origin[0], origin[1], 1.0); + + _inv[i] = invert(_mat[i]); + } +} + +MorphGrid::TriangleTree:: +TriangleTree(Triangle *a, Triangle *b) { + _has_tris = true; + _u._tri[0] = a; + _u._tri[1] = b; +} + +MorphGrid::TriangleTree:: +TriangleTree(TriangleTree *a, TriangleTree *b) { + _has_tris = false; + _u._tree[0] = a; + _u._tree[1] = b; +} + +MorphGrid::TriangleTree:: +~TriangleTree() { + if (!_has_tris) { + delete _u._tree[0]; + delete _u._tree[1]; + } +} + +void MorphGrid::TriangleTree:: +recompute() { + if (_has_tris) { + _u._tri[0]->recompute(); + _u._tri[1]->recompute(); + + for (int i = 0; i < (int)TT_num; i++) { + for (int a = 0; a < 2; a++) { + _min_p[i][a] = + min(_u._tri[0]->_min_p[i][a], _u._tri[1]->_min_p[i][a]); + _max_p[i][a] = + max(_u._tri[0]->_max_p[i][a], _u._tri[1]->_max_p[i][a]); + } + } + } else { + _u._tree[0]->recompute(); + _u._tree[1]->recompute(); + + for (int i = 0; i < (int)TT_num; i++) { + for (int a = 0; a < 2; a++) { + _min_p[i][a] = + min(_u._tree[0]->_min_p[i][a], _u._tree[1]->_min_p[i][a]); + _max_p[i][a] = + max(_u._tree[0]->_max_p[i][a], _u._tree[1]->_max_p[i][a]); + } + } + } +} + +MorphGrid::Triangle *MorphGrid::TriangleTree:: +find_triangle(const LPoint2d &p, TableType from) const { + if ((p[0] < _min_p[from][0] || p[0] > _max_p[from][0]) || + (p[1] < _min_p[from][1] || p[1] > _max_p[from][1])) { + // Doesn't pass the minmax test. + return NULL; + } + + if (_has_tris) { + if (_u._tri[0]->contains_point(p, from)) { + return _u._tri[0]; + } + if (_u._tri[1]->contains_point(p, from)) { + return _u._tri[1]; + } + return NULL; + } else { + Triangle *t = _u._tree[0]->find_triangle(p, from); + if (t == NULL) { + t = _u._tree[1]->find_triangle(p, from); + } + return t; + } +} + +MorphGrid:: +MorphGrid() { + _x_verts = 0; + _y_verts = 0; + _last_triangle = NULL; + _tree = NULL; +} + +MorphGrid:: +~MorphGrid() { + if (_tree != NULL) { + delete _tree; + } +} + +bool MorphGrid:: +is_empty() const { + return _x_verts <= 0 || _y_verts <= 0; +} + +void MorphGrid:: +clear() { + init(0, 0); +} + +void MorphGrid:: +init(int x_verts, int y_verts) { + _x_verts = x_verts; + _y_verts = y_verts; + + if (_tree != NULL) { + delete _tree; + _tree = NULL; + } + _triangles.clear(); + _last_triangle = NULL; + _table.clear(); + + if (is_empty()) { + return; + } + + // Create a 2-d table of vertices. + _table.reserve(_y_verts); + int x, y; + for (y = 0; y < _y_verts; y++) { + _table.push_back(Row()); + _table[y].clear(); + _table[y].reserve(_x_verts); + for (x = 0; x < _x_verts; x++) { + LPoint2d p((double)x / (double)(_x_verts - 1), + 1.0 - (double)y / (double)(_y_verts - 1)); + _table[y].push_back(Vertex(p)); + } + } + + // Now create a bunch of triangles for these vertices. + int num_tris = (_y_verts - 1) * (_x_verts - 1) * 2; + + _triangles.reserve(num_tris); + for (y = 0; y + 1 < _y_verts; y++) { + for (x = 0; x + 1 < _x_verts; x++) { + _triangles.push_back(Triangle(&_table[y][x], + &_table[y + 1][x], + &_table[y + 1][x + 1])); + _triangles.push_back(Triangle(&_table[y][x], + &_table[y + 1][x + 1], + &_table[y][x + 1])); + } + } + assert(_triangles.size() == num_tris); + + // Now create a 2-d table of TriangleTree nodes, each of which + // points to a pair of triangles. We'll use this to build up the + // TriangleTree structure. + typedef vector TRow; + typedef vector TTable; + + TTable tree; + + int x_tree = _x_verts - 1; + int y_tree = _y_verts - 1; + tree.reserve(y_tree); + int i = 0; + for (y = 0; y < y_tree; y++) { + tree.push_back(TRow()); + tree[y].clear(); + tree[y].reserve(x_tree); + for (x = 0; x < x_tree; x++) { + tree[y].push_back(new TriangleTree(&_triangles[i], + &_triangles[i + 1])); + i += 2; + } + } + assert(i == num_tris); + + // Now repeatedly pair up adjacent TriangleTree nodes, each time + // making a new level with half the number of nodes, until we end up + // with a single node. + while (x_tree > 1 || y_tree > 1) { + // Collapse horizontal pairs. + int tx = 0; + for (int y = 0; y < y_tree; y++) { + tx = 0; + int fx = 0; + while (fx + 1 < x_tree) { + tree[y][tx++] = new TriangleTree(tree[y][fx], tree[y][fx + 1]); + fx += 2; + } + if (fx < x_tree) { + // One more odd element remaining, just copy it up. + tree[y][tx++] = tree[y][fx]; + fx++; + } + assert(fx == x_tree); + } + x_tree = tx; + + // Collapse vertical pairs. + int ty = 0; + for (int x = 0; x < x_tree; x++) { + ty = 0; + int fy = 0; + while (fy + 1 < y_tree) { + tree[ty++][x] = new TriangleTree(tree[fy][x], tree[fy + 1][x]); + fy += 2; + } + if (fy < y_tree) { + // One more odd element remaining, just copy it up. + tree[ty++][x] = tree[fy][x]; + fy++; + } + assert(fy == y_tree); + } + y_tree = ty; + } + + assert(x_tree == 1 && y_tree == 1); + _tree = tree[0][0]; +} + +void MorphGrid:: +recompute() { + _tree->recompute(); +} + +void MorphGrid:: +fill_alpha() { + // The stitcher has already made a distinction between interior + // points (that is, points which are over no other image, and must + // be 100% opaque) and exterior points (points which lay over + // another image, and should be feathered). We now need to + // determine the distance each exterior point is from this + // interior/exterior dividing line. + + // To do this, we first find an interior point. + bool found_interior = false; + int x, y; + for (y = 0; y < _y_verts && !found_interior; y++) { + for (x = 0; x < _x_verts && !found_interior; x++) { + if (!_table[y][x]._over_another) { + // Here's one! + found_interior = true; + count_dist_from_interior(x, y, 0); + } + } + } + + if (!found_interior) { + // There are no interior points in this image--it entirely covers + // other images. (Doesn't seem to be much point to it, does + // there?) We'll just feather the edges a little. + for (y = 0; y < _y_verts; y++) { + _table[y][0]._alpha = 0.0; + _table[y][_x_verts - 1]._alpha = 0.0; + } + for (x = 0; x < _x_verts; x++) { + _table[0][x]._alpha = 0.0; + _table[_y_verts - 1][x]._alpha = 0.0; + } + return; + } + + // Now go back through and assign the alpha based on the relative + // distance of each point from the edge and from the interior. + for (y = 0; y < _y_verts; y++) { + for (x = 0; x < _x_verts; x++) { + if (!_table[y][x]._over_another) { + _table[y][x]._alpha = 1.0; + + } else { + int dist_from_edge = + min(min(x, y), + min(_x_verts - 1 - x, _y_verts - 1 - y)); + + assert(_table[y][x]._dist_from_interior >= 0); + + // We subtract one from dist_from_interior to give us a bit of + // comfort zone around the interior edge--we're not precisely + // sure where the actual edge is. + int dist_from_interior = + max(_table[y][x]._dist_from_interior - 1, 0); + + // Now if dist_from_edge is 0, it must be transparent; if + // dist_from_interior is 0, it must be opaque. Any other + // combination should be some value in between. + if (dist_from_interior == 0) { + _table[y][x]._alpha = 1.0; + + } else if (dist_from_edge == 0) { + _table[y][x]._alpha = 0.0; + + } else { + double ratio = (double)dist_from_interior / + (double)(dist_from_interior + dist_from_edge); + + _table[y][x]._alpha = (cos(ratio * MathNumbers::pi) + 1.0) / 2.0; + } + } + } + } +} + +LPoint2d MorphGrid:: +morph_point(const LPoint2d &p, TableType from, TableType to) { + if (is_empty()) { + return p; + } + + if (_last_triangle != NULL) { + // First, check to see if the point is within the same triangle as + // the last point was. This will save a bit of time if it is. + if (_last_triangle->contains_point(p, from)) { + return _last_triangle->morph_point(p, from, to); + } + } + + // Nope, we just blew cache. We'll have to look for the containing + // triangle the hard way. + assert(_tree != NULL); + _last_triangle = _tree->find_triangle(p, from); + + if (_last_triangle == NULL) { + return p; + } else { + return _last_triangle->morph_point(p, from, to); + } +} + +double MorphGrid:: +get_alpha(const LPoint2d &p, TableType from) { + if (is_empty()) { + return 1.0; + } + + if (_last_triangle != NULL) { + // First, check to see if the point is within the same triangle as + // the last point was. This will save a bit of time if it is. + if (_last_triangle->contains_point(p, from)) { + return _last_triangle->get_alpha(p, from); + } + } + + // Nope, we just blew cache. We'll have to look for the containing + // triangle the hard way. + assert(_tree != NULL); + _last_triangle = _tree->find_triangle(p, from); + + if (_last_triangle == NULL) { + return 1.0; + } else { + return _last_triangle->get_alpha(p, from); + } +} + + +LPoint2d MorphGrid:: +morph_in(const LPoint2d &p) const { + return ((MorphGrid *)this)->morph_point(p, TT_out, TT_in); +} + +LPoint2d MorphGrid:: +morph_out(const LPoint2d &p) const { + return ((MorphGrid *)this)->morph_point(p, TT_in, TT_out); +} + +double MorphGrid:: +get_alpha(const LPoint2d &p) const { + return ((MorphGrid *)this)->get_alpha(p, TT_in); +} + +void MorphGrid:: +count_dist_from_interior(int x, int y, int dist) { + if (x >= 0 && x < _x_verts && + y >= 0 && y < _y_verts) { + Vertex &v = _table[y][x]; + if (!v._over_another) { + // Here we are in the interior. + dist = 0; + } + + if (v._dist_from_interior < 0 || dist < v._dist_from_interior) { + // Update this point, and recurse to our neighbors. + v._dist_from_interior = dist; + + count_dist_from_interior(x + 1, y, dist + 1); + count_dist_from_interior(x - 1, y, dist + 1); + count_dist_from_interior(x, y + 1, dist + 1); + count_dist_from_interior(x, y - 1, dist + 1); + } + } +} diff --git a/pandatool/src/stitchbase/morphGrid.h b/pandatool/src/stitchbase/morphGrid.h new file mode 100644 index 0000000000..180ca3b199 --- /dev/null +++ b/pandatool/src/stitchbase/morphGrid.h @@ -0,0 +1,100 @@ +// Filename: morphGrid.h +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef MORPHGRID_H +#define MORPHGRID_H + +#include + +class MorphGrid { +public: + MorphGrid(); + ~MorphGrid(); + + enum TableType { + TT_in = 0, + TT_out = 1, + TT_num = 2 + }; + + bool is_empty() const; + void clear(); + + void init(int x_verts, int y_verts); + void recompute(); + void fill_alpha(); + + LPoint2d morph_point(const LPoint2d &p, TableType from, TableType to); + double get_alpha(const LPoint2d &p, TableType from); + + LPoint2d morph_in(const LPoint2d &p) const; + LPoint2d morph_out(const LPoint2d &p) const; + double get_alpha(const LPoint2d &p) const; + +private: + class Triangle; +public: + + class Vertex { + public: + Vertex(const LPoint2d &p); + + LPoint2d _p[TT_num]; // TT_in, TT_out + + // These members are used to feather the edges of the images where + // they overlap other images. Once the Stitcher sets the + // _over_another flags appropriately, MorphGrid::fill_alpha() will + // assign the alpha values to feather the edges. + double _alpha; + bool _over_another; + int _dist_from_interior; + }; + + int _x_verts, _y_verts; + typedef vector Row; + typedef vector Table; + Table _table; + +private: + void count_dist_from_interior(int x, int y, int dist); + + class Triangle { + public: + Triangle(Vertex *v0, Vertex *v1, Vertex *v2); + bool contains_point(const LPoint2d &p, TableType from) const; + LPoint2d morph_point(const LPoint2d &p, TableType from, TableType to) const; + double get_alpha(const LPoint2d &p, TableType from) const; + void recompute(); + + Vertex *_v[3]; + LPoint2d _min_p[TT_num], _max_p[TT_num]; + LMatrix3d _mat[TT_num], _inv[TT_num]; + }; + + typedef vector Triangles; + Triangles _triangles; + Triangle *_last_triangle; + + class TriangleTree { + public: + TriangleTree(Triangle *a, Triangle *b); + TriangleTree(TriangleTree *a, TriangleTree *b); + ~TriangleTree(); + void recompute(); + Triangle *find_triangle(const LPoint2d &p, TableType from) const; + + bool _has_tris; + union { + Triangle *_tri[2]; + TriangleTree *_tree[2]; + } _u; + + LPoint2d _min_p[TT_num], _max_p[TT_num]; + }; + + TriangleTree *_tree; +}; + +#endif diff --git a/pandatool/src/stitchbase/stitchCommand.cxx b/pandatool/src/stitchbase/stitchCommand.cxx new file mode 100644 index 0000000000..6b2666ea84 --- /dev/null +++ b/pandatool/src/stitchbase/stitchCommand.cxx @@ -0,0 +1,624 @@ +// Filename: stitchCommand.cxx +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchCommand.h" +#include "stitchImage.h" +#include "stitchLens.h" +#include "stitchPerspectiveLens.h" +#include "stitchFisheyeLens.h" +#include "stitchCylindricalLens.h" +#include "stitchPSphereLens.h" +#include "stitchImageOutputter.h" +#include "stitcher.h" + +#include +#include + +ostream & +operator << (ostream &out, StitchCommand::Command c) { + switch (c) { + case StitchCommand::C_global: + return out << "global"; + break; + + case StitchCommand::C_define: + return out << "define"; + break; + + case StitchCommand::C_lens: + return out << "lens"; + break; + + case StitchCommand::C_input_image: + return out << "input_image"; + break; + + case StitchCommand::C_output_image: + return out << "output_image"; + break; + + case StitchCommand::C_perspective: + return out << "perspective"; + break; + + case StitchCommand::C_fisheye: + return out << "fisheye"; + break; + + case StitchCommand::C_cylindrical: + return out << "cylindrical"; + break; + + case StitchCommand::C_psphere: + return out << "psphere"; + break; + + case StitchCommand::C_focal_length: + return out << "focal_length"; + break; + + case StitchCommand::C_fov: + return out << "fov"; + break; + + case StitchCommand::C_singularity_tolerance: + return out << "singularity_tolerance"; + break; + + case StitchCommand::C_resolution: + return out << "resolution"; + break; + + case StitchCommand::C_filename: + return out << "filename"; + break; + + case StitchCommand::C_point2d: + case StitchCommand::C_point3d: + return out << "point"; + break; + + case StitchCommand::C_show_points: + return out << "show_points"; + break; + + case StitchCommand::C_image_size: + return out << "image_size"; + break; + + case StitchCommand::C_film_size: + return out << "film_size"; + break; + + case StitchCommand::C_grid: + return out << "grid"; + break; + + case StitchCommand::C_untextured_color: + return out << "untextured_color"; + break; + + case StitchCommand::C_hpr: + return out << "hpr"; + break; + + case StitchCommand::C_layers: + return out << "layers"; + break; + + case StitchCommand::C_stitch: + return out << "stitch"; + break; + + case StitchCommand::C_using: + return out << "using"; + break; + + case StitchCommand::C_user_command: + return out << "user_command"; + break; + + default: + return out << "(**unknown command**)"; + } +} + +StitchCommand:: +StitchCommand(StitchCommand *parent, StitchCommand::Command command) : + _parent(parent), + _command(command) +{ + _params = 0; + _lens = NULL; + if (parent != NULL) { + parent->add_nested(this); + } +} + +StitchCommand:: +~StitchCommand() { + Commands::iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + delete (*ci); + } +} + +void StitchCommand:: +clear() { + Commands::iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + delete (*ci); + } + _nested.clear(); + _using.clear(); + _params = 0; + _command = C_global; +} + +void StitchCommand:: +set_name(const string &name) { + if (!name.empty()) { + _params |= P_name; + _name = name; + } +} + +void StitchCommand:: +set_length(double number) { + _params |= P_length; + _number = number; +} + +void StitchCommand:: +set_resolution(double number) { + _params |= P_resolution; + _number = number; +} + +void StitchCommand:: +set_number(double number) { + _params |= P_number; + _number = number; +} + +void StitchCommand:: +set_point2d(const LVecBase2d &point) { + _params |= P_point2d; + _n[0] = point[0]; + _n[1] = point[1]; +} + +void StitchCommand:: +set_point3d(const LVecBase3d &point) { + _params |= P_point3d; + _n[0] = point[0]; + _n[1] = point[1]; + _n[2] = point[2]; +} + +void StitchCommand:: +set_length_pair(const LVecBase2d &length_pair) { + _params |= P_length_pair; + _n[0] = length_pair[0]; + _n[1] = length_pair[1]; +} + +void StitchCommand:: +set_color(const Colord &color) { + _params |= P_color; + _n[0] = color[0]; + _n[1] = color[1]; + _n[2] = color[2]; + _n[3] = color[3]; +} + +void StitchCommand:: +set_str(const string &str) { + _params |= P_str; + _str = str; +} + +bool StitchCommand:: +add_using(const string &name) { + StitchCommand *def = find_definition(name); + if (def != NULL) { + _params |= P_using; + _using.push_back(def); + return true; + } + return false; +} + +void StitchCommand:: +add_nested(StitchCommand *nested) { + _params |= P_nested; + _nested.push_back(nested); +} + +string StitchCommand:: +get_name() const { + return _name; +} + +double StitchCommand:: +get_number() const { + return _number; +} + +LVecBase2d StitchCommand:: +get_point2d() const { + return LVecBase2d(_n[0], _n[1]); +} + +LVecBase3d StitchCommand:: +get_point3d() const { + return LVecBase3d(_n[0], _n[1], _n[2]); +} + +LVector3d StitchCommand:: +get_vector3d() const { + return LVector3d(_n[0], _n[1], _n[2]); +} + +Colord StitchCommand:: +get_color() const { + return Colord(_n[0], _n[1], _n[2], _n[3]); +} + +string StitchCommand:: +get_str() const { + return _str; +} + +void StitchCommand:: +process(StitchImageOutputter &outputter, Stitcher *stitcher, + StitchFile &file) { + if (_command == C_input_image) { + StitchImage *image = create_image(); + + if (stitcher != NULL) { + stitcher->add_image(image); + } else { + outputter.add_input_image(image); + } + + } else if (_command == C_output_image) { + StitchImage *image = create_image(); + outputter.add_output_image(image); + + } else if (_command == C_stitch) { + Stitcher *new_stitcher = new Stitcher; + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + (*ci)->process(outputter, new_stitcher, file); + } + new_stitcher->stitch(); + + // Now add all of the stitched images to the outputter, in order. + Stitcher::Images::const_iterator ii; + for (ii = new_stitcher->_placed.begin(); + ii != new_stitcher->_placed.end(); + ++ii) { + outputter.add_input_image(*ii); + } + outputter.add_stitcher(new_stitcher); + + } else if (_command == C_point3d) { + if (stitcher != NULL) { + stitcher->add_point(_name, get_vector3d()); + } + + } else if (_command == C_show_points) { + if (stitcher != NULL) { + stitcher->show_points(get_number(), get_color()); + } + + } else if (_params & P_nested) { + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + (*ci)->process(outputter, stitcher, file); + } + } +} + +void StitchCommand:: +write(ostream &out, int indent_level) const { + if (_command == C_user_command) { + assert(_using.size() == 1); + indent(out, indent_level) << _using.front()->_name << ";\n"; + + } else if (_command == C_global) { + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + (*ci)->write(out, indent_level ); + } + + } else { + indent(out, indent_level) << _command; + if (_params & P_name) { + out << " " << _name; + } + if (_params & P_length) { + out << " " << get_number() << "mm"; + } + if (_params & P_resolution) { + out << " " << get_number() << "p/mm"; + } + if (_params & P_number) { + out << " " << get_number(); + } + if (_params & P_point2d) { + out << " (" << get_point2d() << ")"; + } + if (_params & P_point3d) { + out << " (" << get_point3d() << ")"; + } + if (_params & P_length_pair) { + out << " (" << _n[0] << "mm " << _n[1] << "mm)"; + } + if (_params & P_color) { + out << " (" << get_color() << ")"; + } + if (_params & P_str) { + out << " \"" << _str << "\""; + } + if (_params & P_using) { + Commands::const_iterator ci; + ci = _using.begin(); + if (ci != _using.end()) { + out << " " << (*ci)->_name; + ++ci; + while (ci != _using.end()) { + out << ", " << (*ci)->_name; + ++ci; + } + } + } + if (_params & P_nested) { + out << " {\n"; + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + (*ci)->write(out, indent_level + 2); + } + indent(out, indent_level) << "}\n"; + } else { + out << ";\n"; + } + } +} + + + +StitchCommand *StitchCommand:: +find_definition(const string &name) { + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + if (((*ci)->_command == C_define || (*ci)->_command == C_lens) && + (*ci)->_name == name) { + return (*ci); + } + } + if (_parent != NULL) { + return _parent->find_definition(name); + } + return NULL; +} + +StitchLens *StitchCommand:: +find_using_lens() { + if (!_using.empty()) { + Commands::const_iterator ci; + for (ci = _using.begin(); ci != _using.end(); ++ci) { + StitchLens *lens = (*ci)->find_lens(); + if (lens != NULL) { + return lens; + } + } + } + if (_parent != NULL) { + return _parent->find_using_lens(); + } + return NULL; +} + +StitchLens *StitchCommand:: +find_lens() { + if (_command == C_lens) { + return make_lens(); + } + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + if ((*ci)->_command == C_lens) { + return (*ci)->make_lens(); + } + } + if (_parent != NULL) { + return _parent->find_using_lens(); + } + return NULL; +} + +StitchLens *StitchCommand:: +make_lens() { + if (_lens != NULL) { + return _lens; + } + + if (find_command(C_fisheye) != NULL) { + _lens = new StitchFisheyeLens(); + } else if (find_command(C_cylindrical) != NULL) { + _lens = new StitchCylindricalLens(); + } else if (find_command(C_psphere) != NULL) { + _lens = new StitchPSphereLens(); + } else { + _lens = new StitchPerspectiveLens(); + } + + StitchCommand *cmd = find_command(C_focal_length); + if (cmd != NULL) { + _lens->set_focal_length(cmd->get_number()); + } + cmd = find_command(C_fov); + if (cmd != NULL) { + _lens->set_hfov(cmd->get_number()); + } + + if (!_lens->is_defined()) { + _lens->set_hfov(60.0); + } + + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + switch ((*ci)->_command) { + case C_singularity_tolerance: + _lens->set_singularity_tolerance((*ci)->get_number()); + break; + } + } + + return _lens; +} + + +StitchCommand *StitchCommand:: +find_using_command(Command command) { + if (!_using.empty()) { + Commands::const_iterator ci; + for (ci = _using.begin(); ci != _using.end(); ++ci) { + StitchCommand *cmd = (*ci)->find_command(command); + if (cmd != NULL) { + return cmd; + } + } + } + + if (_parent != NULL) { + return _parent->find_using_command(command); + } + + return NULL; +} + +StitchCommand *StitchCommand:: +find_command(Command command) { + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + if ((*ci)->_command == command) { + return (*ci); + } + } + + if (_parent != NULL) { + return _parent->find_using_command(command); + } + + return NULL; +} + +string StitchCommand:: +find_parameter(Command command, const string &dflt) { + StitchCommand *cmd = find_command(command); + if (cmd != NULL) { + return cmd->_str; + } else { + return dflt; + } +} + +double StitchCommand:: +find_parameter(Command command, double dflt) { + StitchCommand *cmd = find_command(command); + if (cmd != NULL) { + return cmd->get_number(); + } else { + return dflt; + } +} + +LVecBase2d StitchCommand:: +find_parameter(Command command, const LVecBase2d &dflt) { + StitchCommand *cmd = find_command(command); + if (cmd != NULL) { + return cmd->get_point2d(); + } else { + return dflt; + } +} + + +StitchImage *StitchCommand:: +create_image() { + string filename = find_parameter(C_filename, ""); + LVecBase2d size_pixels(256, 256); + LVecBase2d resolution(72.0 / 25.4, 72.0 / 25.4); + StitchLens *lens = find_lens(); + if (lens == NULL) { + nout << "Warning: No lens defined for " << filename << "\n"; + lens = make_lens(); + } + + StitchCommand *cmd; + cmd = find_command(C_image_size); + if (cmd != NULL) { + size_pixels = cmd->get_point2d(); + + } else if (!filename.empty()) { + // If we don't get an explicit image size, try to determine it + // from the image file. + PNMImageHeader header; + if (header.read_header(filename)) { + size_pixels.set(header.get_x_size(), header.get_y_size()); + } + } + + cmd = find_command(C_film_size); + if (cmd != NULL) { + LVecBase2d size_mm = cmd->get_point2d(); + resolution.set((size_pixels[0]-1) / size_mm[0], + (size_pixels[1]-1) / size_mm[1]); + } else { + cmd = find_command(C_resolution); + if (cmd != NULL) { + resolution.set(cmd->get_number(), cmd->get_number()); + } + } + + StitchImage *image = + new StitchImage(get_name(), filename, lens, size_pixels, resolution); + image->setup_grid(50, 50); + + // Also look for points and other stuff. + Commands::const_iterator ci; + for (ci = _nested.begin(); ci != _nested.end(); ++ci) { + switch ((*ci)->_command) { + case C_point2d: + image->add_point((*ci)->_name, (*ci)->get_point2d()); + break; + + case C_show_points: + image->show_points((*ci)->get_number(), (*ci)->get_color()); + break; + + case C_untextured_color: + image->_untextured_color = (*ci)->get_color(); + break; + + case C_hpr: + image->set_hpr((*ci)->get_point3d()); + break; + + case C_layers: + image->_layered_type = StitchImage::LT_separate; + break; + + case C_grid: + image->setup_grid((int)(*ci)->_n[0], (int)(*ci)->_n[1]); + break; + } + } + + return image; +} + diff --git a/pandatool/src/stitchbase/stitchCommand.h b/pandatool/src/stitchbase/stitchCommand.h new file mode 100644 index 0000000000..fbdfad9703 --- /dev/null +++ b/pandatool/src/stitchbase/stitchCommand.h @@ -0,0 +1,141 @@ +// Filename: stitchCommand.h +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHCOMMAND_H +#define STITCHCOMMAND_H + +#include + +#include + +#include +#include + +class StitchLens; +class StitchImage; +class StitchImageOutputter; +class StitchFile; +class Stitcher; + +class StitchCommand { +public: + enum Command { + C_global, + C_define, + C_lens, + C_input_image, + C_output_image, + C_perspective, + C_fisheye, + C_cylindrical, + C_psphere, + C_focal_length, + C_fov, + C_singularity_tolerance, + C_resolution, + C_filename, + C_point2d, + C_point3d, + C_show_points, + C_image_size, + C_film_size, + C_grid, + C_untextured_color, + C_hpr, + C_layers, + C_stitch, + C_using, + C_user_command, + }; + + StitchCommand(StitchCommand *parent = NULL, + Command command = C_global); + ~StitchCommand(); + + void clear(); + + void set_name(const string &name); + void set_length(double number); + void set_resolution(double number); + void set_number(double number); + void set_point2d(const LVecBase2d &point); + void set_point3d(const LVecBase3d &point); + void set_length_pair(const LVecBase2d &point); + void set_color(const Colord &color); + void set_str(const string &str); + bool add_using(const string &name); + void add_nested(StitchCommand *nested); + + string get_name() const; + double get_number() const; + LVecBase2d get_point2d() const; + LVecBase3d get_point3d() const; + LVector3d get_vector3d() const; + Colord get_color() const; + string get_str() const; + + StitchCommand *find_definition(const string &name); + + void process(StitchImageOutputter &outputter, Stitcher *stitcher, + StitchFile &file); + + void write(ostream &out, int indent) const; + + +private: + StitchLens *find_using_lens(); + StitchLens *find_lens(); + StitchLens *make_lens(); + + StitchCommand *find_using_command(Command command); + StitchCommand *find_command(Command command); + string find_parameter(Command command, const string &dflt); + double find_parameter(Command command, double dflt); + LVecBase2d find_parameter(Command command, const LVecBase2d &dflt); + + StitchImage *create_image(); + + + StitchCommand *_parent; + Command _command; + + enum Parameters { + P_name = 0x001, + P_length = 0x002, + P_resolution = 0x004, + P_number = 0x008, + P_point2d = 0x010, + P_point3d = 0x020, + P_length_pair = 0x040, + P_color = 0x080, + P_str = 0x100, + P_using = 0x200, + P_nested = 0x400 + }; + + int _params; + + string _name; + double _number; + double _n[4]; + string _str; + + // This will only get filled in by make_lens(). + StitchLens *_lens; + + typedef vector Commands; + Commands _using; + Commands _nested; +}; + +inline ostream &operator << (ostream &out, const StitchCommand &c) { + c.write(out, 0); + return out; +} + +ostream &operator << (ostream &out, StitchCommand::Command c); + + +#endif diff --git a/pandatool/src/stitchbase/stitchCommandReader.cxx b/pandatool/src/stitchbase/stitchCommandReader.cxx new file mode 100644 index 0000000000..f8e68d1c40 --- /dev/null +++ b/pandatool/src/stitchbase/stitchCommandReader.cxx @@ -0,0 +1,39 @@ +// Filename: stitchCommandReader.cxx +// Created by: drose (16Mar00) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchCommandReader.h" + +//////////////////////////////////////////////////////////////////// +// Function: StitchCommandReader::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +StitchCommandReader:: +StitchCommandReader() { + clear_runlines(); + add_runline("[opts] input.st"); +} + + +//////////////////////////////////////////////////////////////////// +// Function: StitchCommandReader::handle_args +// Access: Protected, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool StitchCommandReader:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the stitch command file to read on the\n" + << "command line.\n"; + return false; + } + if (args.size() > 1) { + nout << "You must specify only one stitch command file to read on the\n" + << "command line.\n"; + return false; + } + + return _command_file.read(args[0]); +} diff --git a/pandatool/src/stitchbase/stitchCommandReader.h b/pandatool/src/stitchbase/stitchCommandReader.h new file mode 100644 index 0000000000..0ca7ac98c1 --- /dev/null +++ b/pandatool/src/stitchbase/stitchCommandReader.h @@ -0,0 +1,34 @@ +// Filename: stitchCommandReader.h +// Created by: drose (16Mar00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHCOMMANDREADER_H +#define STITCHCOMMANDREADER_H + +#include + +#include "stitchFile.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : StitchCommandReader +// Description : This specialization of ProgramBase is intended for +// programs in this directory that read and process a +// stitch command file. +////////////////////////////////////////////////////////////////////// +class StitchCommandReader : public ProgramBase { +public: + StitchCommandReader(); + +protected: + virtual bool handle_args(Args &args); + +protected: + StitchFile _command_file; +}; + +#endif + + diff --git a/pandatool/src/stitchbase/stitchCylindricalLens.cxx b/pandatool/src/stitchbase/stitchCylindricalLens.cxx new file mode 100644 index 0000000000..9b9b879381 --- /dev/null +++ b/pandatool/src/stitchbase/stitchCylindricalLens.cxx @@ -0,0 +1,182 @@ +// Filename: stitchCylindricalLens.cxx +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchCylindricalLens.h" +#include "stitchCommand.h" +#include "triangleRasterizer.h" + +#include +#include + +#include + +// This is the focal-length constant for fisheye lenses. See +// stitchFisheyeLens.h. +static const double k = 60.0; + +StitchCylindricalLens:: +StitchCylindricalLens() { +} + +double StitchCylindricalLens:: +get_focal_length(double width_mm) const { + if (_flags & F_focal_length) { + return _focal_length; + } + if (_flags & F_fov) { + return width_mm * k / _fov; + } + return 0.0; +} + +double StitchCylindricalLens:: +get_hfov(double width_mm) const { + if (_flags & F_fov) { + return _fov; + } + if (_flags & F_focal_length) { + return width_mm * k / _focal_length; + } + return 0.0; +} + +double StitchCylindricalLens:: +get_vfov(double height_mm) const { + return 2.0 * rad_2_deg(atan(height_mm / + (2.0 * get_focal_length(height_mm)))); +} + +LVector3d StitchCylindricalLens:: +extrude(const LPoint2d &point_mm, double width_mm) const { + LVector2d v2 = point_mm; + + double fl = get_focal_length(width_mm); + return LVector3d(sin(deg_2_rad(v2[0] * k / fl)) * fl, + cos(deg_2_rad(v2[0] * k / fl)) * fl, + v2[1]); +} + + +LPoint2d StitchCylindricalLens:: +project(const LVector3d &vec, double width_mm) const { + // A cylindrical lens is a cross between a fisheye and a normal + // lens. It is curved in the horizontal direction, and straight in + // the vertical direction. + + LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right); + + // To compute the x position on the frame, we only need to consider + // the angle of the vector about the Z axis. Project the vector + // into the XY plane to do this. + + LVector2d xy(v3[0], v3[1]); + + // The x position is the angle about the Z axis. + double x = + rad_2_deg(atan2(xy[0], xy[1])) * get_focal_length(width_mm) / k; + + // The y position is the Z height divided by the perspective + // distance. + double y = v3[2] / length(xy) * get_focal_length(width_mm); + + return LPoint2d(x, y); +} + +LPoint2d StitchCylindricalLens:: +project_left(const LVector3d &vec, double width_mm) const { + // This is just like project(), except that if the vertex extends + // below -180 degrees, it remains on the left side of the film + // (instead of wrapping around to the right side). + + LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right); + LVector2d xy(v3[0], v3[1]); + double x = + (rad_2_deg(atan2(-xy[0], -xy[1])) - 180.0) * + get_focal_length(width_mm) / k; + + double y = v3[2] / length(xy) * get_focal_length(width_mm); + return LPoint2d(x, y); +} + +LPoint2d StitchCylindricalLens:: +project_right(const LVector3d &vec, double width_mm) const { + // This is just like project(), except that if the vertex extends + // above 180 degrees, it remains on the right side of the film + // (instead of wrapping around to the left side). + + LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right); + LVector2d xy(v3[0], v3[1]); + double x = + (rad_2_deg(atan2(-xy[0], -xy[1])) + 180.0) * + get_focal_length(width_mm) / k; + + double y = v3[2] / length(xy) * get_focal_length(width_mm); + return LPoint2d(x, y); +} + +void StitchCylindricalLens:: +draw_triangle(TriangleRasterizer &rast, const LMatrix3d &mm_to_pixels, + double width_mm, const RasterizerVertex *v0, + const RasterizerVertex *v1, const RasterizerVertex *v2) { + // A cylindrical lens has a seam at 180 and -180 degrees (regardless + // of its field of view). If the triangle crosses that seam, we'll + // simply draw it twice: once at each side. + + // Determine which quadrant each of the vertices is in. The + // triangle crosses the seam if no vertices are in quadrants I and + // II, and some vertices are in quadrant III and others are in + // quadrant IV. + + LVector2d xy0(dot(v0->_space, LVector3d::right()), + dot(v0->_space, LVector3d::forward())); + LVector2d xy1(dot(v1->_space, LVector3d::right()), + dot(v1->_space, LVector3d::forward())); + LVector2d xy2(dot(v2->_space, LVector3d::right()), + dot(v2->_space, LVector3d::forward())); + + if (xy0[1] >= 0.0 || xy1[1] >= 0.0 || xy2[1] >= 0.0) { + // Some vertices are in quadrants I or II. + rast.draw_triangle(v0, v1, v2); + + } else if (xy0[0] > 0.0 && xy1[0] > 0.0 && xy2[0] > 0.0) { + // All vertices are in quadrant IV. + rast.draw_triangle(v0, v1, v2); + + } else if (xy0[0] < 0.0 && xy1[0] < 0.0 && xy2[0] < 0.0) { + // All vertices are in quadrant III. + rast.draw_triangle(v0, v1, v2); + + } else { + // The triangle crosses the seam. Draw it twice. + RasterizerVertex v0a = *v0; + RasterizerVertex v1a = *v1; + RasterizerVertex v2a = *v2; + + v0a._p = project_left(v0a._space, width_mm) * mm_to_pixels; + v1a._p = project_left(v1a._space, width_mm) * mm_to_pixels; + v2a._p = project_left(v2a._space, width_mm) * mm_to_pixels; + rast.draw_triangle(&v0a, &v1a, &v2a); + + v0a._p = project_right(v0a._space, width_mm) * mm_to_pixels; + v1a._p = project_right(v1a._space, width_mm) * mm_to_pixels; + v2a._p = project_right(v2a._space, width_mm) * mm_to_pixels; + rast.draw_triangle(&v0a, &v1a, &v2a); + } +} + +void StitchCylindricalLens:: +make_lens_command(StitchCommand *parent) { + StitchCommand *lens_cmd = new StitchCommand(parent, StitchCommand::C_lens); + StitchCommand *cmd; + cmd = new StitchCommand(lens_cmd, StitchCommand::C_cylindrical); + if (_flags & F_focal_length) { + cmd = new StitchCommand(lens_cmd, StitchCommand::C_focal_length); + cmd->set_length(_focal_length); + } + if (_flags & F_fov) { + cmd = new StitchCommand(lens_cmd, StitchCommand::C_fov); + cmd->set_number(_fov); + } +} diff --git a/pandatool/src/stitchbase/stitchCylindricalLens.h b/pandatool/src/stitchbase/stitchCylindricalLens.h new file mode 100644 index 0000000000..50c3728c24 --- /dev/null +++ b/pandatool/src/stitchbase/stitchCylindricalLens.h @@ -0,0 +1,37 @@ +// Filename: stitchCylindricalLens.h +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHCYLINDRICALLENS_H +#define STITCHCYLINDRICALLENS_H + +#include "stitchLens.h" + +class StitchCylindricalLens : public StitchLens { +public: + StitchCylindricalLens(); + + virtual double get_focal_length(double width_mm) const; + virtual double get_hfov(double width_mm) const; + virtual double get_vfov(double height_mm) const; + + virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const; + virtual LPoint2d project(const LVector3d &vec, double width_mm) const; + + LPoint2d project_left(const LVector3d &vec, double width_mm) const; + LPoint2d project_right(const LVector3d &vec, double width_mm) const; + + virtual void draw_triangle(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + double width_mm, + const RasterizerVertex *v0, + const RasterizerVertex *v1, + const RasterizerVertex *v2); + + virtual void make_lens_command(StitchCommand *parent); +}; + +#endif + + diff --git a/pandatool/src/stitchbase/stitchFile.cxx b/pandatool/src/stitchbase/stitchFile.cxx new file mode 100644 index 0000000000..830c2e48ef --- /dev/null +++ b/pandatool/src/stitchbase/stitchFile.cxx @@ -0,0 +1,49 @@ +// Filename: stitchFile.cxx +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchFile.h" +#include "stitchImage.h" +#include "stitchImageOutputter.h" +#include "stitchParserDefs.h" +#include "stitchLexerDefs.h" + +#include + +StitchFile:: +StitchFile() { +} + +StitchFile:: +~StitchFile() { +} + +bool StitchFile:: +read(const string &filename) { + _root.clear(); + + ifstream in(filename.c_str()); + if (!in) { + nout << "Unable to read " << filename << "\n"; + return false; + } + stitch_init_parser(in, filename, &_root); + stitchyyparse(); + if (stitch_error_count() != 0) { + return false; + } + + return true; +} + +void StitchFile:: +write(ostream &out) const { + _root.write(out, 0); +} + +void StitchFile:: +process(StitchImageOutputter &outputter) { + _root.process(outputter, (Stitcher *)NULL, *this); + outputter.execute(); +} diff --git a/pandatool/src/stitchbase/stitchFile.h b/pandatool/src/stitchbase/stitchFile.h new file mode 100644 index 0000000000..d446193619 --- /dev/null +++ b/pandatool/src/stitchbase/stitchFile.h @@ -0,0 +1,34 @@ +// Filename: stitchFile.h +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHFILE_H +#define STITCHFILE_H + +#include "stitchCommand.h" + +class StitchImage; +class StitchImageOutputter; + +class StitchFile { +public: + StitchFile(); + ~StitchFile(); + + bool read(const string &filename); + void write(ostream &out) const; + + void process(StitchImageOutputter &outputter); + + StitchCommand _root; +}; + +inline ostream &operator << (ostream &out, const StitchFile &f) { + f.write(out); + return out; +} + +#endif + + diff --git a/pandatool/src/stitchbase/stitchFisheyeLens.cxx b/pandatool/src/stitchbase/stitchFisheyeLens.cxx new file mode 100644 index 0000000000..b77d6014cc --- /dev/null +++ b/pandatool/src/stitchbase/stitchFisheyeLens.cxx @@ -0,0 +1,314 @@ +// Filename: stitchFisheyeLens.cxx +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchFisheyeLens.h" +#include "stitchImage.h" +#include "stitchCommand.h" +#include "triangleRasterizer.h" +#include "triangle.h" + +#include +#include + +#include + +// This is the focal-length constant for fisheye lenses. The focal +// length of a fisheye lens relates to its fov by the equation: + +// w = Fd/k + +// Where w is the width of the negative, F is the focal length, and d +// is the total field of view in degrees. + +// k is chosen here by simple examination of a couple of actual lenses +// for 35mm film. Don't know how well this extends to other lenses +// and other negative sizes. + +static const double k = 60.0; + +StitchFisheyeLens:: +StitchFisheyeLens() { +} + +double StitchFisheyeLens:: +get_focal_length(double width_mm) const { + if (_flags & F_focal_length) { + return _focal_length; + } + if (_flags & F_fov) { + return width_mm * k / _fov; + } + return 0.0; +} + +double StitchFisheyeLens:: +get_hfov(double width_mm) const { + if (_flags & F_fov) { + return _fov; + } + if (_flags & F_focal_length) { + return width_mm * k / _focal_length; + } + return 0.0; +} + +LVector3d StitchFisheyeLens:: +extrude(const LPoint2d &point_mm, double width_mm) const { + // This operation is essentially a conversion from Cartesian to + // polar coordinates. + + // First, get the vector from the center of the film to the point, + // and normalize it. + LVector2d v2 = point_mm; + + double r = length(v2); + if (r == 0.0) { + // Special case: directly forward. + return LVector3d::forward(); + } + + v2 /= r; + + // Now get the point r units around the circle in the YZ plane. + double dist = r * k / get_focal_length(width_mm); + LVector3d p(0.0, cos(deg_2_rad(dist)), sin(deg_2_rad(dist))); + + // And rotate this point around the Y axis. + LVector3d result = LVector3d::rfu(p[0]*v2[1] + p[2]*v2[0], + p[1], + p[2]*v2[1] - p[0]*v2[0]); + return result; +} + +LPoint2d StitchFisheyeLens:: +project(const LVector3d &vec, double width_mm) const { + // A fisheye lens projection has the property that the distance from + // the center point to any other point on the projection is + // proportional to the actual distance on the sphere along the great + // circle. Also, the angle to the point on the projection is equal + // to the angle to the point on the sphere. + + // First, discard the distance by normalizing the vector. + LVector3d v2 = normalize(vec * LMatrix4d::convert_mat(CS_default, + CS_zup_right)); + + // Now, project the point into the XZ plane and measure its angle + // to the Z axis. This is the same angle it will have to the + // vertical axis on the film. + LVector2d y(v2[0], v2[2]); + y = normalize(y); + + if (y == LVector2d(0.0, 0.0)) { + // Special case. This point is either directly ahead or directly + // behind. + return LPoint2d(0.0, 0.0); + } + + // Now bring the vector into the YZ plane by rotating about the Y + // axis. + LVector2d x(v2[1], v2[0]*y[0]+v2[2]*y[1]); + x = normalize(x); + + // Now the angle of x to the forward vector represents the distance + // along the great circle to the point. + double r = 90.0 - rad_2_deg(atan2(x[0], x[1])); + + return y * (r * get_focal_length(width_mm) / k); +} + +void StitchFisheyeLens:: +draw_triangle(TriangleRasterizer &rast, const LMatrix3d &, + double, const RasterizerVertex *v0, + const RasterizerVertex *v1, const RasterizerVertex *v2) { + // A fisheye lens has a singularity at 180 degrees--this point maps + // to the entire outer rim of the circle. Near this singularity, + // small distances in space map to very large distances on the film, + // meaning that our use of triangles to approximate curvature + // becomes very bad near the singularity. Furthermore, triangles + // that cross the singularity will be incorrectly drawn across the + // entire image on the film. + + // We resolve this by simply not drawing any triangles that come + // with a user-specified angle (the _singularity_tolerance) from the + // singularity point. + + + // Determine which quadrant each of the vertices is in. The + // triangle crosses the singularity if all vertices' y coordinate is + // negative, and if the projection of the triangle into the x, z + // plane intersects the origin. It comes within + // _singularity_tolerance of the singularity if the projection into + // x, z intersects a circle about the origin with radius + // _singularity_radius. + + if (dot(v0->_space, LVector3d::forward()) < 0.0 && + dot(v1->_space, LVector3d::forward()) < 0.0 && + dot(v2->_space, LVector3d::forward()) < 0.0) { + LPoint2d xz0(dot(v0->_space, LVector3d::right()), + dot(v0->_space, LVector3d::up())); + LPoint2d xz1(dot(v1->_space, LVector3d::right()), + dot(v1->_space, LVector3d::up())); + LPoint2d xz2(dot(v2->_space, LVector3d::right()), + dot(v2->_space, LVector3d::up())); + + // This projection will reverse the vertex order. + if (triangle_contains_circle(LPoint2d(0.0, 0.0), + _singularity_radius, + xz0, xz2, xz1)) { + // The triangle does cross the singularity! Reject it. + /* + nout << "Rejecting:\n" + << " " << v0->_space << "\n" + << " " << v1->_space << "\n" + << " " << v2->_space << "\n\n"; + */ + _singularity_detected = 1; + return; + } + } + + rast.draw_triangle(v0, v1, v2); +} + +void StitchFisheyeLens:: +pick_up_singularity(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + const LMatrix3d &pixels_to_mm, + const LMatrix3d &rotate, + double width_mm, StitchImage *input) { + if (_singularity_detected) { + nout << "Picking up singularity\n"; + + // We will be drawing all the pixels between the circle + // representing points 180 degrees from forward, and the circle + // represent points (180 - _singularity_tolerance * 2) degrees + // from forward. + + double outer_mm = + (180 * get_focal_length(width_mm) / k); + double inner_mm = + ((180 - _singularity_tolerance * 2) * get_focal_length(width_mm) / k); + + int xsize = rast._output->get_x_size(); + int ysize = rast._output->get_y_size(); + + LPoint2d py = LPoint2d(0.0, outer_mm) * mm_to_pixels; + int top_y = max((int)floor(py[1]), 0); + py = LPoint2d(0.0, -outer_mm) * mm_to_pixels; + int bot_y = min((int)ceil(py[1]), ysize - 1); + + py = LPoint2d(0.0, inner_mm) * mm_to_pixels; + int inner_top_y = (int)floor(py[1]); + py = LPoint2d(0.0, -inner_mm) * mm_to_pixels; + int inner_bot_y = (int)ceil(py[1]); + + RasterizerVertex v0; + v0._p.set(0.0, 0.0); + v0._uv.set(0.0, 0.0); + v0._space.set(0.0, 0.0, 0.0); + v0._alpha = 1.0; + v0._visibility = 0; + + int xi, yi; + for (yi = top_y; yi <= bot_y; yi++) { + int left_x_1, right_x_1; + int left_x_2, right_x_2; + + // Where are the left and right X pixels at this slice? + if (yi <= inner_top_y) { + // This is the top slice of the ring: between the top of the + // outer circle and the top of the inner circle. + + LPoint2d pmm = LPoint2d(0.0, yi) * pixels_to_mm; + pmm[0] = sqrt(outer_mm * outer_mm - pmm[1] * pmm[1]); + + LPoint2d px = LPoint2d(-pmm[0], pmm[1]) * mm_to_pixels; + left_x_1 = max((int)floor(px[0]), 0); + px = LPoint2d(pmm[0], pmm[1]) * mm_to_pixels; + right_x_1 = min((int)ceil(px[0]), xsize - 1); + + right_x_2 = right_x_1; + left_x_2 = right_x_2 + 1; + + } else if (yi < inner_bot_y) { + // This is the inner section: within the inner circle area. + // We have both a left and a right section here. + + LPoint2d pmm = LPoint2d(0.0, yi) * pixels_to_mm; + pmm[0] = sqrt(outer_mm * outer_mm - pmm[1] * pmm[1]); + + LPoint2d px = LPoint2d(-pmm[0], pmm[1]) * mm_to_pixels; + left_x_1 = max((int)floor(px[0]), 0); + px = LPoint2d(pmm[0], pmm[1]) * mm_to_pixels; + right_x_2 = min((int)ceil(px[0]), xsize - 1); + + pmm[0] = sqrt(inner_mm * inner_mm - pmm[1] * pmm[1]); + px = LPoint2d(-pmm[0], pmm[1]) * mm_to_pixels; + right_x_1 = max((int)floor(px[0]), 0); + px = LPoint2d(pmm[0], pmm[1]) * mm_to_pixels; + left_x_2 = min((int)ceil(px[0]), xsize - 1); + + } else { + // This is the bottom slice of the ring: between the bottom of + // the inner circle and the bottom of the outer circle. + + LPoint2d pmm = LPoint2d(0.0, yi) * pixels_to_mm; + pmm[0] = sqrt(outer_mm * outer_mm - pmm[1] * pmm[1]); + + LPoint2d px = LPoint2d(-pmm[0], pmm[1]) * mm_to_pixels; + left_x_1 = max((int)floor(px[0]), 0); + px = LPoint2d(pmm[0], pmm[1]) * mm_to_pixels; + right_x_1 = min((int)ceil(px[0]), xsize - 1); + + right_x_2 = right_x_1; + left_x_2 = right_x_2 + 1; + + } + + // Project xi point 1 to determine the radius. + v0._p.set(left_x_1 + 1, yi); + v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate; + v0._uv = input->project(v0._space); + + for (xi = left_x_1; xi <= right_x_1; xi++) { + double last_u = v0._uv[0]; + + v0._p.set(xi, yi); + v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate; + v0._uv = input->project(v0._space); + rast.draw_pixel(&v0, fabs(v0._uv[0] - last_u)); + } + + // Project xi point 1 to determine the radius. + v0._p.set(left_x_2 + 1, yi); + v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate; + v0._uv = input->project(v0._space); + + for (xi = left_x_2; xi <= right_x_2; xi++) { + double last_u = v0._uv[0]; + + v0._p.set(xi, yi); + v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate; + v0._uv = input->project(v0._space); + rast.draw_pixel(&v0, fabs(v0._uv[0] - last_u)); + } + } + } +} + +void StitchFisheyeLens:: +make_lens_command(StitchCommand *parent) { + StitchCommand *lens_cmd = new StitchCommand(parent, StitchCommand::C_lens); + StitchCommand *cmd; + cmd = new StitchCommand(lens_cmd, StitchCommand::C_fisheye); + if (_flags & F_focal_length) { + cmd = new StitchCommand(lens_cmd, StitchCommand::C_focal_length); + cmd->set_length(_focal_length); + } + if (_flags & F_fov) { + cmd = new StitchCommand(lens_cmd, StitchCommand::C_fov); + cmd->set_number(_fov); + } +} diff --git a/pandatool/src/stitchbase/stitchFisheyeLens.h b/pandatool/src/stitchbase/stitchFisheyeLens.h new file mode 100644 index 0000000000..0a3ca22fba --- /dev/null +++ b/pandatool/src/stitchbase/stitchFisheyeLens.h @@ -0,0 +1,39 @@ +// Filename: stitchFisheyeLens.h +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHFISHEYELENS_H +#define STITCHFISHEYELENS_H + +#include "stitchLens.h" + +class StitchFisheyeLens : public StitchLens { +public: + StitchFisheyeLens(); + + virtual double get_focal_length(double width_mm) const; + virtual double get_hfov(double width_mm) const; + + virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const; + virtual LPoint2d project(const LVector3d &vec, double width_mm) const; + + virtual void draw_triangle(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + double width_mm, + const RasterizerVertex *v0, + const RasterizerVertex *v1, + const RasterizerVertex *v2); + + virtual void pick_up_singularity(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + const LMatrix3d &pixels_to_mm, + const LMatrix3d &rotate, + double width_mm, + StitchImage *input); + + virtual void make_lens_command(StitchCommand *parent); +}; + +#endif + diff --git a/pandatool/src/stitchbase/stitchImage.cxx b/pandatool/src/stitchbase/stitchImage.cxx new file mode 100644 index 0000000000..543e7353ad --- /dev/null +++ b/pandatool/src/stitchbase/stitchImage.cxx @@ -0,0 +1,350 @@ +// Filename: stitchImage.cxx +// Created by: drose (04Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchImage.h" +#include "stitchLens.h" +#include "layeredImage.h" + +#include +#include + +StitchImage:: +StitchImage(const string &name, const string &filename, + StitchLens *lens, const LVecBase2d &size_pixels, + const LVecBase2d &pixels_per_mm) : + _filename(filename), + _name(name), + _lens(lens), + _size_pixels(size_pixels), + _pixels_per_mm(pixels_per_mm), + _rotate(LMatrix3d::ident_mat()), + _inv_rotate(LMatrix3d::ident_mat()) +{ + _size_mm.set((_size_pixels[0] - 1.0) / _pixels_per_mm[0], + (_size_pixels[1] - 1.0) / _pixels_per_mm[1]); + + // There are several coordinate systems to talk about points on the + // image. + + // UV's are used for doing most operations. They range from (0, 0) + // at the lower-left corner to (1, 1) at the upper-right. + + // Pixels are used when interfacing with the user. They range from + // (0, 0) at the upper-left corner to (_size_pixels[0] - 1, + // _size_pixels[1] - 1) at the lower-right. + + // Millimeters are used when interfacing with the lens. They start + // at (0, 0) at the center, and range from -size_mm at the + // lower-left, to size_mm at the upper-right. + + LVector2d pixels_per_uv(_size_pixels[0] - 1.0, _size_pixels[1] - 1.0); + + _pixels_to_uv = + LMatrix3d::translate_mat(LVector2d(0.0, -pixels_per_uv[1])) * + LMatrix3d::scale_mat(1.0 / pixels_per_uv[0], -1.0 / pixels_per_uv[1]); + + _uv_to_pixels = + LMatrix3d::scale_mat(pixels_per_uv[0], -pixels_per_uv[1]) * + LMatrix3d::translate_mat(LVector2d(0.0, pixels_per_uv[1])); + + /* + nout << "_pixels_to_uv * _uv_to_pixels is\n" + << _pixels_to_uv * _uv_to_pixels << "\n" + << "Corners in pixels:\n" + << "ll " << LPoint2d(0.0, 0.0) * _uv_to_pixels + << " lr " << LPoint2d(1.0, 0.0) * _uv_to_pixels + << " ul " << LPoint2d(0.0, 1.0) * _uv_to_pixels + << " ur " << LPoint2d(1.0, 1.0) * _uv_to_pixels << "\n" + << "center " << LPoint2d(0.5, 0.5) * _uv_to_pixels << "\n\n"; + */ + + LVector2d mm_per_uv = get_size_mm(); + + _uv_to_mm = + LMatrix3d::translate_mat(LVector2d(-0.5, -0.5)) * + LMatrix3d::scale_mat(mm_per_uv); + + _mm_to_uv = + LMatrix3d::scale_mat(1.0 / mm_per_uv[0], 1.0 / mm_per_uv[1]) * + LMatrix3d::translate_mat(LVector2d(0.5, 0.5)); + + _pixels_to_mm = _pixels_to_uv * _uv_to_mm; + _mm_to_pixels = _mm_to_uv * _uv_to_pixels; + + _show_points = false; + setup_grid(2, 2); + + _data = NULL; + _untextured_color.set(1.0, 1.0, 1.0, 1.0); + _index = 0; + _hpr_set = false; + _layered_type = LT_flat; + _layer_index = 0; + _layered_image = NULL; + + if (_filename.get_extension() == "xcf") { + _layered_type = LT_combined; + } +} + +bool StitchImage:: +has_name() const { + return !_name.empty(); +} + +string StitchImage:: +get_name() const { + if (_name.empty()) { + return _filename.get_basename_wo_extension(); + } + return _name; +} + +bool StitchImage:: +has_filename() const { + return !_filename.empty(); +} + +string StitchImage:: +get_filename() const { + return _filename; +} + +bool StitchImage:: +read_file() { + if (_data != NULL) { + delete _data; + _data = NULL; + } + if (!has_filename()) { + return false; + } + _data = new PNMImage; + nout << "Reading " << _filename << "\n"; + bool result = _data->read(_filename); + if (!result) { + delete _data; + _data = NULL; + } + return result; +} + +void StitchImage:: +clear_file() { + if (_data != NULL) { + delete _data; + _data = NULL; + } + if (_layered_image != NULL) { + delete _layered_image; + _layered_image = NULL; + } +} + +void StitchImage:: +open_output_file() { + clear_file(); + if (_layered_type == LT_flat) { + _data = new PNMImage(_size_pixels[0], _size_pixels[1], 4); + + } else if (_layered_type == LT_combined) { + _layered_image = new LayeredImage(_size_pixels[0], _size_pixels[1]); + } +} + +void StitchImage:: +open_layer(const string &layer_name) { + _layer_name = layer_name; + + if (_layered_type == LT_separate || _layered_type == LT_combined) { + _data = new PNMImage(_size_pixels[0], _size_pixels[1], 4); + } +} + +bool StitchImage:: +close_layer(bool nonempty) { + bool result = true; + + if (_layered_type == LT_separate) { + if (_data == NULL) { + result = false; + } else { + if (nonempty) { + char buff[1024]; + _layer_index++; + sprintf(buff, _filename.c_str(), _layer_index); + nout << "Writing layer " << _layer_name << " as " << buff << "\n"; + result = _data->write(buff); + } + } + clear_file(); + + } else if (_layered_type == LT_combined) { + if (_data == NULL) { + result = false; + } else { + if (nonempty) { + _layered_image->add_layer(_layer_name, LVector2d(0.0, 0.0), + _data); + _data = NULL; + } + } + } + return result; +} + +bool StitchImage:: +close_output_file() { + bool result = true; + + if (_layered_type == LT_separate) { + + } else if (_layered_type == LT_combined) { + if (_layered_image == NULL) { + result = false; + } else { + nout << "Writing " << _filename << "\n"; + result = _layered_image->write_file(_filename); + } + + } else { // _layered_type == LT_flat + if (_data == NULL) { + result = false; + } else { + nout << "Writing " << _filename << "\n"; + result = _data->write(_filename); + } + } + + clear_file(); + return result; +} + +void StitchImage:: +clear_transform() { + _rotate = LMatrix3d::ident_mat(); + _inv_rotate = LMatrix3d::ident_mat(); + _morph.clear(); +} + +void StitchImage:: +set_transform(const LMatrix3d &rot) { + _rotate = rot; + _inv_rotate = invert(rot); +} + +void StitchImage:: +set_hpr(const LVecBase3d &hpr) { + compose_matrix(_rotate, LVecBase3d(1.0, 1.0, 1.0), hpr); + _inv_rotate = invert(_rotate); + _hpr_set = true; + _hpr = hpr; +} + +void StitchImage:: +show_points(double radius, const Colord &color) { + _show_points = true; + _point_radius = radius; + _point_color = color; +} + +void StitchImage:: +setup_grid(int x_verts, int y_verts) { + _x_verts = x_verts; + _y_verts = y_verts; +} + +int StitchImage:: +get_x_verts() const { + return _x_verts; +} + +int StitchImage:: +get_y_verts() const { + return _y_verts; +} + +LPoint2d StitchImage:: +get_grid_uv(int xv, int yv) { + return LPoint2d((double)xv / (double)(_x_verts - 1), + 1.0 - (double)yv / (double)(_y_verts - 1)); + +} + +LVector3d StitchImage:: +get_grid_vector(int xv, int yv) { + return extrude(get_grid_uv(xv, yv)); +} + +double StitchImage:: +get_grid_alpha(int xv, int yv) { + return get_alpha(get_grid_uv(xv, yv)); +} + +const LVecBase2d &StitchImage:: +get_size_pixels() const { + return _size_pixels; +} + +LVecBase2d StitchImage:: +get_size_mm() const { + return _size_mm; +} + +LVector3d StitchImage:: +extrude(const LPoint2d &point_uv) const { + LPoint2d p = _morph.morph_out(point_uv); + return _lens->extrude(p * _uv_to_mm, _size_mm[0]) * _rotate; +} + +LPoint2d StitchImage:: +project(const LVector3d &vec) const { + LPoint2d m = _lens->project(vec * _inv_rotate, _size_mm[0]); + return _morph.morph_in(m * _mm_to_uv); +} + +double StitchImage:: +get_alpha(const LPoint2d &point_uv) const { + return _morph.get_alpha(point_uv); +} + +void StitchImage:: +reset_singularity_detected() { + _lens->reset_singularity_detected(); +} + +void StitchImage:: +draw_triangle(TriangleRasterizer &rast, const RasterizerVertex *v0, + const RasterizerVertex *v1, const RasterizerVertex *v2) { + _lens->draw_triangle(rast, _mm_to_pixels, _size_mm[0], v0, v1, v2); +} + +void StitchImage:: +pick_up_singularity(TriangleRasterizer &rast, StitchImage *input) { + _lens->pick_up_singularity(rast, _mm_to_pixels, _pixels_to_mm, + _rotate, _size_mm[0], input); +} + +void StitchImage:: +add_point(const string &name, const LPoint2d &pixel) { + _points[name] = pixel * _pixels_to_uv; +} + + +void StitchImage:: +output(ostream &out) const { + out << "image " << get_name() << ":\n" + << get_size_pixels() << " pixels, or " << get_size_mm() + << " mm\n"; + + LVecBase3d scale, hpr; + if (decompose_matrix(_rotate, scale, hpr)) { + out << "rotate " << hpr << "\n"; + } else { + out << "Invalid rotation transform:\n"; + _rotate.write(out); + } +} + diff --git a/pandatool/src/stitchbase/stitchImage.h b/pandatool/src/stitchbase/stitchImage.h new file mode 100644 index 0000000000..fdc59ebd5e --- /dev/null +++ b/pandatool/src/stitchbase/stitchImage.h @@ -0,0 +1,154 @@ +// Filename: stitchImage.h +// Created by: drose (04Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHIMAGE_H +#define STITCHIMAGE_H + +#include + +#include "stitchPoint.h" +#include "morphGrid.h" + +#include +#include +#include +#include + +#include + +class StitchLens; +class TriangleRasterizer; +class RasterizerVertex; +class LayeredImage; + +class StitchImage { +public: + StitchImage(const string &name, const string &filename, + StitchLens *lens, + const LVecBase2d &size_pixels, + const LVecBase2d &pixels_per_mm); + + bool has_name() const; + string get_name() const; + bool has_filename() const; + string get_filename() const; + + // This function reads the image file if it is available. + bool read_file(); + void clear_file(); + + // These functions handle the writing of the image file. + // open_output_file() should be called first. open_layer() and + // close_layer() should be called in pairs; after a call to + // open_layer(), the _data member is guaranteed to contain a + // PNMImage that may be empty or may contain the contents of + // previous layers. close_layer() should be called as each layer is + // filled, and close_output_file() should be called when the image + // is completely done. + void open_output_file(); + void open_layer(const string &layer_name); + bool close_layer(bool nonempty); + bool close_output_file(); + + void clear_transform(); + void set_transform(const LMatrix3d &rot); + void set_hpr(const LVecBase3d &hpr); + + void show_points(double radius, const Colord &color); + void setup_grid(int x_verts, int y_verts); + int get_x_verts() const; + int get_y_verts() const; + + LPoint2d get_grid_uv(int xv, int yv); + LPoint2d get_grid_pixel(int xv, int yv); + LVector3d get_grid_vector(int xv, int yv); + double get_grid_alpha(int xv, int yv); + + const LVecBase2d &get_size_pixels() const; + LVecBase2d get_size_mm() const; + + LVector3d extrude(const LPoint2d &point_uv) const; + LPoint2d project(const LVector3d &vec) const; + double get_alpha(const LPoint2d &point_uv) const; + + void reset_singularity_detected(); + + // This function simply passes the indicated triangle on to the + // rasterizer. It exists here in the lens so that the lens may do + // something special if the triangle crosses a seam or singularity + // in the lens' coordinate space. + void draw_triangle(TriangleRasterizer &rast, + const RasterizerVertex *v0, + const RasterizerVertex *v1, + const RasterizerVertex *v2); + + // This function is to be called after all triangles have been + // drawn; it will draw pixel-by-pixel all the points within + // _singularity_radius of any singularity points the lens may have + // (these points were not draw by draw_triangle(), above). + void pick_up_singularity(TriangleRasterizer &rast, + StitchImage *input); + + void add_point(const string &name, const LPoint2d &pixel); + + void output(ostream &out) const; + + PNMImage *_data; + StitchLens *_lens; + LVecBase2d _size_pixels, _size_mm; + LVecBase2d _pixels_per_mm; + LMatrix3d _mm_to_uv, _uv_to_mm; + LMatrix3d _pixels_to_mm, _mm_to_pixels; + LMatrix3d _pixels_to_uv, _uv_to_pixels; + bool _hpr_set; + LVecBase3d _hpr; + + enum LayeredType { + LT_flat, // One flat image--no layers. + LT_separate, // A separate image file for each layer. + LT_combined, // A single image file with multiple layers. + }; + LayeredType _layered_type; + + bool _show_points; + double _point_radius; + Colord _point_color; + Colord _untextured_color; + + typedef map Points; + Points _points; + LMatrix3d _rotate, _inv_rotate; + MorphGrid _morph; + + // This index number is filled in by the Stitcher. It allows us to + // sort the images in order as they are specified in the command + // file. + int _index; + +private: + Filename _filename; + string _name; + + int _x_verts, _y_verts; + int _layer_index; + string _layer_name; + LayeredImage *_layered_image; +}; + +inline ostream &operator << (ostream &out, const StitchImage &i) { + i.output(out); + return out; +} + +// An STL function object to sort image pointers by index number. +class StitchImageByIndex { +public: + bool operator()(const StitchImage *a, const StitchImage *b) const { + return a->_index < b->_index; + } +}; + +#endif + diff --git a/pandatool/src/stitchbase/stitchImageCommandOutput.cxx b/pandatool/src/stitchbase/stitchImageCommandOutput.cxx new file mode 100644 index 0000000000..c865a19ce3 --- /dev/null +++ b/pandatool/src/stitchbase/stitchImageCommandOutput.cxx @@ -0,0 +1,91 @@ +// Filename: stitchImageCommandOutput.cxx +// Created by: drose (29Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchImageCommandOutput.h" +#include "stitchImage.h" +#include "stitchLens.h" +#include "stitcher.h" +#include "stitchCommand.h" + +#include +#include + +StitchImageCommandOutput:: +StitchImageCommandOutput() { +} + + +void StitchImageCommandOutput:: +add_input_image(StitchImage *image) { + _input_images.push_back(image); +} + +void StitchImageCommandOutput:: +add_output_image(StitchImage *image) { + _output_images.push_back(image); +} + +void StitchImageCommandOutput:: +add_stitcher(Stitcher *stitcher) { + _stitchers.push_back(stitcher); +} + +void StitchImageCommandOutput:: +execute() { + StitchCommand root; + + Stitchers::const_iterator si; + for (si = _stitchers.begin(); si != _stitchers.end(); ++si) { + Stitcher *stitcher = (*si); + Stitcher::LoosePoints::const_iterator pi; + for (pi = stitcher->_loose_points.begin(); + pi != stitcher->_loose_points.end(); + ++pi) { + StitchCommand *cmd = new StitchCommand(&root, StitchCommand::C_point3d); + cmd->set_name((*pi)->_name); + cmd->set_point3d((*pi)->_space); + } + } + + Images::const_iterator ii; + for (ii = _input_images.begin(); ii != _input_images.end(); ++ii) { + StitchImage *input = (*ii); + StitchCommand *image_cmd = new StitchCommand(&root, StitchCommand::C_input_image); + fill_image_cmd(image_cmd, input); + } + + for (ii = _output_images.begin(); ii != _output_images.end(); ++ii) { + StitchImage *output = (*ii); + StitchCommand *image_cmd = new StitchCommand(&root, StitchCommand::C_output_image); + fill_image_cmd(image_cmd, output); + } + + cout << root << "\n"; +} + +void StitchImageCommandOutput:: +fill_image_cmd(StitchCommand *image_cmd, StitchImage *image) { + if (image->has_name()) { + image_cmd->set_name(image->get_name()); + } + + StitchCommand *cmd; + cmd = new StitchCommand(image_cmd, StitchCommand::C_filename); + cmd->set_str(image->get_filename()); + + cmd = new StitchCommand(image_cmd, StitchCommand::C_image_size); + cmd->set_point2d(image->get_size_pixels()); + + cmd = new StitchCommand(image_cmd, StitchCommand::C_film_size); + cmd->set_length_pair(image->get_size_mm()); + + image->_lens->make_lens_command(image_cmd); + + LVecBase3d scale, hpr; + if (decompose_matrix(image->_rotate, scale, hpr)) { + cmd = new StitchCommand(image_cmd, StitchCommand::C_hpr); + cmd->set_point3d(hpr); + } +} diff --git a/pandatool/src/stitchbase/stitchImageCommandOutput.h b/pandatool/src/stitchbase/stitchImageCommandOutput.h new file mode 100644 index 0000000000..0b13bd1ae9 --- /dev/null +++ b/pandatool/src/stitchbase/stitchImageCommandOutput.h @@ -0,0 +1,39 @@ +// Filename: stitchImageCommandOutput.h +// Created by: drose (29Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHIMAGECOMMANDOUTPUT_H +#define STITCHIMAGECOMMANDOUTPUT_H + +#include "stitchImageOutputter.h" + +#include + +class Stitcher; +class StitchImage; +class StitchCommand; + +class StitchImageCommandOutput : public StitchImageOutputter { +public: + StitchImageCommandOutput(); + + virtual void add_input_image(StitchImage *image); + virtual void add_output_image(StitchImage *image); + virtual void add_stitcher(Stitcher *stitcher); + + virtual void execute(); + +protected: + void fill_image_cmd(StitchCommand *image_cmd, StitchImage *image); + + typedef vector Images; + Images _input_images; + Images _output_images; + + typedef vector Stitchers; + Stitchers _stitchers; +}; + +#endif + diff --git a/pandatool/src/stitchbase/stitchImageOutputter.cxx b/pandatool/src/stitchbase/stitchImageOutputter.cxx new file mode 100644 index 0000000000..6a2477d52a --- /dev/null +++ b/pandatool/src/stitchbase/stitchImageOutputter.cxx @@ -0,0 +1,15 @@ +// Filename: stitchImageOutputter.cxx +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchImageOutputter.h" + + +StitchImageOutputter:: +StitchImageOutputter() { +} + +StitchImageOutputter:: +~StitchImageOutputter() { +} diff --git a/pandatool/src/stitchbase/stitchImageOutputter.h b/pandatool/src/stitchbase/stitchImageOutputter.h new file mode 100644 index 0000000000..99000e6655 --- /dev/null +++ b/pandatool/src/stitchbase/stitchImageOutputter.h @@ -0,0 +1,26 @@ +// Filename: stitchImageOutputter.h +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHIMAGEOUTPUTTER_H +#define STITCHIMAGEOUTPUTTER_H + +class StitchImage; +class Stitcher; + +#include + +class StitchImageOutputter { +public: + StitchImageOutputter(); + virtual ~StitchImageOutputter(); + + virtual void add_input_image(StitchImage *image)=0; + virtual void add_output_image(StitchImage *image)=0; + virtual void add_stitcher(Stitcher *stitcher)=0; + + virtual void execute()=0; +}; + +#endif diff --git a/pandatool/src/stitchbase/stitchImageRasterizer.cxx b/pandatool/src/stitchbase/stitchImageRasterizer.cxx new file mode 100644 index 0000000000..18c8c06969 --- /dev/null +++ b/pandatool/src/stitchbase/stitchImageRasterizer.cxx @@ -0,0 +1,218 @@ +// Filename: stitchImageRasterizer.cxx +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchImageRasterizer.h" +#include "triangleRasterizer.h" +#include "stitchImage.h" +#include "stitcher.h" + +#include + +StitchImageRasterizer:: +StitchImageRasterizer() { + //_filter_output = false; + _filter_output = true; +} + + +void StitchImageRasterizer:: +add_input_image(StitchImage *image) { + _input_images.push_back(image); +} + +void StitchImageRasterizer:: +add_output_image(StitchImage *image) { + _output_images.push_back(image); +} + +void StitchImageRasterizer:: +add_stitcher(Stitcher *stitcher) { + _stitchers.push_back(stitcher); +} + +void StitchImageRasterizer:: +execute() { + Images::iterator oi; + for (oi = _output_images.begin(); oi != _output_images.end(); ++oi) { + StitchImage *output = (*oi); + if (!output->has_filename()) { + nout << "Output image has no filename; cannot generate.\n"; + } else { + nout << "Generating " << output->get_name() << "\n"; + output->open_output_file(); + + Images::const_iterator ii; + for (ii = _input_images.begin(); ii != _input_images.end(); ++ii) { + StitchImage *input = (*ii); + draw_image(output, input); + } + + output->open_layer("points"); + bool shown_points = false; + Stitchers::const_iterator si; + for (si = _stitchers.begin(); si != _stitchers.end(); ++si) { + Stitcher *stitcher = (*si); + if (stitcher->_show_points && !stitcher->_loose_points.empty()) { + draw_points(output, stitcher, stitcher->_point_color, + stitcher->_point_radius); + shown_points = true; + } + } + for (ii = _input_images.begin(); ii != _input_images.end(); ++ii) { + StitchImage *input = (*ii); + if (input->_show_points && !input->_points.empty()) { + draw_points(output, input, input->_point_color, + input->_point_radius); + shown_points = true; + } + } + output->close_layer(shown_points); + + if (!output->close_output_file()) { + nout << "Error in writing.\n"; + } + } + } +} + +void StitchImageRasterizer:: +draw_points(StitchImage *output, StitchImage *input, + const Colord &color, double radius) { + StitchImage::Points::const_iterator pi; + for (pi = input->_points.begin(); pi != input->_points.end(); ++pi) { + LPoint2d to = output->project(input->extrude((*pi).second)); + draw_spot(output, to * output->_uv_to_pixels, color, radius); + } +} + +void StitchImageRasterizer:: +draw_points(StitchImage *output, Stitcher *input, + const Colord &color, double radius) { + Stitcher::LoosePoints::const_iterator pi; + for (pi = input->_loose_points.begin(); + pi != input->_loose_points.end(); ++pi) { + LPoint2d to = output->project((*pi)->_space); + draw_spot(output, to * output->_uv_to_pixels, color, radius); + } +} + + +void StitchImageRasterizer:: +draw_image(StitchImage *output, StitchImage *input) { + nout << "Rasterizing " << input->get_name() << "\n"; + output->open_layer(input->get_name()); + + TriangleRasterizer rast; + rast._output = output->_data; + rast._input = input; + rast._filter_output = _filter_output; + rast._untextured_color = input->_untextured_color; + + int x_verts = input->get_x_verts(); + int y_verts = input->get_y_verts(); + + // Build up the table of verts. + typedef vector VRow; + typedef vector VTable; + VTable _table(x_verts, VRow()); + + int xi, yi; + for (xi = 0; xi < x_verts; xi++) { + _table[xi] = VRow(y_verts, RasterizerVertex()); + + for (yi = 0; yi < y_verts; yi++) { + LVector3d space = input->get_grid_vector(xi, yi); + double alpha = input->get_grid_alpha(xi, yi); + LPoint2d to = output->project(space); + LPoint2d from = input->get_grid_uv(xi, yi); + + _table[xi][yi]._p = to * output->_uv_to_pixels; + _table[xi][yi]._uv = from; + _table[xi][yi]._space = space * output->_inv_rotate; + _table[xi][yi]._alpha = alpha; + + _table[xi][yi]._space = normalize(_table[xi][yi]._space); + + // We assign one bit for each quadrant the vertex may be out of + // bounds. If all three vertices of a triangle are out in the + // same quadrant, then the entire triangle is out of bounds. + _table[xi][yi]._visibility = + ((to[0] < 0.0) | + ((to[0] > 1.0) << 1) | + ((to[1] < 0.0) << 2) | + ((to[1] > 1.0) << 3) | + ((from[0] < 0.0) << 4) | + ((from[0] > 1.0) << 5) | + ((from[1] < 0.0) << 6) | + ((from[1] > 1.0) << 7)); + } + } + + // Now draw all of the triangles, top-to-bottom. + output->reset_singularity_detected(); + + for (yi = 0; yi < y_verts - 1; yi++) { + for (xi = 0; xi < x_verts - 1; xi++) { + output->draw_triangle(rast, + &_table[xi][yi], + &_table[xi][yi + 1], + &_table[xi + 1][yi + 1]); + output->draw_triangle(rast, + &_table[xi][yi], + &_table[xi + 1][yi + 1], + &_table[xi + 1][yi]); + } + } + output->pick_up_singularity(rast, input); + output->close_layer(rast._read_input); + + input->clear_file(); +} + + +void StitchImageRasterizer:: +draw_spot(StitchImage *output, + const LPoint2d pixel_center, const Colord &color, double radius) { + LPoint2d minp = pixel_center - LPoint2d(radius, radius); + LPoint2d maxp = pixel_center + LPoint2d(radius, radius); + + int min_x = (int)floor(minp[0]); + int max_x = (int)ceil(maxp[0]); + + int min_y = (int)floor(minp[1]); + int max_y = (int)ceil(maxp[1]); + + double r2 = radius * radius; + + for (int yi = min_y; yi <= max_y; yi++) { + if (yi >= 0 && yi < output->_data->get_y_size()) { + for (int xi = min_x; xi <= max_x; xi++) { + if (xi >= 0 && xi < output->_data->get_x_size()) { + // Check the coverage of the four points around the pixel, and + // the pixel center. + LPoint2d ul = pixel_center - LPoint2d(xi - 0.5, yi - 0.5); + LPoint2d ll = pixel_center - LPoint2d(xi - 0.5, yi + 0.5); + LPoint2d ur = pixel_center - LPoint2d(xi + 0.5, yi - 0.5); + LPoint2d lr = pixel_center - LPoint2d(xi + 0.5, yi + 0.5); + LPoint2d pc = pixel_center - LPoint2d(xi, yi); + + // Net coverage. + int coverage = + (dot(ul, ul) <= r2) + + (dot(ll, ll) <= r2) + + (dot(ur, ur) <= r2) + + (dot(lr, lr) <= r2) + + (dot(pc, pc) <= r2); + + if (coverage != 0) { + output->_data->blend(xi, yi, color[0], color[1], color[2], + color[3] * (double)coverage / 5.0); + } + } + } + } + } +} + diff --git a/pandatool/src/stitchbase/stitchImageRasterizer.h b/pandatool/src/stitchbase/stitchImageRasterizer.h new file mode 100644 index 0000000000..468f3170ec --- /dev/null +++ b/pandatool/src/stitchbase/stitchImageRasterizer.h @@ -0,0 +1,49 @@ +// Filename: stitchImageRasterizer.h +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHIMAGERASTERIZER_H +#define STITCHIMAGERASTERIZER_H + +#include "stitchImageOutputter.h" + +#include + +class Stitcher; +class StitchImage; + +class StitchImageRasterizer : public StitchImageOutputter { +public: + StitchImageRasterizer(); + + virtual void add_input_image(StitchImage *image); + virtual void add_output_image(StitchImage *image); + virtual void add_stitcher(Stitcher *stitcher); + + virtual void execute(); + + bool _filter_output; + +protected: + void draw_points(StitchImage *output, StitchImage *input, + const Colord &color, double radius); + void draw_points(StitchImage *output, Stitcher *input, + const Colord &color, double radius); + void draw_image(StitchImage *output, StitchImage *input); + + typedef vector Images; + Images _input_images; + Images _output_images; + + typedef vector Stitchers; + Stitchers _stitchers; + +protected: + void draw_spot(StitchImage *output, + const LPoint2d pixel_center, const Colord &color, + double radius); +}; + +#endif + diff --git a/pandatool/src/stitchbase/stitchLens.cxx b/pandatool/src/stitchbase/stitchLens.cxx new file mode 100644 index 0000000000..45a20fe4a4 --- /dev/null +++ b/pandatool/src/stitchbase/stitchLens.cxx @@ -0,0 +1,69 @@ +// Filename: stitchLens.cxx +// Created by: drose (04Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchLens.h" +#include "triangleRasterizer.h" + +#include +#include + +#include + +StitchLens:: +StitchLens() { + _flags = 0; + _singularity_detected = 0; + set_singularity_tolerance(5.0); +} + +StitchLens:: +~StitchLens() { +} + +void StitchLens:: +set_focal_length(double focal_length_mm) { + _flags |= F_focal_length; + _focal_length = focal_length_mm; +} + +void StitchLens:: +set_hfov(double fov_deg) { + _flags |= F_fov; + _fov = fov_deg; +} + +void StitchLens:: +set_singularity_tolerance(double tol) { + _singularity_tolerance = tol; + _singularity_radius = sin(deg_2_rad(tol)); +} + +void StitchLens:: +reset_singularity_detected() { + _singularity_detected = 0; +} + +bool StitchLens:: +is_defined() const { + return (_flags != 0); +} + +double StitchLens:: +get_vfov(double height_mm) const { + return get_hfov(height_mm); +} + +void StitchLens:: +draw_triangle(TriangleRasterizer &rast, const LMatrix3d &, + double, const RasterizerVertex *v0, + const RasterizerVertex *v1, const RasterizerVertex *v2) { + rast.draw_triangle(v0, v1, v2); +} + +void StitchLens:: +pick_up_singularity(TriangleRasterizer &, const LMatrix3d &, + const LMatrix3d &, const LMatrix3d &, + double, StitchImage *) { +} diff --git a/pandatool/src/stitchbase/stitchLens.h b/pandatool/src/stitchbase/stitchLens.h new file mode 100644 index 0000000000..36be5115f5 --- /dev/null +++ b/pandatool/src/stitchbase/stitchLens.h @@ -0,0 +1,74 @@ +// Filename: stitchLens.h +// Created by: drose (04Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHLENS_H +#define STITCHLENS_H + +#include + +class TriangleRasterizer; +class RasterizerVertex; +class StitchImage; +class StitchCommand; + +class StitchLens { +public: + StitchLens(); + virtual ~StitchLens(); + + virtual void set_focal_length(double focal_length_mm); + virtual void set_hfov(double fov_deg); + + void set_singularity_tolerance(double tol); + void reset_singularity_detected(); + + bool is_defined() const; + virtual double get_focal_length(double width_mm) const=0; + virtual double get_hfov(double width_mm) const=0; + virtual double get_vfov(double height_mm) const; + + virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const=0; + virtual LPoint2d project(const LVector3d &vec, double width_mm) const=0; + + // This function simply passes the indicated triangle on to the + // rasterizer. It exists here in the lens so that the lens may do + // something special if the triangle crosses a seam or singularity + // in the lens' coordinate space. + virtual void draw_triangle(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + double width_mm, + const RasterizerVertex *v0, + const RasterizerVertex *v1, + const RasterizerVertex *v2); + + // This function is to be called after all triangles have been + // drawn; it will draw pixel-by-pixel all the points within + // _singularity_radius of any singularity points the lens may have + // (these points were not draw by draw_triangle(), above). + virtual void pick_up_singularity(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + const LMatrix3d &pixels_to_mm, + const LMatrix3d &rotate, + double width_mm, + StitchImage *input); + + // This generates a StitchCommand that represents the given lens. + virtual void make_lens_command(StitchCommand *parent)=0; + +protected: + enum Flags { + F_focal_length = 0x01, + F_fov = 0x02, + }; + int _flags; + int _singularity_detected; + double _focal_length; + double _fov; + double _singularity_tolerance; + double _singularity_radius; +}; + +#endif + diff --git a/pandatool/src/stitchbase/stitchLexer.lxx b/pandatool/src/stitchbase/stitchLexer.lxx new file mode 100644 index 0000000000..74ec0014af --- /dev/null +++ b/pandatool/src/stitchbase/stitchLexer.lxx @@ -0,0 +1,369 @@ +/* +// Filename: lexer.l +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// +*/ + +%{ +#include "stitchLexerDefs.h" +#include "stitchParserDefs.h" +#include "stitchParser.h" + +#include + +#include +#include + +extern "C" int stitchyywrap(void); // declared below. + +static int yyinput(void); // declared by flex. + + +//////////////////////////////////////////////////////////////////// +// Static variables +//////////////////////////////////////////////////////////////////// + +// We'll increment line_number and col_number as we parse the file, so +// that we can report the position of an error. +static int line_number = 0; +static int col_number = 0; + +// current_line holds as much of the current line as will fit. Its +// only purpose is for printing it out to report an error to the user. +static const int max_error_width = 1024; +static char current_line[max_error_width + 1]; + +static int error_count = 0; +static int warning_count = 0; + +// This is the pointer to the current input stream. +static istream *inp = NULL; + +// This is the name of the stitch file we're parsing. We keep it so we +// can print it out for error messages. +static string stitch_filename; + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the lexer. +//////////////////////////////////////////////////////////////////// + +void +stitch_init_lexer(istream &in, const string &filename) { + inp = ∈ + stitch_filename = filename; + line_number = 0; + col_number = 0; + error_count = 0; + warning_count = 0; +} + +int +stitch_error_count() { + return error_count; +} + +int +stitch_warning_count() { + return warning_count; +} + + +//////////////////////////////////////////////////////////////////// +// Internal support functions. +//////////////////////////////////////////////////////////////////// + +int +stitchyywrap(void) { + return 1; +} + +void +stitchyyerror(const string &msg) { + nout << "\nError"; + if (!stitch_filename.empty()) { + nout << " in " << stitch_filename; + } + nout + << " at line " << line_number << ", column " << col_number << ":\n" + << current_line << "\n"; + indent(nout, col_number-1) + << "^\n" << msg << "\n\n" << flush; + error_count++; +} + +void +stitchyyerror(ostrstream &strm) { + char *s = strm.str(); + stitchyyerror(s); + delete[] s; +} + +void +stitchyywarning(const string &msg) { + nout + << "\nWarning at line " << line_number << ", column " << col_number << ":\n" + << current_line << "\n"; + indent(nout, col_number-1) + << "^\n" << msg << "\n\n" << flush; + warning_count++; +} + +void +stitchyywarning(ostrstream &strm) { + char *s = strm.str(); + stitchyywarning(s); + delete[] s; +} + +// Now define a function to take input from an istream instead of a +// stdio FILE pointer. This is flex-specific. +static void +input_chars(char *buffer, int &result, int max_size) { + assert(inp != NULL); + if (*inp) { + inp->read(buffer, max_size); + result = inp->gcount(); + + if (line_number == 0) { + // This is a special case. If we are reading the very first bit + // from the stream, copy it into the current_line array. This + // is because the \n.* rule below, which fills current_line + // normally, doesn't catch the first line. + strncpy(current_line, yytext, max_error_width); + current_line[max_error_width] = '\0'; + line_number++; + col_number = 0; + + // Truncate it at the newline. + char *end = strchr(current_line, '\n'); + if (end != NULL) { + *end = '\0'; + } + } + + } else { + // End of file or I/O error. + result = 0; + } +} +#undef YY_INPUT +#define YY_INPUT(buffer, result, max_size) input_chars(buffer, result, max_size) + +// read_char reads and returns a single character, incrementing the +// supplied line and column numbers as appropriate. A convenience +// function for the scanning functions below. +static int +read_char(int &line, int &col) { + int c = yyinput(); + if (c == '\n') { + line++; + col = 0; + } else { + col++; + } + return c; +} + +// scan_quoted_string reads a string delimited by quotation marks and +// returns it. +static string +scan_quoted_string() { + string result; + + // We don't touch the current line number and column number during + // scanning, so that if we detect an error while scanning the string + // (e.g. an unterminated string), we'll report the error as + // occurring at the start of the string, not at the end--somewhat + // more convenient for the user. + + // Instead of adjusting the global line_number and col_number + // variables, we'll operate on our own local variables for the + // interim. + int line = line_number; + int col = col_number; + + int c; + c = read_char(line, col); + while (c != '"' && c != EOF) { + result += c; + c = read_char(line, col); + } + + if (c == EOF) { + stitchyyerror("This quotation mark is unterminated."); + } + + line_number = line; + col_number = col; + + return result; +} + +// eat_c_comment scans past all characters up until the first */ +// encountered. +static void +eat_c_comment() { + // As above, we'll operate on our own local copies of line_number + // and col_number within this function. + + int line = line_number; + int col = col_number; + + int c, last_c; + + last_c = '\0'; + c = read_char(line, col); + while (c != EOF && !(last_c == '*' && c == '/')) { + if (last_c == '/' && c == '*') { + ostrstream errmsg; + errmsg << "This comment contains a nested /* symbol at line " + << line << ", column " << col-1 << "--possibly unclosed?" + << ends; + stitchyywarning(errmsg); + } + last_c = c; + c = read_char(line, col); + } + + if (c == EOF) { + stitchyyerror("This comment marker is unclosed."); + } + + line_number = line; + col_number = col; +} + + + +// accept() is called below as each piece is pulled off and +// accepted by the lexer; it increments the current column number. +INLINE void accept() { + col_number += yyleng; +} + +%} + +NUMERIC ([+-]?(([0-9]+[.]?)|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) + +%% + +\n.* { + // New line. Save a copy of the line so we can print it out for the + // benefit of the user in case we get an error. + + strncpy(current_line, yytext+1, max_error_width); + current_line[max_error_width] = '\0'; + line_number++; + col_number=0; + + // Return the whole line to the lexer, except the newline character, + // which we eat. + yyless(1); +} + +[ \t] { + // Eat whitespace. + accept(); +} + +"//".* { + // Eat C++-style comments. + accept(); +} + +"/*" { + // Eat C-style comments. + accept(); + eat_c_comment(); +} + + + +{NUMERIC} { + // An integer or floating-point number. + accept(); + stitchyylval.number = atof(stitchyytext); + stitchyylval.str = yytext; + return NUMBER; +} + +["] { + // Quoted string. + accept(); + stitchyylval.str = scan_quoted_string(); + return STRING; +} + +[a-zA-Z][a-zA-Z0-9_]* { + // Identifier or keyword. + accept(); + string str = yytext; + stitchyylval.str = str; + + if (str == "define") { + return KW_DEFINE; + } else if (str == "lens") { + return KW_LENS; + } else if (str == "input_image") { + return KW_INPUT_IMAGE; + } else if (str == "output_image") { + return KW_OUTPUT_IMAGE; + } else if (str == "perspective") { + return KW_PERSPECTIVE; + } else if (str == "fisheye") { + return KW_FISHEYE; + } else if (str == "cylindrical") { + return KW_CYLINDRICAL; + } else if (str == "psphere") { + return KW_PSPHERE; + } else if (str == "focal_length") { + return KW_FOCAL_LENGTH; + } else if (str == "fov") { + return KW_FOV; + } else if (str == "singularity_tolerance") { + return KW_SINGULARITY_TOLERANCE; + } else if (str == "resolution") { + return KW_RESOLUTION; + } else if (str == "filename") { + return KW_FILENAME; + } else if (str == "point") { + return KW_POINT; + } else if (str == "show_points") { + return KW_SHOW_POINTS; + } else if (str == "image_size") { + return KW_IMAGE_SIZE; + } else if (str == "film_size") { + return KW_FILM_SIZE; + } else if (str == "grid") { + return KW_GRID; + } else if (str == "untextured_color") { + return KW_UNTEXTURED_COLOR; + } else if (str == "hpr") { + return KW_HPR; + } else if (str == "layers") { + return KW_LAYERS; + } else if (str == "stitch") { + return KW_STITCH; + } else if (str == "points") { + return KW_POINTS; + } else if (str == "using") { + return KW_USING; + } else if (str == "in") { + return KW_IN; + } else if (str == "mm") { + return KW_MM; + } else if (str == "cm") { + return KW_CM; + } else if (str == "p") { + return KW_P; + } + + return IDENTIFIER; +} + +. { + // Send any other character as itself. + accept(); + return stitchyytext[0]; +} diff --git a/pandatool/src/stitchbase/stitchLexerDefs.h b/pandatool/src/stitchbase/stitchLexerDefs.h new file mode 100644 index 0000000000..6b7436ae2d --- /dev/null +++ b/pandatool/src/stitchbase/stitchLexerDefs.h @@ -0,0 +1,23 @@ +// Filename: stitchLexerDefs.h +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHLEXERDEFS_H +#define STITCHLEXERDEFS_H + +#include + +void stitch_init_lexer(istream &in, const string &filename); +int stitch_error_count(); +int stitch_warning_count(); + +void stitchyyerror(const string &msg); +void stitchyyerror(ostrstream &strm); + +void stitchyywarning(const string &msg); +void stitchyywarning(ostrstream &strm); + +int stitchyylex(); + +#endif diff --git a/pandatool/src/stitchbase/stitchPSphereLens.cxx b/pandatool/src/stitchbase/stitchPSphereLens.cxx new file mode 100644 index 0000000000..583e9a6b43 --- /dev/null +++ b/pandatool/src/stitchbase/stitchPSphereLens.cxx @@ -0,0 +1,300 @@ +// Filename: stitchPSphereLens.cxx +// Created by: drose (16Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchPSphereLens.h" +#include "stitchImage.h" +#include "stitchCommand.h" +#include "triangleRasterizer.h" +#include "triangle.h" + +#include +#include + +#include + +// This is the focal-length constant for fisheye lenses. See +// stitchFisheyeLens.h. +static const double k = 60.0; + +StitchPSphereLens:: +StitchPSphereLens() { +} + +double StitchPSphereLens:: +get_focal_length(double width_mm) const { + if (_flags & F_focal_length) { + return _focal_length; + } + if (_flags & F_fov) { + return width_mm * k / _fov; + } + return 0.0; +} + +double StitchPSphereLens:: +get_hfov(double width_mm) const { + if (_flags & F_fov) { + return _fov; + } + if (_flags & F_focal_length) { + return width_mm * k / _focal_length; + } + return 0.0; +} + +LVector3d StitchPSphereLens:: +extrude(const LPoint2d &point_mm, double width_mm) const { + LVector2d v2 = point_mm; + + double fl = get_focal_length(width_mm); + return LVector3d::forward() * + LMatrix3d::rotate_mat(v2[1] * k / fl, LVector3d::right()) * + LMatrix3d::rotate_mat(-v2[0] * k / fl, LVector3d::up()); +} + + +LPoint2d StitchPSphereLens:: +project(const LVector3d &vec, double width_mm) const { + // A PSphere lens is a toroidal lens. It is independently curved in + // the horizontal and vertical directions. + + LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right); + + // To compute the x position on the frame, we only need to consider + // the angle of the vector about the Z axis. Project the vector + // into the XY plane to do this. + + LVector2d xy(v3[0], v3[1]); + + // The x position is the angle about the Z axis. + double x = + rad_2_deg(atan2(xy[0], xy[1])) * get_focal_length(width_mm) / k; + + // Unroll the Z angle, and the y position is the angle about the X + // axis. + xy = normalize(xy); + LVector2d yz(v3[0]*xy[0] + v3[1]*xy[1], v3[2]); + double y = + rad_2_deg(atan2(yz[1], yz[0])) * get_focal_length(width_mm) / k; + + return LPoint2d(x, y); +} + +LPoint2d StitchPSphereLens:: +project_left(const LVector3d &vec, double width_mm) const { + // This is just like project(), except that if the vertex extends + // below -180 degrees, it remains on the left side of the film + // (instead of wrapping around to the right side). + + LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right); + LVector2d xy(v3[0], v3[1]); + double x = + (rad_2_deg(atan2(-xy[0], -xy[1])) - 180.0) * + get_focal_length(width_mm) / k; + + xy = normalize(xy); + LVector2d yz(v3[0]*xy[0] + v3[1]*xy[1], v3[2]); + double y = + rad_2_deg(atan2(yz[1], yz[0])) * get_focal_length(width_mm) / k; + + return LPoint2d(x, y); +} + +LPoint2d StitchPSphereLens:: +project_right(const LVector3d &vec, double width_mm) const { + // This is just like project(), except that if the vertex extends + // above 180 degrees, it remains on the right side of the film + // (instead of wrapping around to the left side). + + LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right); + LVector2d xy(v3[0], v3[1]); + double x = + (rad_2_deg(atan2(-xy[0], -xy[1])) + 180.0) * + get_focal_length(width_mm) / k; + + xy = normalize(xy); + LVector2d yz(v3[0]*xy[0] + v3[1]*xy[1], v3[2]); + double y = + rad_2_deg(atan2(yz[1], yz[0])) * get_focal_length(width_mm) / k; + + return LPoint2d(x, y); +} + +void StitchPSphereLens:: +draw_triangle(TriangleRasterizer &rast, const LMatrix3d &mm_to_pixels, + double width_mm, const RasterizerVertex *v0, + const RasterizerVertex *v1, const RasterizerVertex *v2) { + // A PSphere lens has two singularities, at the north and south + // poles, as well as a seam at 180 and -180 degrees. + + // First, we reject any triangles within _singularity_tolerance of + // either pole, similar to the fisheye lens. + + LVector2d xy0(dot(v0->_space, LVector3d::right()), + dot(v0->_space, LVector3d::forward())); + LVector2d xy1(dot(v1->_space, LVector3d::right()), + dot(v1->_space, LVector3d::forward())); + LVector2d xy2(dot(v2->_space, LVector3d::right()), + dot(v2->_space, LVector3d::forward())); + + double z0 = dot(v0->_space, LVector3d::up()); + double z1 = dot(v0->_space, LVector3d::up()); + double z2 = dot(v0->_space, LVector3d::up()); + + if (z0 < 0.0 && z1 < 0.0 && z2 < 0.0) { + // A triangle on the southern hemisphere. This projection will + // reverse the vertex order. + if (triangle_contains_circle(LPoint2d(0.0, 0.0), + _singularity_radius, + xy0, xy2, xy1)) { + // The triangle does cross the singularity! Reject it. + _singularity_detected |= 1; + return; + } + } else if (z0 > 0.0 && z1 > 0.0 && z2 > 0.0) { + // A triangle on the northern hemisphere. This projection will + // preserve the vertex order. + if (triangle_contains_circle(LPoint2d(0.0, 0.0), + _singularity_radius, + xy0, xy1, xy2)) { + // The triangle does cross the singularity! Reject it. + _singularity_detected |= 2; + return; + } + } + + // So the triangle is not at the north or south poles. But it might + // cross the seam at the back. If it does, we'll simply draw it + // twice: once at each side. + + // Determine which quadrant each of the vertices is in. The + // triangle crosses the seam if no vertices are in quadrants I and + // II, and some vertices are in quadrant III and others are in + // quadrant IV. + + if (xy0[1] >= 0.0 || xy1[1] >= 0.0 || xy2[1] >= 0.0) { + // Some vertices are in quadrants I or II. + rast.draw_triangle(v0, v1, v2); + + } else if (xy0[0] > 0.0 && xy1[0] > 0.0 && xy2[0] > 0.0) { + // All vertices are in quadrant IV. + rast.draw_triangle(v0, v1, v2); + + } else if (xy0[0] < 0.0 && xy1[0] < 0.0 && xy2[0] < 0.0) { + // All vertices are in quadrant III. + rast.draw_triangle(v0, v1, v2); + + } else { + // The triangle crosses the seam. Draw it twice. + RasterizerVertex v0a = *v0; + RasterizerVertex v1a = *v1; + RasterizerVertex v2a = *v2; + + v0a._p = project_left(v0a._space, width_mm) * mm_to_pixels; + v1a._p = project_left(v1a._space, width_mm) * mm_to_pixels; + v2a._p = project_left(v2a._space, width_mm) * mm_to_pixels; + rast.draw_triangle(&v0a, &v1a, &v2a); + + v0a._p = project_right(v0a._space, width_mm) * mm_to_pixels; + v1a._p = project_right(v1a._space, width_mm) * mm_to_pixels; + v2a._p = project_right(v2a._space, width_mm) * mm_to_pixels; + rast.draw_triangle(&v0a, &v1a, &v2a); + } +} + +void StitchPSphereLens:: +pick_up_singularity(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + const LMatrix3d &pixels_to_mm, + const LMatrix3d &rotate, + double width_mm, StitchImage *input) { + if (_singularity_detected & 2) { + nout << "Picking up north pole singularity\n"; + // Determine what the bottom y pixel is of the circle around the + // north pole. + double d = deg_2_rad(_singularity_tolerance * 2.0); + LPoint2d pmm = project(LVector3d(0.0, sin(d), cos(d)), width_mm); + LPoint2d p = pmm * mm_to_pixels; + + int xsize = rast._output->get_x_size(); + int ysize = rast._output->get_y_size(); + + int bot_y = min((int)ceil(p[1]), ysize - 1); + RasterizerVertex v0; + v0._p.set(0.0, 0.0); + v0._uv.set(0.0, 0.0); + v0._space.set(0.0, 0.0, 0.0); + v0._alpha = 1.0; + v0._visibility = 0; + + int xi, yi; + for (yi = 0; yi <= bot_y; yi++) { + // Project xi point 1 to determine the radius. + v0._p.set(xi, yi); + v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate; + v0._uv = input->project(v0._space); + + for (xi = 0; xi < xsize; xi++) { + double last_u = v0._uv[0]; + + v0._p.set(xi, yi); + v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate; + v0._uv = input->project(v0._space); + rast.draw_pixel(&v0, fabs(v0._uv[0] - last_u)); + } + } + } + if (_singularity_detected & 1) { + nout << "Picking up south pole singularity\n"; + // Determine what the top y pixel is of the circle around the + // south pole. + double d = deg_2_rad(_singularity_tolerance * 2.0); + LPoint2d pmm = project(LVector3d(0.0, sin(d), -cos(d)), width_mm); + LPoint2d p = pmm * mm_to_pixels; + + int xsize = rast._output->get_x_size(); + int ysize = rast._output->get_y_size(); + + int top_y = max((int)floor(p[1]), 0); + RasterizerVertex v0; + v0._p.set(0.0, 0.0); + v0._uv.set(0.0, 0.0); + v0._space.set(0.0, 0.0, 0.0); + v0._alpha = 1.0; + v0._visibility = 0; + + int xi, yi; + for (yi = top_y; yi < ysize; yi++) { + // Project xi point 1 to determine the radius. + v0._p.set(xi, yi); + v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate; + v0._uv = input->project(v0._space); + + for (xi = 0; xi < xsize; xi++) { + double last_u = v0._uv[0]; + + v0._p.set(xi, yi); + v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate; + v0._uv = input->project(v0._space); + rast.draw_pixel(&v0, fabs(v0._uv[0] - last_u)); + } + } + } +} + +void StitchPSphereLens:: +make_lens_command(StitchCommand *parent) { + StitchCommand *lens_cmd = new StitchCommand(parent, StitchCommand::C_lens); + StitchCommand *cmd; + cmd = new StitchCommand(lens_cmd, StitchCommand::C_psphere); + if (_flags & F_focal_length) { + cmd = new StitchCommand(lens_cmd, StitchCommand::C_focal_length); + cmd->set_length(_focal_length); + } + if (_flags & F_fov) { + cmd = new StitchCommand(lens_cmd, StitchCommand::C_fov); + cmd->set_number(_fov); + } +} diff --git a/pandatool/src/stitchbase/stitchPSphereLens.h b/pandatool/src/stitchbase/stitchPSphereLens.h new file mode 100644 index 0000000000..b34d2b9131 --- /dev/null +++ b/pandatool/src/stitchbase/stitchPSphereLens.h @@ -0,0 +1,43 @@ +// Filename: stitchPSphereLens.h +// Created by: drose (16Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHPSPHERELENS_H +#define STITCHPSPHERELENS_H + +#include "stitchLens.h" + +class StitchPSphereLens : public StitchLens { +public: + StitchPSphereLens(); + + virtual double get_focal_length(double width_mm) const; + virtual double get_hfov(double width_mm) const; + + virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const; + virtual LPoint2d project(const LVector3d &vec, double width_mm) const; + + LPoint2d project_left(const LVector3d &vec, double width_mm) const; + LPoint2d project_right(const LVector3d &vec, double width_mm) const; + + virtual void draw_triangle(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + double width_mm, + const RasterizerVertex *v0, + const RasterizerVertex *v1, + const RasterizerVertex *v2); + + virtual void pick_up_singularity(TriangleRasterizer &rast, + const LMatrix3d &mm_to_pixels, + const LMatrix3d &pixels_to_mm, + const LMatrix3d &rotate, + double width_mm, + StitchImage *input); + + virtual void make_lens_command(StitchCommand *parent); +}; + +#endif + + diff --git a/pandatool/src/stitchbase/stitchParser.yxx b/pandatool/src/stitchbase/stitchParser.yxx new file mode 100644 index 0000000000..0a957db708 --- /dev/null +++ b/pandatool/src/stitchbase/stitchParser.yxx @@ -0,0 +1,417 @@ +// Filename: stitchParser.y +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +%{ + +#include "stitchParserDefs.h" +#include "stitchLexerDefs.h" +#include "stitchCommand.h" + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the parser. +//////////////////////////////////////////////////////////////////// + +#define YYERROR_VERBOSE + +typedef vector CommandStack; +static CommandStack cstack; +static StitchCommand *parent; + +void +stitch_init_parser(istream &in, const string &filename, + StitchCommand *tos) { + stitch_init_lexer(in, filename); + parent = tos; + cstack.push_back(parent); +} + +%} + +%token NUMBER +%token IDENTIFIER +%token STRING + +%token KW_DEFINE +%token KW_LENS +%token KW_INPUT_IMAGE +%token KW_OUTPUT_IMAGE +%token KW_PERSPECTIVE +%token KW_FISHEYE +%token KW_CYLINDRICAL +%token KW_PSPHERE +%token KW_FOCAL_LENGTH +%token KW_FOV +%token KW_SINGULARITY_TOLERANCE +%token KW_RESOLUTION +%token KW_FILENAME +%token KW_POINT +%token KW_SHOW_POINTS +%token KW_IMAGE_SIZE +%token KW_FILM_SIZE +%token KW_GRID +%token KW_UNTEXTURED_COLOR +%token KW_HPR +%token KW_LAYERS +%token KW_STITCH +%token KW_POINTS +%token KW_USING +%token KW_IN +%token KW_MM +%token KW_CM +%token KW_P + +%type command +%type group_command +%type simple_command +%type length +%type length_units +%type resolution +%type resolution_units +%type point +%type vec2 +%type vec3 +%type length_pair +%type color +%type name +%type optional_name + +%% + +stitch_file: + commands + +commands: + empty + | commands command +{ + // parent->add_nested($2); +} + | commands KW_POINTS '{' points_list '}' + ; + +command: + group_command +{ + parent = $1; + cstack.push_back(parent); +} + nested_commands +{ + cstack.pop_back(); + parent = cstack.back(); +} + | simple_command ';' +{ + $$ = $1; +} + ; + +nested_commands: + '{' commands '}' + | ';' + ; + +group_command: + KW_DEFINE name +{ + $$ = new StitchCommand(parent, StitchCommand::C_define); + $$->set_name($2); +} + | KW_LENS optional_name +{ + $$ = new StitchCommand(parent, StitchCommand::C_lens); + $$->set_name($2); +} + | KW_INPUT_IMAGE optional_name +{ + $$ = new StitchCommand(parent, StitchCommand::C_input_image); + $$->set_name($2); +} + | KW_OUTPUT_IMAGE optional_name +{ + $$ = new StitchCommand(parent, StitchCommand::C_output_image); + $$->set_name($2); +} + | KW_STITCH optional_name +{ + $$ = new StitchCommand(parent, StitchCommand::C_stitch); + $$->set_name($2); +} + | KW_USING +{ + cstack.push_back(new StitchCommand(parent, StitchCommand::C_using)); +} + using_list +{ + $$ = cstack.back(); + cstack.pop_back(); +} + ; + +simple_command: + KW_PERSPECTIVE +{ + $$ = new StitchCommand(parent, StitchCommand::C_perspective); +} + | KW_FISHEYE +{ + $$ = new StitchCommand(parent, StitchCommand::C_fisheye); +} + | KW_CYLINDRICAL +{ + $$ = new StitchCommand(parent, StitchCommand::C_cylindrical); +} + | KW_PSPHERE +{ + $$ = new StitchCommand(parent, StitchCommand::C_psphere); +} + | KW_FOCAL_LENGTH length +{ + $$ = new StitchCommand(parent, StitchCommand::C_focal_length); + $$->set_length($2); +} + | KW_FOV NUMBER +{ + $$ = new StitchCommand(parent, StitchCommand::C_fov); + $$->set_number($2); +} + | KW_SINGULARITY_TOLERANCE NUMBER +{ + $$ = new StitchCommand(parent, StitchCommand::C_singularity_tolerance); + $$->set_number($2); +} + | KW_RESOLUTION resolution +{ + $$ = new StitchCommand(parent, StitchCommand::C_resolution); + $$->set_resolution($2); +} + | KW_FILENAME STRING +{ + $$ = new StitchCommand(parent, StitchCommand::C_filename); + $$->set_str($2); +} + | KW_POINT name point +{ + if ($3 == 2) { + $$ = new StitchCommand(parent, StitchCommand::C_point2d); + $$->set_point2d((const LPoint2d &)$3); + } else { + $$ = new StitchCommand(parent, StitchCommand::C_point3d); + $$->set_point3d((const LPoint3d &)$3); + } + $$->set_name($2); +} + | KW_SHOW_POINTS NUMBER color +{ + $$ = new StitchCommand(parent, StitchCommand::C_show_points); + $$->set_number($2); + $$->set_color($3); +} + | KW_IMAGE_SIZE vec2 +{ + $$ = new StitchCommand(parent, StitchCommand::C_image_size); + $$->set_point2d((const LPoint2d &)$2); +} + | KW_FILM_SIZE length_pair +{ + $$ = new StitchCommand(parent, StitchCommand::C_film_size); + $$->set_length_pair((const LPoint2d &)$2); +} + | KW_GRID vec2 +{ + $$ = new StitchCommand(parent, StitchCommand::C_grid); + $$->set_point2d((const LPoint2d &)$2); +} + | KW_UNTEXTURED_COLOR color +{ + $$ = new StitchCommand(parent, StitchCommand::C_untextured_color); + $$->set_color((const Colord &)$2); +} + | KW_HPR vec3 +{ + $$ = new StitchCommand(parent, StitchCommand::C_hpr); + $$->set_point3d((const LPoint3d &)$2); +} + | KW_LAYERS +{ + $$ = new StitchCommand(parent, StitchCommand::C_layers); +} + | IDENTIFIER +{ + $$ = new StitchCommand(parent, StitchCommand::C_user_command); + if (!$$->add_using($1)) { + yyerror("Undefined identifier " + $1); + } +} + ; + +using_list: + IDENTIFIER +{ + if (!cstack.back()->add_using($1)) { + yyerror("Undefined identifier " + $1); + } +} + | using_list ',' IDENTIFIER +{ + if (!cstack.back()->add_using($3)) { + yyerror("Undefined identifier " + $3); + } +} + ; + +points_list: + empty + | points_list name point ';' +{ + StitchCommand *cmd; + if ($3 == 2) { + cmd = new StitchCommand(parent, StitchCommand::C_point2d); + cmd->set_point2d((const LPoint2d &)$3); + } else { + cmd = new StitchCommand(parent, StitchCommand::C_point3d); + cmd->set_point3d((const LPoint3d &)$3); + } + cmd->set_name($2); + // parent->add_nested(cmd); +} + ; + + +length: + NUMBER length_units +{ + $$ = $1 * $2; // convert to mm +} + ; + +length_units: + KW_IN +{ + $$ = 25.4; // in to mm +} + | KW_CM +{ + $$ = 10.0; // cm to mm +} + | KW_MM +{ + $$ = 1.0; +} + ; + + +resolution: + NUMBER resolution_units +{ + $$ = $1 * $2; // convert to pixels per mm +} + ; + +resolution_units: + KW_P '/' length_units +{ + $$ = 1.0 / $3; +} + ; + +point: + '(' NUMBER NUMBER ')' +{ + $$.set($2, $3, 0.0, 0.0); + $$ = 2; +} + | '(' NUMBER ',' NUMBER ')' +{ + $$.set($2, $4, 0.0, 0.0); + $$ = 2; +} + | '(' NUMBER NUMBER NUMBER ')' +{ + $$.set($2, $3, $4, 0.0); + $$ = 3; +} + | '(' NUMBER ',' NUMBER ',' NUMBER ')' +{ + $$.set($2, $4, $6, 0.0); + $$ = 3; +} + ; + +vec2: + '(' NUMBER NUMBER ')' +{ + $$.set($2, $3, 0.0, 0.0); + $$ = 2; +} + | '(' NUMBER ',' NUMBER ')' +{ + $$.set($2, $4, 0.0, 0.0); + $$ = 2; +} + ; + +vec3: '(' NUMBER NUMBER NUMBER ')' +{ + $$.set($2, $3, $4, 0.0); + $$ = 3; +} + | '(' NUMBER ',' NUMBER ',' NUMBER ')' +{ + $$.set($2, $4, $6, 0.0); + $$ = 3; +} + ; + +length_pair: + '(' length length ')' +{ + $$.set($2, $3, 0.0, 0.0); + $$ = 2; +} + | '(' length ',' length ')' +{ + $$.set($2, $4, 0.0, 0.0); + $$ = 2; +} + ; + +color: + '(' NUMBER NUMBER NUMBER ')' +{ + $$.set($2, $3, $4, 1.0); + $$ = 3; +} + | '(' NUMBER ',' NUMBER ',' NUMBER ')' +{ + $$.set($2, $4, $6, 1.0); + $$ = 3; +} + | '(' NUMBER NUMBER NUMBER NUMBER ')' +{ + $$.set($2, $3, $4, $5); + $$ = 4; +} + | '(' NUMBER ',' NUMBER ',' NUMBER ',' NUMBER ')' +{ + $$.set($2, $4, $6, $8); + $$ = 4; +} + ; + +name: + IDENTIFIER + ; + +optional_name: + IDENTIFIER + | empty +{ + $$ = ""; +} + ; + +empty: + ; diff --git a/pandatool/src/stitchbase/stitchParserDefs.h b/pandatool/src/stitchbase/stitchParserDefs.h new file mode 100644 index 0000000000..99f92a14fa --- /dev/null +++ b/pandatool/src/stitchbase/stitchParserDefs.h @@ -0,0 +1,36 @@ +// Filename: stitchParserDefs.h +// Created by: drose (08Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHPARSERDEFS_H +#define STITCHPARSERDEFS_H + +#include + +#include + +class StitchCommand; + +int stitchyyparse(); + +void stitch_init_parser(istream &in, const string &filename, + StitchCommand *tos); + +// This structure holds the return value for each token. +// Traditionally, this is a union, and is declared with the %union +// declaration in the parser.y file, but unions are pretty worthless +// in C++ (you can't include an object that has member functions in a +// union), so we'll use a class instead. That means we need to +// declare it externally, here. + +class YYSTYPE { +public: + double number; + string str; + StitchCommand *command; + LVecBase4d vec; + int num_components; +}; + +#endif diff --git a/pandatool/src/stitchbase/stitchPerspectiveLens.cxx b/pandatool/src/stitchbase/stitchPerspectiveLens.cxx new file mode 100644 index 0000000000..562de5c440 --- /dev/null +++ b/pandatool/src/stitchbase/stitchPerspectiveLens.cxx @@ -0,0 +1,79 @@ +// Filename: stitchPerspectiveLens.cxx +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchPerspectiveLens.h" +#include "stitchCommand.h" + +#include +#include + +#include + +StitchPerspectiveLens:: +StitchPerspectiveLens() { +} + +void StitchPerspectiveLens:: +set_hfov(double fov_deg) { + StitchLens::set_hfov(fov_deg); + _tan_fov = tan(deg_2_rad(_fov / 2.0)) * 2.0; +} + +double StitchPerspectiveLens:: +get_focal_length(double width_mm) const { + if (_flags & F_focal_length) { + return _focal_length; + } + if (_flags & F_fov) { + return width_mm / _tan_fov; + } + return 0.0; +} + +double StitchPerspectiveLens:: +get_hfov(double width_mm) const { + if (_flags & F_fov) { + return _fov; + } + if (_flags & F_focal_length) { + return 2.0 * rad_2_deg(atan(width_mm / (2.0 * _focal_length))); + } + return 0.0; +} + +LVector3d StitchPerspectiveLens:: +extrude(const LPoint2d &point_mm, double width_mm) const { + return LVector3d::rfu(point_mm[0], get_focal_length(width_mm), point_mm[1]); +} + +LPoint2d StitchPerspectiveLens:: +project(const LVector3d &vec, double width_mm) const { + double r = dot(vec, LVector3d::right()); + double f = dot(vec, LVector3d::forward()); + double u = dot(vec, LVector3d::up()); + if (f <= 0.0) { + // If the point is in or behind our view plane, project it out to + // as near to infinity as we can comfortably manage. + return LPoint2d(r / 0.000001, u / 0.000001); + } else { + return LPoint2d(r / f * get_focal_length(width_mm), + u / f * get_focal_length(width_mm)); + } +} + +void StitchPerspectiveLens:: +make_lens_command(StitchCommand *parent) { + StitchCommand *lens_cmd = new StitchCommand(parent, StitchCommand::C_lens); + StitchCommand *cmd; + cmd = new StitchCommand(lens_cmd, StitchCommand::C_perspective); + if (_flags & F_focal_length) { + cmd = new StitchCommand(lens_cmd, StitchCommand::C_focal_length); + cmd->set_length(_focal_length); + } + if (_flags & F_fov) { + cmd = new StitchCommand(lens_cmd, StitchCommand::C_fov); + cmd->set_number(_fov); + } +} diff --git a/pandatool/src/stitchbase/stitchPerspectiveLens.h b/pandatool/src/stitchbase/stitchPerspectiveLens.h new file mode 100644 index 0000000000..a28ac2227a --- /dev/null +++ b/pandatool/src/stitchbase/stitchPerspectiveLens.h @@ -0,0 +1,31 @@ +// Filename: stitchPerspectiveLens.h +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHPERSPECTIVELENS_H +#define STITCHPERSPECTIVELENS_H + +#include "stitchLens.h" + +class StitchPerspectiveLens : public StitchLens { +public: + StitchPerspectiveLens(); + + virtual void set_hfov(double fov_deg); + + virtual double get_focal_length(double width_mm) const; + virtual double get_hfov(double width_mm) const; + + virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const; + virtual LPoint2d project(const LVector3d &vec, double width_mm) const; + + virtual void make_lens_command(StitchCommand *parent); + +private: + double _tan_fov; +}; + +#endif + + diff --git a/pandatool/src/stitchbase/stitchPoint.cxx b/pandatool/src/stitchbase/stitchPoint.cxx new file mode 100644 index 0000000000..aa11cddb2a --- /dev/null +++ b/pandatool/src/stitchbase/stitchPoint.cxx @@ -0,0 +1,20 @@ +// Filename: stitchPoint.cxx +// Created by: drose (04Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchPoint.h" + +StitchPoint:: +StitchPoint(const string &name) : + _name(name) +{ + _space_known = false; +} + +void StitchPoint:: +set_space(const LVector3d &space) { + _space_known = true; + _space = space; +} + diff --git a/pandatool/src/stitchbase/stitchPoint.h b/pandatool/src/stitchbase/stitchPoint.h new file mode 100644 index 0000000000..a6b7538016 --- /dev/null +++ b/pandatool/src/stitchbase/stitchPoint.h @@ -0,0 +1,30 @@ +// Filename: stitchPoint.h +// Created by: drose (04Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHPOINT_H +#define STITCHPOINT_H + +#include + +#include + +class StitchImage; + +class StitchPoint { +public: + StitchPoint(const string &name); + + void set_space(const LVector3d &space); + + string _name; + bool _space_known; + LVector3d _space; + + typedef set Images; + Images _images; +}; + +#endif + diff --git a/pandatool/src/stitchbase/stitcher.cxx b/pandatool/src/stitchbase/stitcher.cxx new file mode 100644 index 0000000000..ad76800ac6 --- /dev/null +++ b/pandatool/src/stitchbase/stitcher.cxx @@ -0,0 +1,464 @@ +// Filename: stitcher.cxx +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitcher.h" +#include "stitchImage.h" +#include "stitchPoint.h" + +#include +#include + + +Stitcher::MatchingPoint:: +MatchingPoint(StitchPoint *p, const LPoint2d &got_uv) : + _p(p), + _got_uv(got_uv) +{ + _need_uv.set(0.0, 0.0); + _diff = 0.0; +} + +Stitcher:: +Stitcher() { + _show_points = false; +} + +Stitcher:: +~Stitcher() { +} + +void Stitcher:: +add_image(StitchImage *image) { + image->_index = _images.size(); + _images.push_back(image); + + // Record all of the points in the image as well. + StitchImage::Points::const_iterator pi; + for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) { + string name = (*pi).first; + + Points::iterator ppi; + ppi = _points.find(name); + StitchPoint *sp; + if (ppi != _points.end()) { + // Previously used point. + sp = (*ppi).second; + } else { + // New point. + sp = new StitchPoint(name); + _points.insert(Points::value_type(name, sp)); + } + sp->_images.insert(image); + } +} + +void Stitcher:: +add_point(const string &name, const LVector3d &vec) { + Points::iterator ppi; + ppi = _points.find(name); + StitchPoint *sp; + if (ppi != _points.end()) { + // Previously used point. + sp = (*ppi).second; + } else { + // New point. + sp = new StitchPoint(name); + _points.insert(Points::value_type(name, sp)); + } + + sp->set_space(normalize(vec)); + _loose_points.push_back(sp); +} + + +void Stitcher:: +show_points(double radius, const Colord &color) { + _show_points = true; + _point_radius = radius; + _point_color = color; +} + + +void Stitcher:: +stitch() { + if (_images.empty()) { + return; + } + + // First place the reference image. All of its points are fixed + // where they are. + if (_loose_points.empty()) { + StitchImage *image = _images.front(); + assert(image != NULL); + + StitchImage::Points::const_iterator pi; + for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) { + string name = (*pi).first; + LPoint2d uv = (*pi).second; + + Points::iterator ppi; + ppi = _points.find(name); + assert(ppi != _points.end()); + StitchPoint *sp = (*ppi).second; + + LVector3d space = normalize(image->extrude(uv)); + sp->set_space(space); + } + + _placed.push_back(image); + + // Report the reference image. + nout << "\n" << *image << "\n"; + _images.erase(_images.begin()); + } + + // Now place each of the other images relative to the already-known + // points. + double net_stitch_score = 0.0; + bool done = false; + + while (!_images.empty() && !done) { + // Find the image with the greatest number of known points. + int max_score = 0; + Images::iterator best_image = _images.end(); + + Images::iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + int score = score_image(*ii); + if (score > max_score) { + max_score = score; + best_image = ii; + } + } + if (best_image == _images.end()) { + // Bad news. None of the images had a score greater than zero, + // so we can't stitch them in--not enough shared points. + done = true; + + } else { + // Now stitch this image in and remove it from the set. + net_stitch_score += stitch_image(*best_image); + _placed.push_back(*best_image); + _images.erase(best_image); + } + } + + // Any of the unplaced images with explicit hpr's get placed where + // they are. + Images::iterator ii = _images.begin(); + while (ii != _images.end()) { + StitchImage *image = (*ii); + if (image->_hpr_set) { + net_stitch_score += stitch_image(image); + _placed.push_back(image); + _images.erase(ii); + } else { + ++ii; + } + } + + if (!_images.empty()) { + nout << "Not enough shared points; " << _images.size() + << " images remain unstitched.\n"; + } + + nout << "Net score is " << net_stitch_score << "\n"; + + // Reorder all of the images by index number order. + sort(_placed.begin(), _placed.end(), StitchImageByIndex()); + + // And feather the edges between them nicely. We don't need to + // feather the first image. + if (_placed.size() > 1) { + nout << "Feathering edges\n"; + Images::iterator ii; + ii = _placed.begin(); + for (++ii; ii != _placed.end(); ++ii) { + feather_image(*ii); + } + } +} + +int Stitcher:: +score_image(StitchImage *image) { + // Give the image one point for each StitchPoint it has that has a + // known location in space. + int score = 0; + + StitchImage::Points::const_iterator pi; + for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) { + string name = (*pi).first; + + Points::iterator ppi; + ppi = _points.find(name); + assert(ppi != _points.end()); + StitchPoint *sp = (*ppi).second; + + if (sp->_space_known) { + score++; + } + } + + // We must have at least two points in common to stitch an image. + if (score < 2) { + score = 0; + } + return score; +} + + +double Stitcher:: +stitch_image(StitchImage *image) { + // First, collect all the points we have that exist somewhere in + // known space. + MatchingPoints mp; + + StitchImage::Points::const_iterator pi; + for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) { + string name = (*pi).first; + LPoint2d uv = (*pi).second; + + Points::iterator ppi; + ppi = _points.find(name); + assert(ppi != _points.end()); + StitchPoint *sp = (*ppi).second; + + if (sp->_space_known) { + mp.push_back(MatchingPoint(sp, uv)); + } + } + + // We need at least two points in common, or one point and an + // explicit hpr to stitch. + if (mp.size() < 2 && !image->_hpr_set) { + nout << "cannot stitch " << image->get_name() << "\n\n"; + return 0.0; + } + + double best_score = 0.0; + + if (mp.empty()) { + // If we have no points, we can at least place it where the hpr says to. + nout << *image << "placed explicitly.\n\n"; + + } else { + // If we have at least one point, we can stitch something. + + // Reset the image's total transform, since we'll be changing it. + image->clear_transform(); + + // Find the best match. + int best_i = -1; + int best_j = -1; + + if (mp.size() < 2) { + // If we don't have two points, there's nothing to choose. + best_i = 0; + best_j = 0; + } else { + for (int i = 0; i < mp.size(); i++) { + for (int j = 0; j < mp.size(); j++) { + if (j != i) { + LMatrix3d rot; + double score = try_match(image, rot, mp, i, j); + if (score < best_score || best_i == -1) { + best_i = i; + best_j = j; + best_score = score; + } + } + } + } + } + + // Now go back and actually use the best match. + LMatrix3d rot; + try_match(image, rot, mp, best_i, best_j); + + image->set_transform(rot); + + if (mp.size() < 2) { + nout << *image << "placed semi-explicitly.\n\n"; + } else { + nout << *image << "score is " << best_score << "\n\n"; + } + + // Now compute the degree of success. + MatchingPoints::iterator mi; + for (mi = mp.begin(); mi != mp.end(); ++mi) { + (*mi)._need_uv = image->project((*mi)._p->_space); + (*mi)._diff = (*mi)._need_uv - (*mi)._got_uv; + } + + // Now morph the image out the last few pixels so that all the + // points will match up exactly. + + int x_verts = image->get_x_verts(); + int y_verts = image->get_y_verts(); + image->_morph.init(x_verts, y_verts); + int x, y; + for (y = 0; y < y_verts; y++) { + for (x = 0; x < x_verts; x++) { + LPoint2d p = image->get_grid_uv(x, y); + LVector2d offset(0.0, 0.0); + double net = 0.0; + + MatchingPoints::const_iterator cmi; + bool done = false; + for (cmi = mp.begin(); cmi != mp.end() && !done; ++cmi) { + LVector2d v = p - (*cmi)._got_uv; + double d = pow(dot(v, v), 0.1); + if (d < 0.0001) { + // This one is dead on; stop here and never mind. + offset = (*cmi)._diff; + net = 1.0; + done = true; + } else { + double scale = 1.0 / d; + offset += (*cmi)._diff * scale; + net += scale; + } + } + + offset /= net; + image->_morph._table[y][x]._p[MorphGrid::TT_out] += offset; + } + } + image->_morph.recompute(); + + + /* + + for (mi = mp.begin(); mi != mp.end(); ++mi) { + LVector3d va = normalize(LVector3d(image->extrude((*mi)._got_uv))); + LVector3d vb = (*mi)._p->_space; + nout << 1000.0 * (1.0 - dot(va, vb)) << " for " << (*mi)._p->_name + << "\n at " << va << " vs. " << vb << "\n"; + } + nout << "\n"; + + // Report the final results, including the morphs, to the user. + for (mi = mp.begin(); mi != mp.end(); ++mi) { + (*mi)._need_uv = image->project((*mi)._p->_space); + (*mi)._diff = (*mi)._need_uv - (*mi)._got_uv; + + nout << (*mi)._p->_name + << " "<< (*mi)._need_uv << " vs. " << (*mi)._got_uv + << " diff is " << length((*mi)._diff * image->_uv_to_pixels) + << " pixels\n"; + } + */ + + // Finally, mark all of the other points in this image as now known + // points in space. + for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) { + string name = (*pi).first; + LPoint2d uv = (*pi).second; + + Points::iterator ppi; + ppi = _points.find(name); + assert(ppi != _points.end()); + StitchPoint *sp = (*ppi).second; + + if (!sp->_space_known) { + LVector3d space = normalize(image->extrude(uv)); + sp->set_space(space); + } + } + } + + return best_score; +} + +void Stitcher:: +feather_image(StitchImage *image) { + // Feather the edges of the image wherever it overlaps with an image + // we have laid down previously. We do this by first determining + // which morph points overlap with some other image. + int x_verts = image->get_x_verts(); + int y_verts = image->get_y_verts(); + + if (image->_morph.is_empty()) { + image->_morph.init(x_verts, y_verts); + image->_morph.recompute(); + } + + int x, y; + for (y = 0; y < y_verts; y++) { + for (x = 0; x < x_verts; x++) { + LVector3d space = image->get_grid_vector(x, y); + Images::const_iterator ii; + for (ii = _placed.begin(); + ii != _placed.end() && + !image->_morph._table[y][x]._over_another; + ++ii) { + StitchImage *other = (*ii); + if (other->_index < image->_index) { + LPoint2d uv = other->project(space); + if (uv[0] >= 0.0 && uv[0] <= 1.0 && + uv[1] >= 0.0 && uv[1] <= 1.0) { + // This point is over the other image. + image->_morph._table[y][x]._over_another = true; + } + } + } + } + } + + image->_morph.fill_alpha(); +} + + +double Stitcher:: +try_match(StitchImage *image, LMatrix3d &rot, + const Stitcher::MatchingPoints &mp, int zero, int one) { + + // Now rotate this image relative to the other so the first pair of + // points exactly coincide. + LVector3d v0a = normalize(image->extrude(mp[zero]._got_uv)); + LVector3d v0b = mp[zero]._p->_space; + + rotate_to(rot, v0a, v0b); + + if (zero == one) { + // Here's a special case: only one matching point. In this case, + // we roll by the explicit angle given by the user. + if (image->_hpr_set) { + rot = rot * LMatrix3d::rotate_mat(image->_hpr[2], v0b); + } + + } else { + // Now (v0a * rot) == v0b. Roll about this vector till the + // second pair of points comes as close as possible to coinciding. + LVector3d v1a = normalize(image->extrude(mp[one]._got_uv)); + LVector3d v1b = mp[one]._p->_space; + + v1a = v1a * rot; + + // We need to determine the appropriate angle to roll. This is the + // angle between the plane that contains v0 and v1a, and the plane + // that contains v0 and v1b. + + LVector3d normal_a = normalize(cross(v0b, v1a)); + LVector3d normal_b = normalize(cross(v0b, v1b)); + + double cos_theta = dot(normal_a, normal_b); + double theta = rad_2_deg(acos(cos_theta)); + + rot = rot * LMatrix3d::rotate_mat(-theta, v0b); + } + + // Now compute the score. + double score = 0.0; + + MatchingPoints::const_iterator mi; + for (mi = mp.begin(); mi != mp.end(); ++mi) { + LVector3d va = normalize(LVector3d(image->extrude((*mi)._got_uv) * rot)); + LVector3d vb = (*mi)._p->_space; + score += 1.0 - dot(va, vb); + } + + return 1000.0 * score; +} diff --git a/pandatool/src/stitchbase/stitcher.h b/pandatool/src/stitchbase/stitcher.h new file mode 100644 index 0000000000..587663d287 --- /dev/null +++ b/pandatool/src/stitchbase/stitcher.h @@ -0,0 +1,62 @@ +// Filename: stitcher.h +// Created by: drose (09Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHER_H +#define STITCHER_H + +#include + +#include +#include + +class StitchPoint; +class StitchImage; + +class Stitcher { +public: + Stitcher(); + ~Stitcher(); + + void add_image(StitchImage *image); + void add_point(const string &name, const LVector3d &vec); + void show_points(double radius, const Colord &color); + void stitch(); + + typedef vector Images; + Images _placed; + + typedef vector LoosePoints; + LoosePoints _loose_points; + bool _show_points; + double _point_radius; + Colord _point_color; + +private: + class MatchingPoint { + public: + MatchingPoint(StitchPoint *p, const LPoint2d &got_uv); + + StitchPoint *_p; + LPoint2d _need_uv; + LPoint2d _got_uv; + LVector2d _diff; + }; + typedef vector MatchingPoints; + + int score_image(StitchImage *image); + double stitch_image(StitchImage *image); + void feather_image(StitchImage *image); + + double try_match(StitchImage *image, LMatrix3d &rot, + const MatchingPoints &mp, int zero, int one); + + Images _images; + + typedef map Points; + Points _points; +}; + + +#endif diff --git a/pandatool/src/stitchbase/triangle.cxx b/pandatool/src/stitchbase/triangle.cxx new file mode 100644 index 0000000000..02a0624353 --- /dev/null +++ b/pandatool/src/stitchbase/triangle.cxx @@ -0,0 +1,54 @@ +// Filename: triangle.cxx +// Created by: drose (16Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "triangle.h" + +inline int +is_right(const LVector2d &v1, const LVector2d &v2) { + return (-v1[0] * v2[1] + v1[1] * v2[0]) < 0.0; +} + +bool +triangle_contains_point(const LPoint2d &p, const LPoint2d &v0, + const LPoint2d &v1, const LPoint2d &v2) { + // In the case of a triangle defined with points in counterclockwise + // order, a point is interior to the triangle iff the point is not + // right of each of the edges. + + if (is_right(p - v0, v1 - v0)) { + return false; + } + if (is_right(p - v1, v2 - v1)) { + return false; + } + if (is_right(p - v2, v0 - v2)) { + return false; + } + + return true; +} + +bool +triangle_contains_circle(const LPoint2d &p, double radius, + const LPoint2d &v0, + const LPoint2d &v1, const LPoint2d &v2) { + // This is a cheesy hack. Instead of performing an actual + // triangle-circle intersection test, we simply move the point + // radius units closer to the centroid of the triangle, and test + // that point for intersection. + + LPoint2d centroid = (v0 + v1 + v2) / 3.0; + + LVector2d vec = centroid - p; + double d = length(vec); + if (d <= radius) { + // We were already closer than radius distance from the centroid; + // this is an automatic intersection. + return true; + } + + LPoint2d new_p = p + radius * (vec / d); + return triangle_contains_point(new_p, v0, v1, v2); +} diff --git a/pandatool/src/stitchbase/triangle.h b/pandatool/src/stitchbase/triangle.h new file mode 100644 index 0000000000..849c8d569b --- /dev/null +++ b/pandatool/src/stitchbase/triangle.h @@ -0,0 +1,24 @@ +// Filename: triangle.h +// Created by: drose (16Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TRIANGLE_H +#define TRIANGLE_H + +#include + +// A handy triangle utility. Maybe more later. + +// The triangle must be defined with vertices in counter-clockwise +// order. +bool +triangle_contains_point(const LPoint2d &p, const LPoint2d &v0, + const LPoint2d &v1, const LPoint2d &v2); + +bool +triangle_contains_circle(const LPoint2d &p, double radius, + const LPoint2d &v0, + const LPoint2d &v1, const LPoint2d &v2); + +#endif diff --git a/pandatool/src/stitchbase/triangleRasterizer.cxx b/pandatool/src/stitchbase/triangleRasterizer.cxx new file mode 100644 index 0000000000..7cc7b0dd99 --- /dev/null +++ b/pandatool/src/stitchbase/triangleRasterizer.cxx @@ -0,0 +1,569 @@ +// Filename: triangleRasterizer.cxx +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "triangleRasterizer.h" +#include "stitchImage.h" + +// Inline function declared up here for the forward reference. +inline void TriangleRasterizer:: +filter_pixel(RGBColord &rgb, double &alpha, + double s, double t, + double dsdx, double dtdx, double dsdy, double dtdy) { + filter_pixel(rgb, alpha, s, t, + max(max(dsdx, dtdx), max(dsdy, dtdy)) / 2.0); +} + + +TriangleRasterizer::Edge:: +Edge(const RasterizerVertex *v0, const RasterizerVertex *v1) : + _v0(v0), _v1(v1) +{ + _dx = v1->_p[0] - v0->_p[0]; + _dy = v1->_p[1] - v0->_p[1]; +} + +TriangleRasterizer:: +TriangleRasterizer() { + _output = NULL; + _input = NULL; + _read_input = false; + _texture = NULL; + _filter_output = false; + _untextured_color.set(1.0, 1.0, 1.0, 1.0); +} + +void TriangleRasterizer:: +draw_triangle(const RasterizerVertex *v0, + const RasterizerVertex *v1, + const RasterizerVertex *v2) { + if ((v0->_visibility & v1->_visibility & v2->_visibility) != 0) { + // All three vertices are out of bounds in the same direction, so + // the triangle is completely out of bounds. Don't bother trying + // to draw it. + return; + } + + assert(_output != NULL); + if (!_read_input) { + read_input(); + } + + double oneOverArea; + const RasterizerVertex *vMin, *vMid, *vMax; + /* Y(vMin)<=Y(vMid)<=Y(vMax) */ + + /* find the order of the 3 vertices along the Y axis */ + { + double y0 = v0->_p[1]; + double y1 = v1->_p[1]; + double y2 = v2->_p[1]; + + if (y0<=y1) { + if (y1<=y2) { + vMin = v0; vMid = v1; vMax = v2; /* y0<=y1<=y2 */ + } else if (y2<=y0) { + vMin = v2; vMid = v0; vMax = v1; /* y2<=y0<=y1 */ + } else { + vMin = v0; vMid = v2; vMax = v1; /* y0<=y2<=y1 */ + } + } else { + if (y0<=y2) { + vMin = v1; vMid = v0; vMax = v2; /* y1<=y0<=y2 */ + } else if (y2<=y1) { + vMin = v2; vMid = v1; vMax = v0; /* y2<=y1<=y0 */ + } else { + vMin = v1; vMid = v2; vMax = v0; /* y1<=y2<=y0 */ + } + } + } + + /* vertex/edge relationship */ + Edge eMaj(vMin, vMax); + Edge eTop(vMid, vMax); + Edge eBot(vMin, vMid); + + /* compute oneOverArea */ + { + double area = eMaj._dx * eBot._dy - eBot._dx * eMaj._dy; + + // We can't cull very small triangles; we might generate small + // triangles through normal operations. + + /* + if (area>-0.05 && area<0.05) { + return; // very small; CULLED + } + */ + oneOverArea = 1.0 / area; + } + + /* Edge setup. For a triangle strip these could be reused... */ + { + /* fixed point Y coordinates */ + FixedPoint vMin_fx = FloatToFixed(vMin->_p[0] + 0.5); + FixedPoint vMin_fy = FloatToFixed(vMin->_p[1] - 0.5); + FixedPoint vMid_fx = FloatToFixed(vMid->_p[0] + 0.5); + FixedPoint vMid_fy = FloatToFixed(vMid->_p[1] - 0.5); + FixedPoint vMax_fy = FloatToFixed(vMax->_p[1] - 0.5); + + eMaj._fsy = FixedCeil(vMin_fy); + eMaj._lines = FixedToInt(vMax_fy + FIXED_ONE - FIXED_EPSILON - eMaj._fsy); + if (eMaj._lines > 0) { + double dxdy = eMaj._dx / eMaj._dy; + eMaj._fdxdy = SignedFloatToFixed(dxdy); + eMaj._adjy = (double) (eMaj._fsy - vMin_fy); /* SCALED! */ + eMaj._fx0 = vMin_fx; + eMaj._fsx = eMaj._fx0 + (FixedPoint) (eMaj._adjy * dxdy); + } + else { + return; /*CULLED*/ + } + + eTop._fsy = FixedCeil(vMid_fy); + eTop._lines = FixedToInt(vMax_fy + FIXED_ONE - FIXED_EPSILON - eTop._fsy); + if (eTop._lines > 0) { + double dxdy = eTop._dx / eTop._dy; + eTop._fdxdy = SignedFloatToFixed(dxdy); + eTop._adjy = (double) (eTop._fsy - vMid_fy); /* SCALED! */ + eTop._fx0 = vMid_fx; + eTop._fsx = eTop._fx0 + (FixedPoint) (eTop._adjy * dxdy); + } + + eBot._fsy = FixedCeil(vMin_fy); + eBot._lines = FixedToInt(vMid_fy + FIXED_ONE - FIXED_EPSILON - eBot._fsy); + if (eBot._lines > 0) { + double dxdy = eBot._dx / eBot._dy; + eBot._fdxdy = SignedFloatToFixed(dxdy); + eBot._adjy = (double) (eBot._fsy - vMin_fy); /* SCALED! */ + eBot._fx0 = vMin_fx; + eBot._fsx = eBot._fx0 + (FixedPoint) (eBot._adjy * dxdy); + } + } + + /* + * Conceptually, we view a triangle as two subtriangles + * separated by a perfectly horizontal line. The edge that is + * intersected by this line is one with maximal absolute dy; we + * call it a ``major'' edge. The other two edges are the + * ``top'' edge (for the upper subtriangle) and the ``bottom'' + * edge (for the lower subtriangle). If either of these two + * edges is horizontal or very close to horizontal, the + * corresponding subtriangle might cover zero sample points; + * we take care to handle such cases, for performance as well + * as correctness. + * + * By stepping rasterization parameters along the major edge, + * we can avoid recomputing them at the discontinuity where + * the top and bottom edges meet. However, this forces us to + * be able to scan both left-to-right and right-to-left. + * Also, we must determine whether the major edge is at the + * left or right side of the triangle. We do this by + * computing the magnitude of the cross-product of the major + * and top edges. Since this magnitude depends on the sine of + * the angle between the two edges, its sign tells us whether + * we turn to the left or to the right when travelling along + * the major edge to the top edge, and from this we infer + * whether the major edge is on the left or the right. + * + * Serendipitously, this cross-product magnitude is also a + * value we need to compute the iteration parameter + * derivatives for the triangle, and it can be used to perform + * backface culling because its sign tells us whether the + * triangle is clockwise or counterclockwise. In this code we + * refer to it as ``area'' because it's also proportional to + * the pixel area of the triangle. + */ + + { + int ltor; /* true if scanning left-to-right */ + + // For interpolating the alpha value. + double dadx, dady; + FixedPoint fdadx; + + // For interpolating texture coordinates. + double dsdx, dsdy; + FixedPoint fdsdx; + double dtdx, dtdy; + FixedPoint fdtdx; + + // Set up values for texture coordinates. + + double twidth, theight; + if (_texture != NULL) { + twidth = (double) _texture->get_x_size(); + theight = (double) _texture->get_y_size(); + } else { + twidth = 1.0; + theight = 1.0; + } + + ltor = (oneOverArea < 0.0); + + // More alpha setup. + { + double eMaj_da, eBot_da; + eMaj_da = vMax->_alpha - vMin->_alpha; + eBot_da = vMid->_alpha - vMin->_alpha; + dadx = oneOverArea * (eMaj_da * eBot._dy - eMaj._dy * eBot_da); + fdadx = SignedFloatToFixed(dadx); + dady = oneOverArea * (eMaj._dx * eBot_da - eMaj_da * eBot._dx); + } + + // Texture coordinates. + { + double eMaj_ds, eBot_ds; + eMaj_ds = (vMax->_uv[0] - vMin->_uv[0]) * twidth; + eBot_ds = (vMid->_uv[0] - vMin->_uv[0]) * twidth; + + dsdx = oneOverArea * (eMaj_ds * eBot._dy - eMaj._dy * eBot_ds); + fdsdx = SignedFloatToFixed(dsdx); + dsdy = oneOverArea * (eMaj._dx * eBot_ds - eMaj_ds * eBot._dx); + } + { + double eMaj_dt, eBot_dt; + eMaj_dt = (vMax->_uv[1] - vMin->_uv[1]) * theight; + eBot_dt = (vMid->_uv[1] - vMin->_uv[1]) * theight; + + dtdx = oneOverArea * (eMaj_dt * eBot._dy - eMaj._dy * eBot_dt); + fdtdx = SignedFloatToFixed(dtdx); + dtdy = oneOverArea * (eMaj._dx * eBot_dt - eMaj_dt * eBot._dx); + } + + /* + * We always sample at pixel centers. However, we avoid + * explicit half-pixel offsets in this code by incorporating + * the proper offset in each of x and y during the + * transformation to window coordinates. + * + * We also apply the usual rasterization rules to prevent + * cracks and overlaps. A pixel is considered inside a + * subtriangle if it meets all of four conditions: it is on or + * to the right of the left edge, strictly to the left of the + * right edge, on or below the top edge, and strictly above + * the bottom edge. (Some edges may be degenerate.) + * + * The following discussion assumes left-to-right scanning + * (that is, the major edge is on the left); the right-to-left + * case is a straightforward variation. + * + * We start by finding the half-integral y coordinate that is + * at or below the top of the triangle. This gives us the + * first scan line that could possibly contain pixels that are + * inside the triangle. + * + * Next we creep down the major edge until we reach that y, + * and compute the corresponding x coordinate on the edge. + * Then we find the half-integral x that lies on or just + * inside the edge. This is the first pixel that might lie in + * the interior of the triangle. (We won't know for sure + * until we check the other edges.) + * + * As we rasterize the triangle, we'll step down the major + * edge. For each step in y, we'll move an integer number + * of steps in x. There are two possible x step sizes, which + * we'll call the ``inner'' step (guaranteed to land on the + * edge or inside it) and the ``outer'' step (guaranteed to + * land on the edge or outside it). The inner and outer steps + * differ by one. During rasterization we maintain an error + * term that indicates our distance from the true edge, and + * select either the inner step or the outer step, whichever + * gets us to the first pixel that falls inside the triangle. + * + * All parameters (z, red, etc.) as well as the buffer + * addresses for color and z have inner and outer step values, + * so that we can increment them appropriately. This method + * eliminates the need to adjust parameters by creeping a + * sub-pixel amount into the triangle at each scanline. + */ + + { + int subTriangle; + FixedPoint fx, fxLeftEdge, fxRightEdge, fdxLeftEdge, fdxRightEdge; + FixedPoint fdxOuter; + int idxOuter; + double dxOuter; + FixedPoint fError, fdError; + double adjx, adjy; + FixedPoint fy; + int iy; + + // Alpha. + FixedPoint fa, fdaOuter, fdaInner; + + // Texture coordinates. + FixedPoint fs, fdsOuter, fdsInner; + FixedPoint ft, fdtOuter, fdtInner; + + for (subTriangle=0; subTriangle<=1; subTriangle++) { + Edge *eLeft, *eRight; + int setupLeft, setupRight; + int lines; + + if (subTriangle==0) { + /* bottom half */ + if (ltor) { + eLeft = &eMaj; + eRight = &eBot; + lines = eRight->_lines; + setupLeft = 1; + setupRight = 1; + } + else { + eLeft = &eBot; + eRight = &eMaj; + lines = eLeft->_lines; + setupLeft = 1; + setupRight = 1; + } + } + else { + /* top half */ + if (ltor) { + eLeft = &eMaj; + eRight = &eTop; + lines = eRight->_lines; + setupLeft = 0; + setupRight = 1; + } + else { + eLeft = &eTop; + eRight = &eMaj; + lines = eLeft->_lines; + setupLeft = 1; + setupRight = 0; + } + if (lines==0) return; + } + + if (setupLeft && eLeft->_lines>0) { + const RasterizerVertex *vLower; + FixedPoint fsx = eLeft->_fsx; + fx = FixedCeil(fsx); + fError = fx - fsx - FIXED_ONE; + fxLeftEdge = fsx - FIXED_EPSILON; + fdxLeftEdge = eLeft->_fdxdy; + fdxOuter = FixedFloor(fdxLeftEdge - FIXED_EPSILON); + fdError = fdxOuter - fdxLeftEdge + FIXED_ONE; + idxOuter = FixedToInt(fdxOuter); + dxOuter = (double) idxOuter; + + fy = eLeft->_fsy; + iy = FixedToInt(fy); + + adjx = (double)(fx - eLeft->_fx0); /* SCALED! */ + adjy = eLeft->_adjy; /* SCALED! */ + + vLower = eLeft->_v0; + + /* + * Now we need the set of parameter (z, color, etc.) values at + * the point (fx, fy). This gives us properly-sampled parameter + * values that we can step from pixel to pixel. Furthermore, + * although we might have intermediate results that overflow + * the normal parameter range when we step temporarily outside + * the triangle, we shouldn't overflow or underflow for any + * pixel that's actually inside the triangle. + */ + + // Interpolate alpha + fa = (FixedPoint)(vLower->_alpha * FIXED_SCALE + dadx * adjx + dady * adjy) + + FIXED_HALF; + fdaOuter = SignedFloatToFixed(dady + dxOuter * dadx); + // Interpolate texture coordinates + { + double s0, t0; + s0 = vLower->_uv[0] * twidth; + fs = (FixedPoint)(s0 * FIXED_SCALE + dsdx * adjx + dsdy * adjy) + FIXED_HALF; + fdsOuter = SignedFloatToFixed(dsdy + dxOuter * dsdx); + t0 = vLower->_uv[1] * theight; + ft = (FixedPoint)(t0 * FIXED_SCALE + dtdx * adjx + dtdy * adjy) + FIXED_HALF; + fdtOuter = SignedFloatToFixed(dtdy + dxOuter * dtdx); + } + + } /*if setupLeft*/ + + + if (setupRight && eRight->_lines>0) { + fxRightEdge = eRight->_fsx - FIXED_EPSILON; + fdxRightEdge = eRight->_fdxdy; + } + + if (lines==0) { + continue; + } + + /* Rasterize setup */ + fdaInner = fdaOuter + fdadx; + fdsInner = fdsOuter + fdsdx; + fdtInner = fdtOuter + fdtdx; + + while (lines>0) { + if (iy >= 0 && iy < _output->get_y_size()) { + /* initialize the span interpolants to the leftmost value */ + /* ff = fixed-pt fragment */ + FixedPoint ffa = fa; + FixedPoint ffs = fs, fft = ft; + + int left = FixedToInt(fxLeftEdge); + int right = FixedToInt(fxRightEdge); + + // Alpha + { + // FixedPoint ffaend = ffa+(right-left-1)*fdadx; + // if (ffaend<0) ffa -= ffaend; + // if (ffa<0) ffa = 0; + } + + // Rasterize left to right at row iy. + if (right > left) { + ffs -= FIXED_HALF; /* off-by-one error? */ + fft -= FIXED_HALF; + ffa -= FIXED_HALF; + for (int ix = left; ix < right; ix++) { + if (ix >= 0 && ix < _output->get_x_size()) { + RGBColord rgb; + double alpha; + filter_pixel(rgb, alpha, + FixedToFloat(ffs), FixedToFloat(fft), + dsdx, dtdx, dsdy, dtdy); + alpha *= FixedToFloat(ffa); + _output->blend(ix, iy, rgb, alpha); + } + + ffs += fdsdx; + fft += fdtdx; + ffa += fdadx; + } + } + } + + /* + * Advance to the next scan line. Compute the + * new edge coordinates, and adjust the + * pixel-center x coordinate so that it stays + * on or inside the major edge. + */ + iy++; + lines--; + + fxLeftEdge += fdxLeftEdge; + fxRightEdge += fdxRightEdge; + + fError += fdError; + if (fError >= 0) { + fError -= FIXED_ONE; + + fa += fdaOuter; + fs += fdsOuter; + ft += fdtOuter; + } else { + fa += fdaInner; + fs += fdsInner; + ft += fdtInner; + } + } /*while lines>0*/ + + } /* for subTriangle */ + + } + } +} + +void TriangleRasterizer:: +draw_pixel(const RasterizerVertex *v0, double radius) { + if (v0->_visibility != 0) { + // The pixel is off the screen. + return; + } + int ix = (int)v0->_p[0]; + int iy = (int)v0->_p[1]; + + if (iy >= 0 && iy < _output->get_y_size() && + ix >= 0 && ix < _output->get_x_size()) { + if (!_read_input) { + read_input(); + } + + RGBColord rgb; + double alpha; + if (_texture == NULL) { + filter_pixel(rgb, alpha, v0->_uv[0], v0->_uv[1], radius); + } else { + filter_pixel(rgb, alpha, + v0->_uv[0] * (_texture->get_x_size() - 1), + v0->_uv[1] * (_texture->get_y_size() - 1), + radius * (_texture->get_x_size() - 1)); + } + alpha *= v0->_alpha; + _output->blend(ix, iy, rgb, alpha); + } +} + +void TriangleRasterizer:: +filter_pixel(RGBColord &rgb, double &alpha, + double s, double t, double radius) { + if (_texture == NULL) { + rgb.set(_untextured_color[0], + _untextured_color[1], + _untextured_color[2]); + alpha = _untextured_color[3]; + return; + } + + int ri = (int)radius; + int si = (int)(s + 0.5); + int ti = _texture->get_y_size() - 1 - (int)(t + 0.5); + + int n = 0; + rgb.set(0.0, 0.0, 0.0); + alpha = 0.0; + + if (!_filter_output) { + if (si >= 0 && si < _texture->get_x_size() && + ti >= 0 && ti < _texture->get_y_size()) { + rgb = _texture->get_xel(si, ti); + alpha = 1.0; + } + return; + } + + for (int yr = -ri; yr <= ri; yr++) { + int tii = ti + yr; + for (int xr = -ri; xr <= ri; xr++) { + int sii = si + xr; + if (sii >= 0 && sii < _texture->get_x_size() && + tii >= 0 && tii < _texture->get_y_size()) { + rgb += _texture->get_xel(sii, tii); + alpha += 1.0; + } + n++; + } + } + + if (alpha != 0.0) { + rgb /= alpha; + } + + // We would do this to antialias the edge of the image. However, it + // seems to cause problems at seams, so we won't do it. + /* + if (n != 0) { + alpha = alpha / (double)n; + } + */ +} + +void TriangleRasterizer:: +read_input() { + if (_input != NULL) { + if (!_input->read_file()) { + nout << "Unable to read image.\n"; + } else { + _texture = _input->_data; + } + } + _read_input = true; +} diff --git a/pandatool/src/stitchbase/triangleRasterizer.h b/pandatool/src/stitchbase/triangleRasterizer.h new file mode 100644 index 0000000000..372c7211d6 --- /dev/null +++ b/pandatool/src/stitchbase/triangleRasterizer.h @@ -0,0 +1,68 @@ +// Filename: triangleRasterizer.h +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TRIANGLERASTERIZER_H +#define TRIANGLERASTERIZER_H + +#include "fixedPoint.h" + +#include +#include + +class StitchImage; + +class RasterizerVertex { +public: + LPoint2d _p; + LPoint2d _uv; + LVector3d _space; + double _alpha; + int _visibility; +}; + +class TriangleRasterizer { +public: + TriangleRasterizer(); + + void draw_triangle(const RasterizerVertex *v0, + const RasterizerVertex *v1, + const RasterizerVertex *v2); + void draw_pixel(const RasterizerVertex *v0, double radius); + + PNMImage *_output; + StitchImage *_input; + bool _read_input; + const PNMImage *_texture; + bool _filter_output; + Colord _untextured_color; + +private: + class Edge { + public: + Edge(const RasterizerVertex *v0, const RasterizerVertex *v1); + + const RasterizerVertex *_v0; // Y(v0) < Y(v1) + const RasterizerVertex *_v1; + double _dx; // X(v1) - X(v0) + double _dy; // Y(v1) - Y(v0) + FixedPoint _fdxdy; // dx/dy in fixed-point + FixedPoint _fsx; // first sample point x coord + FixedPoint _fsy; + double _adjy; // adjust from v[0]->fy to fsy, scaled + int _lines; // number of lines to be sampled on this edge + FixedPoint _fx0; // fixed pt X of lower endpoint + }; + + inline void filter_pixel(RGBColord &rgb, double &alpha, + double s, double t, + double dsdx, double dtdx, double dsdy, double dtdy); + void filter_pixel(RGBColord &rgb, double &alpha, + double s, double t, double radius); + + void read_input(); +}; + +#endif + diff --git a/pandatool/src/stitchviewer/Sources.pp b/pandatool/src/stitchviewer/Sources.pp new file mode 100644 index 0000000000..aee8c4a690 --- /dev/null +++ b/pandatool/src/stitchviewer/Sources.pp @@ -0,0 +1,19 @@ +#begin lib_target + #define TARGET stitchviewer + #define LOCAL_LIBS \ + stitchbase + #define OTHER_LIBS \ + device:c tform:c graph:c dgraph:c sgraph:c gobj:c pnmimage:c \ + sgattrib:c event:c chancfg:c display:c sgraphutil:c light:c putil:c \ + express:c panda:m + + #define SOURCES \ + stitchImageConverter.cxx stitchImageConverter.h \ + stitchImageVisualizer.cxx stitchImageVisualizer.h triangleMesh.cxx \ + triangleMesh.h + + #define INSTALL_HEADERS \ + stitchImageConverter.h stitchImageVisualizer.h triangleMesh.h + +#end lib_target + diff --git a/pandatool/src/stitchviewer/stitchImageConverter.cxx b/pandatool/src/stitchviewer/stitchImageConverter.cxx new file mode 100644 index 0000000000..56e13c4716 --- /dev/null +++ b/pandatool/src/stitchviewer/stitchImageConverter.cxx @@ -0,0 +1,120 @@ +// Filename: stitchImageConverter.cxx +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchImageConverter.h" +#include "stitchImage.h" +#include "triangleMesh.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +StitchImageConverter:: +StitchImageConverter() { + _output_image = NULL; +} + +void StitchImageConverter:: +add_output_image(StitchImage *image) { + _output_image = image; +} + +void StitchImageConverter:: +override_chan_cfg(ChanCfgOverrides &override) { + override.setField(ChanCfgOverrides::Mask, + ((unsigned int)(W_DOUBLE|W_DEPTH|W_MULTISAMPLE))); + override.setField(ChanCfgOverrides::Title, "Stitch"); + + LVecBase2d size = _output_image->get_size_pixels(); + + override.setField(ChanCfgOverrides::SizeX, (int)size[0]); + override.setField(ChanCfgOverrides::SizeY, (int)size[1]); +} + +void StitchImageConverter:: +setup_camera(const RenderRelation &camera_arc) { + PT(Camera) cam = DCAST(Camera, camera_arc.get_child()); + + Frustumf frust; + frust._t = 0.5; + frust._b = -0.5; + frust._l = -0.5; + frust._r = 0.5; + frust._fnear = 0.5; + frust._ffar = 2.0; + + PerspectiveProjection proj(frust); + cam->set_projection(proj); +} + +bool StitchImageConverter:: +is_interactive() const { + //return false; + return true; +} + +void StitchImageConverter:: +create_image_geometry(Image &im) { + assert(_output_image != NULL); + + double dist = 1.0 + (double)im._index / (double)_images.size(); +#if 0 + int x_verts = _output_image->get_x_verts(); + int y_verts = _output_image->get_y_verts(); + TriangleMesh mesh(x_verts, y_verts); + + for (int xi = 0; xi < x_verts; xi++) { + for (int yi = 0; yi < y_verts; yi++) { + LVector2d uvd = + im._image->project(_output_image->get_grid_vector(xi, yi)); + LVector2f uvf(uvd); + + LVector3f p = LVector3f::rfu(2 * (double)xi / (double)(x_verts - 1) - 1, + 1.0, + 1 - 2 * (double)yi / (double)(y_verts - 1)); + mesh._coords.push_back(p); + mesh._texcoords.push_back(uvf); + } + } +#else + int x_verts = im._image->get_x_verts(); + int y_verts = im._image->get_y_verts(); + TriangleMesh mesh(x_verts, y_verts); + + for (int xi = 0; xi < x_verts; xi++) { + for (int yi = 0; yi < y_verts; yi++) { + LVector2d uvd = + _output_image->project(im._image->get_grid_vector(xi, yi)); + + LVector3f p = LVector3f::rfu(2 * uvd[0] - 1, + 1.0, + 2 * uvd[1] - 1); + LPoint2f uvf((double)xi / (double)(x_verts - 1), + 1.0 - (double)yi / (double)(y_verts - 1)); + mesh._coords.push_back(p); + mesh._texcoords.push_back(uvf); + } + } +#endif + + PT(GeomTristrip) geom = mesh.build_mesh(); + + PT(GeomNode) node = new GeomNode; + node->add_geom(geom.p()); + + im._arc = new RenderRelation(_render, node); + + if (im._image->_data != NULL) { + im._tex = new Texture; + im._tex->set_name(im._image->get_filename()); + im._tex->load(*im._image->_data); + im._arc->set_transition(new TextureTransition(im._tex)); + } +} diff --git a/pandatool/src/stitchviewer/stitchImageConverter.h b/pandatool/src/stitchviewer/stitchImageConverter.h new file mode 100644 index 0000000000..340e300fc0 --- /dev/null +++ b/pandatool/src/stitchviewer/stitchImageConverter.h @@ -0,0 +1,28 @@ +// Filename: stitchImageConverter.h +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHIMAGECONVERTER_H +#define STITCHIMAGECONVERTER_H + +#include "stitchImageVisualizer.h" + +class StitchImage; + +class StitchImageConverter : public StitchImageVisualizer { +public: + StitchImageConverter(); + + virtual void add_output_image(StitchImage *image); + +protected: + virtual void override_chan_cfg(ChanCfgOverrides &override); + virtual void setup_camera(const RenderRelation &camera_arc); + virtual bool is_interactive() const; + virtual void create_image_geometry(Image &im); + + StitchImage *_output_image; +}; + +#endif diff --git a/pandatool/src/stitchviewer/stitchImageVisualizer.cxx b/pandatool/src/stitchviewer/stitchImageVisualizer.cxx new file mode 100644 index 0000000000..0f9c4c55fc --- /dev/null +++ b/pandatool/src/stitchviewer/stitchImageVisualizer.cxx @@ -0,0 +1,291 @@ +// Filename: stitchImageVisualizer.cxx +// Created by: drose (05Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "stitchImageVisualizer.h" +#include "config_stitch.h" +#include "triangleMesh.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +StitchImageVisualizer *StitchImageVisualizer::_static_siv; + +StitchImageVisualizer::Image:: +Image(StitchImage *image, int index, bool scale) : + _image(image), + _index(index) +{ + _arc = NULL; + _viz = true; + + if (!image->read_file()) { + nout << "Unable to read image.\n"; + } + + if (scale && image->_data != NULL) { + nout << "Scaling " << image->get_name() << "\n"; + PNMImage *n = new PNMImage(1024, 1024); + n->quick_filter_from(*image->_data); + delete image->_data; + image->_data = n; + } +} + +StitchImageVisualizer::Image:: +Image(const Image ©) : + _image(copy._image), + _arc(copy._arc), + _tex(copy._tex), + _viz(copy._viz), + _index(copy._index) +{ +} + +void StitchImageVisualizer::Image:: +operator = (const Image ©) { + _image = copy._image; + _arc = copy._arc; + _tex = copy._tex; + _viz = copy._viz; + _index = copy._index; +} + +StitchImageVisualizer:: +StitchImageVisualizer() : + _event_handler(EventQueue::get_global_event_queue()) +{ + _event_handler.add_hook("q", static_handle_event); + _event_handler.add_hook("z", static_handle_event); +} + + +void StitchImageVisualizer:: +add_input_image(StitchImage *image) { + int index = _images.size(); + char letter = index + 'a'; + _images.push_back(Image(image, index, true)); + + string event_name(1, letter); + _event_handler.add_hook(event_name, static_handle_event); +} + +void StitchImageVisualizer:: +add_output_image(StitchImage *) { +} + +void StitchImageVisualizer:: +add_stitcher(Stitcher *) { +} + +void StitchImageVisualizer:: +execute() { + setup(); + + if (is_interactive()) { + _main_win->set_draw_callback(this); + _main_win->set_idle_callback(this); + _running = true; + while (_running) { + _main_win->update(); + } + } else { + nout << "Drawing frame\n"; + draw(true); + nout << "Done drawing frame\n"; + } +} + +void StitchImageVisualizer:: +setup() { + ChanCfgOverrides override; + + override_chan_cfg(override); + + // Create a window + TypeHandle want_pipe_type = InteractiveGraphicsPipe::get_class_type(); + if (!is_interactive()) { + want_pipe_type = NoninteractiveGraphicsPipe::get_class_type(); + } + + _main_pipe = GraphicsPipe::_factory.make_instance(want_pipe_type); + + if (_main_pipe == (GraphicsPipe*)0L) { + nout << "No suitable pipe is available! Check your Configrc!\n"; + exit(1); + } + + nout << "Opened a '" << _main_pipe->get_type().get_name() + << "' graphics pipe." << endl; + + // Create the render node + _render = new NamedNode("render"); + + // make a node for the cameras to live under + _cameras = new NamedNode("cameras"); + RenderRelation *cam_trans = new RenderRelation(_render, _cameras); + + _main_win = ChanConfig(_main_pipe, chan_cfg, _cameras, _render, override); + assert(_main_win != (GraphicsWindow*)0L); + + // Turn on culling. + CullFaceAttribute *cfa = new CullFaceAttribute; + cfa->set_mode(CullFaceProperty::M_cull_clockwise); + _initial_state.set_attribute(CullFaceTransition::get_class_type(), cfa); + + // Create the data graph root. + _data_root = new NamedNode( "data" ); + + // Create a mouse and put it in the data graph. + _mak = new MouseAndKeyboard(_main_win, 0); + new RenderRelation(_data_root, _mak); + + // Create a trackball to handle the mouse input. + _trackball = new Trackball("trackball"); + + new RenderRelation(_mak, _trackball); + + // Connect the trackball output to the camera's transform. + PT(Transform2SG) tball2cam = new Transform2SG("tball2cam"); + tball2cam->set_arc(cam_trans); + new RenderRelation(_trackball, tball2cam); + + // Create an ButtonThrower to throw events from the keyboard. + PT(ButtonThrower) et = new ButtonThrower("kb-events"); + new RenderRelation(_mak, et); + + // Create all the images. + Images::iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + create_image_geometry(*ii); + } +} + + +void StitchImageVisualizer:: +override_chan_cfg(ChanCfgOverrides &override) { + override.setField(ChanCfgOverrides::Mask, + ((unsigned int)(W_DOUBLE|W_DEPTH|W_MULTISAMPLE))); + override.setField(ChanCfgOverrides::Title, "Stitch"); +} + +void StitchImageVisualizer:: +setup_camera(const RenderRelation &) { +} + +bool StitchImageVisualizer:: +is_interactive() const { + return true; +} + +void StitchImageVisualizer:: +toggle_viz(StitchImageVisualizer::Image &im) { + im._viz = !im._viz; + if (im._viz) { + im._arc->set_transition(new RenderModeTransition(RenderModeProperty::M_filled)); + im._arc->set_transition(new CullFaceTransition(CullFaceProperty::M_cull_clockwise)); + if (im._tex != (Texture *)NULL) { + im._arc->set_transition(new TextureTransition(im._tex)); + } + } else { + im._arc->set_transition(new RenderModeTransition(RenderModeProperty::M_wireframe)); + im._arc->set_transition(new CullFaceTransition(CullFaceProperty::M_cull_none)); + im._arc->set_transition(new TextureTransition); + } +} + +void StitchImageVisualizer:: +create_image_geometry(StitchImageVisualizer::Image &im) { + int x_verts = im._image->get_x_verts(); + int y_verts = im._image->get_y_verts(); + TriangleMesh mesh(x_verts, y_verts); + + LVector3f center = LCAST(float, im._image->extrude(LPoint2d(0.5, 0.5))); + double scale = 10.0 / length(center); + + for (int xi = 0; xi < x_verts; xi++) { + for (int yi = 0; yi < y_verts; yi++) { + LVector3f p = LCAST(float, im._image->get_grid_vector(xi, yi)); + LPoint2f uv = LCAST(float, im._image->get_grid_uv(xi, yi)); + + mesh._coords.push_back(p * scale); + mesh._texcoords.push_back(uv); + } + } + + PT(GeomTristrip) geom = mesh.build_mesh(); + + PT(GeomNode) node = new GeomNode; + node->add_geom(geom.p()); + + im._arc = new RenderRelation(_render, node); + + if (im._image->_data != NULL) { + im._tex = new Texture; + im._tex->set_name(im._image->get_filename()); + im._tex->load(*im._image->_data); + im._arc->set_transition(new TextureTransition(im._tex)); + } +} + + +void StitchImageVisualizer:: +draw(bool) { + int num_windows = _main_pipe->get_num_windows(); + for (int w = 0; w < num_windows; w++) { + GraphicsWindow *win = _main_pipe->get_window(w); + win->get_gsg()->render_frame(_initial_state); + } + ClockObject::get_global_clock()->tick(); +} + +void StitchImageVisualizer:: +idle() { + // Initiate the data traversal, to send device data down its + // respective pipelines. + traverse_data_graph(_data_root); + + // Throw any events generated recently. + _static_siv = this; + _event_handler.process_events(); +} + +void StitchImageVisualizer:: +static_handle_event(CPT(Event) event) { + _static_siv->handle_event(event); +} + + +void StitchImageVisualizer:: +handle_event(CPT(Event) event) { + string name = event->get_name(); + + if (name.size() == 1 && isalpha(name[0])) { + int index = tolower(name[0]) - 'a'; + if (index >= 0 && index < _images.size()) { + toggle_viz(_images[index]); + return; + } + } + if (name == "q") { + _running = false; + + } else if (name == "z") { + _trackball->reset(); + } +} diff --git a/pandatool/src/stitchviewer/stitchImageVisualizer.h b/pandatool/src/stitchviewer/stitchImageVisualizer.h new file mode 100644 index 0000000000..3cf0686fef --- /dev/null +++ b/pandatool/src/stitchviewer/stitchImageVisualizer.h @@ -0,0 +1,83 @@ +// Filename: stitchImageVisualizer.h +// Created by: drose (05Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef STITCHIMAGEVISUALIZER_H +#define STITCHIMAGEVISUALIZER_H + +#include "stitchImage.h" +#include "stitchImageOutputter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class PNMImage; +class ChanCfgOverrides; + +class StitchImageVisualizer : public StitchImageOutputter, + public GraphicsWindow::Callback { +public: + StitchImageVisualizer(); + virtual void add_input_image(StitchImage *image); + virtual void add_output_image(StitchImage *image); + virtual void add_stitcher(Stitcher *stitcher); + + virtual void execute(); + +protected: + void setup(); + + class Image { + public: + Image(StitchImage *image, int index, bool scale); + Image(const Image ©); + void operator = (const Image ©); + + StitchImage *_image; + RenderRelation *_arc; + PT(Texture) _tex; + bool _viz; + int _index; + }; + + virtual void override_chan_cfg(ChanCfgOverrides &override); + virtual void setup_camera(const RenderRelation &camera_arc); + virtual bool is_interactive() const; + + void toggle_viz(Image &im); + virtual void create_image_geometry(Image &im); + + static void static_handle_event(CPT(Event) event); + void handle_event(CPT(Event) event); + + virtual void draw(bool); + virtual void idle(); + + typedef vector Images; + Images _images; + + PT(GraphicsPipe) _main_pipe; + PT(GraphicsWindow) _main_win; + NodeAttributes _initial_state; + PT(NamedNode) _render; + PT(NamedNode) _cameras; + PT(NamedNode) _data_root; + PT(MouseAndKeyboard) _mak; + PT(Trackball) _trackball; + EventHandler _event_handler; + + static StitchImageVisualizer *_static_siv; + bool _running; +}; + +#endif + diff --git a/pandatool/src/stitchviewer/triangleMesh.cxx b/pandatool/src/stitchviewer/triangleMesh.cxx new file mode 100644 index 0000000000..373822f9d6 --- /dev/null +++ b/pandatool/src/stitchviewer/triangleMesh.cxx @@ -0,0 +1,84 @@ +// Filename: triangleMesh.cxx +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#include "triangleMesh.h" + +#include + +TriangleMesh:: +TriangleMesh(int x_verts, int y_verts) : + _x_verts(x_verts), + _y_verts(y_verts), + _coords(0), _norms(0), _colors(0), _texcoords(0) +{ +} + +int TriangleMesh:: +get_x_verts() const { + return _x_verts; +} + +int TriangleMesh:: +get_y_verts() const { + return _y_verts; +} + +int TriangleMesh:: +get_num_verts() const { + return _x_verts * _y_verts; +} + +GeomTristrip *TriangleMesh:: +build_mesh() const { + int num_verts = _x_verts * _y_verts; + int num_tstrips = (_y_verts-1); + int tstrip_length = 2*(_x_verts-1)+2; + + PTA(int) lengths(num_tstrips); + PTA(ushort) vindex(num_tstrips * tstrip_length); + + // Set the lengths array. We are creating num_tstrips T-strips, + // each of which has t_strip length vertices. + int n; + for (n = 0; n < num_tstrips; n++) { + lengths[n] = tstrip_length; + } + + // Now fill up the index array into the vertices. This lays out the + // order of the vertices in each T-strip. + n = 0; + int ti, si; + for (ti = 1; ti < _y_verts; ti++) { + vindex[n++] = ti * _x_verts; + for (si = 1; si < _x_verts; si++) { + vindex[n++] = (ti - 1) * _x_verts + (si-1); + vindex[n++] = ti * _x_verts + si; + } + vindex[n++] = (ti - 1) * _x_verts + (_x_verts-1); + } + assert(n==num_tstrips * tstrip_length); + + GeomTristrip *geom = new GeomTristrip; + geom->set_num_prims(num_tstrips); + geom->set_lengths(lengths); + + assert(!_coords.empty()); + geom->set_coords(_coords, G_PER_VERTEX, vindex); + + if (!_norms.empty()) { + geom->set_normals(_norms, G_PER_VERTEX, vindex); + } + + if (!_colors.empty()) { + geom->set_colors(_colors, G_PER_VERTEX, vindex); + } + + if (!_texcoords.empty()) { + geom->set_texcoords(_texcoords, G_PER_VERTEX, vindex); + } + + return geom; +} + diff --git a/pandatool/src/stitchviewer/triangleMesh.h b/pandatool/src/stitchviewer/triangleMesh.h new file mode 100644 index 0000000000..da3f506627 --- /dev/null +++ b/pandatool/src/stitchviewer/triangleMesh.h @@ -0,0 +1,35 @@ +// Filename: triangleMesh.h +// Created by: drose (06Nov99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TRIANGLEMESH_H +#define TRIANGLEMESH_H + +#include +#include + +class GeomTristrip; + +class TriangleMesh { +public: + TriangleMesh(int x_verts, int y_verts); + + int get_x_verts() const; + int get_y_verts() const; + int get_num_verts() const; + + GeomTristrip *build_mesh() const; + + PTA(Vertexf) _coords; + PTA(Normalf) _norms; + PTA(Colorf) _colors; + PTA(TexCoordf) _texcoords; + +private: + int _x_verts, _y_verts; +}; + +#endif + + diff --git a/pandatool/src/text-stats/Sources.pp b/pandatool/src/text-stats/Sources.pp new file mode 100644 index 0000000000..eaeb418dc2 --- /dev/null +++ b/pandatool/src/text-stats/Sources.pp @@ -0,0 +1,16 @@ +#begin bin_target + #define TARGET text-stats + #define LOCAL_LIBS \ + progbase pstatserver config compiler + #define OTHER_LIBS \ + pstatclient:c linmath:c putil:c express:c panda:m + #define UNIX_SYS_LIBS \ + m + + #define SOURCES \ + textMonitor.cxx textMonitor.h textStats.cxx textStats.h + + #define INSTALL_HEADERS \ + +#end bin_target + diff --git a/pandatool/src/text-stats/textMonitor.cxx b/pandatool/src/text-stats/textMonitor.cxx new file mode 100644 index 0000000000..b668378930 --- /dev/null +++ b/pandatool/src/text-stats/textMonitor.cxx @@ -0,0 +1,122 @@ +// Filename: textMonitor.cxx +// Created by: drose (12Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "textMonitor.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: TextMonitor::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +TextMonitor:: +TextMonitor() { +} + +//////////////////////////////////////////////////////////////////// +// Function: TextMonitor::get_monitor_name +// Access: Public, Virtual +// Description: Should be redefined to return a descriptive name for +// the type of PStatsMonitor this is. +//////////////////////////////////////////////////////////////////// +string TextMonitor:: +get_monitor_name() { + return "Text Stats"; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextMonitor::got_hello +// Access: Public, Virtual +// Description: Called when the "hello" message has been received +// from the client. At this time, the client's hostname +// and program name will be known. +//////////////////////////////////////////////////////////////////// +void TextMonitor:: +got_hello() { + nout << "Now connected to " << get_client_progname() << " on host " + << get_client_hostname() << "\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextMonitor::new_data +// Access: Public, Virtual +// Description: Called as each frame's data is made available. There +// is no gurantee the frames will arrive in order, or +// that all of them will arrive at all. The monitor +// should be prepared to accept frames received +// out-of-order or missing. +//////////////////////////////////////////////////////////////////// +void TextMonitor:: +new_data(int thread_index, int frame_number) { + PStatView &view = get_view(thread_index); + const PStatThreadData *thread_data = view.get_thread_data(); + + if (frame_number = thread_data->get_latest_frame_number()) { + view.set_to_frame(frame_number); + + if (view.all_collectors_known()) { + nout << "\rThread " + << get_client_data()->get_thread_name(thread_index) + << " frame " << frame_number << ", " + << view.get_net_time() * 1000.0 << " ms (" + << thread_data->get_frame_rate() << " Hz):\n"; + const PStatViewLevel *level = view.get_top_level(); + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + show_level(level->get_child(i), 2); + } + } + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: TextMonitor::lost_connection +// Access: Public, Virtual +// Description: Called whenever the connection to the client has been +// lost. This is a permanent state change. The monitor +// should update its display to represent this, and may +// choose to close down automatically. +//////////////////////////////////////////////////////////////////// +void TextMonitor:: +lost_connection() { + nout << "Lost connection.\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextMonitor::is_thread_safe +// Access: Public, Virtual +// Description: Should be redefined to return true if this monitor +// class can handle running in a sub-thread. +// +// This is not related to the question of whether it can +// handle multiple different PStatThreadDatas; this is +// strictly a question of whether or not the monitor +// itself wants to run in a sub-thread. +//////////////////////////////////////////////////////////////////// +bool TextMonitor:: +is_thread_safe() { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextMonitor::show_level +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void TextMonitor:: +show_level(const PStatViewLevel *level, int indent_level) { + int collector_index = level->get_collector(); + + indent(nout, indent_level) + << get_client_data()->get_collector_name(collector_index) + << " = " << level->get_net_time() * 1000.0 << " ms\n"; + + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + show_level(level->get_child(i), indent_level + 2); + } +} diff --git a/pandatool/src/text-stats/textMonitor.h b/pandatool/src/text-stats/textMonitor.h new file mode 100644 index 0000000000..76afdf5351 --- /dev/null +++ b/pandatool/src/text-stats/textMonitor.h @@ -0,0 +1,32 @@ +// Filename: textMonitor.h +// Created by: drose (12Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTMONITOR_H +#define TEXTMONITOR_H + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Class : TextMonitor +// Description : A simple, scrolling-text stats monitor. Guaranteed +// to compile on every platform. +//////////////////////////////////////////////////////////////////// +class TextMonitor : public PStatMonitor { +public: + TextMonitor(); + + virtual string get_monitor_name(); + + virtual void got_hello(); + virtual void new_data(int thread_index, int frame_number); + virtual void lost_connection(); + virtual bool is_thread_safe(); + + void show_level(const PStatViewLevel *level, int indent_level); +}; + +#endif diff --git a/pandatool/src/text-stats/textStats.cxx b/pandatool/src/text-stats/textStats.cxx new file mode 100644 index 0000000000..1a670a41de --- /dev/null +++ b/pandatool/src/text-stats/textStats.cxx @@ -0,0 +1,83 @@ +// Filename: textStats.cxx +// Created by: drose (12Jul00) +// +//////////////////////////////////////////////////////////////////// + +#include "textStats.h" +#include "textMonitor.h" + +#include +#include + +#include + +static bool user_interrupted = false; + +// This simple signal handler lets us know when the user has pressed +// control-C, so we can clean up nicely. +static void signal_handler(int) { + user_interrupted = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextStats::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +TextStats:: +TextStats() { + set_program_description + ("This is a simple PStats server that listens on a TCP port for a " + "connection from a PStatClient in a Panda player. It will then report " + "frame rate and timing information sent by the player."); + + add_option + ("p", "port", 0, + "Specify the TCP port to listen for connections on. By default, this " + "is taken from the pstats-host Config variable.", + &TextStats::dispatch_int, NULL, &_port); + + _port = pstats_port; +} + + +//////////////////////////////////////////////////////////////////// +// Function: TextStats::make_monitor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PStatMonitor *TextStats:: +make_monitor() { + return new TextMonitor; +} + + +//////////////////////////////////////////////////////////////////// +// Function: TextStats::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void TextStats:: +run() { + // Set up a global signal handler to catch Interrupt (Control-C) so + // we can clean up nicely if the user stops us. + signal(SIGINT, &signal_handler); + + if (!listen(_port)) { + nout << "Unable to open port.\n"; + exit(1); + } + + nout << "Listening for connections.\n"; + + main_loop(&user_interrupted); + nout << "Exiting.\n"; +} + + +int main(int argc, char *argv[]) { + TextStats prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/text-stats/textStats.h b/pandatool/src/text-stats/textStats.h new file mode 100644 index 0000000000..58fe0ca680 --- /dev/null +++ b/pandatool/src/text-stats/textStats.h @@ -0,0 +1,31 @@ +// Filename: textStats.h +// Created by: drose (12Jul00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTSTATS_H +#define TEXTSTATS_H + +#include + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : TextStats +// Description : A simple, scrolling-text stats server. Guaranteed to +// compile on every platform. +//////////////////////////////////////////////////////////////////// +class TextStats : public ProgramBase, public PStatServer { +public: + TextStats(); + + virtual PStatMonitor *make_monitor(); + + void run(); + + int _port; +}; + +#endif +