/* * @brief The origin is typically calculated using client sided prediction, provided * the client is not viewing a demo, playing in 3rd person mode, or chasing * another player. */ static void Cl_UpdateOrigin(const player_state_t *from, const player_state_t *to) { if (Cl_UsePrediction()) { // use client sided prediction for (int32_t i = 0; i < 3; i++) { r_view.origin[i] = cl.predicted_state.origin[i] + cl.predicted_state.view_offset[i]; r_view.origin[i] -= (1.0 - cl.lerp) * cl.predicted_state.error[i]; } const uint32_t delta = cl.time - cl.predicted_state.step_time; const uint32_t interval = cl.predicted_state.step_interval; if (delta < interval) { // interpolate stair traversal const vec_t lerp = (interval - delta) / (vec_t) interval; r_view.origin[2] -= cl.predicted_state.step * lerp; } } else { // just use interpolated values from frame vec3_t origin; vec3_t from_offset, to_offset, offset; VectorLerp(from->pm_state.origin, to->pm_state.origin, cl.lerp, origin); UnpackVector(from->pm_state.view_offset, from_offset); UnpackVector(to->pm_state.view_offset, to_offset); VectorLerp(from_offset, to_offset, cl.lerp, offset); VectorAdd(origin, offset, r_view.origin); } // update the contents mask for e.g. under-water effects r_view.contents = Cl_PointContents(r_view.origin); }
/* * @brief Run recent movement commands through the player movement code * locally, storing the resulting origin and angles so that they may be * interpolated to by Cl_UpdateView. */ void Cg_PredictMovement(const GList *cmds) { pm_move_t pm; // copy current state to into the move memset(&pm, 0, sizeof(pm)); pm.s = cgi.client->frame.ps.pm_state; pm.ground_entity = cgi.client->predicted_state.ground_entity; pm.PointContents = cgi.PointContents; pm.Trace = Cg_PredictMovement_Trace; pm.Debug = Cg_PredictMovement_Debug; const GList *e = cmds; // run frames while (e) { const cl_cmd_t *cmd = (cl_cmd_t *) e->data; if (cmd->cmd.msec) { pm.cmd = cmd->cmd; Pm_Move(&pm); // for each movement, check for stair interaction and interpolate if (pm.s.flags & PMF_ON_STAIRS) { cgi.client->predicted_state.step_time = cmd->time; cgi.client->predicted_state.step_interval = 120 * (fabs(pm.step) / 16.0); cgi.client->predicted_state.step = pm.step; } // save for debug checking const uint32_t frame = (intptr_t) (cmd - cgi.client->cmds); VectorCopy(pm.s.origin, cgi.client->predicted_state.origins[frame]); } e = e->next; } // copy results out for rendering UnpackVector(pm.s.origin, cgi.client->predicted_state.origin); UnpackVector(pm.s.view_offset, cgi.client->predicted_state.view_offset); UnpackAngles(pm.cmd.angles, cgi.client->predicted_state.view_angles); cgi.client->predicted_state.ground_entity = pm.ground_entity; }
/* * @brief Updates the r_view_t for the renderer. Origin, angles, etc are calculated. * Scene population is then delegated to the client game. */ void Cl_UpdateView(void) { if (!cl.frame.valid && !r_view.update) return; // not a valid frame, and no forced update // find the previous frame to interpolate from cl_frame_t *prev = &cl.frames[(cl.frame.frame_num - 1) & PACKET_MASK]; if (prev->frame_num != cl.frame.frame_num - 1 || !prev->valid) prev = &cl.frame; // previous frame was dropped or invalid Cl_UpdateLerp(prev); const player_state_t *ps = &cl.frame.ps; const player_state_t *ops = &prev->ps; if (ps != ops) { // see if we've teleported vec3_t org, old_org, delta; UnpackVector(ps->pm_state.origin, org); UnpackVector(ops->pm_state.origin, old_org); VectorSubtract(org, old_org, delta); if (VectorLength(delta) > 256.0) { ops = ps; // don't interpolate } } Cl_ClearView(); Cl_UpdateOrigin(ps, ops); Cl_UpdateAngles(ps, ops); Cl_UpdateViewSize(); cls.cgame->UpdateView(&cl.frame); // set time r_view.time = cl.time; // set area bits to mark visible leafs r_view.area_bits = cl.frame.area_bits; // create the thread which populates the view r_view.thread = Thread_Create((ThreadRunFunc) cls.cgame->PopulateView, &cl.frame); }
/* * @brief */ static void Cl_DrawCounters(void) { static vec3_t velocity; static char bps[8], pps[8], fps[8], spd[8]; static int32_t last_draw_time; r_pixel_t cw, ch; if (!cl_draw_counters->value) return; R_BindFont("small", &cw, &ch); const r_pixel_t x = r_context.width - 7 * cw; r_pixel_t y = r_context.height - 4 * ch; cl.frame_counter++; if (cls.real_time - last_draw_time >= 200) { UnpackVector(cl.frame.ps.pm_state.velocity, velocity); velocity[2] = 0.0; g_snprintf(spd, sizeof(spd), "%4.0fspd", VectorLength(velocity)); g_snprintf(fps, sizeof(fps), "%4ufps", cl.frame_counter * 5); g_snprintf(pps, sizeof(pps), "%4upps", cl.packet_counter * 5); g_snprintf(bps, sizeof(bps), "%4ubps", cl.byte_counter * 5); last_draw_time = quetoo.time; cl.frame_counter = 0; cl.packet_counter = 0; cl.byte_counter = 0; } R_DrawString(x, y, spd, CON_COLOR_DEFAULT); y += ch; R_DrawString(x, y, fps, CON_COLOR_DEFAULT); y += ch; R_DrawString(x, y, pps, CON_COLOR_DEFAULT); y += ch; R_DrawString(x, y, bps, CON_COLOR_DEFAULT); R_BindFont(NULL, NULL, NULL); }
void FMD3Model::LoadGeometry() { FMemLump lumpdata = Wads.ReadLump(mLumpNum); const char *buffer = (const char *)lumpdata.GetMem(); md3_header_t * hdr = (md3_header_t *)buffer; md3_surface_t * surf = (md3_surface_t*)(buffer + LittleLong(hdr->Ofs_Surfaces)); for(int i=0;i<numSurfaces;i++) { MD3Surface * s = &surfaces[i]; md3_surface_t * ss = surf; surf = (md3_surface_t *)(((char*)surf) + LittleLong(surf->Ofs_End)); // copy triangle indices md3_triangle_t * tris = (md3_triangle_t*)(((char*)ss)+LittleLong(ss->Ofs_Triangles)); s->tris = new MD3Triangle[s->numTriangles]; for(int i=0;i<s->numTriangles;i++) for (int j=0;j<3;j++) { s->tris[i].VertIndex[j]=LittleLong(tris[i].vt_index[j]); } // Load texture coordinates md3_texcoord_t * tc = (md3_texcoord_t*)(((char*)ss)+LittleLong(ss->Ofs_Texcoord)); s->texcoords = new MD3TexCoord[s->numVertices]; for(int i=0;i<s->numVertices;i++) { s->texcoords[i].s = tc[i].s; s->texcoords[i].t = tc[i].t; } // Load vertices and texture coordinates md3_vertex_t * vt = (md3_vertex_t*)(((char*)ss)+LittleLong(ss->Ofs_XYZNormal)); s->vertices = new MD3Vertex[s->numVertices * numFrames]; for(int i=0;i<s->numVertices * numFrames;i++) { s->vertices[i].x = LittleShort(vt[i].x)/64.f; s->vertices[i].y = LittleShort(vt[i].y)/64.f; s->vertices[i].z = LittleShort(vt[i].z)/64.f; UnpackVector( LittleShort(vt[i].n), s->vertices[i].nx, s->vertices[i].ny, s->vertices[i].nz); } } }
/* * @brief Determines the initial position and directional vectors of a projectile. */ void G_InitProjectile(g_entity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t org) { vec3_t view, pos; // resolve the projectile destination UnpackVector(ent->client->ps.pm_state.view_offset, view); VectorAdd(ent->s.origin, view, view); VectorMA(view, MAX_WORLD_DIST, ent->client->locals.forward, pos); const cm_trace_t tr = gi.Trace(view, pos, NULL, NULL, ent, MASK_CLIP_PROJECTILE); VectorCopy(tr.end, pos); // resolve the projectile origin vec3_t ent_forward, ent_up; AngleVectors(ent->s.angles, ent_forward, NULL, ent_up); VectorMA(view, 12.0, ent_forward, org); if ((ent->client->ps.pm_state.flags & PMF_DUCKED)) { VectorMA(org, -6.0, ent_up, org); } else { VectorMA(org, -12.0, ent_up, org); } // if the projected origin is invalid, use the entity's origin if (gi.Trace(org, org, NULL, NULL, ent, MASK_CLIP_PROJECTILE).start_solid) { VectorCopy(ent->s.origin, org); gi.Print("Doh\n"); } // return the projectile's directional vectors VectorSubtract(pos, org, forward); VectorNormalize(forward); if (right || up) { VectorAngles(forward, view); AngleVectors(view, NULL, right, up); } }
/** * @brief Checks for client side prediction errors. These will occur under normal gameplay * conditions if the client is pushed by another entity on the server (projectile, platform, etc.). */ void Cl_CheckPredictionError(void) { if (!cls.cgame->UsePrediction()) { return; } cl_predicted_state_t *pr = &cl.predicted_state; if (cl.delta_frame) { // calculate the last cl_cmd_t we sent that the server has processed const uint32_t cmd = cls.net_chan.incoming_acknowledged & CMD_MASK; // subtract what the server returned with what we had predicted it to be VectorSubtract(cl.frame.ps.pm_state.origin, pr->origins[cmd], pr->error); // if the error is too large, it was likely a teleport or respawn, so ignore it const vec_t len = VectorLength(pr->error); if (len > 64.0) { Com_Debug(DEBUG_CLIENT, "Clear %s\n", vtos(pr->error)); VectorClear(pr->error); } else if (len > 0.1) { Com_Debug(DEBUG_CLIENT, "Error %s\n", vtos(pr->error)); } } else { Com_Debug(DEBUG_CLIENT, "No delta\n"); VectorCopy(cl.frame.ps.pm_state.origin, pr->view.origin); UnpackVector(cl.frame.ps.pm_state.view_offset, pr->view.offset); UnpackAngles(cl.frame.ps.pm_state.view_angles, pr->view.angles); VectorClear(pr->error); } }
bool FMD3Model::Load(const char * path, int, const char * buffer, int length) { #pragma pack(4) struct md3_header_t { DWORD Magic; DWORD Version; char Name[MAX_QPATH]; DWORD Flags; DWORD Num_Frames; DWORD Num_Tags; DWORD Num_Surfaces; DWORD Num_Skins; DWORD Ofs_Frames; DWORD Ofs_Tags; DWORD Ofs_Surfaces; DWORD Ofs_Eof; }; struct md3_surface_t { DWORD Magic; char Name[MAX_QPATH]; DWORD Flags; DWORD Num_Frames; DWORD Num_Shaders; DWORD Num_Verts; DWORD Num_Triangles; DWORD Ofs_Triangles; DWORD Ofs_Shaders; DWORD Ofs_Texcoord; DWORD Ofs_XYZNormal; DWORD Ofs_End; }; struct md3_triangle_t { DWORD vt_index[3]; }; struct md3_shader_t { char Name[MAX_QPATH]; DWORD index; }; struct md3_texcoord_t { float s,t; }; struct md3_vertex_t { short x,y,z,n; }; struct md3_frame_t { float min_Bounds[3]; float max_Bounds[3]; float localorigin[3]; float radius; char Name[16]; }; #pragma pack() md3_header_t * hdr=(md3_header_t *)buffer; numFrames = LittleLong(hdr->Num_Frames); numTags = LittleLong(hdr->Num_Tags); numSurfaces = LittleLong(hdr->Num_Surfaces); md3_frame_t * frm = (md3_frame_t*)(buffer + LittleLong(hdr->Ofs_Frames)); frames = new MD3Frame[numFrames]; for(int i=0;i<numFrames;i++) { strncpy(frames[i].Name, frm[i].Name, 16); for(int j=0;j<3;j++) frames[i].origin[j] = frm[i].localorigin[j]; } md3_surface_t * surf = (md3_surface_t*)(buffer + LittleLong(hdr->Ofs_Surfaces)); surfaces = new MD3Surface[numSurfaces]; for(int i=0;i<numSurfaces;i++) { MD3Surface * s = &surfaces[i]; md3_surface_t * ss = surf; surf = (md3_surface_t *)(((char*)surf) + LittleLong(surf->Ofs_End)); s->numSkins = LittleLong(ss->Num_Shaders); s->numTriangles = LittleLong(ss->Num_Triangles); s->numVertices = LittleLong(ss->Num_Verts); // copy triangle indices md3_triangle_t * tris = (md3_triangle_t*)(((char*)ss)+LittleLong(ss->Ofs_Triangles)); s->tris = new MD3Triangle[s->numTriangles]; for(int i=0;i<s->numTriangles;i++) for (int j=0;j<3;j++) { s->tris[i].VertIndex[j]=LittleLong(tris[i].vt_index[j]); } // copy shaders (skins) md3_shader_t * shader = (md3_shader_t*)(((char*)ss)+LittleLong(ss->Ofs_Shaders)); s->skins = new FTexture *[s->numSkins]; for(int i=0;i<s->numSkins;i++) { // [BB] According to the MD3 spec, Name is supposed to include the full path. s->skins[i] = LoadSkin("", shader[i].Name); // [BB] Fall back and check if Name is relative. if ( s->skins[i] == NULL ) s->skins[i] = LoadSkin(path, shader[i].Name); } // Load texture coordinates md3_texcoord_t * tc = (md3_texcoord_t*)(((char*)ss)+LittleLong(ss->Ofs_Texcoord)); s->texcoords = new MD3TexCoord[s->numVertices]; for(int i=0;i<s->numVertices;i++) { s->texcoords[i].s = tc[i].s; s->texcoords[i].t = tc[i].t; } // Load vertices and texture coordinates md3_vertex_t * vt = (md3_vertex_t*)(((char*)ss)+LittleLong(ss->Ofs_XYZNormal)); s->vertices = new MD3Vertex[s->numVertices * numFrames]; for(int i=0;i<s->numVertices * numFrames;i++) { s->vertices[i].x = LittleShort(vt[i].x)/64.f; s->vertices[i].y = LittleShort(vt[i].y)/64.f; s->vertices[i].z = LittleShort(vt[i].z)/64.f * rModelAspectMod; UnpackVector( LittleShort(vt[i].n), s->vertices[i].nx, s->vertices[i].ny, s->vertices[i].nz); } } return true; }
/** * @brief Decides which entities are going to be visible to the client, and * copies off the player state and area_bits. */ void Sv_BuildClientFrame(sv_client_t *client) { vec3_t org, off; g_entity_t *cent = client->entity; if (!cent->client) { return; // not in game yet } // this is the frame we are creating sv_frame_t *frame = &client->frames[sv.frame_num & PACKET_MASK]; frame->sent_time = quetoo.ticks; // timestamp for ping calculation // grab the current player_state_t frame->ps = cent->client->ps; // find the client's PVS const pm_state_t *pm = ¢->client->ps.pm_state; UnpackVector(pm->view_offset, off); VectorAdd(pm->origin, off, org); const int32_t leaf = Cm_PointLeafnum(org, 0); const int32_t area = Cm_LeafArea(leaf); // calculate the visible areas frame->area_bytes = Cm_WriteAreaBits(area, frame->area_bits); // resolve the visibility data byte pvs[MAX_BSP_LEAFS >> 3], phs[MAX_BSP_LEAFS >> 3]; Sv_ClientVisibility(org, pvs, phs); // build up the list of relevant entities frame->num_entities = 0; frame->entity_state = svs.next_entity_state; for (uint16_t e = 1; e < svs.game->num_entities; e++) { g_entity_t *ent = ENTITY_FOR_NUM(e); // ignore entities that are local to the server if (ent->sv_flags & SVF_NO_CLIENT) { continue; } // ignore entities without visible presence unless they have an effect if (!ent->s.event && !ent->s.effects && !ent->s.trail && !ent->s.model1 && !ent->s.sound) { continue; } // ignore entities not in PVS / PHS if (ent != cent) { const sv_entity_t *sent = &sv.entities[e]; // by first checking area if (!Cm_AreasConnected(area, sent->areas[0])) { if (!sent->areas[1] || !Cm_AreasConnected(area, sent->areas[1])) { continue; } } const byte *vis = ent->s.sound || ent->s.event ? phs : pvs; if (sent->num_clusters == -1) { // use top_node if (!Cm_HeadnodeVisible(sent->top_node, vis)) { continue; } } else { // or check individual leafs int32_t i; for (i = 0; i < sent->num_clusters; i++) { const int32_t c = sent->clusters[i]; if (vis[c >> 3] & (1 << (c & 7))) { break; } } if (i == sent->num_clusters) { continue; // not visible } } } // copy it to the circular entity_state_t array entity_state_t *s = &svs.entity_states[svs.next_entity_state % svs.num_entity_states]; if (ent->s.number != e) { Com_Warn("Fixing entity number: %d -> %d\n", ent->s.number, e); ent->s.number = e; } *s = ent->s; // don't mark our own missiles as solid for prediction if (ent->owner == client->entity) { s->solid = SOLID_NOT; } svs.next_entity_state++; frame->num_entities++; } }