Skip to content

Commit 25a4db6

Browse files
cgame: Add bendy beam feature, looks more or less the same as BFP
1 parent 99d698a commit 25a4db6

1 file changed

Lines changed: 121 additions & 20 deletions

File tree

source/cgame/cg_trails.c

Lines changed: 121 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ BFP TRAILS
2020
typedef 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

2527
static 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

Comments
 (0)