-
Notifications
You must be signed in to change notification settings - Fork 1k
/
track.cpp
2340 lines (2089 loc) · 84.3 KB
/
track.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SuperTuxKart - a fun racing game with go-kart
//
// Copyright (C) 2004-2013 Steve Baker <sjbaker1@airmail.net>
// Copyright (C) 2009-2013 Joerg Henrichs, Steve Baker
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "tracks/track.hpp"
#include "addons/addon.hpp"
#include "audio/music_manager.hpp"
#include "challenges/challenge_status.hpp"
#include "challenges/unlock_manager.hpp"
#include "config/player_manager.hpp"
#include "config/stk_config.hpp"
#include "config/user_config.hpp"
#include "graphics/camera.hpp"
#include "graphics/CBatchingMesh.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/lod_node.hpp"
#include "graphics/material_manager.hpp"
#include "graphics/mesh_tools.hpp"
#include "graphics/moving_texture.hpp"
#include "graphics/particle_emitter.hpp"
#include "graphics/particle_kind.hpp"
#include "graphics/particle_kind_manager.hpp"
#include "guiengine/scalable_font.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "items/item.hpp"
#include "items/item_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/kart_properties.hpp"
#include "modes/linear_world.hpp"
#include "modes/easter_egg_hunt.hpp"
#include "modes/world.hpp"
#include "physics/physical_object.hpp"
#include "physics/physics.hpp"
#include "physics/triangle_mesh.hpp"
#include "race/race_manager.hpp"
#include "tracks/bezier_curve.hpp"
#include "tracks/check_manager.hpp"
#include "tracks/model_definition_loader.hpp"
#include "tracks/track_manager.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/quad_set.hpp"
#include "tracks/track_object_manager.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp"
#include "utils/string_utils.hpp"
#include "utils/translation.hpp"
#include <IBillboardTextSceneNode.h>
#include <ILightSceneNode.h>
#include <IMeshCache.h>
#include <IMeshManipulator.h>
#include <IMeshSceneNode.h>
#include <ISceneManager.h>
#include <iostream>
#include <stdexcept>
#include <sstream>
using namespace irr;
const float Track::NOHIT = -99999.9f;
// ----------------------------------------------------------------------------
Track::Track(const std::string &filename)
{
#ifdef DEBUG
m_magic_number = 0x17AC3802;
#endif
m_materials_loaded = false;
m_filename = filename;
m_root =
StringUtils::getPath(StringUtils::removeExtension(m_filename));
m_ident = StringUtils::getBasename(m_root);
// If this is an addon track, add "addon_" to the identifier - just in
// case that an addon track has the same directory name (and therefore
// identifier) as an included track.
if(Addon::isAddon(filename))
m_ident = Addon::createAddonId(m_ident);
// The directory should always have a '/' at the end, but getBasename
// above returns "" if a "/" is at the end, so we add the "/" here.
m_root += "/";
m_designer = "";
m_screenshot = "";
m_version = 0;
m_track_mesh = NULL;
m_gfx_effect_mesh = NULL;
m_internal = false;
m_enable_auto_rescue = true; // Below set to false in arenas
m_enable_push_back = true;
m_reverse_available = false;
m_is_arena = false;
m_has_easter_eggs = false;
m_is_soccer = false;
m_is_cutscene = false;
m_camera_far = 1000.0f;
m_mini_map = NULL;
m_bloom = true;
m_bloom_threshold = 0.75f;
m_color_inlevel = core::vector3df(0.0,1.0, 255.0);
m_color_outlevel = core::vector2df(0.0, 255.0);
m_clouds = false;
m_lensflare = false;
m_godrays = false;
m_displacement_speed = 1.0f;
m_caustics_speed = 1.0f;
m_shadows = true;
m_sky_particles = NULL;
m_sky_dx = 0.05f;
m_sky_dy = 0.0f;
m_weather_type = WEATHER_NONE;
m_cache_track = UserConfigParams::m_cache_overworld &&
m_ident=="overworld";
m_minimap_x_scale = 1.0f;
m_minimap_y_scale = 1.0f;
m_all_nodes.clear();
m_all_physics_only_nodes.clear();
m_all_cached_meshes.clear();
loadTrackInfo();
} // Track
//-----------------------------------------------------------------------------
/** Destructor, removes quad data structures etc. */
Track::~Track()
{
// Note that the music information in m_music is globally managed
// by the music_manager, and is freed there. So no need to free it
// here (esp. since various track might share the same music).
#ifdef DEBUG
assert(m_magic_number == 0x17AC3802);
m_magic_number = 0xDEADBEEF;
#endif
} // ~Track
//-----------------------------------------------------------------------------
/** Returns number of completed challenges */
unsigned int Track::getNumOfCompletedChallenges()
{
unsigned int unlocked_challenges = 0;
PlayerProfile *player = PlayerManager::getCurrentPlayer();
for (unsigned int i=0; i<m_challenges.size(); i++)
{
if (m_challenges[i].m_challenge_id == "tutorial")
{
unlocked_challenges++;
continue;
}
if (player->getChallengeStatus(m_challenges[i].m_challenge_id)
->isSolvedAtAnyDifficulty())
{
unlocked_challenges++;
}
}
return unlocked_challenges;
} // getNumOfCompletedChallenges
//-----------------------------------------------------------------------------
/** Removes all cached data structures. This is called before the resolution
* is changed.
*/
void Track::removeCachedData()
{
m_materials_loaded = false;
} // cleanCachedData
//-----------------------------------------------------------------------------
/** Prepates the track for a new race. This function must be called after all
* karts are created, since the check objects allocate data structures
* depending on the number of karts.
*/
void Track::reset()
{
m_ambient_color = m_default_ambient_color;
CheckManager::get()->reset(*this);
ItemManager::get()->reset();
m_track_object_manager->reset();
} // reset
//-----------------------------------------------------------------------------
/** Removes the physical body from the world.
* Called at the end of a race.
*/
void Track::cleanup()
{
QuadGraph::destroy();
ItemManager::destroy();
ParticleKindManager::get()->cleanUpTrackSpecificGfx();
for(unsigned int i=0; i<m_animated_textures.size(); i++)
{
delete m_animated_textures[i];
}
m_animated_textures.clear();
for(unsigned int i=0; i<m_all_nodes.size(); i++)
{
irr_driver->removeNode(m_all_nodes[i]);
}
m_all_nodes.clear();
m_all_physics_only_nodes.clear();
m_all_emitters.clearAndDeleteAll();
CheckManager::destroy();
delete m_track_object_manager;
m_track_object_manager = NULL;
irr_driver->removeNode(m_sun);
delete m_track_mesh;
m_track_mesh = NULL;
delete m_gfx_effect_mesh;
m_gfx_effect_mesh = NULL;
// The m_all_cached_mesh contains each mesh loaded from a file, which
// means that the mesh is stored in irrlichts mesh cache. To clean
// everything loaded by this track, we drop the ref count for each mesh
// here, till the ref count is 1, which means the mesh is only contained
// in the mesh cache, and can therefore be removed. Meshes load more
// than once are in m_all_cached_mesh more than once (which is easier
// than storing the mesh only once, but then having to test for each
// mesh if it is already contained in the list or not).
for(unsigned int i=0; i<m_all_cached_meshes.size(); i++)
{
irr_driver->dropAllTextures(m_all_cached_meshes[i]);
// If a mesh is not in Irrlicht's texture cache, its refcount is
// 1 (since its scene node was removed, so the only other reference
// is in m_all_cached_meshes). In this case we only drop it once
// and don't try to remove it from the cache.
if(m_all_cached_meshes[i]->getReferenceCount()==1)
{
m_all_cached_meshes[i]->drop();
continue;
}
m_all_cached_meshes[i]->drop();
if(m_all_cached_meshes[i]->getReferenceCount()==1)
irr_driver->removeMeshFromCache(m_all_cached_meshes[i]);
}
m_all_cached_meshes.clear();
// Now free meshes that are not associated to any scene node.
for (unsigned int i=0; i<m_detached_cached_meshes.size(); i++)
{
irr_driver->dropAllTextures(m_detached_cached_meshes[i]);
irr_driver->removeMeshFromCache(m_detached_cached_meshes[i]);
}
m_detached_cached_meshes.clear();
if(m_mini_map)
{
assert(m_mini_map->getReferenceCount()==1);
irr_driver->removeTexture(m_mini_map);
m_mini_map = NULL;
}
for(unsigned int i=0; i<m_sky_textures.size(); i++)
{
m_sky_textures[i]->drop();
if(m_sky_textures[i]->getReferenceCount()==1)
irr_driver->removeTexture(m_sky_textures[i]);
}
m_sky_textures.clear();
for (unsigned int i = 0; i<m_spherical_harmonics_textures.size(); i++)
{
m_spherical_harmonics_textures[i]->drop();
if (m_spherical_harmonics_textures[i]->getReferenceCount() == 1)
irr_driver->removeTexture(m_spherical_harmonics_textures[i]);
}
m_spherical_harmonics_textures.clear();
if(m_cache_track)
material_manager->makeMaterialsPermanent();
else
{
// remove temporary materials loaded by the material manager
material_manager->popTempMaterial();
}
irr_driver->clearGlowingNodes();
irr_driver->clearLights();
irr_driver->clearForcedBloom();
irr_driver->clearDisplacingNodes();
irr_driver->clearBackgroundNodes();
if(UserConfigParams::logMemory())
{
Log::debug("track",
"[memory] After cleaning '%s': mesh cache %d texture cache %d\n",
getIdent().c_str(),
irr_driver->getSceneManager()->getMeshCache()->getMeshCount(),
irr_driver->getVideoDriver()->getTextureCount());
#ifdef DEBUG
scene::IMeshCache *cache = irr_driver->getSceneManager()->getMeshCache();
for(unsigned int i=0; i<cache->getMeshCount(); i++)
{
const io::SNamedPath &name = cache->getMeshName(i);
std::vector<std::string>::iterator p;
p = std::find(m_old_mesh_buffers.begin(), m_old_mesh_buffers.end(),
name.getInternalName().c_str());
if(p!=m_old_mesh_buffers.end())
m_old_mesh_buffers.erase(p);
else
{
Log::debug("track", "[memory] Leaked mesh buffer '%s'.\n",
name.getInternalName().c_str());
} // if name not found
} // for i < cache size
video::IVideoDriver *vd = irr_driver->getVideoDriver();
for(unsigned int i=0; i<vd->getTextureCount(); i++)
{
video::ITexture *t = vd->getTextureByIndex(i);
std::vector<video::ITexture*>::iterator p;
p = std::find(m_old_textures.begin(), m_old_textures.end(),
t);
if(p!=m_old_textures.end())
{
m_old_textures.erase(p);
}
else
{
Log::debug("track", "[memory] Leaked texture '%s'.\n",
t->getName().getInternalName().c_str());
}
}
#endif
} // if verbose
} // cleanup
//-----------------------------------------------------------------------------
void Track::loadTrackInfo()
{
irr_driver->setLwhite(1.);
irr_driver->setExposure(0.09);
// Default values
m_use_fog = false;
m_fog_max = 1.0f;
m_fog_start = 0.0f;
m_fog_end = 1000.0f;
m_fog_height_start = 0.0f;
m_fog_height_end = 100.0f;
m_gravity = 9.80665f;
m_smooth_normals = false;
/* ARGB */
m_fog_color = video::SColor(255, 77, 179, 230);
m_default_ambient_color = video::SColor(255, 120, 120, 120);
m_sun_specular_color = video::SColor(255, 255, 255, 255);
m_sun_diffuse_color = video::SColor(255, 255, 255, 255);
m_sun_position = core::vector3df(0, 0, 0);
XMLNode *root = file_manager->createXMLTree(m_filename);
if(!root || root->getName()!="track")
{
std::ostringstream o;
o<<"Can't load track '"<<m_filename<<"', no track element.";
throw std::runtime_error(o.str());
}
root->get("name", &m_name);
std::string designer;
root->get("designer", &designer);
m_designer = StringUtils::decodeFromHtmlEntities(designer);
root->get("version", &m_version);
std::vector<std::string> filenames;
root->get("music", &filenames);
getMusicInformation(filenames, m_music);
root->get("screenshot", &m_screenshot);
root->get("gravity", &m_gravity);
root->get("soccer", &m_is_soccer);
root->get("arena", &m_is_arena);
root->get("cutscene", &m_is_cutscene);
root->get("groups", &m_groups);
root->get("internal", &m_internal);
root->get("reverse", &m_reverse_available);
root->get("push-back", &m_enable_push_back);
root->get("clouds", &m_clouds);
root->get("bloom", &m_bloom);
root->get("bloom-threshold", &m_bloom_threshold);
root->get("lens-flare", &m_lensflare);
root->get("shadows", &m_shadows);
root->get("god-rays", &m_godrays);
root->get("displacement-speed", &m_displacement_speed);
root->get("caustics-speed", &m_caustics_speed);
root->get("color-level-in", &m_color_inlevel);
root->get("color-level-out", &m_color_outlevel);
// Make the default for auto-rescue in battle mode and soccer mode to be false
if(m_is_arena || m_is_soccer)
m_enable_auto_rescue = false;
root->get("auto-rescue", & m_enable_auto_rescue);
root->get("smooth-normals", &m_smooth_normals);
// Reverse is meaningless in arena
if(m_is_arena || m_is_soccer)
m_reverse_available = false;
for(unsigned int i=0; i<root->getNumNodes(); i++)
{
const XMLNode *mode=root->getNode(i);
if(mode->getName()!="mode") continue;
TrackMode tm;
mode->get("name", &tm.m_name );
mode->get("quads", &tm.m_quad_name );
mode->get("graph", &tm.m_graph_name);
mode->get("scene", &tm.m_scene );
m_all_modes.push_back(tm);
}
// If no mode is specified, add a default mode.
if(m_all_modes.size()==0)
{
TrackMode tm;
m_all_modes.push_back(tm);
}
if(m_groups.size()==0) m_groups.push_back(DEFAULT_GROUP_NAME);
const XMLNode *xml_node = root->getNode("curves");
if(xml_node) loadCurves(*xml_node);
// Set the correct paths
m_screenshot = m_root+m_screenshot;
delete root;
std::string dir = StringUtils::getPath(m_filename);
std::string easter_name = dir+"/easter_eggs.xml";
XMLNode *easter = file_manager->createXMLTree(easter_name);
if(easter)
{
for(unsigned int i=0; i<easter->getNumNodes(); i++)
{
const XMLNode *eggs = easter->getNode(i);
if(eggs->getNumNodes() > 0)
{
m_has_easter_eggs = true;
break;
}
}
delete easter;
}
} // loadTrackInfo
//-----------------------------------------------------------------------------
/** Loads all curves from the XML node.
*/
void Track::loadCurves(const XMLNode &node)
{
for(unsigned int i=0; i<node.getNumNodes(); i++)
{
const XMLNode *curve = node.getNode(i);
m_all_curves.push_back(new BezierCurve(*curve));
} // for i<node.getNumNodes
} // loadCurves
//-----------------------------------------------------------------------------
/** Loads all music information for the specified files (which is taken from
* the track.xml file).
* \param filenames List of filenames to load.
* \param music On return contains the music information object for the
* specified files.
*/
void Track::getMusicInformation(std::vector<std::string>& filenames,
std::vector<MusicInformation*>& music )
{
for(int i=0; i<(int)filenames.size(); i++)
{
std::string full_path = m_root+filenames[i];
MusicInformation* mi = music_manager->getMusicInformation(full_path);
if(!mi)
{
try
{
std::string shared_name = file_manager->searchMusic(filenames[i]);
if(shared_name!="")
mi = music_manager->getMusicInformation(shared_name);
}
catch (...)
{
mi = NULL;
}
}
if(mi)
m_music.push_back(mi);
else
Log::warn("track",
"Music information file '%s' not found for track '%s' - ignored.\n",
filenames[i].c_str(), m_name.c_str());
} // for i in filenames
} // getMusicInformation
//-----------------------------------------------------------------------------
/** Select and set the music for this track (doesn't actually start it yet).
*/
void Track::startMusic() const
{
// In case that the music wasn't found (a warning was already printed)
if(m_music.size()>0)
music_manager->startMusic(m_music[rand()% m_music.size()], false);
else
music_manager->clearCurrentMusic();
} // startMusic
//-----------------------------------------------------------------------------
/** Loads the quad graph, i.e. the definition of all quads, and the way
* they are connected to each other.
*/
void Track::loadQuadGraph(unsigned int mode_id, const bool reverse)
{
QuadGraph::create(m_root+m_all_modes[mode_id].m_quad_name,
m_root+m_all_modes[mode_id].m_graph_name,
reverse);
QuadGraph::get()->setupPaths();
#ifdef DEBUG
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
{
assert(QuadGraph::get()->getNode(i).getPredecessor(0)!=-1);
}
#endif
if(QuadGraph::get()->getNumNodes()==0)
{
Log::warn("track", "No graph nodes defined for track '%s'\n",
m_filename.c_str());
if (race_manager->getNumberOfKarts() > 1)
{
Log::fatal("track", "I can handle the lack of driveline in single"
"kart mode, but not with AIs\n");
}
}
else
{
//Check whether the hardware can do nonsquare or
// non power-of-two textures
video::IVideoDriver* const video_driver = irr_driver->getVideoDriver();
bool nonpower = false; //video_driver->queryFeature(video::EVDF_TEXTURE_NPOT);
bool nonsquare =
video_driver->queryFeature(video::EVDF_TEXTURE_NSQUARE);
//Create the minimap resizing it as necessary.
m_mini_map_size = World::getWorld()->getRaceGUI()->getMiniMapSize();
core::dimension2du size = m_mini_map_size
.getOptimalSize(!nonpower,!nonsquare);
m_mini_map = QuadGraph::get()->makeMiniMap(size, "minimap::"+m_ident);
if (m_mini_map)
{
m_minimap_x_scale = float(m_mini_map_size.Width) / float(m_mini_map->getSize().Width);
m_minimap_y_scale = float(m_mini_map_size.Height) / float(m_mini_map->getSize().Height);
}
else
{
m_minimap_x_scale = 0;
m_minimap_y_scale = 0;
}
}
} // loadQuadGraph
// -----------------------------------------------------------------------------
void Track::mapPoint2MiniMap(const Vec3 &xyz, Vec3 *draw_at) const
{
QuadGraph::get()->mapPoint2MiniMap(xyz, draw_at);
draw_at->setX(draw_at->getX() * m_minimap_x_scale);
draw_at->setY(draw_at->getY() * m_minimap_y_scale);
}
// -----------------------------------------------------------------------------
/** Convert the track tree into its physics equivalents.
* \param main_track_count The number of meshes that are already converted
* when the main track was converted. Only the additional meshes
* added later still need to be converted.
*/
void Track::createPhysicsModel(unsigned int main_track_count)
{
// Remove the temporary track rigid body, and then convert all objects
// (i.e. the track and all additional objects) into a new rigid body
// and convert this again. So this way we have an optimised track
// rigid body which includes all track objects.
// Note that removing the rigid body does not remove the already collected
// triangle information, so there is no need to convert the actual track
// (first element in m_track_mesh) again!
if (m_track_mesh == NULL)
{
Log::error("track",
"m_track_mesh == NULL, cannot createPhysicsModel\n");
return;
}
m_track_mesh->removeAll();
m_gfx_effect_mesh->removeAll();
for(unsigned int i=main_track_count; i<m_all_nodes.size(); i++)
{
convertTrackToBullet(m_all_nodes[i]);
}
m_track_mesh->createPhysicalBody();
m_gfx_effect_mesh->createCollisionShape();
} // createPhysicsModel
// -----------------------------------------------------------------------------
/** Convert the graohics track into its physics equivalents.
* \param mesh The mesh to convert.
* \param node The scene node.
*/
void Track::convertTrackToBullet(scene::ISceneNode *node)
{
const core::vector3df &hpr = node->getRotation();
const core::vector3df &scale = node->getScale();
if (node->getType() == scene::ESNT_LOD_NODE)
{
node = ((LODNode*)node)->getFirstNode();
if (node == NULL)
{
Log::warn("track",
"This track contains an empty LOD group.");
return;
}
node->updateAbsolutePosition();
}
node->updateAbsolutePosition();
const core::vector3df &pos = node->getAbsolutePosition();
scene::IMesh *mesh;
// In case of readonly materials we have to get the material from
// the mesh, otherwise from the node. This is esp. important for
// water nodes, which only have the material defined in the node,
// but not in the mesh at all!
bool is_readonly_material=false;
switch(node->getType())
{
case scene::ESNT_MESH :
case scene::ESNT_WATER_SURFACE :
case scene::ESNT_OCTREE :
mesh = ((scene::IMeshSceneNode*)node)->getMesh();
is_readonly_material =
((scene::IMeshSceneNode*)node)->isReadOnlyMaterials();
break;
case scene::ESNT_ANIMATED_MESH :
mesh = ((scene::IAnimatedMeshSceneNode*)node)->getMesh();
is_readonly_material =
((scene::IAnimatedMeshSceneNode*)node)->isReadOnlyMaterials();
break;
case scene::ESNT_SKY_BOX :
case scene::ESNT_SKY_DOME:
case scene::ESNT_PARTICLE_SYSTEM :
case scene::ESNT_TEXT:
// These are non-physical
return;
break;
default:
int type_as_int = node->getType();
char* type = (char*)&type_as_int;
Log::debug("track",
"[convertTrackToBullet] Unknown scene node type : %c%c%c%c.\n",
type[0], type[1], type[2], type[3]);
return;
} // switch node->getType()
core::matrix4 mat;
mat.setRotationDegrees(hpr);
mat.setTranslation(pos);
core::matrix4 mat_scale;
// Note that we can't simply call mat.setScale, since this would
// overwrite the elements on the diagonal, making any rotation incorrect.
mat_scale.setScale(scale);
mat *= mat_scale;
for(unsigned int i=0; i<mesh->getMeshBufferCount(); i++)
{
scene::IMeshBuffer *mb = mesh->getMeshBuffer(i);
// FIXME: take translation/rotation into account
if (mb->getVertexType() != video::EVT_STANDARD &&
mb->getVertexType() != video::EVT_2TCOORDS &&
mb->getVertexType() != video::EVT_TANGENTS)
{
Log::warn("track", "convertTrackToBullet: Ignoring type '%d'!\n",
mb->getVertexType());
continue;
}
// Handle readonly materials correctly: mb->getMaterial can return
// NULL if the node is not using readonly materials. E.g. in case
// of a water scene node, the mesh (which is the animated copy of
// the original mesh) does not contain any material information,
// the material is only available in the node.
const video::SMaterial &irrMaterial =
is_readonly_material ? mb->getMaterial()
: node->getMaterial(i);
video::ITexture* t=irrMaterial.getTexture(0);
const Material* material=0;
TriangleMesh *tmesh = m_track_mesh;
if(t)
{
std::string image = std::string(core::stringc(t->getName()).c_str());
// the third boolean argument is false because at this point we're
// dealing physics, so it's useless to warn about missing textures,
// we'd just get duplicate/useless warnings
material=material_manager->getMaterial(StringUtils::getBasename(image),
false, false, false);
// Special gfx meshes will not be stored as a normal physics body,
// but converted to a collision body only, so that ray tests
// against them can be done.
if(material->isSurface())
tmesh = m_gfx_effect_mesh;
// A material which is a surface must be converted,
// even if it's marked as ignore. So only ignore
// non-surface materials.
else if(material->isIgnore())
continue;
}
u16 *mbIndices = mb->getIndices();
Vec3 vertices[3];
Vec3 normals[3];
if (mb->getVertexType() == video::EVT_STANDARD)
{
irr::video::S3DVertex* mbVertices=(video::S3DVertex*)mb->getVertices();
for(unsigned int j=0; j<mb->getIndexCount(); j+=3)
{
for(unsigned int k=0; k<3; k++)
{
int indx=mbIndices[j+k];
core::vector3df v = mbVertices[indx].Pos;
mat.transformVect(v);
vertices[k]=v;
normals[k]=mbVertices[indx].Normal;
} // for k
if(tmesh) tmesh->addTriangle(vertices[0], vertices[1],
vertices[2], normals[0],
normals[1], normals[2],
material );
} // for j
}
else if (mb->getVertexType() == video::EVT_2TCOORDS)
{
irr::video::S3DVertex2TCoords* mbVertices = (video::S3DVertex2TCoords*)mb->getVertices();
for(unsigned int j=0; j<mb->getIndexCount(); j+=3)
{
for(unsigned int k=0; k<3; k++)
{
int indx=mbIndices[j+k];
core::vector3df v = mbVertices[indx].Pos;
mat.transformVect(v);
vertices[k]=v;
normals[k]=mbVertices[indx].Normal;
} // for k
if(tmesh) tmesh->addTriangle(vertices[0], vertices[1],
vertices[2], normals[0],
normals[1], normals[2],
material );
} // for j
}
else if (mb->getVertexType() == video::EVT_TANGENTS)
{
irr::video::S3DVertexTangents* mbVertices = (video::S3DVertexTangents*)mb->getVertices();
for(unsigned int j=0; j<mb->getIndexCount(); j+=3)
{
for(unsigned int k=0; k<3; k++)
{
int indx=mbIndices[j+k];
core::vector3df v = mbVertices[indx].Pos;
mat.transformVect(v);
vertices[k]=v;
normals[k]=mbVertices[indx].Normal;
} // for k
if(tmesh) tmesh->addTriangle(vertices[0], vertices[1],
vertices[2], normals[0],
normals[1], normals[2],
material );
} // for j
}
} // for i<getMeshBufferCount
} // convertTrackToBullet
// ----------------------------------------------------------------------------
/** Loads the main track model (i.e. all other objects contained in the
* scene might use raycast on this track model to determine the actual
* height of the terrain.
*/
bool Track::loadMainTrack(const XMLNode &root)
{
assert(m_track_mesh==NULL);
assert(m_gfx_effect_mesh==NULL);
m_challenges.clear();
m_force_fields.clear();
m_track_mesh = new TriangleMesh();
m_gfx_effect_mesh = new TriangleMesh();
const XMLNode *track_node = root.getNode("track");
std::string model_name;
track_node->get("model", &model_name);
std::string full_path = m_root+model_name;
scene::IMesh *mesh = irr_driver->getMesh(full_path);
if(!mesh)
{
Log::fatal("track",
"Main track model '%s' in '%s' not found, aborting.\n",
track_node->getName().c_str(), model_name.c_str());
}
// The mesh as returned does not have all mesh buffers with the same
// texture combined. This can result in a _HUGE_ overhead. E.g. instead
// of 46 different mesh buffers over 500 (for some tracks even >1000)
// were created. This means less effect from hardware support, less
// vertices per opengl operation, more overhead on CPU, ...
// So till we have a better b3d exporter which can combine the different
// meshes which use the same texture when exporting, the meshes are
// combined using CBatchingMesh.
scene::CBatchingMesh *merged_mesh = new scene::CBatchingMesh();
merged_mesh->addMesh(mesh);
merged_mesh->finalize();
scene::IMeshManipulator* manip = irr_driver->getVideoDriver()->getMeshManipulator();
// TODO: memory leak?
scene::IMesh* tangent_mesh = manip->createMeshWithTangents(merged_mesh);
adjustForFog(tangent_mesh, NULL);
// The merged mesh is grabbed by the octtree, so we don't need
// to keep a reference to it.
scene::ISceneNode *scene_node = irr_driver->addMesh(tangent_mesh);
//scene::IMeshSceneNode *scene_node = irr_driver->addOctTree(merged_mesh);
// We should drop the merged mesh (since it's now referred to in the
// scene node), but then we need to grab it since it's in the
// m_all_cached_meshes.
m_all_cached_meshes.push_back(tangent_mesh);
irr_driver->grabAllTextures(tangent_mesh);
// The reference count of the mesh is 1, since it is in irrlicht's
// cache. So we only have to remove it from the cache.
irr_driver->removeMeshFromCache(mesh);
#ifdef DEBUG
std::string debug_name=model_name+" (main track, octtree)";
scene_node->setName(debug_name.c_str());
#endif
//merged_mesh->setHardwareMappingHint(scene::EHM_STATIC);
core::vector3df xyz(0,0,0);
track_node->getXYZ(&xyz);
core::vector3df hpr(0,0,0);
track_node->getHPR(&hpr);
scene_node->setPosition(xyz);
scene_node->setRotation(hpr);
handleAnimatedTextures(scene_node, *track_node);
m_all_nodes.push_back(scene_node);
MeshTools::minMax3D(merged_mesh, &m_aabb_min, &m_aabb_max);
// Increase the maximum height of the track: since items that fly
// too high explode, e.g. cakes can not be show when being at the
// top of the track (since they will explode when leaving the AABB
// of the track). While the test for this in Flyable::updateAndDelete
// could be relaxed to fix this, it is not certain how the physics
// will handle items that are out of the AABB
m_aabb_max.setY(m_aabb_max.getY()+30.0f);
World::getWorld()->getPhysics()->init(m_aabb_min, m_aabb_max);
ModelDefinitionLoader lodLoader(this);
// Load LOD groups
const XMLNode *lod_xml_node = root.getNode("lod");
if (lod_xml_node != NULL)
{
for (unsigned int i = 0; i < lod_xml_node->getNumNodes(); i++)
{
const XMLNode* lod_group_xml = lod_xml_node->getNode(i);
for (unsigned int j = 0; j < lod_group_xml->getNumNodes(); j++)
{
lodLoader.addModelDefinition(lod_group_xml->getNode(j));
}
}
}
// Load instancing models (for the moment they are loaded the same way as LOD to simplify implementation)
const XMLNode *instancing_xml_node = root.getNode("instancing");
if (instancing_xml_node != NULL)
{
for (unsigned int i = 0; i < instancing_xml_node->getNumNodes(); i++)
{
const XMLNode* lod_group_xml = instancing_xml_node->getNode(i);
for (unsigned int j = 0; j < lod_group_xml->getNumNodes(); j++)
{
lodLoader.addModelDefinition(lod_group_xml->getNode(j));
}
}
}
for (unsigned int i=0; i<track_node->getNumNodes(); i++)
{
const XMLNode *n=track_node->getNode(i);
// Animated textures have already been handled
if(n->getName()=="animated-texture") continue;
// Only "object" entries are allowed now inside of the model tag
if(n->getName()!="static-object")
{
Log::error("track",
"Incorrect tag '%s' inside <model> of scene file - ignored\n",
n->getName().c_str());
continue;
}
core::vector3df xyz(0,0,0);
n->get("xyz", &xyz);
core::vector3df hpr(0,0,0);
n->get("hpr", &hpr);
core::vector3df scale(1.0f, 1.0f, 1.0f);
n->get("scale", &scale);
// some static meshes are conditional
std::string condition;
n->get("if", &condition);
if (condition == "splatting")
{
if (!irr_driver->supportsSplatting()) continue;
}
else if (condition == "trophies")
{
// Associate force fields and challenges
// FIXME: this assumes that challenges will appear before force fields in scene.xml
// (which however seems to be the case atm)
int closest_challenge_id = -1;
float closest_distance = 99999.0f;
for (unsigned int c=0; c<m_challenges.size(); c++)
{
float dist = xyz.getDistanceFromSQ(m_challenges[c].m_position);
if (closest_challenge_id == -1 || dist < closest_distance)
{
closest_challenge_id = c;
closest_distance = dist;
}
}
assert(closest_challenge_id >= 0);
assert(closest_challenge_id < (int)m_challenges.size());
const std::string &s = m_challenges[closest_challenge_id].m_challenge_id;
const ChallengeData* challenge = unlock_manager->getChallengeData(s);
if (challenge == NULL)
{
if (s != "tutorial")
Log::error("track", "Cannot find challenge named '%s'\n",
m_challenges[closest_challenge_id].m_challenge_id.c_str());
continue;
}
const unsigned int val = challenge->getNumTrophies();
bool shown = (PlayerManager::getCurrentPlayer()->getPoints() < val);
m_force_fields.push_back(OverworldForceField(xyz, shown, val));
m_challenges[closest_challenge_id].setForceField(
m_force_fields[m_force_fields.size() - 1]);
core::stringw msg = StringUtils::toWString(val);
core::dimension2d<u32> textsize = GUIEngine::getHighresDigitFont()
->getDimension(msg.c_str());
assert(GUIEngine::getHighresDigitFont() != NULL);
// TODO: Add support in the engine for BillboardText or find a replacement