Rigs of Rods 2026.01
Soft-body Physics Simulation
Loading...
Searching...
No Matches
TObjFileFormat.cpp
Go to the documentation of this file.
1/*
2 This source file is part of Rigs of Rods
3 Copyright 2016-2017 Petr Ohlidal
4
5 For more information, see http://www.rigsofrods.org/
6
7 Rigs of Rods is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 3, as
9 published by the Free Software Foundation.
10
11 Rigs of Rods is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include "TObjFileFormat.h"
21
22#include "Actor.h"
23#include "ProceduralRoad.h"
24
25#define LOGSTREAM Ogre::LogManager::getSingleton().stream() << "[RoR|TObj fileformat] "
26
27using namespace RoR;
28using namespace Ogre;
29
30// --------------------------------
31// TObjEntry
32
33TObjEntry::TObjEntry(Ogre::Vector3 pos, Ogre::Vector3 rot, const char* odef, TObjSpecialObject spc, const char* ty, const char* nam):
34 position(pos),
35 rotation(rot),
36 special(spc)
37{
38 strcpy(type, ty);
39 strcpy(instance_name, nam);
40 strcpy(odef_name, odef);
41}
42
47
54
55// --------------------------------
56// Parser
57
71
72bool TObjParser::ProcessLine(const char* line)
73{
74 bool result = true;
75 if ((line != nullptr) && (line[0] != 0))
76 {
77 bool is_comment = (line[0] == '/') || (line[0] == ';');
78 if (is_comment)
79 {
80 int text_start = 1;
81 while (line[text_start] == '/')
82 {
83 text_start++;
84 }
85 m_preceding_line_comments += std::string(line + text_start) + "\n";
86 }
87 else
88 {
89 m_cur_line = line; // No trimming by design.
90 m_cur_line_trimmed = line;
91 while (m_cur_line_trimmed[0] == ' ' || m_cur_line_trimmed[0] == '\t')
92 {
94 }
95 result = this->ProcessCurrentLine();
97 }
98 }
100 return result;
101}
102
103// retval true = continue processing (false = stop)
105{
106 // ** Process keywords
107
108 if (!strcmp(m_cur_line, "end"))
109 {
110 return false;
111 }
112 if (strncmp(m_cur_line, "collision-tris", 14) == 0)
113 {
114 return true; // Obsolete - ignore it.
115 }
116 if (strncmp(m_cur_line, "grid", 4) == 0)
117 {
118 this->ProcessGridLine();
119 return true;
120 }
121 if (strncmp(m_cur_line, "trees", 5) == 0)
122 {
123 this->ProcessTreesLine();
124 return true;
125 }
126 if (strncmp(m_cur_line, "grass", 5) == 0)
127 {
128 this->ProcessGrassLine();
129 return true;
130 }
131 if (strncmp(m_cur_line, "set_default_rendering_distance", 30) == 0)
132 {
133 const int result = sscanf(m_cur_line, "set_default_rendering_distance %f", &m_default_rendering_distance);
134 if (result != 1)
135 {
136 LOG(fmt::format("too few parameters on line: '{}' ({}, line {})", m_cur_line, m_filename, m_line_number));
137 }
138 return true;
139 }
140 if (strncmp(m_cur_line, "rot_yxz", 7) == 0)
141 {
142 m_rot_yxz = true;
143 return true;
144 }
145 if (strncmp("begin_procedural_roads", m_cur_line, 22) == 0)
146 {
147 m_cur_procedural_obj = new ProceduralObject(); // Hard reset, discarding last "non-procedural" road strip. For backwards compatibility. ~ Petr Ohlidal, 08/2020
150 return true;
151 }
152 if (strncmp("end_procedural_roads", m_cur_line, 20) == 0)
153 {
155 {
156 this->FlushProceduralObject();
157 }
158 m_in_procedural_road = false;
159 return true;
160 }
161
162 // Procedural road directives:
163
164 if (strncmp("smoothing_num_splits", m_cur_line_trimmed, 20) == 0)
165 {
167 {
168 int result = sscanf(m_cur_line_trimmed, "smoothing_num_splits %d", &m_cur_procedural_obj->smoothing_num_splits);
169 if (result != 1)
170 {
171 LOG(fmt::format("[RoR|TObj] not enough parameters at line '{}' ({}, line {})", m_cur_line, m_filename, m_line_number));
172 }
173 }
174 return true;
175 }
176 if (strncmp("collision_enabled", m_cur_line_trimmed, 17) == 0)
177 {
179 {
180 char valuebuf[100] = {};
181 int result = sscanf(m_cur_line_trimmed, "collision_enabled %s", &valuebuf);
182 if (result != 1)
183 {
184 LOG(fmt::format("[RoR|TObj] not enough parameters at line '{}' ({}, line {})", m_cur_line, m_filename, m_line_number));
185 }
186 m_cur_procedural_obj->collision_enabled = Ogre::StringConverter::parseBool(valuebuf, false);
187 }
188 return true;
189 }
190 if (strncmp("custom_material", m_cur_line_trimmed, 15) == 0)
191 {
193 {
194 char valuebuf[100] = {};
195 int result = sscanf(m_cur_line_trimmed, "custom_material %s", &valuebuf);
196 if (result != 1)
197 {
198 LOG(fmt::format("[RoR|TObj] not enough parameters at line '{}' ({}, line {})", m_cur_line, m_filename, m_line_number));
199 }
200 valuebuf[99] = '\0';
202 }
203 return true;
204 }
205 if (strncmp("custom_groundmodel", m_cur_line_trimmed, 18) == 0)
206 {
208 {
209 char valuebuf[100] = {};
210 int result = sscanf(m_cur_line_trimmed, "custom_groundmodel %s", &valuebuf);
211 if (result != 1)
212 {
213 LOG(fmt::format("[RoR|TObj] not enough parameters at line '{}' ({}, line {})", m_cur_line, m_filename, m_line_number));
214 }
215 valuebuf[99] = '\0';
217 }
218 return true;
219 }
220
221 // ** Process entries (ODEF or special objects)
222
224 {
225 this->ProcessProceduralLine();
226 }
227 else
228 {
229 TObjEntry object;
230 if (this->ParseObjectLine(object))
231 {
232 if (object.IsActor())
233 {
234 this->ProcessActorObject(object);
235 }
236 else if (object.IsRoad())
237 {
238 this->ProcessRoadObject(object);
239 }
240 else
241 {
242 m_def->objects.push_back(object);
243 }
244 }
245 }
246 return true;
247}
248
250{
251 // finish the last road
252 if (m_road2_num_blocks > 0)
253 {
254 Vector3 pp_pos = m_road2_last_pos + m_road2_last_rot * Vector3(10.0f, 0.0f, 0.9f);
256
257 this->FlushProceduralObject();
258 }
259
260 m_def->document_name = m_filename;
261 m_def->rot_yxz = m_rot_yxz;
262 m_filename = "";
263
264 TObjDocumentPtr tmp_def = m_def;
265 m_def.reset();
266 return tmp_def; // Pass ownership
267}
268
269void TObjParser::ProcessOgreStream(Ogre::DataStream* stream)
270{
271 m_filename = stream->getName();
272 char raw_line_buf[TObj::LINE_BUF_LEN];
273 bool keep_reading = true;
274 while (keep_reading && !stream->eof())
275 {
276 stream->readLine(raw_line_buf, TObj::LINE_BUF_LEN);
277 keep_reading = this->ProcessLine(raw_line_buf);
278 }
279}
280
281// --------------------------------
282// Processing
283
285{
286 ProceduralPoint point;
287 Str<300> obj_name;
288 Ogre::Vector3 rot = Ogre::Vector3::ZERO;
289 sscanf(m_cur_line, "%f, %f, %f, %f, %f, %f, %f, %f, %f, %s",
290 &point.position.x, &point.position.y, &point.position.z,
291 &rot.x, &rot.y, &rot.z,
292 &point.width, &point.bwidth, &point.bheight, obj_name.GetBuffer());
293
294 point.rotation = this->CalcRotation(rot, m_rot_yxz);
295
296 if (obj_name == "flat" ) { point.type = RoadType::ROAD_FLAT; }
297 else if (obj_name == "left" ) { point.type = RoadType::ROAD_LEFT; }
298 else if (obj_name == "right" ) { point.type = RoadType::ROAD_RIGHT; }
299 else if (obj_name == "both" ) { point.type = RoadType::ROAD_BOTH; }
300 else if (obj_name == "bridge" ) { point.type = RoadType::ROAD_BRIDGE; point.pillartype = 1; }
301 else if (obj_name == "monorail" ) { point.type = RoadType::ROAD_MONORAIL; point.pillartype = 2; }
302 else if (obj_name == "monorail2" ) { point.type = RoadType::ROAD_MONORAIL; point.pillartype = 0; }
303 else if (obj_name == "bridge_no_pillars") { point.type = RoadType::ROAD_BRIDGE; point.pillartype = 0; }
304 else { point.type = RoadType::ROAD_AUTOMATIC; point.pillartype = 1; }
305
306 // Attach comments
308
309 m_cur_procedural_obj->points.push_back(new ProceduralPoint(point));
310}
311
313{
314 Ogre::Vector3 & pos = m_def->grid_position;
315 sscanf(m_cur_line, "grid %f, %f, %f", &pos.x, &pos.y, &pos.z); // No error check by design
316 m_def->grid_enabled = true;
317}
318
320{
321 TObjTree tree;
322 sscanf(m_cur_line, "trees %f, %f, %f, %f, %f, %f, %f, %s %s %s %f %s",
323 &tree.yaw_from, &tree.yaw_to,
324 &tree.scale_from, &tree.scale_to,
325 &tree.high_density,
326 &tree.min_distance, &tree.max_distance,
327 tree.tree_mesh, tree.color_map, tree.density_map,
328 &tree.grid_spacing, tree.collision_mesh);
329
331 m_def->trees.push_back(tree);
332}
333
335{
336 TObjGrass grass;
337 if (strncmp(m_cur_line, "grass2", 6) == 0)
338 {
339 sscanf(m_cur_line, "grass2 %d, %f, %f, %f, %f, %f, %f, %f, %f, %d, %f, %f, %d, %s %s %s",
340 &grass.range,
341 &grass.sway_speed, &grass.sway_length, &grass.sway_distrib, &grass.density,
342 &grass.min_x, &grass.min_y, &grass.max_x, &grass.max_y,
343 &grass.fadetech, &grass.min_h, &grass.max_h, &grass.technique,
344 grass.material_name,
345 grass.color_map_filename,
347 }
348 else
349 {
350 // Same as 'grass2', except without 'technique' parameter
351 sscanf(m_cur_line, "grass %d, %f, %f, %f, %f, %f, %f, %f, %f, %d, %f, %f, %s %s %s",
352 &grass.range,
353 &grass.sway_speed, &grass.sway_length, &grass.sway_distrib, &grass.density,
354 &grass.min_x, &grass.min_y, &grass.max_x, &grass.max_y,
355 &grass.fadetech, &grass.min_h, &grass.max_h,
356 grass.material_name,
357 grass.color_map_filename,
359 }
360
363 m_def->grass.push_back(grass);
364}
365
367{
368 TObjVehicle v;
369 v.position = object.position;
370 v.tobj_rotation = object.rotation;
371 v.rotation = this->CalcRotation(object.rotation, m_rot_yxz);
372 v.type = object.special;
373 strcpy(v.name, object.type);
374
375 m_def->vehicles.push_back(v);
376}
377
379{
380 // ** Import road objects as procedural road
381
382 if (object.position.distance(m_road2_last_pos) > 20.0f)
383 {
384 // break the road
385 if (m_road2_num_blocks > 0)
386 {
387 Vector3 pp_pos = m_road2_last_pos + this->CalcRotation(m_road2_last_rot, m_rot_yxz) * Vector3(10.0f, 0.0f, 0.9f);
388 this->ImportProceduralPoint(pp_pos, m_road2_last_rot, object.special);
389 this->FlushProceduralObject();
390 }
392
393 // beginning of new
394 this->ImportProceduralPoint(object.position, object.rotation, object.special);
395 }
396 else
397 {
398 this->ImportProceduralPoint(object.position, object.rotation, object.special);
399 }
400 m_road2_last_pos=object.position;
401 m_road2_last_rot=object.rotation;
402}
403
404// --------------------------------
405// Helpers
406
407void TObjParser::ImportProceduralPoint(Ogre::Vector3 const& pos, Ogre::Vector3 const& rot, TObjSpecialObject special)
408{
410 pp.bheight = 0.2;
411 pp.bwidth = 1.4;
413 pp.position = pos;
416 pp.width = 8;
417
418 // Attach comments
420
421 m_cur_procedural_obj->points.push_back(new ProceduralPoint(pp));
423 {
425 }
426}
427
428Ogre::Quaternion TObjParser::CalcRotation(Ogre::Vector3 const& rot, bool rot_yxz)
429{
430 if (rot_yxz)
431 {
432 return Quaternion(Degree(rot.y), Vector3::UNIT_Y) * // y global
433 Quaternion(Degree(rot.x), Vector3::UNIT_X) * // x local
434 Quaternion(Degree(rot.z), Vector3::UNIT_Z); // z local
435 }
436 else
437 {
438 return Quaternion(Degree(rot.x), Vector3::UNIT_X) *
439 Quaternion(Degree(rot.y), Vector3::UNIT_Y) *
440 Quaternion(Degree(rot.z), Vector3::UNIT_Z);
441 }
442}
443
445{
446 Str<TObj::STR_LEN> odef("generic");
447 Str<TObj::STR_LEN> type("");
448 Str<TObj::STR_LEN> instance_name("");
449 Ogre::Vector3 pos(Ogre::Vector3::ZERO);
450 Ogre::Vector3 rot(Ogre::Vector3::ZERO);
451 int r = sscanf(m_cur_line, "%f, %f, %f, %f, %f, %f, %s %s %s",
452 &pos.x, &pos.y, &pos.z, &rot.x, &rot.y, &rot.z, odef.GetBuffer(), type.GetBuffer(), instance_name.GetBuffer());
453 if (r < 6)
454 {
455 return false;
456 }
457
459 if (odef == "truck" ) { special = TObjSpecialObject::TRUCK ; }
460 else if (odef == "load" ) { special = TObjSpecialObject::LOAD ; }
461 else if (odef == "machine" ) { special = TObjSpecialObject::MACHINE ; }
462 else if (odef == "boat" ) { special = TObjSpecialObject::BOAT ; }
463 else if (odef == "truck2" ) { special = TObjSpecialObject::TRUCK2 ; }
464 else if (odef == "grid" ) { special = TObjSpecialObject::GRID ; }
465 else if (odef == "road" ) { special = TObjSpecialObject::ROAD ; }
466 else if (odef == "roadborderleft" ) { special = TObjSpecialObject::ROAD_BORDER_LEFT ; }
467 else if (odef == "roadborderright" ) { special = TObjSpecialObject::ROAD_BORDER_RIGHT ; }
468 else if (odef == "roadborderboth" ) { special = TObjSpecialObject::ROAD_BORDER_BOTH ; }
469 else if (odef == "roadbridgenopillar") { special = TObjSpecialObject::ROAD_BRIDGE_NO_PILLARS; }
470 else if (odef == "roadbridge" ) { special = TObjSpecialObject::ROAD_BRIDGE ; }
471
472 // If no instance name given, generate one, so that scripts can use `game.destroyObject(instanceName)` even for pre-placed objects.
473 // Don't use spaces because TOBJ parser doesn't support "" yet (game tries to strip the 'auto^' instancenames on save, but someone could export via custom script).
474 if (instance_name == "")
475 {
476 instance_name << "auto^" << m_filename << "(line:" << m_line_number << ")";
477 }
478
479 object = TObjEntry(pos, rot, odef.ToCStr(), special, type, instance_name);
480 object.rendering_distance = m_default_rendering_distance;
481
482 // Attach comments
483 object.comments = m_preceding_line_comments;
484
485 return true;
486}
487
489{
490 // finish it and start new object
492 m_def->proc_objects.push_back(m_cur_procedural_obj);
496}
497
498void WriteTObjDelimiter(Ogre::DataStreamPtr& stream, const std::string& title, size_t count)
499{
500 if (count > 0)
501 {
502 std::string line = fmt::format("\n\n// ~~~~~~~~~~ {} ({}) ~~~~~~~~~~\n\n", title, count);
503 stream->write(line.c_str(), line.length());
504 }
505}
506
507void TObj::WriteToStream(TObjDocumentPtr doc, Ogre::DataStreamPtr stream)
508{
509 // assert on Debug, play safe on Release
510 ROR_ASSERT(doc);
511 ROR_ASSERT(stream);
512 if (!doc || !stream)
513 {
514 return;
515 }
516
517 // 'grid'
518 WriteTObjDelimiter(stream, "grid", (int)doc->grid_enabled);
519 if (doc->grid_enabled)
520 {
521 std::string line = fmt::format("grid {}, {}, {}\n");
522 stream->write(line.c_str(), line.length());
523 }
524
525 // 'trees'
526 WriteTObjDelimiter(stream, "trees", doc->trees.size());
527 for (TObjTree& tree : doc->trees)
528 {
529 std::string line = fmt::format("trees {:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {} {} {} {:9f} {}\n",
530 tree.yaw_from, tree.yaw_to,
531 tree.scale_from, tree.scale_to,
532 tree.high_density,
533 tree.min_distance, tree.max_distance,
534 tree.tree_mesh, tree.color_map, tree.density_map,
535 tree.grid_spacing, tree.collision_mesh);
536 stream->write(line.c_str(), line.length());
537 }
538
539 // 'grass2' (incudes 'grass' elements)
540 WriteTObjDelimiter(stream, "grass", doc->grass.size());
541 for (TObjGrass& grass : doc->grass)
542 {
543 std::string line = fmt::format("grass2 {}, {:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {}, {:9f}, {:9f}, {}, {} {} {}\n",
544 grass.range,
545 grass.sway_speed, grass.sway_length, grass.sway_distrib, grass.density,
546 grass.min_x, grass.min_y, grass.max_x, grass.max_y,
547 (int)grass.fadetech, grass.min_h, grass.max_h, (int)grass.technique,
548 grass.material_name,
549 grass.color_map_filename,
550 grass.density_map_filename);
551 stream->write(line.c_str(), line.length());
552 }
553
554 // vehicles ('truck', 'truck2', 'boat', 'load', 'machine')
555 WriteTObjDelimiter(stream, "vehicles/loads/machines", doc->vehicles.size());
556 for (TObjVehicle& vehicle : doc->vehicles)
557 {
558 // Handle preceding comments
559 if (vehicle.comments != "")
560 {
561 for (Ogre::String& commenttext : Ogre::StringUtil::split(vehicle.comments, "\n"))
562 {
563 std::string commentline = fmt::format("// {}\n", commenttext);
564 stream->write(commentline.c_str(), commentline.length());
565 }
566 }
567
568 std::string line = fmt::format("{:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {:9f}, {} {}\n",
569 vehicle.position.x, vehicle.position.y, vehicle.position.z,
570 vehicle.tobj_rotation.x, vehicle.tobj_rotation.y, vehicle.tobj_rotation.z,
571 TObjSpecialObjectToString(vehicle.type), (const char*)vehicle.name);
572 stream->write(line.c_str(), line.length());
573 }
574
575 // procedural objects
576 WriteTObjDelimiter(stream, "roads", doc->proc_objects.size());
577 for (ProceduralObjectPtr& procobj : doc->proc_objects)
578 {
579 std::string bline = "begin_procedural_roads\n";
580 stream->write(bline.c_str(), bline.length());
581
582 std::string sline = fmt::format(" smoothing_num_splits {}\n", procobj->smoothing_num_splits);
583 stream->write(sline.c_str(), sline.length());
584
585 std::string cline = fmt::format(" collision_enabled {}\n", procobj->collision_enabled);
586 stream->write(cline.c_str(), cline.length());
587
588 for (ProceduralPointPtr& point : procobj->points)
589 {
590 std::string type_str;
591 switch (point->type)
592 {
593 case RoadType::ROAD_AUTOMATIC: type_str = "auto"; break;
594 case RoadType::ROAD_FLAT: type_str = "flat"; break;
595 case RoadType::ROAD_LEFT: type_str = "left"; break;
596 case RoadType::ROAD_RIGHT: type_str = "right"; break;
597 case RoadType::ROAD_BOTH: type_str = "both"; break;
598 case RoadType::ROAD_BRIDGE: type_str = (point->pillartype == 1) ? "bridge" : "bridge_no_pillars"; break;
599 case RoadType::ROAD_MONORAIL: type_str = (point->pillartype == 2) ? "monorail" : "monorail2"; break;
600 }
601
602 // Handle preceding comments
603 if (point->comments != "")
604 {
605 for (Ogre::String& commenttext : Ogre::StringUtil::split(point->comments, "\n"))
606 {
607 std::string commentline = fmt::format("// {}\n", commenttext);
608 stream->write(commentline.c_str(), commentline.length());
609 }
610 }
611
612 Ogre::Matrix3 point_rot_matrix;
613 point->rotation.ToRotationMatrix(point_rot_matrix);
614 Ogre::Radian yaw, pitch, roll;
615 point_rot_matrix.ToEulerAnglesYXZ(yaw, pitch, roll);
616
617 std::string line = fmt::format(
618 "\t{:13f}, {:13f}, {:13f}, 0, {:13f}, 0, {:13f}, {:13f}, {:13f}, {}\n",
619 point->position.x, point->position.y, point->position.z,
620 yaw.valueDegrees(),
621 point->width, point->bwidth, point->bheight, type_str);
622 stream->write(line.c_str(), line.length());
623 }
624
625 std::string eline = "end_procedural_roads\n";
626 stream->write(eline.c_str(), eline.length());
627 }
628
629 // static objects
630 WriteTObjDelimiter(stream, "static objects", doc->objects.size());
631 for (TObjEntry& entry : doc->objects)
632 {
633 // Handle preceding comments
634 if (entry.comments != "")
635 {
636 for (Ogre::String& commenttext : Ogre::StringUtil::split(entry.comments, "\n"))
637 {
638 std::string commentline = fmt::format("// {}\n", commenttext);
639 stream->write(commentline.c_str(), commentline.length());
640 }
641 }
642
643 // Don't save autogenerated instance names
644 std::string valid_instance_name;
645 if (strncmp(entry.instance_name, "auto^", 5) != 0)
646 {
647 valid_instance_name = entry.instance_name;
648 }
649
650 std::string line = fmt::format("{:8.3f}, {:8.3f}, {:8.3f}, {:9f}, {:9f}, {:9f}, {} {} {}\n",
651 entry.position.x, entry.position.y, entry.position.z,
652 entry.rotation.x, entry.rotation.y, entry.rotation.z,
653 entry.odef_name, entry.type, valid_instance_name);
654 stream->write(line.c_str(), line.length());
655 }
656}
657
#define ROR_ASSERT(_EXPR)
Definition Application.h:40
void LOG(const char *msg)
Legacy alias - formerly a macro.
void WriteTObjDelimiter(Ogre::DataStreamPtr &stream, const std::string &title, size_t count)
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition Str.h:36
const char * ToCStr() const
Definition Str.h:46
char * GetBuffer()
Definition Str.h:48
TObjDocumentPtr Finalize()
Passes ownership.
Ogre::Vector3 m_road2_last_rot
ProceduralObjectPtr m_cur_procedural_obj
bool ProcessLine(const char *line)
float m_default_rendering_distance
std::string m_filename
TObjDocumentPtr m_def
void ProcessRoadObject(const TObjEntry &object)
std::string m_preceding_line_comments
bool ParseObjectLine(TObjEntry &object)
void ProcessActorObject(const TObjEntry &object)
const char * m_cur_line_trimmed
void ImportProceduralPoint(Ogre::Vector3 const &pos, Ogre::Vector3 const &rot, TObjSpecialObject special)
static Ogre::Quaternion CalcRotation(Ogre::Vector3 const &rot, bool rot_yxz)
const char * m_cur_line
int m_cur_procedural_obj_start_line
Ogre::Vector3 m_road2_last_pos
void ProcessOgreStream(Ogre::DataStream *stream)
GrassTechnique ValidateGrassTech(GrassTechnique src)
const int LINE_BUF_LEN
void WriteToStream(TObjDocumentPtr doc, Ogre::DataStreamPtr stream)
std::shared_ptr< TObjDocument > TObjDocumentPtr
const char * TObjSpecialObjectToString(TObjSpecialObject val)
TObjSpecialObject
@ TRUCK2
Free position (not auto-adjusted to fit terrain or water surface)
std::string custom_groundmodel
If empty, the default ground model ("asphalt" for roads, "concrete" for others) is used for collision...
bool collision_enabled
Generate collision triangles?
std::vector< ProceduralPointPtr > points
std::string custom_material
If empty, the default material is used.
std::string comments
Comment line(s) preceding the point-line in the .TOBJ file.
Ogre::Quaternion rotation
char instance_name[TObj::STR_LEN]
bool IsRoad() const
bool IsActor() const
char type[TObj::STR_LEN]
char odef_name[TObj::STR_LEN]
TObjSpecialObject special
TObjEntry()
Unified 'grass' and 'grass2'.
char density_map_filename[TObj::STR_LEN]
char material_name[TObj::STR_LEN]
Forests::GrassTechnique technique
Forests::FadeTechnique fadetech
char color_map_filename[TObj::STR_LEN]
char collision_mesh[TObj::STR_LEN]
char density_map[TObj::STR_LEN]
char tree_mesh[TObj::STR_LEN]
int origin_tobj_line_number
char color_map[TObj::STR_LEN]
Ogre::Quaternion rotation
Ogre::Vector3 position
Ogre::Vector3 tobj_rotation
Original rotation specified in .TOBJ file.
char name[TObj::STR_LEN]
TObjSpecialObject type