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); #if DEBUG /* * GL.PushMatrix(); * GL.LineWidth(4); * GL.Color3(Color.Red); * GL.Begin(BeginMode.Lines); * GL.Vertex3(-100, 0, 0); * GL.Vertex3(100, 0, 0); * GL.End(); * GL.Color3(Color.Green); * GL.Begin(BeginMode.Lines); * GL.Vertex3(0, -100, 0); * GL.Vertex3(0, 100, 0); * GL.End(); * GL.Color3(Color.White); * GL.Begin(BeginMode.Lines); * GL.Vertex3(0, 0, -100); * GL.Vertex3(0, 0, 100); * GL.End(); * GL.PopMatrix();*/ #endif var lookat = Matrix4.LookAt(0, 0, -10, 0, 0, 0, 0, 1, 0); GL.LoadMatrix(ref lookat); GL.Translate(0, 0, 10); float direction = (obj is OwnableObject) ? (obj as OwnableObject).Direction : 0; float objectRotation = 45f - direction / 256f * 360f; // convert game rotation to world degrees var world = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(60)); world = Matrix4.CreateRotationY(MathHelper.DegreesToRadians(180)) * world; // this is how the game places voxels flat on the world world = Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(objectRotation)) * world; // object facing world = Matrix4.Scale(0.028f, 0.028f, 0.028f) * world; // art.ini TurretOffset value positions some voxel parts over our x-axis world = Matrix4.CreateTranslation(0.18f * props.TurretVoxelOffset, 0, 0) * world; GL.MultMatrix(ref world); // direction of light vector given by pitch & yaw float pitch = MathHelper.DegreesToRadians(210); float yaw = MathHelper.DegreesToRadians(120); // helps to find good pitch/yaw /*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 < 360; i += 30) { * for (int j = 0; j < 360; j += 30) { * GL.Color3(colors[i / 30]); * var shadowTransform2 = * Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(210)) * Matrix4.CreateRotationY(MathHelper.DegreesToRadians(120)); * 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); 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); // undo world transformations on light direction var lightDirection = ExtractRotationVector(ToOpenGL(Matrix4.Invert(world * frame * shadowTransform))); // 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); /* 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); }
public static Rectangle GetBounds(GameObject obj, VxlFile vxl, HvaFile hva, DrawProperties props) { vxl.Initialize(); hva.Initialize(); float direction = (obj is OwnableObject) ? (obj as OwnableObject).Direction : 0; float objectRotation = 45f - direction / 256f * 360f; // convert game rotation to world degrees var world = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(60)); world = Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(objectRotation)) * world; // object facing world = Matrix4.Scale(0.25f, 0.25f, 0.25f) * world; // art.ini TurretOffset value positions some voxel parts over our x-axis world = Matrix4.CreateTranslation(0.18f * props.TurretVoxelOffset, 0, 0) * world; var camera = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(30), 1f, 1, 100); world = world * camera; Rectangle ret = Rectangle.Empty; foreach (var section in vxl.Sections) { 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 * world; // floor rect of the bounding box Vector3 floorTopLeft = new Vector3(0, 0, 0); Vector3 floorTopRight = new Vector3(section.SpanX, 0, 0); Vector3 floorBottomRight = new Vector3(section.SpanX, section.SpanY, 0); Vector3 floorBottomLeft = new Vector3(0, section.SpanY, 0); // ceil rect of the bounding box Vector3 ceilTopLeft = new Vector3(0, 0, section.SpanZ); Vector3 ceilTopRight = new Vector3(section.SpanX, 0, section.SpanZ); Vector3 ceilBottomRight = new Vector3(section.SpanX, section.SpanY, section.SpanZ); Vector3 ceilBottomLeft = new Vector3(0, section.SpanY, section.SpanZ); // apply transformations floorTopLeft = Vector3.Transform(floorTopLeft, frame); floorTopRight = Vector3.Transform(floorTopRight, frame); floorBottomRight = Vector3.Transform(floorBottomRight, frame); floorBottomLeft = Vector3.Transform(floorBottomLeft, frame); ceilTopLeft = Vector3.Transform(ceilTopLeft, frame); ceilTopRight = Vector3.Transform(ceilTopRight, frame); ceilBottomRight = Vector3.Transform(ceilBottomRight, frame); ceilBottomLeft = Vector3.Transform(ceilBottomLeft, frame); int FminX = (int)Math.Floor(Math.Min(Math.Min(Math.Min(floorTopLeft.X, floorTopRight.X), floorBottomRight.X), floorBottomLeft.X)); int FmaxX = (int)Math.Ceiling(Math.Max(Math.Max(Math.Max(floorTopLeft.X, floorTopRight.X), floorBottomRight.X), floorBottomLeft.X)); int FminY = (int)Math.Floor(Math.Min(Math.Min(Math.Min(floorTopLeft.Y, floorTopRight.Y), floorBottomRight.Y), floorBottomLeft.Y)); int FmaxY = (int)Math.Ceiling(Math.Max(Math.Max(Math.Max(floorTopLeft.Y, floorTopRight.Y), floorBottomRight.Y), floorBottomLeft.Y)); int TminX = (int)Math.Floor(Math.Min(Math.Min(Math.Min(ceilTopLeft.X, ceilTopRight.X), ceilBottomRight.X), ceilBottomLeft.X)); int TmaxX = (int)Math.Ceiling(Math.Max(Math.Max(Math.Max(ceilTopLeft.X, ceilTopRight.X), ceilBottomRight.X), ceilBottomLeft.X)); int TminY = (int)Math.Floor(Math.Min(Math.Min(Math.Min(ceilTopLeft.Y, ceilTopRight.Y), ceilBottomRight.Y), ceilBottomLeft.Y)); int TmaxY = (int)Math.Ceiling(Math.Max(Math.Max(Math.Max(ceilTopLeft.Y, ceilTopRight.Y), ceilBottomRight.Y), ceilBottomLeft.Y)); int minX = Math.Min(FminX, TminX); int maxX = Math.Max(FmaxX, TmaxX); int minY = Math.Min(FminY, TminY); int maxY = Math.Max(FmaxY, TmaxY); ret = Rectangle.Union(ret, Rectangle.FromLTRB(minX, minY, maxX, maxY)); } // return new Rectangle(-ret.Width / 2, -ret.Height / 2, ret.Width, ret.Height); return(ret); }
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) { var img = (obj.Tile.Drawable as TileDrawable).GetTileImage(obj.Tile); int ramp = img?.RampType ?? 0; 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); }