Forum

Owned: Bobbing Platforms (Tutorial)

Discuss programming in the QuakeC language.

Moderator: InsideQC Admins

Owned: Bobbing Platforms (Tutorial)

Postby Baker » Wed May 18, 2011 5:13 am

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 ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby Dr. Shadowborg » Wed May 18, 2011 3:36 pm

Definitely gotta give this a try later, would be awesome for stuff floating in water / slime / lava. :D
User avatar
Dr. Shadowborg
InsideQC Staff
 
Posts: 1110
Joined: Sat Oct 16, 2004 3:34 pm

Postby Baker » Wed May 18, 2011 5:35 pm

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 ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby mh » Wed May 18, 2011 5:41 pm

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
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Postby Baker » Wed May 18, 2011 5:51 pm

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 ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby Lardarse » Wed May 18, 2011 5:52 pm

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
User avatar
Lardarse
 
Posts: 266
Joined: Sat Nov 05, 2005 1:58 pm
Location: Bristol, UK

Postby behind_you » Tue May 31, 2011 7:30 am

haha nice job baker!
User avatar
behind_you
 
Posts: 237
Joined: Sat Feb 05, 2011 6:57 am
Location: Tripoli, Libya


Return to QuakeC Programming

Who is online

Users browsing this forum: No registered users and 1 guest