using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MdxScene.Shapes;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace MdxScene.Cameras
{
public abstract class D3dCamera
{
protected D3dCamera()
{
CoordinateSystem = CameraCoordinateSystem.RightHanded;
PositionX = 0;
PositionY = 0;
PositionZ = 1000;
TargetX = TargetY = TargetZ = 0;
UpX = 0;
UpY = 1;
UpZ = 0;
ZNearPlane = 1;
ZFarPlane = 10000;
MaxMoveTriesForAdjustmentAlgorithm = 100;
}
public virtual void Render(Device d3dDevice)
{
if (null == d3dDevice)
{
return;
}
CurrentViewMatrix = ViewMatrix;
CurrentProjectionMatrix = ProjectionMatrix;
lock (d3dDevice)
{
d3dDevice.Transform.View = CurrentViewMatrix;
d3dDevice.Transform.Projection = CurrentProjectionMatrix;
}
}
#region Properties
#region CoordinateSystem
private CameraCoordinateSystem _coordinateSystem;
public CameraCoordinateSystem CoordinateSystem
{
get { return _coordinateSystem; }
set
{
lock (this)
{
_coordinateSystem = value;
_isViewMatrixValid = false;
_isProjectionMatrixValid = false;
}
}
}
#endregion
#region PositionX
private float _positionX;
public float PositionX
{
get { return _positionX; }
set
{
lock (this)
{
_positionX = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region PositionY
private float _positionY;
public float PositionY
{
get { return _positionY; }
set
{
lock (this)
{
_positionY = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region PositionZ
private float _positionZ;
public float PositionZ
{
get { return _positionZ; }
set
{
lock (this)
{
_positionZ = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region TargetX
private float _targetX;
public float TargetX
{
get { return _targetX; }
set
{
lock (this)
{
_targetX = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region TargetY
private float _targetY;
public float TargetY
{
get { return _targetY; }
set
{
lock (this)
{
_targetY = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region TargetZ
private float _targetZ;
public float TargetZ
{
get { return _targetZ; }
set
{
lock (this)
{
_targetZ = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region UpX
private float _upX;
public float UpX
{
get { return _upX; }
set
{
lock (this)
{
_upX = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region UpY
private float _upY;
public float UpY
{
get { return _upY; }
set
{
lock (this)
{
_upY = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region UpZ
private float _upZ;
public float UpZ
{
get { return _upZ; }
set
{
lock (this)
{
_upZ = value;
_isViewMatrixValid = false;
}
}
}
#endregion
#region ZNearPlane
private float _zNearPlane;
public float ZNearPlane
{
get { return _zNearPlane; }
set
{
lock (this)
{
_zNearPlane = value;
_isProjectionMatrixValid = false;
}
}
}
#endregion
#region ZFarPlane
private float _zFarPlane;
public float ZFarPlane
{
get { return _zFarPlane; }
set
{
lock (this)
{
_zFarPlane = value;
_isProjectionMatrixValid = false;
}
}
}
#endregion
#region ViewMatrix
private Matrix _viewMatrix;
public Matrix ViewMatrix
{
get
{
lock (this)
{
if (!_isViewMatrixValid)
{
_viewMatrix = GetViewMatrix();
_isViewMatrixValid = true;
}
}
return _viewMatrix;
}
}
protected bool _isViewMatrixValid;
#endregion
#region ProjectionMatrix
private Matrix _projectionMatrix;
public Matrix ProjectionMatrix
{
get
{
lock (this)
{
if (!_isProjectionMatrixValid)
{
_projectionMatrix = GetProjectionMatrix();
_isProjectionMatrixValid = true;
}
}
return _projectionMatrix;
}
}
protected bool _isProjectionMatrixValid;
#endregion
public Matrix CurrentViewMatrix { get; protected set; }
public Matrix CurrentProjectionMatrix { get; protected set; }
public int MaxMoveTriesForAdjustmentAlgorithm { get; set; }
#endregion
public virtual Matrix GetViewMatrix()
{
Matrix res;
if (CameraCoordinateSystem.LeftHanded == CoordinateSystem)
{
res = Matrix.LookAtLH(new Vector3(PositionX, PositionY, PositionZ),
new Vector3(TargetX, TargetY, TargetZ),
new Vector3(UpX, UpY, UpZ));
}
else
{
// It's a right-handed coordinate system.
res = Matrix.LookAtRH(new Vector3(PositionX, PositionY, PositionZ),
new Vector3(TargetX, TargetY, TargetZ),
new Vector3(UpX, UpY, UpZ));
}
return res;
}
public abstract Matrix GetProjectionMatrix();
public abstract void Zoom(float scalingFactorX, float scalingFactorY);
public virtual void RelativeZMove(float relativeDistance)
{
float projectionRegionDepth = ZFarPlane - ZNearPlane;
Vector3 translationVector = GetRelativeZAxisDirection();
translationVector.Multiply(projectionRegionDepth*relativeDistance);
Matrix translation = Matrix.Translation(translationVector);
TransformPosition(translation);
TransformTarget(translation);
}
public abstract void RelativeXyMove(float relativeDistanceX, float relativeDistanceY);
public virtual void RelativeMove(float relativeDistanceX, float relativeDistanceY, float relativeDistanceZ)
{
RelativeXyMove(relativeDistanceX, relativeDistanceY);
RelativeZMove(relativeDistanceZ);
}
public void RelativeRotateX(float relativeArcLength,
CameraTransformationCenterPosition centerPosition,
CameraRotationDirection rotationDirection)
{
const float pi = (float) Math.PI;
float rotation = relativeArcLength*(pi*2);
if ((CameraCoordinateSystem.LeftHanded == CoordinateSystem &&
CameraRotationDirection.CounterClockwise == rotationDirection) ||
(CameraCoordinateSystem.RightHanded == CoordinateSystem &&
CameraRotationDirection.Clockwise == rotationDirection))
{
rotation *= -1f;
}
Vector3 relativeXAxisDirection = GetRelativeXAxisDirection();
Matrix rotationTransformation = Matrix.RotationAxis(relativeXAxisDirection, rotation);
Rotate(rotationTransformation, centerPosition);
}
public void RelativeRotateY(float relativeArcLength,
CameraTransformationCenterPosition centerPosition,
CameraRotationDirection rotationDirection)
{
const float pi = (float) Math.PI;
float rotation = relativeArcLength*(pi*2);
if ((CameraCoordinateSystem.LeftHanded == CoordinateSystem &&
CameraRotationDirection.CounterClockwise == rotationDirection) ||
(CameraCoordinateSystem.RightHanded == CoordinateSystem &&
CameraRotationDirection.Clockwise == rotationDirection))
{
rotation *= -1f;
}
Vector3 relativeYAxisDirection = GetRelativeYAxisDirection();
Matrix rotationTransformation = Matrix.RotationAxis(relativeYAxisDirection, rotation);
Rotate(rotationTransformation, centerPosition);
}
public void RelativeRotateZ(float relativeArcLength,
CameraTransformationCenterPosition centerPosition,
CameraRotationDirection rotationDirection)
{
const float pi = (float) Math.PI;
float rotation = relativeArcLength*(pi*2);
if ((CameraRotationDirection.Clockwise == rotationDirection &&
CameraTransformationCenterPosition.CameraPosition == centerPosition) ||
(CameraRotationDirection.CounterClockwise == rotationDirection &&
CameraTransformationCenterPosition.TargetPosition == centerPosition))
{
if (CameraCoordinateSystem.RightHanded == CoordinateSystem)
{
rotation *= -1f;
}
}
else
{
if (CameraCoordinateSystem.LeftHanded == CoordinateSystem)
{
rotation *= -1f;
}
}
Vector3 relativeZAxisDirection = GetRelativeZAxisDirection();
Matrix rotationTransformation = Matrix.RotationAxis(relativeZAxisDirection, rotation);
Rotate(rotationTransformation, centerPosition);
}
public void Rotate(Matrix rotationTransformation, CameraTransformationCenterPosition centerPosition)
{
Matrix translationBefore;
Matrix translationAfter;
Matrix transformation;
if (CameraTransformationCenterPosition.TargetPosition == centerPosition)
{
translationBefore = Matrix.Translation(-TargetX, -TargetY, -TargetZ);
translationAfter = Matrix.Translation(TargetX, TargetY, TargetZ);
transformation = translationBefore*rotationTransformation*translationAfter;
TransformPosition(transformation);
}
else
{
// The transform center is the camera's position.
translationBefore = Matrix.Translation(-PositionX, -PositionY, -PositionZ);
translationAfter = Matrix.Translation(PositionX, PositionY, PositionZ);
transformation = translationBefore*rotationTransformation*translationAfter;
TransformTarget(transformation);
}
TransformUp(rotationTransformation);
}
public void TransformPosition(Matrix transformation)
{
Vector3 cameraPosition = new Vector3(PositionX, PositionY, PositionZ);
cameraPosition.TransformCoordinate(transformation);
PositionX = cameraPosition.X;
PositionY = cameraPosition.Y;
PositionZ = cameraPosition.Z;
}
public void TransformTarget(Matrix transformation)
{
Vector3 targetPosition = new Vector3(TargetX, TargetY, TargetZ);
targetPosition.TransformCoordinate(transformation);
TargetX = targetPosition.X;
TargetY = targetPosition.Y;
TargetZ = targetPosition.Z;
}
public void TransformUp(Matrix transformation)
{
Vector3 upDirection = new Vector3(UpX, UpY, UpZ);
upDirection.TransformCoordinate(transformation);
UpX = upDirection.X;
UpY = upDirection.Y;
UpZ = upDirection.Z;
}
public Vector3 GetRelativeXAxisDirection()
{
Vector3 relativeZAxisDirection = GetRelativeZAxisDirection();
Vector3 relativeXAxisDirection = GetRelativeYAxisDirection();
float rotationRadians = (CameraCoordinateSystem.RightHanded == CoordinateSystem)
? (float) Math.PI/2
: (float) -Math.PI/2;
relativeXAxisDirection.TransformNormal(Matrix.RotationAxis(relativeZAxisDirection, rotationRadians));
return relativeXAxisDirection;
}
public Vector3 GetRelativeYAxisDirection()
{
Vector3 relativeYAxisDirection = new Vector3(UpX, UpY, UpZ);
relativeYAxisDirection.Normalize();
return relativeYAxisDirection;
}
public Vector3 GetRelativeZAxisDirection()
{
Vector3 cameraPosition = new Vector3(PositionX, PositionY, PositionZ);
Vector3 targetPosition = new Vector3(TargetX, TargetY, TargetZ);
Vector3 relativeZAxisDirection = targetPosition - cameraPosition;
relativeZAxisDirection.Normalize();
return relativeZAxisDirection;
}
public void NormalizeUpVector()
{
Vector3 upDirection = new Vector3(UpX, UpY, UpZ);
upDirection.Normalize();
UpX = upDirection.X;
UpY = upDirection.Y;
UpZ = upDirection.Z;
}
public void ZoomToProjectionRegion(float projectionRegionLeft, float projectionRegionTop,
float projectionRegionRight, float projectionRegionBottom, bool keepAspectRatio = false)
{
// Move the camera to look at the center of the projection region.
float centerX = (projectionRegionRight + projectionRegionLeft) / 2;
float centerY = (projectionRegionBottom + projectionRegionTop) / 2;
// Since the projection coordinates are between -1 and 1 (and we are in the prjection coordinates),
// the relative distance is equal to the actual distance...
RelativeXyMove(centerX, centerY);
float projectionRegionWidth = Math.Abs(projectionRegionRight - projectionRegionLeft);
float projectionRegionHeight = Math.Abs(projectionRegionTop - projectionRegionBottom);
if (keepAspectRatio)
{
float scalingFactor = Math.Max(projectionRegionWidth, projectionRegionHeight) / 2;
Zoom(scalingFactor, scalingFactor);
}
else
{
Zoom(projectionRegionWidth / 2, projectionRegionHeight / 2);
}
}
public virtual void AdjustView(IEnumerable<D3dShape> shapes, bool keepAspectRatio = false)
{
if (null == shapes)
{
return;
}
D3dShape[] shapesArray = shapes.ToArray();
float projectionRegionLeft;
float projectionRegionTop;
float projectionRegionFront;
float projectionRegionRight;
float projectionRegionBottom;
float projectionRegionBack;
int moveTriesCount = 0;
float centerX;
float centerY;
const float epsilon = 0.000000000001f;
do
{
// Get the shapes' projection region.
GetShapesProjectionRegion(shapesArray, out projectionRegionLeft, out projectionRegionTop,
out projectionRegionFront,
out projectionRegionRight, out projectionRegionBottom,
out projectionRegionBack);
// Move the camera to look at the center of the projection region.
centerX = (projectionRegionRight + projectionRegionLeft)/2;
centerY = (projectionRegionBottom + projectionRegionTop)/2;
// Since the projection coordinates are between -1 and 1 (and we are in the prjection coordinates),
// the relative distance is equal to the actual distance...
RelativeXyMove(centerX, centerY);
// Get the shapes' projection region, after the move.
GetShapesProjectionRegion(shapesArray, out projectionRegionLeft, out projectionRegionTop,
out projectionRegionFront,
out projectionRegionRight, out projectionRegionBottom,
out projectionRegionBack);
if (0 > projectionRegionFront || 1 < projectionRegionFront)
{
float zMove = projectionRegionFront - ((projectionRegionBack - projectionRegionFront)*0.1f);
RelativeZMove(zMove);
// Get the shapes' projection region, after the move.
GetShapesProjectionRegion(shapesArray, out projectionRegionLeft, out projectionRegionTop,
out projectionRegionFront,
out projectionRegionRight, out projectionRegionBottom,
out projectionRegionBack);
}
if (1 < projectionRegionBack)
{
ZFarPlane = ZNearPlane + (ZFarPlane - ZNearPlane)*(projectionRegionBack + 0.2f);
// Get the shapes' projection region, after the far-plane change.
GetShapesProjectionRegion(shapesArray, out projectionRegionLeft, out projectionRegionTop,
out projectionRegionFront,
out projectionRegionRight, out projectionRegionBottom,
out projectionRegionBack);
}
// Zoom to contain the projection region.
ZoomToProjectionRegion(projectionRegionLeft, projectionRegionTop,
projectionRegionRight, projectionRegionBottom, keepAspectRatio);
++moveTriesCount;
} while (moveTriesCount < MaxMoveTriesForAdjustmentAlgorithm &&
(Math.Abs(0 - centerX) > epsilon || Math.Abs(0 - centerY) > epsilon));
}
#region GetShapesProjectionBoundingBox
public BoundingBox GetShapesProjectionBoundingBox(IEnumerable<D3dShape> shapes)
{
float projectionRegionLeft;
float projectionRegionTop;
float projectionRegionFront;
float projectionRegionRight;
float projectionRegionBottom;
float projectionRegionBack;
GetShapesProjectionRegion(shapes, out projectionRegionLeft, out projectionRegionTop, out projectionRegionFront,
out projectionRegionRight, out projectionRegionBottom, out projectionRegionBack);
return new BoundingBox(new Vector3(projectionRegionLeft, projectionRegionBottom, projectionRegionFront),
new Vector3(projectionRegionRight, projectionRegionTop, projectionRegionBack));
}
protected virtual void GetShapesProjectionRegion(IEnumerable<D3dShape> shapes, out float projectionRegionLeft, out float projectionRegionTop,
out float projectionRegionFront, out float projectionRegionRight,
out float projectionRegionBottom, out float projectionRegionBack)
{
// Initialize the values to a full projection region boundaries.
float minX = -1;
float minY = -1;
float minZ = 0;
float maxX = 1;
float maxY = 1;
float maxZ = 1;
if (null != shapes)
{
D3dShape[] shapesArray = shapes.ToArray();
if (shapesArray.Any())
{
D3dShape firstShape = shapesArray.First();
GetShapeProjectionRegion(firstShape, out minX, out maxY, out minZ, out maxX,
out minY, out maxZ);
Parallel.ForEach(shapesArray, s =>
{
float currMinX;
float currMinY;
float currMinZ;
float currMaxX;
float currMaxY;
float currMaxZ;
GetShapeProjectionRegion(s, out currMinX, out currMaxY, out currMinZ, out currMaxX,
out currMinY, out currMaxZ);
lock (this)
{
minX = Math.Min(minX, currMinX);
minY = Math.Min(minY, currMinY);
minZ = Math.Min(minZ, currMinZ);
maxX = Math.Max(maxX, currMaxX);
maxY = Math.Max(maxY, currMaxY);
maxZ = Math.Max(maxZ, currMaxZ);
}
});
}
}
projectionRegionLeft = minX;
projectionRegionTop = maxY;
projectionRegionFront = minZ;
projectionRegionRight = maxX;
projectionRegionBottom = minY;
projectionRegionBack = maxZ;
}
public BoundingBox GetShapeProjectionBoundingBox(D3dShape shape)
{
float projectionRegionLeft;
float projectionRegionTop;
float projectionRegionFront;
float projectionRegionRight;
float projectionRegionBottom;
float projectionRegionBack;
GetShapeProjectionRegion(shape, out projectionRegionLeft, out projectionRegionTop, out projectionRegionFront,
out projectionRegionRight, out projectionRegionBottom, out projectionRegionBack);
return new BoundingBox(new Vector3(projectionRegionLeft, projectionRegionBottom, projectionRegionFront),
new Vector3(projectionRegionRight, projectionRegionTop, projectionRegionBack));
}
protected virtual void GetShapeProjectionRegion(D3dShape shape, out float projectionRegionLeft, out float projectionRegionTop,
out float projectionRegionFront, out float projectionRegionRight,
out float projectionRegionBottom, out float projectionRegionBack)
{
// Initialize the values to a full projection region boundaries.
float minX = -1;
float minY = -1;
float minZ = 0;
float maxX = 1;
float maxY = 1;
float maxZ = 1;
if (null != shape)
{
BoundingBox boundingBox = shape.GetBoundingBoxBeforeTransformation();
boundingBox.TransformCoordinates(shape.GetActualWorldMatrix() * ViewMatrix);
float projectionRegionDepth = ZFarPlane - ZNearPlane;
minZ = boundingBox.GetMinimalZ();
maxZ = boundingBox.GetMaximalZ();
if (CameraCoordinateSystem.RightHanded == CoordinateSystem)
{
minZ *= -1;
maxZ *= -1;
}
minZ -= ZNearPlane;
maxZ -= ZNearPlane;
minZ /= projectionRegionDepth;
maxZ /= projectionRegionDepth;
boundingBox.TransformCoordinates(ProjectionMatrix);
minX = boundingBox.GetMinimalX();
minY = boundingBox.GetMinimalY();
maxX = boundingBox.GetMaximalX();
maxY = boundingBox.GetMaximalY();
}
projectionRegionLeft = minX;
projectionRegionTop = maxY;
projectionRegionFront = (CameraCoordinateSystem.RightHanded == CoordinateSystem) ? maxZ : minZ;
projectionRegionRight = maxX;
projectionRegionBottom = minY;
projectionRegionBack = (CameraCoordinateSystem.RightHanded == CoordinateSystem) ? minZ : maxZ;
}
#endregion
}
}