Owned: Bobbing Platforms (Tutorial)

Discuss programming in the QuakeC language.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Owned: Bobbing Platforms (Tutorial)

Post by Baker »

frag.machine wrote:AFAIK you cannot have bobbing platforms with just vanilla QuakeC.
You had me scared :D
Lardarse wrote:You can make them bob, but they won't be that smooth. Basic idea is that the platform nextthinks a lot to adjust velocity.
Well, actually it is smooth :D I was a bit afraid it wouldn't be.
Lardarse wrote:Not sure how you make sure it stops at exactly the correct top/bottom points every time, though.
That was a difficult challenge at first, actually. In the end, it was just math and timing. Kind of ironic, since this is the first QuakeC I've ever truly wrote myself. :P

----

Getting to the tutorial

I was rather worried that it couldn't be done in vanilla QuakeC or that it would suck or be jerky. It isn't. It always wasn't easy. FrikaC's mathlib sin function wasn't giving me the right values (could be user error somehow, but I couldn't get it to work) and this almost presented an insurmountable obstacle. I wanted to have base QuakeC functionality and was about ready to add the DP SIN engine extension, which would make the usefulness of bobbing platforms rather engine-locked. Then I realized I could fake the sin function with a table of 91 values.

Changed files: Just misc.qc

Instructions:

Locate the void func_wall () function in misc.qc. Replace it with this. The END.

Code: Select all


float pi = 3.14159265;


//START - Baker mod
.vector		orbitorigin;

.float          nextangle;

// For calculating speed to reach next destination point in our 
// Circlish thing
.float 		sinresult;
.float          orbitrelative;
.vector         targetspot;
.float          targetdistance;

.float          lastthink;
// END - Baker mod

// Baker:  Sad to report, but mathlib's sin function doesn't actually work (or at least I could find no evidence
//         that it does --- it gave me values, but they weren't correct, but could be user error on my part.  In fact, go with possible user error as the default assumption)
//         So we fake it with a table.  This limits us to integer values.
float(float x) sin_angle_pi =
{
	if (x== 0) return 0;
	else if (x== 1) return .035;
	else if (x== 2) return .07;
	else if (x== 3) return .105;
	else if (x== 4) return .139;
	else if (x== 5) return .174;
	else if (x== 6) return .208;
	else if (x== 7) return .242;
	else if (x== 8) return .276;
	else if (x== 9) return .309;
	else if (x== 10) return .342;
	else if (x== 11) return .375;
	else if (x== 12) return .407;
	else if (x== 13) return .438;
	else if (x== 14) return .469;
	else if (x== 15) return .5;
	else if (x== 16) return .53;
	else if (x== 17) return .559;
	else if (x== 18) return .588;
	else if (x== 19) return .616;
	else if (x== 20) return .643;
	else if (x== 21) return .669;
	else if (x== 22) return .695;
	else if (x== 23) return .719;
	else if (x== 24) return .743;
	else if (x== 25) return .766;
	else if (x== 26) return .788;
	else if (x== 27) return .809;
	else if (x== 28) return .829;
	else if (x== 29) return .848;
	else if (x== 30) return .866;
	else if (x== 31) return .883;
	else if (x== 32) return .899;
	else if (x== 33) return .914;
	else if (x== 34) return .927;
	else if (x== 35) return .94;
	else if (x== 36) return .951;
	else if (x== 37) return .961;
	else if (x== 38) return .97;
	else if (x== 39) return .978;
	else if (x== 40) return .985;
	else if (x== 41) return .99;
	else if (x== 42) return .995;
	else if (x== 43) return .998;
	else if (x== 44) return .999;
	else if (x== 45) return 1;
	else if (x== 46) return .999;
	else if (x== 47) return .998;
	else if (x== 48) return .995;
	else if (x== 49) return .99;
	else if (x== 50) return .985;
	else if (x== 51) return .978;
	else if (x== 52) return .97;
	else if (x== 53) return .961;
	else if (x== 54) return .951;
	else if (x== 55) return .94;
	else if (x== 56) return .927;
	else if (x== 57) return .914;
	else if (x== 58) return .899;
	else if (x== 59) return .883;
	else if (x== 60) return .866;
	else if (x== 61) return .848;
	else if (x== 62) return .829;
	else if (x== 63) return .809;
	else if (x== 64) return .788;
	else if (x== 65) return .766;
	else if (x== 66) return .743;
	else if (x== 67) return .719;
	else if (x== 68) return .695;
	else if (x== 69) return .669;
	else if (x== 70) return .643;
	else if (x== 71) return .616;
	else if (x== 72) return .588;
	else if (x== 73) return .559;
	else if (x== 74) return .53;
	else if (x== 75) return .5;
	else if (x== 76) return .469;
	else if (x== 77) return .438;
	else if (x== 78) return .407;
	else if (x== 79) return .375;
	else if (x== 80) return .342;
	else if (x== 81) return .309;
	else if (x== 82) return .276;
	else if (x== 83) return .242;
	else if (x== 84) return .208;
	else if (x== 85) return .174;
	else if (x== 86) return .139;
	else if (x== 87) return .105;
	else if (x== 88) return .07;
	else if (x== 89) return .035;
	else if (x== 90) return 0;
};

void() bobbing_think
{
	// Calculate next angle.  Used to calculate the speed we need
	self.nextangle = self.nextangle + 1;  
	if (self.nextangle > 90) 
		self.nextangle = self.nextangle - 90; 
	if (self.nextangle < 0) // In case we are cycling backwards with speed value of -1 or some negative integer value
		self.nextangle = self.nextangle + 90; 
	
	// Calculate next z position
	
	self.sinresult      =  sin_angle_pi(self.nextangle);                // Get the sin result of angle div 90 times pi
	self.orbitrelative  =  (self.sinresult - 0.5) * self.height*2;      // Calculate the relative Z distance from the center of the origin
	self.targetspot     =  self.origin;                                 // The target spot is the orbit origin ...
	self.targetspot_z   =  self.orbitorigin + self.orbitrelative;
	self.targetdistance =  self.targetspot_z - self.origin_z;
	
	// What velocity is required to reach that by the nextthink
	self.velocity_z     =  self.targetdistance/(self.ltime - self.lastthink); // Speed required = distance divided by time between thinks

	self.think = bobbing_think;
	self.nextthink = self.ltime + 0.00001;  // Act next frame!
	self.lastthink = self.ltime;		// Store this.  Our only home for trying to calculate real time orbit correction is to measure last reading
	
}




/*QUAKED func_wall (0 .5 .8) ?
This is just a solid wall if not inhibitted
*/
void() func_wall =
{
	self.angles = '0 0 0';
	self.movetype = MOVETYPE_PUSH;	// so it doesn't get pushed by anything
	self.solid = SOLID_BSP;
	self.use = func_wall_use;
	setmodel (self, self.model);
	
	// Begin Baker mod
	if (self.speed > 0 || self.speed <0) // Non-zero speed = bobbing.  Must be integer.  1 is bobbing starting up.  -1 is bobbing starting down.  2 or -2 = faster
	{
		local vector fakeorigin;

		// speed  = Starting direction 1 up or -1 down.  Can be integer value like 2 or -2 to increase the speed.  Cannot be non-integer due to sin fakery unless using extension like DarkPlaces
		// height = the radius of the bob.
			
		self.orbitorigin_x = 0; 
		self.orbitorigin_y = 0; 
		self.orbitorigin_z = self.size_z / 2;		
		self.nextangle = 15; // Set the anglecycle to 15 which is equilbrium.  

		if (self.speed < 0)
			self.height = -self.height; // "height" is the bobbing radius.  Invert the radius if speed is negative
			
		// Give it a nextthink
		self.think = bobbing_think;
		self.nextthink = self.ltime + .00001;
		self.lastthink = self.ltime;	// We need this to measure time between thinks to calculate speed (Quake's quote unquote "velocity") to hit target 
	}
	// End Baker mod
};
Video, map, map source code, progs and progs source code tomorrow. I'm tired. But happy and satisfied. It works like a charm.

It is as smooth as frag,machine's Q2K4 wavey water or Lardarse's board gaming.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Dr. Shadowborg
InsideQC Staff
Posts: 1120
Joined: Sat Oct 16, 2004 3:34 pm

Post by Dr. Shadowborg »

Definitely gotta give this a try later, would be awesome for stuff floating in water / slime / lava. :D
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Crappy fullbright testing map.

Video link: http://www.youtube.com/watch?v=OD_kZYFdBJ4

Image

It's totally smooth, no jerkiness, no weirdness when standing on the platforms. Feels completely natural.

Map and mod download: http://quake-1.com/docs/mods/bobbing.zip

Unzip in Quake folder, start Quake with -game bobbing +map bobbing

Making a bobbing platform: Make a func_wall. Set the speed property to -1 or 1 (or some integer like 2). Set the height property to the max radius of the bob.
"classname" "func_wall"
"speed" "1"
"height" "16"
Map source looks like this:
{
"classname" "func_wall"
"speed" "1"
"height" "16"
{
( -64 -192 96 ) ( 64 -192 96 ) ( 64 -320 96 ) SPEEDBZ_W0 [ 1 0 0 128 ] [ 0 -1 0 0 ] 0 1 1
( -64 -320 80 ) ( 64 -320 80 ) ( 64 -192 80 ) SPEEDBZ_W0 [ 1 0 0 128 ] [ 0 -1 0 0 ] 0 1 1
( -64 -192 96 ) ( -64 -320 96 ) ( -64 -320 80 ) SPEEDBZ_W0 [ 0 1 0 0 ] [ 0 0 -1 32 ] 0 1 1
( 64 -192 80 ) ( 64 -320 80 ) ( 64 -320 96 ) SPEEDBZ_W0 [ 0 1 0 0 ] [ 0 0 -1 32 ] 0 1 1
( 64 -192 96 ) ( -64 -192 96 ) ( -64 -192 80 ) SPEEDBZ_W0 [ 1 0 0 128 ] [ 0 0 -1 32 ] 0 1 1
( 64 -320 80 ) ( -64 -320 80 ) ( -64 -320 96 ) SPEEDBZ_W0 [ 1 0 0 128 ] [ 0 0 -1 32 ] 0 1 1
}
}
{
"classname" "func_wall"
"speed" "2"
"height" "15"
{
( 336 -192 96 ) ( 464 -192 96 ) ( 464 -320 96 ) SPEEDBZ_W0 [ 1 0 0 112 ] [ 0 -1 0 0 ] 0 1 1
( 336 -320 80 ) ( 464 -320 80 ) ( 464 -192 80 ) SPEEDBZ_W0 [ 1 0 0 112 ] [ 0 -1 0 0 ] 0 1 1
( 336 -192 96 ) ( 336 -320 96 ) ( 336 -320 80 ) SPEEDBZ_W0 [ 0 1 0 0 ] [ 0 0 -1 48 ] 0 1 1
( 464 -192 80 ) ( 464 -320 80 ) ( 464 -320 96 ) SPEEDBZ_W0 [ 0 1 0 0 ] [ 0 0 -1 48 ] 0 1 1
( 464 -192 96 ) ( 336 -192 96 ) ( 336 -192 80 ) SPEEDBZ_W0 [ 1 0 0 112 ] [ 0 0 -1 48 ] 0 1 1
( 464 -320 80 ) ( 336 -320 80 ) ( 336 -320 96 ) SPEEDBZ_W0 [ 1 0 0 112 ] [ 0 0 -1 48 ] 0 1 1
}
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Mathlib sin uses radians, not degrees. sin (90) degrees == sin (M_PI / 2) radians.

PF_sin and friends should be in every engine, and there should also be a PF_sin_degrees, etc to avoid some QC ops.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:Mathlib sin uses radians, not degrees. sin (90) degrees == sin (M_PI / 2) radians.

PF_sin and friends should be in every engine, and there should also be a PF_sin_degrees, etc to avoid some QC ops.
Ah well I didn't know that. Knowing how thorough FrikaC is, I was a little surprised it wasn't working for me (in the way I was expecting).
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Lardarse
Posts: 266
Joined: Sat Nov 05, 2005 1:58 pm
Location: Bristol, UK

Post by Lardarse »

Nice. Didn't think to use a sine table. That long mad elseif chain can be made more efficient (for matters of runaway loops). But other than that, good stuff.

Edit:
mh wrote:Mathlib sin uses radians, not degrees. sin (90) degrees == sin (M_PI / 2) radians.
Actually, the other way around. The sin() builtin uses radians. The mathlib version of the function uses degrees. Mathlib also provides a wrapper for sin() and cos() to give degrees. So I don't know what caused the issues.

Edit 2: Ok, I see what you're doing now. sin() should reach its peak at 90, not 45. To get the correct answer out of mathlib sin(), you would need to double the angle you give to it.
Roaming status: Testing and documentation
behind_you
Posts: 237
Joined: Sat Feb 05, 2011 6:57 am
Location: Tripoli, Libya

Post by behind_you »

haha nice job baker!
Post Reply