diff --git a/MUNGA/MOVER.cpp b/MUNGA/MOVER.cpp index 7e2f809..cf06aff 100644 --- a/MUNGA/MOVER.cpp +++ b/MUNGA/MOVER.cpp @@ -21,6 +21,21 @@ static Logical gLastLerpUsed = False; static Scalar gLastPercent = 0.0f; +// +// The one replicant the RP412CAMLOG traces describe. Latched here because +// the renderer reports on the same entity from the other end - what its +// motion looks like on screen - and two traces about two different pods +// would compare nothing. +// +static EntityID gTracedEntity = EntityID::Null; +static Logical gTracedLatched = False; + +EntityID + MoverTracedEntity() +{ + return gTracedEntity; +} + // // Bounds on the replication interval estimate, in seconds. // @@ -693,15 +708,12 @@ void // if (RPCameraLog()) { - static EntityID watched = EntityID::Null; - static Logical latched = False; - - if (!latched) + if (!gTracedLatched) { - latched = True; - watched = GetEntityID(); + gTracedLatched = True; + gTracedEntity = GetEntityID(); } - if (watched == GetEntityID()) + if (gTracedEntity == GetEntityID()) { static Scalar next_say = 0.0f; static Point3D last_pos(0.0f, 0.0f, 0.0f); diff --git a/MUNGA/MOVER.h b/MUNGA/MOVER.h index 53cb685..3073d17 100644 --- a/MUNGA/MOVER.h +++ b/MUNGA/MOVER.h @@ -509,3 +509,11 @@ public: Logical TestInstance() const; }; + +// +// The replicant the RP412CAMLOG traces are describing, so the renderer can +// report on the same pod from the other end - what its motion looks like on +// screen. Null until a replicant has stepped at least once. +// +EntityID + MoverTracedEntity(); diff --git a/MUNGA_L4/L4VIDRND.cpp b/MUNGA_L4/L4VIDRND.cpp index 50e2383..bc31eb1 100644 --- a/MUNGA_L4/L4VIDRND.cpp +++ b/MUNGA_L4/L4VIDRND.cpp @@ -11,6 +11,16 @@ #include "l4app.h" #include "..\RP\VTV.h" +// +// Where the eye was this frame, and a counter of frames the eye has been +// built for. Written by DPLEyeRenderable::Execute, read by +// RootRenderable::Execute, which draws afterwards in the same frame - the +// counter is what proves that rather than assuming it. +// +static D3DXVECTOR3 gEyeWorld(0.0f, 0.0f, 0.0f); +static unsigned gEyeFrame = 0; +static unsigned gLastSampledFrame = 0; + // RB 1/14/07 //#include //#include @@ -1095,6 +1105,126 @@ void Matrix4x4 tempMatrix; tempMatrix = renderToWorld; + // + // RP412CAMLOG: what the tick actually looks like on screen. + // + // The complaint is a RHYTHMIC tick as a pod moves past the camera, + // and rhythm is the clue: a fixed period points at a cadence in our + // own code rather than at the network, which has no period. So + // measure the angle the pod subtends at the eye, frame by frame, + // and report the INTERVAL BETWEEN the lurches rather than merely + // counting them. That interval names the culprit - about 0.02s is + // the physics step, 0.03s the update rate, 0.4s the twenty-step + // quaternion renormalisation in Mover::BeginStep. + // + // Angle rather than distance because a pod crossing the view moves + // far across the screen while barely changing range, which is + // exactly the geometry the tick was reported in. + // + // Both samples are taken in one frame - the eye's, marked by + // gEyeFrame - because a pod and a camera read from different frames + // alias against each other no matter what the game is doing. + // + if (RPCameraLog() && gEyeFrame != gLastSampledFrame) + { + EntityID traced = MoverTracedEntity(); + + if (traced != EntityID::Null && traced == myEntity->GetEntityID()) + { + gLastSampledFrame = gEyeFrame; + + Vector3D to_pod; + to_pod.x = renderToWorld(3,0) - gEyeWorld.x; + to_pod.y = renderToWorld(3,1) - gEyeWorld.y; + to_pod.z = renderToWorld(3,2) - gEyeWorld.z; + + Scalar range = to_pod.Length(); + + if (range > 0.1f) + { + static Vector3D last_dir; + static Logical have_last = False; + static Scalar next_say = 0.0f; + static Scalar mean_angle = 0.0f; + static Scalar last_event = 0.0f; + static Scalar interval_sum = 0.0f; + static Scalar interval_min = 1000.0f; + static Scalar interval_max = 0.0f; + static int interval_count = 0; + static int frames = 0; + static int lurches = 0; + + Vector3D dir; + dir.Divide(to_pod, range); + ++frames; + + if (have_last) + { + Scalar dot = + dir.x*last_dir.x + dir.y*last_dir.y + dir.z*last_dir.z; + + if (dot > 1.0f) { dot = 1.0f; } + if (dot < -1.0f) { dot = -1.0f; } + + Scalar angle = (Scalar) acos((double) dot); + + // + // Only judge while the pod is actually crossing the + // view. A pod held near the centre subtends almost + // no angle and every ratio against it is noise. + // + if (mean_angle > 0.0002f) + { + if (angle > mean_angle * 2.5f) + { + ++lurches; + + Scalar now = (Scalar) Now(); + + if (last_event > 0.0f) + { + Scalar gap = now - last_event; + + interval_sum += gap; + ++interval_count; + if (gap < interval_min) { interval_min = gap; } + if (gap > interval_max) { interval_max = gap; } + } + last_event = now; + } + } + mean_angle = (mean_angle > 0.0f) + ? (mean_angle * 0.95f + angle * 0.05f) + : angle; + } + last_dir = dir; + have_last = True; + + if ((Scalar) Now() >= next_say) + { + if (next_say > 0.0f) + { + DEBUG_STREAM << "CamLog: on-screen motion - " << frames + << " frames, " << lurches << " lurch(es), period " + << ((interval_count > 0) + ? (interval_sum / interval_count) + : 0.0f) + << "s (" << interval_min << ".." << interval_max + << "), mean step " << (mean_angle * 1000.0f) + << " mrad, range " << range << "m\n" << std::flush; + } + next_say = ((Scalar) Now()) + 5.0f; + frames = 0; + lurches = 0; + interval_sum = 0.0f; + interval_min = 1000.0f; + interval_max = 0.0f; + interval_count = 0; + } + } + } + } + myRenderer->GetMatrixStack()->MultMatrix(&tempMatrix.ToD3DMatrix()); //myLocalToWorld = *myRenderer->GetMatrixStack()->GetTop(); HierarchicalDrawComponent::SetLocalToWorld(myRenderer->GetMatrixStack()->GetTop()); @@ -5695,6 +5825,20 @@ void mat = mat4; D3DXVECTOR3 pos(mat(3,0), mat(3, 1), mat(3,2)); + + // + // Where the eye ended up this frame, for the on-screen motion + // trace in RootRenderable::Execute. The eye is built once per + // frame and before the world is drawn, so it also serves as the + // frame marker that keeps the two samples in the same frame - + // which is the whole point, since sampling a pod and a camera + // from different frames is what made three earlier attempts at + // this question meaningless. + // + gEyeWorld.x = mat(3,0); + gEyeWorld.y = mat(3,1); + gEyeWorld.z = mat(3,2); + gEyeFrame++; mat(3,0) = 0; mat(3,1) = 0; mat(3,2) = 0;