123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- package h3d;
- // use left-handed coordinate system, more suitable for 2D games X=0,Y=0 at screen top-left and Z towards user
- class Camera {
- public var zoom : Float;
- /**
- The screenRatio represents the W/H screen ratio.
- **/
- public var screenRatio : Float;
- /**
- The vertical FieldOfView, in degrees.
- Usually cameras are using an horizontal FOV, but the value will change depending on the screen ratio.
- For instance a 4:3 screen will have a lower horizontal FOV than a 16:9 one, however the vertical FOV remains constant.
- Use setFovX to initialize fovY based on an horizontal FOV and an initial screen ratio.
- **/
- public var fovY : Float;
- public var zNear : Float;
- public var zFar : Float;
- /**
- Set orthographic bounds.
- **/
- public var orthoBounds : h3d.col.Bounds;
- public var rightHanded : Bool;
- public var mproj : Matrix;
- public var mcam : Matrix;
- public var m : Matrix;
- public var pos : Vector;
- public var up : Vector;
- public var target : Vector;
- public var viewX : Float = 0.;
- public var viewY : Float = 0.;
- public var follow : { pos : h3d.scene.Object, target : h3d.scene.Object };
- public var frustum(default, null) : h3d.col.Frustum;
- var minv : Matrix;
- var mcamInv : Matrix;
- var mprojInv : Matrix;
- var needInv : Bool;
- public function new( fovY = 25., zoom = 1., screenRatio = 1.333333, zNear = 0.02, zFar = 4000., rightHanded = false ) {
- this.fovY = fovY;
- this.zoom = zoom;
- this.screenRatio = screenRatio;
- this.zNear = zNear;
- this.zFar = zFar;
- this.rightHanded = rightHanded;
- pos = new Vector(2, 3, 4);
- up = new Vector(0, 0, 1);
- target = new Vector(0, 0, 0);
- m = new Matrix();
- mcam = new Matrix();
- mproj = new Matrix();
- frustum = new h3d.col.Frustum();
- update();
- }
- /**
- Set the vertical fov based on a given horizontal fov (in degrees) for a specified screen ratio.
- **/
- public function setFovX( fovX : Float, withRatio : Float ) {
- var degToRad = Math.PI / 180;
- fovY = 2 * Math.atan( Math.tan(fovX * 0.5 * degToRad) / withRatio ) / degToRad;
- }
- /**
- Calculate the current horizontal fov (in degrees).
- **/
- public function getFovX() {
- var degToRad = Math.PI / 180;
- var halfFovX = Math.atan( Math.tan(fovY * 0.5 * degToRad) * screenRatio );
- var fovX = halfFovX * 2 / degToRad;
- return fovX;
- }
- public function clone() {
- var c = new Camera(fovY, zoom, screenRatio, zNear, zFar, rightHanded);
- c.pos = pos.clone();
- c.up = up.clone();
- c.target = target.clone();
- c.update();
- return c;
- }
- /**
- Returns the inverse of the camera matrix view and projection. Cache the result until the next update().
- **/
- public function getInverseViewProj() {
- if( minv == null ) minv = new h3d.Matrix();
- if( needInv ) {
- minv.initInverse(m);
- needInv = false;
- }
- return minv;
- }
- /**
- Returns the inverse of the camera matrix projection. Cache the result until the next update().
- **/
- public function getInverseProj() {
- if( mprojInv == null ) {
- mprojInv = new h3d.Matrix();
- mprojInv._44 = 0;
- }
- if( mprojInv._44 == 0 )
- mprojInv.initInverse(mproj);
- return mprojInv;
- }
- /**
- Returns the inverse of the camera matrix view only. Cache the result until the next update().
- **/
- public function getInverseView() {
- if( mcamInv == null ) {
- mcamInv = new h3d.Matrix();
- mcamInv._44 = 0;
- }
- if( mcamInv._44 == 0 )
- mcamInv.initInverse(mcam);
- return mcamInv;
- }
- /**
- Setup camera for cubemap rendering on the given face.
- **/
- public function setCubeMap( face : Int, ?position : h3d.Vector ) {
- var dx = 0, dy = 0, dz = 0;
- switch( face ) {
- case 0: dx = 1; up.set(0,1,0);
- case 1: dx = -1; up.set(0,1,0);
- case 2: dy = 1; up.set(0,0,-1);
- case 3: dy = -1; up.set(0,0,1);
- case 4: dz = 1; up.set(0,1,0);
- case 5: dz = -1; up.set(0,1,0);
- }
- if( position != null )
- pos.load(position);
- target.set(pos.x + dx,pos.y + dy,pos.z + dz);
- }
- /**
- Transforms a 2D screen position into the 3D one according to the current camera.
- The screenX and screenY values must be in the [-1,1] range.
- The camZ value represents the normalized z in the frustum in the [0,1] range.
- [unproject] can be used to get the ray from the camera position to a given screen position by using two different camZ values.
- For instance the 3D ray between unproject(0,0,0) and unproject(0,0,1) is the center axis of the 3D frustum.
- **/
- public function unproject( screenX : Float, screenY : Float, camZ ) {
- var p = new h3d.Vector(screenX, screenY, camZ);
- p.project(getInverseViewProj());
- return p;
- }
- public function rayFromScreen( pixelX : Float, pixelY : Float, sceneWidth = -1, sceneHeight = -1 ) {
- var engine = h3d.Engine.getCurrent();
- if( sceneWidth < 0 ) sceneWidth = engine.width;
- if( sceneHeight < 0 ) sceneHeight = engine.height;
- var rx = (pixelX / sceneWidth - 0.5) * 2;
- var ry = (0.5 - pixelY / sceneHeight) * 2;
- return h3d.col.Ray.fromPoints(unproject(rx, ry, 0).toPoint(), unproject(rx, ry, 1).toPoint());
- }
- public function update() {
- if( follow != null ) {
- var fpos = follow.pos.localToGlobal();
- var ftarget = follow.target.localToGlobal();
- pos.set(fpos.x, fpos.y, fpos.z);
- target.set(ftarget.x, ftarget.y, ftarget.z);
- // Animate FOV
- if( follow.pos.name != null ) {
- var p = follow.pos;
- while( p != null ) {
- if( p.currentAnimation != null ) {
- var v = p.currentAnimation.getPropValue(follow.pos.name, "FOVY");
- if( v != null ) {
- fovY = v;
- break;
- }
- }
- p = p.parent;
- }
- }
- }
- makeCameraMatrix(mcam);
- makeFrustumMatrix(mproj);
- m.multiply(mcam, mproj);
- needInv = true;
- if( mcamInv != null ) mcamInv._44 = 0;
- if( mprojInv != null ) mprojInv._44 = 0;
- frustum.loadMatrix(m);
- }
- public function getFrustumCorners(zMax=1., zMin=0.) : Array<h3d.Vector> {
- return [
- unproject(-1, 1, zMin), unproject(1, 1, zMin), unproject(1, -1, zMin), unproject(-1, -1, zMin),
- unproject(-1, 1, zMax), unproject(1, 1, zMax), unproject(1, -1, zMax), unproject(-1, -1, zMax)
- ];
- }
- public function lostUp() {
- var p2 = pos.clone();
- p2.normalize();
- return Math.abs(p2.dot(up)) > 0.999;
- }
- public function getViewDirection( dx : Float, dy : Float, dz = 0. ) {
- var a = new h3d.col.Point(dx,dy,dz);
- a.transform3x3(mcam);
- a.normalize();
- return a;
- }
- public function movePosAxis( dx : Float, dy : Float, dz = 0. ) {
- var p = new h3d.col.Point(dx, dy, dz);
- p.transform3x3(mcam);
- pos.x += p.x;
- pos.y += p.y;
- pos.z += p.z;
- }
- public function moveTargetAxis( dx : Float, dy : Float, dz = 0. ) {
- var p = new h3d.col.Point(dx, dy, dz);
- p.transform3x3(mcam);
- target.x += p.x;
- target.y += p.y;
- target.z += p.z;
- }
- public function forward(speed = 1.) {
- var c = 1 - 0.025 * speed;
- pos.set(
- target.x + (pos.x - target.x) * c,
- target.y + (pos.y - target.y) * c,
- target.z + (pos.z - target.z) * c
- );
- }
- public function backward(speed = 1.) {
- var c = 1 + 0.025 * speed;
- pos.set(
- target.x + (pos.x - target.x) * c,
- target.y + (pos.y - target.y) * c,
- target.z + (pos.z - target.z) * c
- );
- }
- function makeCameraMatrix( m : Matrix ) {
- // in leftHanded the z axis is positive else it's negative
- // this way we make sure that our [ax,ay,-az] matrix follow the same handness as our world
- // We build a transposed version of Matrix.lookAt
- var az = target.sub(pos);
- if( rightHanded ) az.scale(-1);
- az.normalize();
- var ax = up.cross(az);
- ax.normalize();
- if( ax.length() == 0 ) {
- ax.x = az.y;
- ax.y = az.z;
- ax.z = az.x;
- }
- var ay = az.cross(ax);
- m._11 = ax.x;
- m._12 = ay.x;
- m._13 = az.x;
- m._14 = 0;
- m._21 = ax.y;
- m._22 = ay.y;
- m._23 = az.y;
- m._24 = 0;
- m._31 = ax.z;
- m._32 = ay.z;
- m._33 = az.z;
- m._34 = 0;
- m._41 = -ax.dot(pos);
- m._42 = -ay.dot(pos);
- m._43 = -az.dot(pos);
- m._44 = 1;
- }
- public function setTransform( m : Matrix ) {
- pos.set(m._41, m._42, m._43);
- target.load(pos.add(m.getDirection()));
- }
- function makeFrustumMatrix( m : Matrix ) {
- m.zero();
- // this will take into account the aspect ratio and normalize the z value into [0,1] once it's been divided by w
- // Matrixes have to solve the following formulaes :
- //
- // transform P by Mproj and divide everything by
- // [x,y,-zNear,1] => [sx/zNear, sy/zNear, 0, 1]
- // [x,y,-zFar,1] => [sx/zFar, sy/zFar, 1, 1]
- // we apply the screen ratio to the height in order to have the fov being a horizontal FOV. This way we don't have to change the FOV when the screen is enlarged
- var bounds = orthoBounds;
- if( bounds != null ) {
- var w = 1 / (bounds.xMax - bounds.xMin);
- var h = 1 / (bounds.yMax - bounds.yMin);
- var d = 1 / (bounds.zMax - bounds.zMin);
- m._11 = 2 * w;
- m._22 = 2 * h;
- m._33 = d;
- m._41 = -(bounds.xMin + bounds.xMax) * w;
- m._42 = -(bounds.yMin + bounds.yMax) * h;
- m._43 = -bounds.zMin * d;
- m._44 = 1;
- } else {
- var degToRad = (Math.PI / 180);
- var halfFovX = Math.atan( Math.tan(fovY * 0.5 * degToRad) * screenRatio );
- var scale = zoom / Math.tan(halfFovX);
- m._11 = scale;
- m._22 = scale * screenRatio;
- m._33 = zFar / (zFar - zNear);
- m._34 = 1;
- m._43 = -(zNear * zFar) / (zFar - zNear);
- }
- m._11 += viewX * m._14;
- m._21 += viewX * m._24;
- m._31 += viewX * m._34;
- m._41 += viewX * m._44;
- m._12 += viewY * m._14;
- m._22 += viewY * m._24;
- m._32 += viewY * m._34;
- m._42 += viewY * m._44;
- // our z is negative in that case
- if( rightHanded ) {
- m._33 *= -1;
- m._34 *= -1;
- }
- }
- /**
- Project a 3D point into the 2D screen. Make sure to update() the camera if it's been moved before using that.
- **/
- public function project( x : Float, y : Float, z : Float, screenWidth : Float, screenHeight : Float, snapToPixel = true, ?p: h3d.Vector) {
- if(p == null)
- p = new h3d.Vector();
- p.set(x, y, z);
- p.project(m);
- p.x = (p.x + 1) * 0.5 * screenWidth;
- p.y = (-p.y + 1) * 0.5 * screenHeight;
- if( snapToPixel ) {
- p.x = Math.round(p.x);
- p.y = Math.round(p.y);
- }
- return p;
- }
- public function distanceToDepth( dist : Float ) {
- return ((zFar + zNear - 2.0 * zNear * zFar / hxd.Math.clamp(dist, zNear, zFar)) / (zFar - zNear) + 1.0) / 2.0;
- }
- public function depthToDistance( depth : Float ) {
- return (hxd.Math.clamp(depth, 0, 1) * zFar - zNear * zFar) / (zFar - zNear);
- }
- public function load( cam : Camera ) {
- pos.load(cam.pos);
- target.load(cam.target);
- up.load(cam.up);
- if( cam.orthoBounds != null ) {
- orthoBounds = new h3d.col.Bounds();
- orthoBounds.load(cam.orthoBounds);
- } else
- orthoBounds = null;
- fovY = cam.fovY;
- screenRatio = cam.screenRatio;
- zoom = cam.zoom;
- zNear = cam.zNear;
- zFar = cam.zFar;
- if( cam.follow != null )
- follow = { pos : cam.follow.pos, target : cam.follow.target };
- else
- follow = null;
- viewX = cam.viewX;
- viewY = cam.viewY;
- update();
- }
- }
|