public DrawingSurface Render(VxlFile vxl, HvaFile hva, GameObject obj, DrawProperties props)
{
if (!_isInit) Initialize();
if (!_canRender) {
Logger.Warn("Not rendering {0} because no OpenGL context could be obtained", vxl.FileName);
return null;
}
Logger.Debug("Rendering voxel {0}", vxl.FileName);
vxl.Initialize();
hva.Initialize();
GL.Viewport(0, 0, _surface.BitmapData.Width, _surface.BitmapData.Height);
GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit);
// RA2 uses dimetric projection with camera elevated 30° off the ground
GL.MatrixMode(MatrixMode.Projection);
var persp = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(30), _surface.BitmapData.Width / (float)_surface.BitmapData.Height, 1, _surface.BitmapData.Height);
GL.LoadMatrix(ref persp);
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadIdentity();
var lookat = Matrix4.LookAt(0, 0, -10, 0, 0, 0, 0, 1, 0);
GL.MultMatrix(ref lookat);
var trans = Matrix4.CreateTranslation(0, 0, 10);
GL.MultMatrix(ref trans);
// align and zoom
var world = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(60));
world = Matrix4.CreateRotationY(MathHelper.DegreesToRadians(180)) * world;
world = Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(-45)) * world;
world = Matrix4.Scale(0.028f, 0.028f, 0.028f) * world;
GL.MultMatrix(ref world);
// DrawAxes();
// determine tilt vectors
Matrix4 tilt = Matrix4.Identity;
int tiltPitch =0 , tiltYaw = 0;
if (obj.Tile.Drawable != null) {
int ramp = (obj.Tile.Drawable as TileDrawable).GetTileImage(obj.Tile).RampType;
if (ramp == 0 || ramp >= 17) {
tiltPitch = tiltYaw = 0;
}
else if (ramp <= 4) {
// screen-diagonal facings (perpendicular to axes)
tiltPitch = 25;
tiltYaw = -90 * ramp;
}
else {
// world-diagonal facings (perpendicular to screen)
tiltPitch = 25;
tiltYaw = 225 - 90 * ((ramp - 1) % 4);
}
tilt *= Matrix4.CreateRotationX(MathHelper.DegreesToRadians(tiltPitch));
tilt *= Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(tiltYaw));
/*// show tilt direction
GL.Color3(Color.Black);
GL.Begin(BeginMode.Lines);
GL.Vertex3(Vector3.Zero);
var tiltVec = Vector3.UnitZ;
tiltVec = Vector3.Transform(tiltVec, tilt);
tiltVec = Vector3.Multiply(tiltVec, 1000f);
GL.Vertex3(tiltVec);
GL.End();*/
}
/*// draw slope normals
GL.LineWidth(2);
var colors = new[] { Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Orange, Color.Black, Color.Purple, Color.SlateBlue, Color.DimGray, Color.White, Color.Teal, Color.Tan };
for (int i = 0; i < 8; i++) {
GL.Color3(colors[i]);
const float roll = 25f;
float syaw = 45f * i;
var slopeNormal = Vector3.UnitZ;
slopeNormal = Vector3.Transform(slopeNormal, Matrix4.CreateRotationX(MathHelper.DegreesToRadians(roll)));
slopeNormal = Vector3.Transform(slopeNormal, Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(syaw)));
GL.Begin(BeginMode.Lines);
GL.Vertex3(0, 0, 0);
GL.Vertex3(Vector3.Multiply(slopeNormal, 1000f));
GL.End();
}*/
// object rotation around Z
float direction = (obj is OwnableObject) ? (obj as OwnableObject).Direction : 0;
float objectRotation = 90 - direction / 256f * 360f - tiltYaw; // convert game rotation to world degrees
Matrix4 @object = Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(objectRotation)) * tilt; // object facing
// art.ini TurretOffset value positions some voxel parts over our x-axis
@object = Matrix4.CreateTranslation(0.18f * props.TurretVoxelOffset, 0, 0) * @object;
GL.MultMatrix(ref @object);
// DrawAxes();
float pitch = MathHelper.DegreesToRadians(210);
float yaw = MathHelper.DegreesToRadians(120);
/*// helps to find good pitch/yaw
// direction of light vector given by pitch & yaw
for (int i = 0; i < 360; i += 30) {
for (int j = 0; j < 360; j += 30) {
GL.Color3(colors[i / 30]);
var shadowTransform2 =
Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(i))
* Matrix4.CreateRotationY(MathHelper.DegreesToRadians(j));
GL.LineWidth(2);
GL.Begin(BeginMode.Lines);
GL.Vertex3(0, 0, 0);
GL.Vertex3(Vector3.Multiply(ExtractRotationVector(ToOpenGL(Matrix4.Invert(world * shadowTransform2))), 100f));
GL.End();
}
}*/
var shadowTransform = Matrix4.CreateRotationZ(pitch) * Matrix4.CreateRotationY(yaw);
// clear shadowbuf
var shadBuf = _surface.GetShadows();
Array.Clear(shadBuf, 0, shadBuf.Length);
foreach (var section in vxl.Sections) {
GL.PushMatrix();
var frameRot = hva.LoadGLMatrix(section.Index);
frameRot.M41 *= section.HVAMultiplier * section.ScaleX;
frameRot.M42 *= section.HVAMultiplier * section.ScaleY;
frameRot.M43 *= section.HVAMultiplier * section.ScaleZ;
var frameTransl = Matrix4.CreateTranslation(section.MinBounds);
var frame = frameTransl * frameRot;
GL.MultMatrix(ref frame);
var shadowScale = Matrix4.Scale(0.5f);
//var shadowTilt = null;
var shadowToScreen = frameTransl * shadowScale * frameRot * (@object * world) * trans * lookat;
// undo world transformations on light direction
var v = @object*world*frame*shadowTransform;
var lightDirection = (v.Determinant != 0.0) ? ExtractRotationVector(ToOpenGL(Matrix4.Invert(v))) : Vector3.Zero;
// draw line in direction light comes from
/*GL.Color3(Color.Red);
GL.LineWidth(4f);
GL.Begin(BeginMode.Lines);
GL.Vertex3(0, 0, 0);
GL.Vertex3(Vector3.Multiply(lightDirection, 100f));
GL.End();*/
GL.Begin(BeginMode.Quads);
for (uint x = 0; x != section.SizeX; x++) {
for (uint y = 0; y != section.SizeY; y++) {
foreach (VxlFile.Voxel vx in section.Spans[x, y].Voxels) {
Color color = obj.Palette.Colors[vx.ColorIndex];
Vector3 normal = section.GetNormal(vx.NormalIndex);
// shader function taken from https://github.com/OpenRA/OpenRA/blob/bleed/cg/vxl.fx
// thanks to pchote for a LOT of help getting it right
Vector3 colorMult = Vector3.Add(Ambient, Diffuse * Math.Max(Vector3.Dot(normal, lightDirection), 0f));
GL.Color3(
(byte)Math.Min(255, color.R * colorMult.X),
(byte)Math.Min(255, color.G * colorMult.Y),
(byte)Math.Min(255, color.B * colorMult.Z));
Vector3 vxlPos = Vector3.Multiply(new Vector3(x, y, vx.Z), section.Scale);
RenderVoxel(vxlPos);
var shadpos = new Vector3(x, y, 0);
var screenPos = Vector3.Transform(shadpos, shadowToScreen);
screenPos = Vector3.Transform(screenPos, persp);
screenPos.X /= screenPos.Z;
screenPos.Y /= screenPos.Z;
screenPos.X = (screenPos.X + 1) * _surface.Width / 2;
screenPos.Y = (screenPos.Y + 1) * _surface.Height / 2;
if (0 <= screenPos.X && screenPos.X < _surface.Width && 0 <= screenPos.Y && screenPos.Y < _surface.Height)
shadBuf[(int)screenPos.X + (_surface.Height - 1 - (int)screenPos.Y) * _surface.Width] = true;
/* draw line in normal direction
if (r.Next(100) == 4) {
float m = Math.Max(Vector3.Dot(normal, lightDirection), 0f);
GL.Color3(m, m, m);
GL.LineWidth(1);
GL.Begin(BeginMode.Lines);
GL.Vertex3(new Vector3(x, y, vx.Z));
GL.Vertex3(new Vector3(x, y, vx.Z) + Vector3.Multiply(normal, 100f));
GL.End();
}*/
}
}
}
GL.End();
GL.PopMatrix();
}
// read pixels back to surface
GL.ReadPixels(0, 0, _surface.BitmapData.Width, _surface.BitmapData.Height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, _surface.BitmapData.Scan0);
return _surface;
}