Based on some study of 3D rotation matrixes, I made this jsfiddle, which doesn't quite work on its own, because you can't use 'ctx.getImageData()' on external videos or images (but if you want to try the same code on a locally hosted video source, it should work just fine).
Most of the 3D magic happens in two matrix multiplications. For each point of the image, the xyz coordinates are multiplied by the rotation matrix in the function 'rotatePoint', which transforms the point 'xyz' by the axis of rotation from origin 'p' in the direction of the unit vector 'u' by 'theta' radians.
function rotatePoint(xyz,p,u,rho,phi){
return new Vector(
((p.x*(u.y*u.y+u.z*u.z)-u.x*(p.y*u.y+p.z*u.z-u.x*xyz.x-u.y*xyz.y-u.z*xyz.z))*(1-rho)+xyz.x*rho+(-p.z*u.y+p.y*u.z-u.z*xyz.y+u.y*xyz.z)*phi)|0,
((p.y*(u.x*u.x+u.z*u.z)-u.y*(p.x*u.x+p.z*u.z-u.x*xyz.x-u.y*xyz.y-u.z*xyz.z))*(1-rho)+xyz.y*rho+( p.z*u.x-p.x*u.z+u.z*xyz.x-u.x*xyz.z)*phi)|0,
((p.z*(u.x*u.x+u.y*u.y)-u.z*(p.x*u.x+p.y*u.y-u.x*xyz.x-u.y*xyz.y-u.z*xyz.z))*(1-rho)+xyz.z*rho+(-p.y*u.x+p.x*u.y-u.y*xyz.x+u.x*xyz.y)*phi)|0)
}
Where 'rho' is sin(theta), and 'phi' is cos(theta), and theta is the angle of rotation.
Finally the point is multiplied by the perspective matrix, in the following function, which transforms the point from world coordinates to screen coordinates.
function perspective(xyz){
return {
x : ((this.camera.e.z*(xyz.x-this.camera.e.x))/(this.camera.e.z+xyz.z)+this.camera.e.x)|0,
y : ((this.camera.e.z*(xyz.y-this.camera.e.y))/(this.camera.e.z+xyz.z)+this.camera.e.y)|0
}
}
Now all of this works just fine for the most part. But for whatever reason, the 3D rotation results in the image repeating off into infinity along the axis of rotation. Here's an example screenshot:
Anyone have any idea why this is happening?