pview.cxx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. /**
  2. * PANDA 3D SOFTWARE
  3. * Copyright (c) Carnegie Mellon University. All rights reserved.
  4. *
  5. * All use of this software is subject to the terms of the revised BSD
  6. * license. You should have received a copy of this license along
  7. * with this source code in a file named "LICENSE."
  8. *
  9. * @file pview.cxx
  10. * @author drose
  11. * @date 2002-02-25
  12. */
  13. #include "pandaFramework.h"
  14. #include "pandaSystem.h"
  15. #include "textNode.h"
  16. #include "configVariableBool.h"
  17. #include "texturePool.h"
  18. #include "multitexReducer.h"
  19. #include "sceneGraphReducer.h"
  20. #include "partGroup.h"
  21. #include "cardMaker.h"
  22. #include "bamCache.h"
  23. #include "virtualFileSystem.h"
  24. #include "panda_getopt.h"
  25. #include "preprocess_argv.h"
  26. #include "graphicsPipeSelection.h"
  27. #include "asyncTaskManager.h"
  28. #include "asyncTask.h"
  29. #include "boundingSphere.h"
  30. using std::cerr;
  31. using std::endl;
  32. PandaFramework framework;
  33. ConfigVariableBool pview_test_hack
  34. ("pview-test-hack", false,
  35. "Enable the '0' key in pview to run whatever hacky test happens to be in "
  36. "there right now.");
  37. bool
  38. output_screenshot(Filename &fn)
  39. {
  40. Thread *current_thread = Thread::get_current_thread();
  41. // Only one frame crashes.
  42. framework.do_frame(current_thread);
  43. framework.do_frame(current_thread);
  44. WindowFramework *wf = framework.get_window(0);
  45. bool ok = wf->get_graphics_output()->save_screenshot(fn, "from pview");
  46. if (!ok) {
  47. cerr << "Could not generate screenshot " << fn << "\n";
  48. }
  49. return ok;
  50. }
  51. void
  52. event_W(const Event *, void *) {
  53. // shift-W: open a new window on the same scene.
  54. // If we already have a window, use the same GSG.
  55. GraphicsPipe *pipe = nullptr;
  56. GraphicsStateGuardian *gsg = nullptr;
  57. if (framework.get_num_windows() > 0) {
  58. WindowFramework *old_window = framework.get_window(0);
  59. GraphicsOutput *win = old_window->get_graphics_output();
  60. pipe = win->get_pipe();
  61. // gsg = win->get_gsg();
  62. }
  63. WindowFramework *window = framework.open_window(pipe, gsg);
  64. if (window != nullptr) {
  65. window->enable_keyboard();
  66. window->setup_trackball();
  67. framework.get_models().instance_to(window->get_render());
  68. }
  69. }
  70. void
  71. event_F(const Event *, void *) {
  72. // shift-F: flatten the model hierarchy.
  73. framework.get_models().flatten_strong();
  74. }
  75. void
  76. event_Enter(const Event *, void *) {
  77. // alt-enter: toggle between windowfullscreen in the same scene.
  78. // If we already have a window, use the same GSG.
  79. GraphicsPipe *pipe = nullptr;
  80. GraphicsStateGuardian *gsg = nullptr;
  81. WindowProperties props;
  82. for (int i = 0; i < framework.get_num_windows(); ++i) {
  83. WindowFramework *old_window = framework.get_window(i);
  84. GraphicsWindow *win = old_window->get_graphics_window();
  85. if (win != nullptr) {
  86. pipe = win->get_pipe();
  87. gsg = win->get_gsg();
  88. props = win->get_properties();
  89. framework.close_window(old_window);
  90. break;
  91. }
  92. }
  93. // set the toggle
  94. props.set_fullscreen(!props.get_fullscreen());
  95. int flags = GraphicsPipe::BF_require_window;
  96. WindowFramework *window = framework.open_window(props, flags, pipe, gsg);
  97. if (window != nullptr) {
  98. window->enable_keyboard();
  99. window->setup_trackball();
  100. framework.get_models().instance_to(window->get_render());
  101. }
  102. }
  103. void
  104. event_2(const Event *event, void *) {
  105. // 2: split the window into two display regions.
  106. EventParameter param = event->get_parameter(0);
  107. WindowFramework *wf;
  108. DCAST_INTO_V(wf, param.get_ptr());
  109. WindowFramework *split = wf->split_window();
  110. if (split != nullptr) {
  111. split->enable_keyboard();
  112. split->setup_trackball();
  113. framework.get_models().instance_to(split->get_render());
  114. }
  115. }
  116. void
  117. event_0(const Event *event, void *) {
  118. // 0: run hacky test.
  119. EventParameter param = event->get_parameter(0);
  120. WindowFramework *wf;
  121. DCAST_INTO_V(wf, param.get_ptr());
  122. // Create a new offscreen buffer.
  123. GraphicsOutput *win = wf->get_graphics_output();
  124. PT(GraphicsOutput) buffer = win->make_texture_buffer("tex", 256, 256);
  125. cerr << buffer->get_type() << "\n";
  126. // Set the offscreen buffer to render the same scene as the main camera.
  127. DisplayRegion *dr = buffer->make_display_region();
  128. dr->set_camera(NodePath(wf->get_camera(0)));
  129. // Make the clear color on the buffer be yellow, so it's obviously different
  130. // from the main scene's background color.
  131. buffer->set_clear_color(LColor(1, 1, 0, 0));
  132. // Apply the offscreen buffer's texture to a card in the main window.
  133. CardMaker cm("card");
  134. cm.set_frame(0, 1, 0, 1);
  135. NodePath card_np(cm.generate());
  136. card_np.reparent_to(wf->get_render_2d());
  137. card_np.set_texture(buffer->get_texture());
  138. }
  139. void
  140. usage() {
  141. cerr <<
  142. "\n"
  143. "Usage: pview [opts] model [model ...]\n"
  144. " pview -h\n\n";
  145. }
  146. void
  147. help() {
  148. usage();
  149. cerr <<
  150. "pview opens a quick Panda window for viewing one or more models and/or\n"
  151. "animations.\n\n"
  152. "Options:\n\n"
  153. " -a\n"
  154. " Convert and play animations, if loading an external file type\n"
  155. " (like .mb) directly and if the converter supports animations.\n"
  156. " Also implicitly enables the animation controls.\n\n"
  157. " -c\n"
  158. " Automatically center models within the viewing window on startup.\n"
  159. " This can also be achieved with the 'c' hotkey at runtime.\n\n"
  160. " -l\n"
  161. " Open the window before loading any models with the text \"Loading\"\n"
  162. " displayed in the window. The default is not to open the window\n"
  163. " until all models are loaded.\n\n"
  164. " -i\n"
  165. " Ignore bundle/group names. Normally, the <group> name must match\n"
  166. " the <bundle> name, or the animation will not be used.\n\n"
  167. " -s filename\n"
  168. " After displaying the models, immediately take a screenshot and\n"
  169. " exit.\n\n"
  170. " -D\n"
  171. " Delete the model files after loading them (presumably this option\n"
  172. " will only be used when loading a temporary model file).\n\n"
  173. " -L\n"
  174. " Enable lighting in the scene. This can also be achieved with\n"
  175. " the 'l' hotkey at runtime.\n\n"
  176. " -P <pipe>\n"
  177. " Select the given graphics pipe for the window, rather than using\n"
  178. " the platform default. The allowed values for <pipe> are those\n"
  179. " from the Config.prc variables 'load-display' and 'aux-display'.\n\n"
  180. " -V\n"
  181. " Report the current version of Panda, and exit.\n\n"
  182. " -h\n"
  183. " Display this help text.\n\n";
  184. }
  185. void
  186. report_version() {
  187. nout << "\n";
  188. PandaSystem *ps = PandaSystem::get_global_ptr();
  189. ps->write(nout);
  190. nout << "\n";
  191. }
  192. // Task that dynamically adjusts the camera len's near/far clipping
  193. // planes to ensure the user can zoom in as close as needed to a model.
  194. //
  195. // Code adapted from WindowFramework::center_trackball(), but
  196. // without moving the camera. When the camera is inside the model,
  197. // the near clip is set to near-zero.
  198. //
  199. class AdjustCameraClipPlanesTask : public AsyncTask {
  200. public:
  201. AdjustCameraClipPlanesTask(const std::string &name, Camera *camera) :
  202. AsyncTask(name), _camera(camera), _lens(camera->get_lens(0)), _sphere(nullptr)
  203. {
  204. NodePath np = framework.get_models();
  205. PT(BoundingVolume) volume = np.get_bounds();
  206. // We expect at least a geometric bounding volume around the world.
  207. nassertv(volume != nullptr);
  208. nassertv(volume->is_of_type(GeometricBoundingVolume::get_class_type()));
  209. CPT(GeometricBoundingVolume) gbv = DCAST(GeometricBoundingVolume, volume);
  210. if (np.has_parent()) {
  211. CPT(TransformState) net_transform = np.get_parent().get_net_transform();
  212. PT(GeometricBoundingVolume) new_gbv = DCAST(GeometricBoundingVolume, gbv->make_copy());
  213. new_gbv->xform(net_transform->get_mat());
  214. gbv = new_gbv;
  215. }
  216. // Determine the bounding sphere around the object.
  217. if (gbv->is_infinite()) {
  218. framework_cat.warning()
  219. << "Infinite bounding volume for " << np << "\n";
  220. return;
  221. }
  222. if (gbv->is_empty()) {
  223. framework_cat.warning()
  224. << "Empty bounding volume for " << np << "\n";
  225. return;
  226. }
  227. // The BoundingVolume might be a sphere (it's likely), but since it
  228. // might not, we'll take no chances and make our own sphere.
  229. _sphere = new BoundingSphere(gbv->get_approx_center(), 0.0f);
  230. if (!_sphere->extend_by(gbv)) {
  231. framework_cat.warning()
  232. << "Cannot determine bounding volume of " << np << "\n";
  233. return;
  234. }
  235. }
  236. ALLOC_DELETED_CHAIN(AdjustCameraClipPlanesTask);
  237. virtual DoneStatus do_task() {
  238. if (!_sphere) {
  239. return DS_done;
  240. }
  241. if (framework.get_num_windows() == 0) {
  242. return DS_cont;
  243. }
  244. WindowFramework *wf = framework.get_window(0);
  245. if (!wf) {
  246. return DS_cont;
  247. }
  248. // Get current camera position.
  249. NodePath cameraNP = wf->get_camera_group();
  250. LPoint3 pos = cameraNP.get_pos();
  251. // See how far or close the camera is
  252. LPoint3 center = _sphere->get_center();
  253. PN_stdfloat radius = _sphere->get_radius();
  254. PN_stdfloat min_distance = 0.001 * radius;
  255. // Choose a suitable distance to view the whole volume in our frame.
  256. // This is based on the camera lens in use.
  257. PN_stdfloat distance;
  258. CPT(GeometricBoundingVolume) gbv = DCAST(GeometricBoundingVolume, _sphere);
  259. if (gbv->contains(pos)) {
  260. // See as up-close to the model as possible
  261. distance = min_distance;
  262. } else {
  263. // View from a distance
  264. distance = (center - pos).length();
  265. }
  266. // Ensure the far plane is far enough back to see the entire object.
  267. PN_stdfloat ideal_far_plane = distance + radius * 1.5;
  268. _lens->set_far(std::max(_lens->get_default_far(), ideal_far_plane));
  269. // And that the near plane is far enough forward, but if inside
  270. // the sphere, keep above 0.
  271. PN_stdfloat ideal_near_plane = std::max(min_distance * 10, distance - radius);
  272. _lens->set_near(std::min(_lens->get_default_near(), ideal_near_plane));
  273. return DS_cont;
  274. }
  275. Camera *_camera;
  276. Lens *_lens;
  277. PT(BoundingSphere) _sphere;
  278. };
  279. int
  280. main(int argc, char **argv) {
  281. preprocess_argv(argc, argv);
  282. framework.open_framework(argc, argv);
  283. framework.set_window_title("Panda Viewer");
  284. bool anim_controls = false;
  285. bool auto_center = false;
  286. bool show_loading = false;
  287. bool auto_screenshot = false;
  288. int hierarchy_match_flags = PartGroup::HMF_ok_part_extra |
  289. PartGroup::HMF_ok_anim_extra;
  290. Filename screenshotfn;
  291. bool delete_models = false;
  292. bool apply_lighting = false;
  293. PointerTo<GraphicsPipe> pipe = nullptr;
  294. extern char *optarg;
  295. extern int optind;
  296. static const char *optflags = "acls:DVhiLP:";
  297. int flag = getopt(argc, argv, optflags);
  298. while (flag != EOF) {
  299. switch (flag) {
  300. case 'a':
  301. anim_controls = true;
  302. PandaFramework::_loader_options.set_flags(PandaFramework::_loader_options.get_flags() | LoaderOptions::LF_convert_anim);
  303. break;
  304. case 'c':
  305. auto_center = true;
  306. break;
  307. case 'l':
  308. show_loading = true;
  309. break;
  310. case 'i':
  311. hierarchy_match_flags |= PartGroup::HMF_ok_wrong_root_name;
  312. break;
  313. case 's':
  314. auto_screenshot = true;
  315. screenshotfn = optarg;
  316. break;
  317. case 'D':
  318. delete_models = true;
  319. break;
  320. case 'L':
  321. apply_lighting = true;
  322. break;
  323. case 'P': {
  324. pipe = GraphicsPipeSelection::get_global_ptr()->make_module_pipe(optarg);
  325. if (!pipe) {
  326. cerr << "No such pipe '" << optarg << "' available." << endl;
  327. return 1;
  328. }
  329. break;
  330. }
  331. case 'V':
  332. report_version();
  333. return 1;
  334. case 'h':
  335. help();
  336. return 1;
  337. case '?':
  338. usage();
  339. return 1;
  340. default:
  341. cerr << "Unhandled switch: " << flag << endl;
  342. break;
  343. }
  344. flag = getopt(argc, argv, optflags);
  345. }
  346. argc -= (optind - 1);
  347. argv += (optind - 1);
  348. WindowFramework *window = framework.open_window(pipe, nullptr);
  349. if (window != nullptr) {
  350. // We've successfully opened a window.
  351. NodePath loading_np;
  352. if (show_loading) {
  353. // Put up a "loading" message for the user's benefit.
  354. NodePath aspect_2d = window->get_aspect_2d();
  355. PT(TextNode) loading = new TextNode("loading");
  356. loading_np = aspect_2d.attach_new_node(loading);
  357. loading_np.set_scale(0.125f);
  358. loading->set_text_color(1.0f, 1.0f, 1.0f, 1.0f);
  359. loading->set_shadow_color(0.0f, 0.0f, 0.0f, 1.0f);
  360. loading->set_shadow(0.04, 0.04);
  361. loading->set_align(TextNode::A_center);
  362. loading->set_text("Loading...");
  363. // Allow a couple of frames to go by so the window will be fully created
  364. // and the text will be visible.
  365. Thread *current_thread = Thread::get_current_thread();
  366. framework.do_frame(current_thread);
  367. framework.do_frame(current_thread);
  368. }
  369. window->enable_keyboard();
  370. window->setup_trackball();
  371. framework.get_models().instance_to(window->get_render());
  372. if (argc < 2) {
  373. // If we have no arguments, get that trusty old triangle out.
  374. window->load_default_model(framework.get_models());
  375. } else {
  376. window->load_models(framework.get_models(), argc, argv);
  377. if (delete_models) {
  378. VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  379. for (int i = 1; i < argc && argv[i] != nullptr; i++) {
  380. Filename model = Filename::from_os_specific(argv[i]);
  381. if (vfs->exists(model)) {
  382. nout << "Deleting " << model << "\n";
  383. vfs->delete_file(model);
  384. }
  385. }
  386. }
  387. }
  388. window->loop_animations(hierarchy_match_flags);
  389. // Make sure the textures are preloaded.
  390. framework.get_models().prepare_scene(window->get_graphics_output()->get_gsg());
  391. loading_np.remove_node();
  392. if (apply_lighting) {
  393. window->set_lighting(true);
  394. }
  395. if (auto_center) {
  396. window->center_trackball(framework.get_models());
  397. }
  398. if (auto_screenshot) {
  399. return(output_screenshot(screenshotfn) ? 0:1);
  400. }
  401. if (anim_controls) {
  402. window->set_anim_controls(true);
  403. }
  404. PT(AdjustCameraClipPlanesTask) task = new AdjustCameraClipPlanesTask("Adjust Camera Bounds", window->get_camera(0));
  405. framework.get_task_mgr().add(task);
  406. framework.enable_default_keys();
  407. framework.define_key("shift-w", "open a new window", event_W, nullptr);
  408. framework.define_key("shift-f", "flatten hierarchy", event_F, nullptr);
  409. framework.define_key("alt-enter", "toggle between window/fullscreen", event_Enter, nullptr);
  410. framework.define_key("2", "split the window", event_2, nullptr);
  411. if (pview_test_hack) {
  412. framework.define_key("0", "run quick hacky test", event_0, nullptr);
  413. }
  414. framework.main_loop();
  415. framework.report_frame_rate(nout);
  416. }
  417. framework.close_framework();
  418. return (0);
  419. }