Richard Boegli's CnC_Generals_Zero_Hour Fork WIP
This is documentation of Richard Boegil's Zero Hour Fork
 
Loading...
Searching...
No Matches
vehiclecurve.cpp
Go to the documentation of this file.
1/*
2** Command & Conquer Generals Zero Hour(tm)
3** Copyright 2025 Electronic Arts Inc.
4**
5** This program is free software: you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation, either version 3 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19/***********************************************************************************************
20 *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
21 ***********************************************************************************************
22 * *
23 * Project Name : LevelEdit *
24 * *
25 * $Archive:: /Commando/Code/wwmath/vehiclecurve.cpp $*
26 * *
27 * Author:: Patrick Smith *
28 * *
29 * $Modtime:: 6/12/01 10:02a $*
30 * *
31 * $Revision:: 8 $*
32 * *
33 *---------------------------------------------------------------------------------------------*
34 * Functions: *
35 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
36
37#include "vehiclecurve.h"
38#include "vector3.h"
39#include "matrix3d.h"
40#include "persistfactory.h"
41#include "wwmathids.h"
42#include "wwmemlog.h"
43
44
46// Save-Load stuff
49
51// Save/Load constants
53enum
54{
55 CHUNKID_PARENT = 0x11071217,
58};
59
60enum
61{
64};
65
66
68// Local prototypes
70bool Find_Tangent (const Vector3 &center, float radius, const Vector3 &point, bool clockwise, float *result);
71float Get_Angle_Delta (float angle1, float angle2, bool clockwise);
72void Find_Turn_Arc (const Matrix3D &transform, float radius, const Vector3 &prev_pt, const Vector3 &curr_pt, const Vector3 &next_pt, Vector3 *arc_center, bool *is_right_turn);
73void Find_Tangents (float radius, const Vector3 &prev_pt, const Vector3 &curr_pt, const Vector3 &next_pt, const Vector3 &arc_center, bool is_right_turn, float *point_angle, float *angle_in_delta, float *angle_out_delta);
74
75
77//
78// Find_Tangent
79//
81bool
83(
84 const Vector3 & center,
85 float radius,
86 const Vector3 & point,
87 bool clockwise,
88 float * result
89)
90{
91 bool retval = false;
92
93 //
94 // Calculate the distance from the point to the center of the circle
95 //
96 float delta_x = point.X - center.X;
97 float delta_y = point.Y - center.Y;
98 float dist = ::sqrt (delta_x * delta_x + delta_y * delta_y);
99 if (dist >= radius) {
100
101 //
102 // Determine the offset angle (from the line between the point and center)
103 // where the 2 tangent points lie.
104 //
105 float angle_offset = WWMath::Acos (radius / dist);
106 float base_angle = WWMath::Atan2 (delta_x, -delta_y);
107 base_angle = WWMath::Wrap (base_angle, 0, DEG_TO_RADF (360));
108
109 //
110 // Determine which tangent angle we would come across first, depending
111 // on our orientation
112 //
113 float angle = 0;
114 if (clockwise) {
115 angle = base_angle - angle_offset;
116 } else {
117 angle = base_angle + angle_offset;
118 }
119 angle = WWMath::Wrap (angle, 0, DEG_TO_RADF (360));
120 (*result) = angle;
121
122 retval = true;
123 }
124
125 return retval;
126}
127
128
130//
131// Get_Angle_Delta
132//
133// Angle deltas need to be wrapped around 360 degrees differently
134// depending on the orientation (clockwise/counterclockwise). This
135// function takes orientation into consideration when determining
136// the delta.
137//
139float
141(
142 float angle1,
143 float angle2,
144 bool clockwise
145)
146{
147 float result = angle1 - angle2;
148
149 if (clockwise) {
150 if (angle1 < angle2) {
151 result = angle1 - (angle2 - DEG_TO_RADF (360));
152 }
153 } else {
154 if (angle1 > angle2) {
155 result = (angle1 - DEG_TO_RADF (360)) - angle2;
156 }
157 }
158
159 return result;
160}
161
162
164//
165// Find_Turn_Arc
166//
168void
170(
171 const Matrix3D & transform,
172 float radius,
173 const Vector3 & prev_pt,
174 const Vector3 & curr_pt,
175 const Vector3 & next_pt,
176 Vector3 * arc_center,
177 bool * is_right_turn
178)
179{
180 //
181 // The center of the turn arc can lie anywhere on the circle centered
182 // at the current point and 'radius' meters in radius.
183 //
184 // We will assume the optimal center of the turn arc will lie at
185 // the point halfway between the angles formed by the (prev-curr) and
186 // (next-curr) vectors.
187 //
188 float angle1 = ::WWMath::Atan2 ((prev_pt.Y - curr_pt.Y), prev_pt.X - curr_pt.X);
189 angle1 = WWMath::Wrap (angle1, 0, DEG_TO_RADF (360));
190
191 float angle2 = ::WWMath::Atan2 ((next_pt.Y - curr_pt.Y), next_pt.X - curr_pt.X);
192 angle2 = WWMath::Wrap (angle2, 0, DEG_TO_RADF (360));
193
194 float avg_angle = (angle1 + angle2) * 0.5F;
195
196 //
197 // Find the shortest delta between the two angles (either clockwise or
198 // counterclockwise).
199 //
200 float delta1 = WWMath::Fabs (::Get_Angle_Delta (angle1, angle2, true));
201 float delta2 = WWMath::Fabs (::Get_Angle_Delta (angle1, angle2, false));
202 if (delta1 < delta2) {
203 avg_angle = angle1 - (delta1 * 0.5F);
204 } else {
205 avg_angle = angle1 + (delta2 * 0.5F);
206 }
207
208 //
209 // Find the point on the circle at this angle
210 //
211 arc_center->X = curr_pt.X + (radius * ::WWMath::Cos (avg_angle));
212 arc_center->Y = curr_pt.Y + (radius * ::WWMath::Sin (avg_angle));
213 arc_center->Z = curr_pt.Z;
214
215 //
216 // Will we be making a right turn or a left turn?
217 //
218 Vector3 rel_center;
219 Matrix3D::Inverse_Transform_Vector (transform, *arc_center, &rel_center);
220 (*is_right_turn) = (rel_center.Y > 0);
221 return ;
222}
223
224
226//
227// Find_Tangents
228//
230void
232(
233 float radius,
234 const Vector3 & prev_pt,
235 const Vector3 & curr_pt,
236 const Vector3 & next_pt,
237 const Vector3 & arc_center,
238 bool is_right_turn,
239 float * point_angle,
240 float * angle_in_delta,
241 float * angle_out_delta
242)
243{
244
245 //
246 // Find the 'in' and 'out' tangent angles
247 //
248 float angle_in = 0;
249 float angle_out = 0;
250 bool valid_in = ::Find_Tangent (arc_center, radius, prev_pt, is_right_turn, &angle_in);
251 bool valid_out = ::Find_Tangent (arc_center, radius, next_pt, !is_right_turn, &angle_out);
252
253 //
254 // Find the angle where the current position lies on the turn arc
255 //
256 (*point_angle) = ::WWMath::Atan2 (curr_pt.X - arc_center.X, -(curr_pt.Y - arc_center.Y));
257 (*point_angle) = WWMath::Wrap ((*point_angle), 0, DEG_TO_RADF (360));
258
259 //
260 // If the tangent-in is valid, find its delta from the 'point angle.
261 //
262 if (valid_in) {
263 (*angle_in_delta) = ::Get_Angle_Delta (angle_in, (*point_angle), is_right_turn);
264 } else {
265 (*angle_in_delta) = 0;
266 }
267
268 //
269 // If the tangent-out is valid, find its delta from the 'point angle.
270 //
271 if (valid_out) {
272 (*angle_out_delta) = ::Get_Angle_Delta (angle_out, (*point_angle), !is_right_turn);
273 } else {
274 (*angle_out_delta) = 0;
275 }
276
277 return ;
278}
279
280
282//
283// Update_Arc_List
284//
286void
288{
290 m_ArcList.Delete_All ();
291
292 //
293 // Bail out if there is nothing to do
294 //
295 int count = Key_Count ();
296 if (count == 0) {
297 return ;
298 }
299
300 //
301 // Add a record for the starting point of the arc...
302 //
303 ArcInfoStruct arc_start;
304 arc_start.point_in = Keys[0].Point;
305 arc_start.point_out = Keys[0].Point;
306 arc_start.center = Keys[0].Point;
307 arc_start.point_angle = 0;
308 arc_start.radius = 0;
309 arc_start.angle_in_delta = 0;
310 arc_start.angle_out_delta = 0;
311 m_ArcList.Add (arc_start);
312
313 //
314 // Loop over each 'interior' point and generate arc information
315 // for each.
316 //
317 for (int index = 1; index < count - 1; index ++) {
318
319 //
320 // Get information about the previous, next, and current points.
321 //
322 Vector3 prev_pt;
323 Vector3 next_pt;
324 Vector3 curr_pt;
325 float time = 0;
326 Get_Key (index-1, &prev_pt, &time);
327 Get_Key (index, &curr_pt, &time);
328 Get_Key (index+1, &next_pt, &time);
329
330 //
331 // Determine the last known point on the path
332 //
333 Vector3 last_path_pt = m_ArcList[index-1].point_out;
334
335 //
336 // Create a transformation matrix to simulate the vehicle's position and
337 // orientation at the last point...
338 //
339 Vector3 x_vector (curr_pt - last_path_pt);
340 Vector3 z_vector (0, 0, 1);
341 x_vector.Normalize ();
342#ifdef ALLOW_TEMPORARIES
343 Vector3 y_vector = Vector3::Cross_Product (x_vector, z_vector);
344#else
345 Vector3 y_vector;
346 Vector3::Cross_Product (x_vector, z_vector, &y_vector);
347#endif
348 Matrix3D tm (x_vector, y_vector, z_vector, last_path_pt);
349
350 //
351 // Find where the turn arc should be centered and whether we should
352 // make a right-turn or a left turn...
353 //
354 bool is_right_turn = false;
355 Vector3 arc_center (0, 0, 0);
356 ::Find_Turn_Arc ( tm,
357 m_Radius,
358 last_path_pt,
359 curr_pt,
360 next_pt,
361 &arc_center,
362 &is_right_turn);
363
364 //
365 // Determine where the vehicle should enter and exit the turn
366 //
367 float angle_in_delta = 0;
368 float angle_out_delta = 0;
369 float point_angle = 0;
371 last_path_pt,
372 curr_pt,
373 next_pt,
374 arc_center,
375 is_right_turn,
376 &point_angle,
377 &angle_in_delta,
378 &angle_out_delta);
379
380 //
381 // Determine at what points these angles intersect the arc
382 //
383 Vector3 point_in (0, 0, 0);
384 point_in.X = arc_center.X + (m_Radius * ::WWMath::Sin (point_angle + angle_in_delta));
385 point_in.Y = arc_center.Y + (m_Radius * -::WWMath::Cos (point_angle + angle_in_delta));
386
387 Vector3 point_out (0, 0, 0);
388 point_out.X = arc_center.X + (m_Radius * ::WWMath::Sin (point_angle + angle_out_delta));
389 point_out.Y = arc_center.Y + (m_Radius * -::WWMath::Cos (point_angle + angle_out_delta));
390
391 //
392 // Sanity check to ensure the vehicle doesn't try to go the long way around the
393 // turn arc...
394 //
395 if ( angle_in_delta > DEG_TO_RADF (200) || angle_out_delta > DEG_TO_RADF (200) ||
396 angle_in_delta < -DEG_TO_RADF (200) || angle_out_delta < -DEG_TO_RADF (200) )
397 {
398 //
399 // Record information about this arc
400 //
401 ArcInfoStruct arc_info;
402 arc_info.center = curr_pt;
403 arc_info.point_angle = 0;
404 arc_info.point_in = curr_pt;
405 arc_info.point_out = curr_pt;
406 arc_info.radius = 0;
407 arc_info.angle_in_delta = 0;
408 arc_info.angle_out_delta = 0;
409 m_ArcList.Add (arc_info);
410
411 } else {
412
413 //
414 // Record information about this arc
415 //
416 ArcInfoStruct arc_info;
417 arc_info.center = arc_center;
418 arc_info.point_angle = point_angle;
419 arc_info.point_in = point_in;
420 arc_info.point_out = point_out;
421 arc_info.radius = m_Radius;
422 arc_info.angle_in_delta = angle_in_delta;
423 arc_info.angle_out_delta = angle_out_delta;
424 m_ArcList.Add (arc_info);
425 }
426 }
427
428 //
429 // Add a record for the starting point of the arc...
430 //
431 if (count > 1) {
432 ArcInfoStruct arc_end;
433 arc_end.point_in = Keys[count-1].Point;
434 arc_end.point_out = Keys[count-1].Point;
435 arc_end.center = Keys[count-1].Point;
436 arc_end.point_angle = 0;
437 arc_end.radius = 0;
438 arc_end.angle_in_delta = 0;
439 arc_end.angle_out_delta = 0;
440 m_ArcList.Add (arc_end);
441 }
442
443 m_IsDirty = false;
444 return ;
445}
446
447
449//
450// Evaluate
451//
453void
455{
456 int count = Keys.Count ();
457 m_Sharpness = 0;
458
459 if (time < Keys[0].Time) {
460 *set_val = Keys[0].Point;
461 m_LastTime = Keys[0].Time;
462 return;
463 }
464
465 if (time >= Keys[count - 1].Time) {
466 *set_val = Keys[count - 1].Point;
467 m_LastTime = Keys[count - 1].Time;
468 return;
469 }
470
471 //
472 // Update the arc information if any of the keys have changed...
473 //
474 if (m_IsDirty) {
476 }
477
478 //
479 // Determine which segment we are on
480 //
481 int index0 = 0;
482 int index1 = 0;
483 float seg_time = 0;
484 Find_Interval (time, &index0, &index1, &seg_time);
485
486 ArcInfoStruct &arc_info0 = m_ArcList[index0];
487 ArcInfoStruct &arc_info1 = m_ArcList[index1];
488
489 //
490 // Determine the lengths of each segment of this curve.
491 // The segments are:
492 // - Exit curve from prev point
493 // - Straight line from exit of last curve to enter of this curve
494 // - Enter curve for the current point
495 //
496 float arc_length0 = arc_info0.radius * WWMath::Fabs (arc_info0.angle_out_delta);
497 float arc_length1 = arc_info1.radius * WWMath::Fabs (arc_info1.angle_in_delta);
498 float other_length = ((arc_info1.point_in - arc_info0.point_out).Length ()) / 2;
499 float total_length = arc_length0 + arc_length1 + other_length;
500
501 //
502 // Determine at what times we should switch between parts of the segment
503 //
504 float time1 = arc_length0 / total_length;
505 float time2 = (arc_length0 + other_length) / total_length;
506
507 //
508 // Determine which part of the segment we are on
509 //
510 if (seg_time < time1) {
511
512 //
513 // We are on the initial curve of the segment, so calculate where
514 // on the curve we are...
515 //
516 //float percent = seg_time / time1;
517 //float angle = arc_info0.point_angle + (arc_info0.angle_out_delta) * percent;
518 float angle = arc_info0.point_angle + arc_info0.angle_out_delta;
519
520 set_val->X = arc_info0.center.X + (arc_info0.radius * ::WWMath::Sin (angle));
521 set_val->Y = arc_info0.center.Y + (arc_info0.radius * -::WWMath::Cos (angle));
522
523 m_Sharpness = WWMath::Clamp (WWMath::Fabs (arc_info0.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F);
524 m_SharpnessPos.X = set_val->X;
525 m_SharpnessPos.Y = set_val->Y;
526 m_SharpnessPos.Z = Keys[index0].Point.Z + (Keys[index1].Point.Z - Keys[index0].Point.Z) * seg_time;
527
528 m_LastTime = Keys[index0].Time + (Keys[index1].Time - Keys[index0].Time) * time1;
529
530 } else if (seg_time < time2) {
531
532 //
533 // We are on the line between the two curves, so calculate where on
534 // the line we are
535 //
536 float percent = (seg_time - time1) / (time2 - time1);
537
538 if (percent == 0) {
539 set_val->X = arc_info0.point_out.X;
540 set_val->Y = arc_info0.point_out.Y;
541 } else {
542 set_val->X = arc_info1.point_in.X;
543 set_val->Y = arc_info1.point_in.Y;
544 }
545
546 //set_val->X = arc_info0.point_out.X + (arc_info1.point_in.X - arc_info0.point_out.X) * percent;
547 //set_val->Y = arc_info0.point_out.Y + (arc_info1.point_in.Y - arc_info0.point_out.Y) * percent;
548
549 m_Sharpness = WWMath::Clamp (WWMath::Fabs (arc_info1.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F);
550 m_SharpnessPos = arc_info1.point_in;
551
552 m_LastTime = Keys[index0].Time + (Keys[index1].Time - Keys[index0].Time) * time2;
553
554 } else {
555
556 //
557 // We are on the ending curve of the segment, so calculate where
558 // on the curve we are...
559 //
560 /*float percent = 1.0F - ((seg_time - time2) / (1.0F - time2));
561 float angle = arc_info1.point_angle + (arc_info1.angle_in_delta * percent);
562
563 set_val->X = arc_info1.center.X + (arc_info1.radius * ::WWMath::Sin (angle));
564 set_val->Y = arc_info1.center.Y + (arc_info1.radius * -::WWMath::Cos (angle)); */
565
566 float angle = arc_info1.point_angle + (arc_info1.angle_out_delta);
567
568 set_val->X = arc_info1.center.X + (arc_info1.radius * ::WWMath::Sin (angle));
569 set_val->Y = arc_info1.center.Y + (arc_info1.radius * -::WWMath::Cos (angle));
570
571 m_Sharpness = WWMath::Clamp (WWMath::Fabs (arc_info1.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F);
572 m_SharpnessPos.X = set_val->X;
573 m_SharpnessPos.Y = set_val->Y;
574 m_SharpnessPos.Z = Keys[index0].Point.Z + (Keys[index1].Point.Z - Keys[index0].Point.Z) * seg_time;
575
576 m_LastTime = Keys[index1].Time;
577 }
578
579 //
580 // Our Z value is just a linear interpolation
581 //
582 set_val->Z = Keys[index0].Point.Z + (Keys[index1].Point.Z - Keys[index0].Point.Z) * seg_time;
583 return ;
584}
585
590
591
593//
594// Save
595//
597bool
599{
601 Curve3DClass::Save (csave);
602 csave.End_Chunk ();
603
605
606 //
607 // Save each variable to its own microchunk
608 //
611
612 csave.End_Chunk ();
613
614 //
615 // Save each arc info struct to its own chunk
616 //
617 for (int index = 0; index < m_ArcList.Count (); index ++) {
618 ArcInfoStruct &arc_info = m_ArcList[index];
619
621 csave.Write (&arc_info, sizeof (arc_info));
622 csave.End_Chunk ();
623 }
624
625 return true;
626}
627
628
630//
631// Load
632//
634bool
636{
637 while (cload.Open_Chunk ()) {
638 switch (cload.Cur_Chunk_ID ()) {
639
640 case CHUNKID_PARENT:
641 Curve3DClass::Load (cload);
642 break;
643
644 case CHUNKID_ARC_INFO:
645 {
646 ArcInfoStruct arc_info;
647 cload.Read (&arc_info, sizeof (arc_info));
648 m_ArcList.Add (arc_info);
649 }
650 break;
651
653 Load_Variables (cload);
654 break;
655 }
656
657 cload.Close_Chunk ();
658 }
659
660 return true;
661}
662
663
665//
666// Load_Variables
667//
669void
671{
672 //
673 // Loop through all the microchunks that define the variables
674 //
675 while (cload.Open_Micro_Chunk ()) {
676 switch (cload.Cur_Micro_Chunk_ID ()) {
677
680 }
681
682 cload.Close_Micro_Chunk ();
683 }
684
685 return ;
686}
#define WRITE_MICRO_CHUNK(csave, id, var)
Definition chunkio.h:293
#define READ_MICRO_CHUNK(cload, id, var)
Definition chunkio.h:334
#define DEG_TO_RADF(x)
Definition wwmath.h:87
@ CHUNKID_VARIABLES
bool Close_Micro_Chunk()
Definition chunkio.cpp:585
bool Close_Chunk()
Definition chunkio.cpp:448
uint32 Cur_Chunk_ID()
Definition chunkio.cpp:484
uint32 Cur_Micro_Chunk_ID()
Definition chunkio.cpp:622
uint32 Read(void *buf, uint32 nbytes)
Definition chunkio.cpp:692
bool Open_Chunk()
Definition chunkio.cpp:412
bool Open_Micro_Chunk()
Definition chunkio.cpp:557
uint32 Write(const void *buf, uint32 nbytes)
Definition chunkio.cpp:264
bool Begin_Chunk(uint32 id)
Definition chunkio.cpp:108
bool End_Chunk()
Definition chunkio.cpp:148
virtual int Key_Count(void)
Definition curve.cpp:136
void Find_Interval(float time, int *i0, int *i1, float *t)
Definition curve.cpp:188
virtual bool Load(ChunkLoadClass &cload)
Definition curve.cpp:221
virtual bool Save(ChunkSaveClass &csave)
Definition curve.cpp:202
virtual void Get_Key(int i, Vector3 *set_point, float *set_t)
Definition curve.cpp:141
DynamicVectorClass< KeyClass > Keys
Definition curve.h:104
static WWINLINE void Inverse_Transform_Vector(const Matrix3D &tm, const Vector3 &in, Vector3 *out)
Definition matrix3d.h:1778
float X
Definition vector3.h:90
float Z
Definition vector3.h:92
float Y
Definition vector3.h:91
void Normalize(void)
Definition vector3.h:417
static WWINLINE void Cross_Product(const Vector3 &a, const Vector3 &b, Vector3 *result)
Definition vector3.h:374
void Evaluate(float time, Vector3 *set_val)
virtual bool Save(ChunkSaveClass &csave)
struct VehicleCurveClass::_ArcInfoStruct ArcInfoStruct
void Load_Variables(ChunkLoadClass &cload)
virtual const PersistFactoryClass & Get_Factory(void) const
void Update_Arc_List(void)
virtual bool Load(ChunkLoadClass &cload)
static float Wrap(float val, float min=0.0f, float max=1.0f)
Definition wwmath.h:229
static float Clamp(float val, float min=0.0f, float max=1.0f)
Definition wwmath.h:208
static WWINLINE float Acos(float val)
Definition wwmath.h:510
static float Atan2(float y, float x)
Definition wwmath.h:150
static WWINLINE float Fabs(float val)
Definition wwmath.h:113
static float Sin(float val)
Definition wwmath.h:378
static float Cos(float val)
Definition wwmath.h:356
int percent
Definition patch.cpp:426
else return(RetVal)
SimplePersistFactoryClass< VehicleCurveClass, WWMATH_CHUNKID_VEHICLECURVE > _VehicleCurveFactory
bool Find_Tangent(const Vector3 &center, float radius, const Vector3 &point, bool clockwise, float *result)
void Find_Tangents(float radius, const Vector3 &prev_pt, const Vector3 &curr_pt, const Vector3 &next_pt, const Vector3 &arc_center, bool is_right_turn, float *point_angle, float *angle_in_delta, float *angle_out_delta)
void Find_Turn_Arc(const Matrix3D &transform, float radius, const Vector3 &prev_pt, const Vector3 &curr_pt, const Vector3 &next_pt, Vector3 *arc_center, bool *is_right_turn)
@ CHUNKID_PARENT
@ CHUNKID_ARC_INFO
float Get_Angle_Delta(float angle1, float angle2, bool clockwise)
@ VARID_RADIUS
@ VARID_IS_DIRTY
@ MEM_PATHFIND
Definition wwmemlog.h:62
#define WWMEMLOG(category)
Definition wwmemlog.h:183