Camera not in sync with position and rotation

Even more, I changed the camera() to

cam.camera(this.position.x, this.position.y, this.position.z-250,
           this.position.x, this.position.y, this.position.z, 0, 1, 0);

which should be locked to the ship, but as soon as it starts moving, it twitches.

When you speed up and slow down the ship, it moves forwards and backwards. It’s as though the camera is one frame behind – which might actually be the case.

I always thought that the camera distance grows because the player speed is an “distance per frame” that grows, the more speed the more offset. But that calculation of the camera position is directly based on the player position, so it should also go with it. I was at a point where I tried to offset the camera manually to counter/account for that, because I thought the calculation of the camera position isn’t using deltaTime… but then always remembered I already use the new position of the player as the origin for the camera.

So yeah something is happening here, translate and applyMatrix for rotation, then camera does another matrix4 combined transform behind the scenes (if you inspect core) right when you call cam.camera(…).

One frame behind of something. The moment I added the camera and put the simple math, it was behaving like this, and I immediately was like “ok, that’s strange, that’s not at all what should happen”.

And here we are, wondering.

EDIT: Oh and I think when I was searching about this topic on the web, I found some stuff related where people had similar problems with following cams. But I can’t find the one I think might was a solution. Something with taking previous position of camera or player.

I’m going to play around with testing this and see if I can track it down in the p5 code.

In the meantime, try changing your code loop around to: draw everything first using the last frame’s positions, update inputs and positions to new values, then set camera based on the new values. And see if that tracks better.

See, the problem is that the camera is passed to the GPU as a matrix once for each buffer of geometry. p5 would like to buffer up all of your geometry into one chunk and for that, it needs a single camera matrix. If you go moving the camera around in the middle of a frame, it would need to have separate buffers for the geometry before and after the camera moved. It looks like they just use the last frame’s camera for all of this frame’s geometry and save any camera changes to be applied to the next frame. This should be easy to test.

I just tested to store the prev positition and rotation and use that for the camera instead. It’s much better, and it only jiggles on occation. So it flickers from time to time, which is ugly. Tho the offset to distance and rotation is still there.

Edit: Also the distance and rotation offset of the camera is then doubled or just larger when I use previous position and rotation, which kinda makes sense.

I’m not sure about that buffer. Not too familiar with, and a lot is how webgl works and not p5.js I guess. I know there’s a matrix for the model and one for view projection for the camera. It translates the world not the camera.

My previous prototype in a similar way like this second prototype, but a lot more stuff going on with all sorts of radars, hit detection and cullings for performance with 100s of objects with 10+thousands of polygons. I used a player centric world, where the player stays at 0,0,0 and only a camera that rotates to get the illusion, and everything else moves inverted to the velocity vector. It’s kinda neat to have a “endless” large world, where the objects just wrap around once they reach the imaginary world boundary. So you can fly endlessly and it would just repeat after a certain "distance. This way you never have to worry around many things and you never run out of precision issues. So I never had an issue with this.

Here’s the version I wrote in Processing a bit over a year ago. Use the arrows to turn, A and D to roll, and W and S to control speed. I’m not adjusting time to the frame rate because Processing is pretty steady with its frame rate. The stuff contains 8 copies of the geometry and the ship position wraps within it so appears to be an infinite space.

int nShapes = 12000;
int[] keys;
PVector xAxis, yAxis, zAxis;
Ship ship;
PShape stuff;

int numFrames = 180;
int saveFrame = 0;
boolean bSaveAnim = false;

void setup() {
  fullScreen( P3D, SPAN );
  //size( 720, 480, P3D );
  keys = new int[256];
  xAxis = new PVector( 1, 0, 0 );
  yAxis = new PVector( 0, 1, 0 );
  zAxis = new PVector( 0, 0, 1 );
  noStroke();
  makeStuff();
  ship = new Ship();
  frameRate( 30 );
}

void draw() {
  background( 0 );
  ship.move();
  ship.setCamera();
  shape( stuff );
  if( bSaveAnim ) {
    save( "frames/fr" + nf( saveFrame, 4 ) + ".png" );
    if( ++saveFrame == numFrames ) {
      println( "done saving "+saveFrame+" frames" );
      bSaveAnim = false;
    }
  }
}

class Quat {
  PVector v;
  float w;
  Quat( ) { v = new PVector( 0., 0., 0. );  w = 1.; }
  Quat( PVector axis, float ang ) { 
    v = PVector.mult( axis, sin( ang*0.5 )/axis.mag() );
    w = cos( ang*0.5 );
  }
  Quat normalize() {
    float t = 1./sqrt( v.magSq() + w*w );
    v.mult( t );
    w *= t;
    return this;
  }
  PVector rotate( PVector p ) {
    // ( w*w - dot( v, v )) * p + 2 * dot( p, v ) * v + 2 * w * cross( v, p )
    return PVector.mult( p, w*w - v.dot(v) )
        .add( PVector.mult( v, 2 * p.dot(v) ) )
        .add( PVector.mult( v, 2*w ).cross( p ) );
  }
  PVector unrotate( PVector p ) {
    // ( w*w - dot( v, v )) * p + 2 * dot( p, v ) * v - 2 * w * cross( v, p )
    return PVector.mult( p, w*w - v.dot(v) )
        .add( PVector.mult( v, 2 * p.dot(v) ) )
        .sub( PVector.mult( v, 2*w ).cross( p ) );
  }
  Quat rotateBy( Quat q ) {
    float t = w*q.w - v.dot( q.v );
    v = q.v.cross( v )
         .add( PVector.mult( v, q.w ) )
         .add( PVector.mult( q.v, w ) );
    w = t;
    normalize();
    return this;
  }
}

float SIDE_THRUST = 0.02;
float BACK_THRUST = 0.05;
float FORE_THRUST = 0.02;

class Ship {
  PVector pos;
  PVector vel;
  Quat rot;
  float speed;
  
  Ship() {
    pos = new PVector();
    speed = 1000.0/numFrames;
    vel = new PVector( 0, 0, -speed );
    rot = new Quat();
  }
  
  void move() {
    // UP, DOWN, LEFT, RIGHT, 'd', 'a'
    PVector turn = new PVector( keys[38]-keys[40], keys[37]-keys[39], keys[68]-keys[65] );
    if( turn.magSq() > 0 )
      rot = new Quat( turn, 0.02 ).rotateBy(rot);
    speed += (keys[87]-keys[83]) * 0.025;   // 'w', 's'
    PVector lclVel = rot.unrotate( vel );
    if( lclVel.x < -SIDE_THRUST ) lclVel.x += SIDE_THRUST;
    else if( lclVel.x > SIDE_THRUST ) lclVel.x -= SIDE_THRUST;
    else lclVel.x = 0;
    if( lclVel.y < -SIDE_THRUST ) lclVel.y += SIDE_THRUST;
    else if( lclVel.y > SIDE_THRUST ) lclVel.y -= SIDE_THRUST;
    else lclVel.y = 0;
    if( lclVel.z > -speed+BACK_THRUST ) lclVel.z -= BACK_THRUST;
    else if( lclVel.z < -speed-FORE_THRUST ) lclVel.z += FORE_THRUST;
    else lclVel.z = -speed;
    vel = rot.rotate( lclVel );
    //pos.add( rot.rotate( zAxis ).mult(-speed) );
    pos.add( vel );
    pos.x = (pos.x+1000) % 1000;
    pos.y = (pos.y+1000) % 1000;
    pos.z = (pos.z+1000) % 1000;
  }
  
  void setCamera() {
    PVector back = rot.rotate( zAxis );
    PVector up = rot.rotate( yAxis );
    camera( pos.x, pos.y, pos.z,
            pos.x-back.x, pos.y-back.y, pos.z-back.z,
            up.x, up.y, up.z );
    perspective( TAU/4, 1.*width/height, 1, 400 );
    //frustum( -1.*width/height, 1.*width/height, -1, 1, 1, 400 );
    //lightFalloff( 1, 0, 1./80000 );
    pointLight( 255, 255, 255, pos.x, pos.y, pos.z );
  }
}

PShape makeStuff() {
  colorMode( HSB, 1, 1, 1 );
  stuff = createShape( GROUP );
  for( int is=0; is<nShapes; is++ ) {
    float size = pow(random(1),2) * 5.5 + 0.5;
    float px, py, pz;
    do {
      px = random(-500, 500);
      py = random(-500, 500);
    } while( px*px+py*py < (size+2)*(size+2) );
    pz = random(-500, 500);
    PVector axis = PVector.random3D();
    float ang = random(TAU);
    color c = color(random(1), 0.7, 1);
    for( int i=0; i<2000; i+=1000 )
      for( int j=0; j<2000; j+=1000 )
        for( int k=0; k<2000; k+=1000 ) {
          PShape thing = createShape( BOX, size, size, size );
          thing.setFill( c );
          thing.rotate( ang, axis.x, axis.y, axis.z );
          thing.translate( px+i, py+j, pz+k );
          stuff.addChild( thing );
        }
  }
  colorMode( RGB, 255, 255, 255 );
  return stuff;
}

void keyPressed() {
  if( keyCode < 256 ) keys[keyCode] = 1;
  if( key == '*' ) {
    saveFrame = 0;
    bSaveAnim = true;
  }
}

void keyReleased() {
  if( keyCode < 256 ) keys[keyCode] = 0;
}

So this “fixes” it https://editor.p5js.org/scudly/sketches/qGIPpq4lY by moving the ship’s push(), translate(), and applyMatrix() to below the camera() call.

It also, however, “fixes” the ship in place relative to the camera which means you no longer see that turn towards your velocity that you had before.

I also moved the rest of the object drawing to be below the obj.draw() call so that the camera is consistent for all of them. And added a bunch of boxes for more variety.

1 Like

Oh, wowo that’s odd, because I swear I tried exactly that so many times and it didn’t work for me. I switched translation of ship to after camera even in it’s own push. Probably was having a push outside still. I have to run, but thank you so much!

Edit: So when I have an extra push from start to end of player draw, and inside change the order and put translate and applyrotation into its own push, it doesn’t work. I think that’s what I had. I think it was the first things I tried, and tried so many combinations. Well glad it’s fixed. Thanks again for the help!

Edit. Oh, and I have many stuff happening before the player draw call in the main draw, like enemy space ships that fly around, and it doesn’t “seem” to matter as they also don’t jitter anymore like they did before.

2 Likes

It plays well in Firefox and Safari :+1: but I get the same problem with Chrome :-1: