@@ -20,6 +20,8 @@ BFP TRAILS
2020typedef struct {
2121 vec3_t segments [TRAIL_SEGMENTS ];
2222 int numSegments ;
23+ vec3_t lastAimAngles ; // track last aim direction
24+ int lastAimChangeTime ; // last time aim direction changed
2325} trail_t ;
2426
2527static trail_t cg_trails [MAX_GENTITIES ][2 ];
@@ -56,6 +58,8 @@ void CG_ResetTrail( const int TRAIL_TYPE, int entityNum, vec3_t origin ) {
5658 VectorCopy ( origin , cg_trails [entityNum ][TRAIL_TYPE ].segments [i ] );
5759 }
5860 cg_trails [entityNum ][TRAIL_TYPE ].numSegments = 0 ;
61+ VectorClear ( cg_trails [entityNum ][TRAIL_TYPE ].lastAimAngles );
62+ cg_trails [entityNum ][TRAIL_TYPE ].lastAimChangeTime = 0 ;
5963}
6064
6165/*
@@ -164,8 +168,8 @@ void CG_BeamTrail( int entityNum, vec3_t origin, vec3_t muzzleOrigin, qhandle_t
164168 int i ;
165169 int nBeamSegments = cg_beamTrail .integer ;
166170 trail_t * beamTrail = & cg_trails [entityNum ][BEAM_TRAIL ];
167-
168- // BFP - TODO: Make beam bendy like BFP did
171+ const float BEAM_STRAIGHTEN_RATE = 0.2f ; // how quickly segments straighten (0.0 - 1.0)
172+ const float BEAM_AIM_THRESHOLD = 1.0f ; // minimum angle change to consider "aiming"
169173
170174 if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) {
171175 return ;
@@ -175,37 +179,135 @@ void CG_BeamTrail( int entityNum, vec3_t origin, vec3_t muzzleOrigin, qhandle_t
175179 nBeamSegments = TRAIL_SEGMENTS ;
176180 }
177181
178- if ( nBeamSegments < 2 ) {
182+ // for better visual bendy effect, the number of segments should be equal or more than 10
183+ if ( nBeamSegments < 10 ) {
179184 nBeamSegments = 2 ;
180185 }
181186
182- VectorCopy ( origin , beamTrail -> segments [nBeamSegments - 1 ] );
187+ beamTrail -> numSegments = nBeamSegments ;
188+
183189 VectorCopy ( muzzleOrigin , beamTrail -> segments [0 ] );
190+ VectorCopy ( origin , beamTrail -> segments [nBeamSegments - 1 ] );
184191
185- // stretching segments
186- {
187- vec3_t dir ;
188- for ( i = 1 ; i < nBeamSegments - 1 ; ++ i ) {
189- float stretchFactor = (float )i / (float )( nBeamSegments - 1 );
190- VectorSubtract ( muzzleOrigin , origin , dir );
191- VectorMA ( origin , stretchFactor , dir , beamTrail -> segments [i ] );
192+ // start stretching segments
193+ if ( nBeamSegments >= 10 ) {
194+ vec3_t direction , currentAngles , beamDir ;
195+ float angleDelta , beamLength , lengthFactor ;
196+ qboolean isAiming ;
197+
198+ // beam length and direction
199+ VectorSubtract ( origin , muzzleOrigin , beamDir );
200+ beamLength = VectorLength ( beamDir );
201+ VectorNormalize ( beamDir );
202+ vectoangles ( beamDir , currentAngles );
203+
204+ // length factor for straightening based on beam length
205+ // longer beam = more straightening (less curve)
206+ // reference length of 5000 units as baseline
207+ lengthFactor = 1.0f + ( beamLength / 5000.0f );
208+ if ( lengthFactor > 2.5f ) {
209+ lengthFactor = 2.5f ;
210+ }
211+
212+ if ( beamTrail -> lastAimChangeTime > 0 ) {
213+ angleDelta = Distance ( currentAngles , beamTrail -> lastAimAngles );
214+ isAiming = ( angleDelta > BEAM_AIM_THRESHOLD );
215+ } else {
216+ isAiming = qfalse ;
217+ angleDelta = 0 ;
218+ }
219+
220+ if ( isAiming ) {
221+ beamTrail -> lastAimChangeTime = cg .time + 200 ;
222+ }
223+ VectorCopy ( currentAngles , beamTrail -> lastAimAngles );
224+
225+ // shift all segments down by one position (creating trail effect)
226+ for ( i = nBeamSegments - 1 ; i > 0 ; -- i ) {
227+ VectorCopy ( beamTrail -> segments [i - 1 ], beamTrail -> segments [i ] );
228+ }
229+
230+ // update all segments with distance-based straightening
231+ for ( i = 1 ; i < nBeamSegments ; ++ i ) {
232+ vec3_t targetPos , segmentToOrigin ;
233+ float distanceToOrigin , segmentFraction ;
234+ float straightenFactor , maxExpectedDistance , stretchRatio ;
235+
236+ // ideal position on straight line from muzzle to origin
237+ segmentFraction = (float )i / (float )( nBeamSegments - 1 );
238+ VectorMA ( muzzleOrigin , segmentFraction * beamLength , beamDir , targetPos );
239+
240+ // how far this segment is from the impact point
241+ VectorSubtract ( origin , beamTrail -> segments [i ], segmentToOrigin );
242+ distanceToOrigin = VectorLength ( segmentToOrigin );
243+
244+ // expected distance if beam was straight
245+ maxExpectedDistance = beamLength * ( 1.0f - segmentFraction );
246+
247+ // stretch ratio (how much segments are stretched)
248+ stretchRatio = 1.0f ;
249+ if ( maxExpectedDistance > 0.1f ) {
250+ stretchRatio = distanceToOrigin / maxExpectedDistance ;
251+ }
252+
253+ // more stretch = more straightening
254+ // stretchRatio: 1.0 = no stretch, >1.0 = stretched
255+
256+ // currently aiming - allow bending with minimal straightening
257+ // length factor: longer beams still straighten a bit more even while aiming
258+ straightenFactor = 0.05f * lengthFactor ;
259+ if ( straightenFactor > 0.3f ) {
260+ straightenFactor = 0.3f ;
261+ }
262+
263+ if ( stretchRatio > 1.0f ) {
264+ // segments are stretching, straighten them out more aggressively
265+ // length factor: longer beams straighten more
266+ straightenFactor = BEAM_STRAIGHTEN_RATE * stretchRatio * lengthFactor ;
267+ if ( straightenFactor > 1.0f ) {
268+ straightenFactor = 1.0f ;
269+ }
270+ } else if ( cg .time - beamTrail -> lastAimChangeTime > 100 ) {
271+ // not aiming and not stretched - gentle straightening
272+ // length factor: longer beams straighten faster
273+ straightenFactor = BEAM_STRAIGHTEN_RATE * lengthFactor ;
274+ if ( straightenFactor > 1.0f ) {
275+ straightenFactor = 1.0f ;
276+ }
277+ }
278+
279+ // transition toward target position
280+ VectorScale ( beamTrail -> segments [i ], 1.0f - straightenFactor , beamTrail -> segments [i ] );
281+ VectorMA ( beamTrail -> segments [i ], straightenFactor , targetPos , beamTrail -> segments [i ] );
192282 }
193283 }
194284
195285 // draw every beam segment
196- for ( i = 0 ; i < nBeamSegments - 1 ; ++ i ) {
286+ {
197287 refEntity_t beam ;
198288 memset ( & beam , 0 , sizeof ( beam ) );
199- beam . reType = RT_LIGHTNING ;
289+
200290 beam .customShader = hShader ;
201291 beam .shaderRGBA [0 ] = beam .shaderRGBA [1 ] = beam .shaderRGBA [2 ] = beam .shaderRGBA [3 ] = 0xff ;
202292
203- VectorCopy ( beamTrail -> segments [i ], beam .origin );
204- VectorCopy ( beamTrail -> segments [i + 1 ], beam .oldorigin );
293+ for ( i = 0 ; i < nBeamSegments - 1 ; ++ i ) {
294+ // BFP - NOTE: Skip the rendering of the 2 last segments.
295+ // Weird. That's why the bendy beam can't visualize that segment correctly.
296+ // On original BFP also happens
297+ if ( nBeamSegments >= 10 && i >= (nBeamSegments - 2 ) ) {
298+ return ;
299+ }
300+ beam .reType = RT_LIGHTNING ;
301+ VectorCopy ( beamTrail -> segments [i ], beam .origin );
302+ VectorCopy ( beamTrail -> segments [i + 1 ], beam .oldorigin );
205303
206- trap_R_AddRefEntityToScene ( & beam );
304+ trap_R_AddRefEntityToScene ( & beam );
305+
306+ // apply sprite in the middle of every segment to avoid showing ugly visual effect
307+ beam .reType = RT_SPRITE ;
308+ trap_R_AddRefEntityToScene ( & beam );
309+ }
207310 }
208- beamTrail -> numSegments = nBeamSegments ;
209311}
210312
211313
@@ -288,11 +390,10 @@ void CG_CorkscrewTrail( int entityNum, vec3_t origin, vec3_t muzzleOrigin, qhand
288390 trap_R_AddRefEntityToScene ( & beam );
289391
290392 // draw every corkscrew segment
393+ beam .customShader = corkscrewShader ;
394+
291395 for ( i = 0 ; i < CORKSCREW_SEGMENTS - 1 ; ++ i ) {
292- memset ( & beam , 0 , sizeof ( beam ) );
293396 beam .reType = RT_RAIL_CORE ;
294- beam .customShader = corkscrewShader ;
295- beam .shaderRGBA [0 ] = beam .shaderRGBA [1 ] = beam .shaderRGBA [2 ] = beam .shaderRGBA [3 ] = 0xff ;
296397 VectorCopy ( spiralSegments [i ], beam .origin );
297398 VectorCopy ( spiralSegments [i + 1 ], beam .oldorigin );
298399 trap_R_AddRefEntityToScene ( & beam );
0 commit comments