00-10-21 latest release ...a port of ID's glQuake code to the lcc-win32 compiler with bug-fixes and improvements from many Quake source projects. |
| web site host
| player download qbexe-00-10-21.zip -executable -mod directory coder download qbism-00-10-21.zip -source + project -assembly library -executable -mod directory
CSE tutorial Netquake prediction with client-side extrapolation Updated 00-10-19 Here's some some mild client-side prediction added to smooth movement and animation in modem network play. It is necessary to add fenix's interpolation tutorial from the Quake Engine Resources site and the improved shadow code tutorial from Muff's site before applying CSE. Background info: Standard Quake blends player movement with interpolation from the previous position to the current position. In an internet modem game the lag time often exceeds the maximum interpolation time (~100ms) resulting in jerky movement. With CSE Quake interpolates movement as usual up to 100ms and then extrapolates movement, assuming the same direction and velocity. The guess is reasonably accurate up to about 250ms, especially if other players are experiencing a similar lag. About 500ms lag is the upper practical limit of the prediction. Note that the delayed response time of motion caused by lag is not addressed by extrapolation, it merely fills in the gaps. Now let's dig in, starting with glr_main.c. The two functions with changes are shown in their entirety so you can cut-and-paste large or small chunks as you please. Highlighted code and comments are CSE changes with descriptions: /* ============= R_BlendedRotateForEntity fenix@io.com: model transform interpolation ============= */ //qbism//00-04-29 muff - definition changed to allow for new shadow code void R_BlendedRotateForEntity (entity_t *e, int shadow) { float timepassed; float blend; vec3_t d, dfudge; int i; trace_t movetrace; //qbism//jf CSE 00-05-28 positional Client-Side Extrapolation // positional interpolation timepassed = realtime - e->translate_start_time; if (e->translate_start_time == 0 || timepassed > 1) { e->translate_start_time = realtime; VectorCopy (e->origin, e->origin1); VectorCopy (e->origin, e->origin2); } if (!VectorCompare (e->origin, e->origin2)) { e->translate_start_time = realtime; VectorCopy (e->origin2, e->origin1); VectorCopy (e->origin, e->origin2); blend = 0; } else { blend = timepassed / 0.1; //CSE continue along line for up to 5(hundred millisec) instead of 1(hundred millisec) if (cl.paused) blend = 1;//qbism//jf CSE 00-05-28 removed || blend > 1) blend = 1; else if (blend >= 5) blend =1; //qbism//jf CSE 00-05-28 changed from 1 to 5 } VectorSubtract (e->origin2, e->origin1, d); memset (&movetrace, 0, sizeof(movetrace));//qbism//jf CSE 00-05-28 //qbism//jf CSE 00-05-28 check if the movement vector collides with world. if so, bail. if (blend>1) { SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, e->origin, d, &movetrace); if(movetrace.fraction <1) blend =1; } glTranslatef ( e->origin1[0] + (d[0]*blend), e->origin1[1] + (d[1]*blend), e->origin1[2] + (d[2]*blend)); // orientation interpolation (Euler angles, yuck!) timepassed = realtime - e->rotate_start_time; if (e->rotate_start_time == 0) //qbism//jf CSE 00-05-28 removed || timepassed > 1) { e->rotate_start_time = realtime; VectorCopy (e->angles, e->angles1); VectorCopy (e->angles, e->angles2); } if (!VectorCompare (e->angles, e->angles2)) { e->rotate_start_time = realtime; VectorCopy (e->angles2, e->angles1); VectorCopy (e->angles, e->angles2); blend = 0; } else { blend = timepassed / 0.1; //CSE continue rotation for up to 5(hundred millisec) instead of 1(hundred millisec) if (cl.paused) blend = 1; //qbism//jf CSE 00-05-28 removed || blend > 1) blend = 1; else if(blend >5) blend =1; //qbism//jf CSE 00-05-28 limit of extrapolation } VectorSubtract (e->angles2, e->angles1, d); // always interpolate along the shortest path for (i = 0; i < 3; i++) { if (d[i] > 180) { d[i] -= 360; } else if (d[i] < -180) { d[i] += 360; } } glRotatef ( e->angles1[1] + ( blend * d[1]), 0, 0, 1); //qbism//2000-04-29 muff@yakko.globalnet.co.uk - 05 Mar 00 // added to allow shadows to behave properly // if shadow it only rotates in horizontal plane // will need updating when slope stuff is in place if (shadow==0) { glRotatef (-e->angles1[0] + (-blend * d[0]), 0, 1, 0); // this rotates tilt glRotatef ( e->angles1[2] + ( blend * d[2]), 1, 0, 0); // this is the one that causes the weird missile stuff } } ...elsewhere in gl_rmain.c: void GL_DrawAliasBlendedShadow (aliashdr_t *paliashdr, int pose1, int pose2, entity_t* e) { trivertx_t* verts1; trivertx_t* verts2; int* order; vec3_t point1; vec3_t point2; vec3_t d; float height; float lheight; int count; float blend; //qbism//00-04-29 muff@yakko.globalnet.co.uk - 17 Mar 2000 trace_t steptrace, downtrace; vec3_t dest,stop,downmove; float s1,c1; //this will store the sin and cos values as they are used so heavily //end of muff /qbism//jf CSE 00-10-15 stretch out animation to match lag. Smoothness. blend = (realtime - e->frame_start_time) / e->frame_interval; //qbism//jf CSE 00-06-07 shadow Client-Side Extrapolation- match animation extrapolation // wierd things start happening if blend passes 1 if (cl.paused || blend >1) blend = 1; //qbism//jf 10-15-00 this is much less likely to happen now. lheight = e->origin[2] - lightspot[2]; //qbism//jf 00-10-15 not used height = -lheight + 1.0; //qbism//00-04-29 muff@yakko.globalnet.co.uk - 17 Mar 2000 // better shadowing, now takes angle of ground into account // cast a traceline into the floor directly below the player // and gets normals from this // for proper surface shadowing, each point should be taken // seperately, we do it this way for speed // NOTE need fixing for when at liquid surface VectorCopy (currententity->origin, downmove); downmove[2] = downmove[2] - 4096; memset (&downtrace, 0, sizeof(downtrace)); SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, currententity->origin, downmove, &downtrace); // calculate the all important angles s1 = sin( currententity->angles[1]/180*M_PI); c1 = cos( currententity->angles[1]/180*M_PI); // end of muff verts1 = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata); verts2 = verts1; verts1 += pose1 * paliashdr->poseverts; verts2 += pose2 * paliashdr->poseverts; order = (int *)((byte *)paliashdr + paliashdr->commands); for (;;) { // get the vertex count and primitive type count = *order++; if (!count) break; if (count < 0) { count = -count; glBegin (GL_TRIANGLE_FAN); } else { glBegin (GL_TRIANGLE_STRIP); } do { order += 2; point1[0] = verts1->v[0] * paliashdr->scale[0] + paliashdr->scale_origin[0]; point1[1] = verts1->v[1] * paliashdr->scale[1] + paliashdr->scale_origin[1]; point1[2] = verts1->v[2] * paliashdr->scale[2] + paliashdr->scale_origin[2]; point1[0] -= shadevector[0]*(point1[2]+lheight); point1[1] -= shadevector[1]*(point1[2]+lheight); point2[0] = verts2->v[0] * paliashdr->scale[0] + paliashdr->scale_origin[0]; point2[1] = verts2->v[1] * paliashdr->scale[1] + paliashdr->scale_origin[1]; point2[2] = verts2->v[2] * paliashdr->scale[2] + paliashdr->scale_origin[2]; point2[0] -= shadevector[0]*(point2[2]+lheight); point2[1] -= shadevector[1]*(point2[2]+lheight); VectorSubtract(point2, point1, d); //qbism//00-04-29 muff@yakko.globalnet.co.uk - 17 Mar 2000 point1[0] = point1[0] + (blend * d[0]); //+lheight); //muff point1[1] = point1[1] + (blend * d[1]); //+lheight); //muff point1[2] = point1[2] + (blend * d[2]); //+lheight); //muff //drop it down to floor point1[2] = - (currententity->origin[2] - downtrace.endpos[2]) ; //now move the z-coordinate as appropriate point1[2] += ( (point1[1] * (s1 * downtrace.plane.normal[0])) - (point1[0] * (c1 * downtrace.plane.normal[0])) - (point1[0] * (s1 * downtrace.plane.normal[1])) - (point1[1] * (c1 * downtrace.plane.normal[1])) ) + ((1.0 - downtrace.plane.normal[2])*20) + 0.2 ; // now load the point we have just calculated glVertex3fv (point1); // remove this as we have new commands in above // glVertex3f (point1[0] + (blend * d[0]), // point1[1] + (blend * d[1]), // height); //end of muff verts1++; verts2++; } while (--count); glEnd (); } } /* ================= R_SetupAliasBlendedFrame fenix@io.com: model animation interpolation ================= */ void R_SetupAliasBlendedFrame (int frame, aliashdr_t *paliashdr, entity_t* e) { int pose; int numposes; float blend; if ((frame >= paliashdr->numframes) || (frame < 0)) { Con_DPrintf ("R_AliasSetupFrame: no such frame %d\n", frame); frame = 0; } pose = paliashdr->frames[frame].firstpose; numposes = paliashdr->frames[frame].numposes; if (numposes > 1) { e->frame_interval = paliashdr->frames[frame].interval; pose += (int)(cl.time / e->frame_interval) % numposes; } else { //qbism//jf 00-10-15 we can do better than this: /* One tenth of a second is a good for most Quake animations. If the nextthink is longer then the animation is usually meant to pause (e.g. check out the shambler magic animation in shambler.qc). If its shorter then things will still be smoothed partly, and the jumps will be less noticable because of the shorter time. So, this is probably a good assumption. */ //qbism//jf 00-10-15 the interval is based on ping, if known. if(cl.scores[cl.viewentity].ping) e->frame_interval = ((float)cl.scores[cl.viewentity].ping*.001); else e->frame_interval = 0.1; } if (e->pose2 != pose) { e->frame_start_time = realtime; e->pose1 = e->pose2; e->pose2 = pose; blend = 0; } else { blend = (realtime - e->frame_start_time) / e->frame_interval; } // wierd things start happening if blend passes 1 if (cl.paused || blend>1) blend = 1; //qbism//jf CSE 00-10-15 this is much less likely to happen now. GL_DrawAliasBlendedFrame (paliashdr, e->pose1, e->pose2, blend); } In cl_main.c, find the function CL_LerpPoint. CL_LerpPoint returns the fraction between the last two messages where objects should be placed. Replace this chunk of code: if (f > 0.1) { // dropped packet, or start of demo cl.mtime[1] = cl.mtime[0] - 0.1; f = 0.1; } with this: //qbism//jf CSE f can often be >0.1 on a high lag connection... try 0.3 if (f > 0.3) { // dropped packet, or start of demo cl.mtime[1] = cl.mtime[0] - 0.3; f = 0.3; } I think that's it. Let me know how CSE works for you. | index news info screenshots links tutorials porting notes c-s prediction draw net data old news 00-01 to 00-03 00-04 to 00-05 contact email To get email notification of qbism updates, bugs, and fixes, put ADD LIST in the subject. web site qbism.telefragged.com