#region ================== Copyright (c) 2007 Pascal vd Heiden /* * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com * This program is released under GNU General Public License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #endregion #region ================== Namespaces using System; using CodeImp.DoomBuilder.Geometry; #endregion namespace CodeImp.DoomBuilder.Rendering { internal unsafe sealed class Plotter : IDisposable { #region ================== Constants private const int DASH_INTERVAL = 16; //mxd #endregion #region ================== Variables // Memory private PixelColor[] pixels; private int width; private int height; private int visiblewidth; private int visibleheight; // GL public Texture Texture { get; private set; } #endregion #region ================== Properties public int VisibleWidth { get { return visiblewidth; } } public int VisibleHeight { get { return visibleheight; } } public int Width { get { return width; } } public int Height { get { return height; } } #endregion #region ================== Constructor / Disposer // Constructor public Plotter(int width, int height) { // Initialize Texture = new Texture(width, height, TextureFormat.Bgra8); this.pixels = new PixelColor[width*height]; this.width = width; this.height = height; this.visiblewidth = width; this.visibleheight = height; } public void Dispose() { if (Texture != null) { Texture.Dispose(); Texture = null; } } #endregion #region ================== Pixel Rendering private int TransformY(int y) { return height - y; } // This clears all pixels black public void Clear() { // Clear memory fixed(PixelColor* pixel = pixels) { uint* op = (uint*)pixel; for (int i = 0; i < pixels.Length; i++) { *op = 0; op++; } } } // This draws a pixel normally public void DrawPixelSolid(int x, int y, ref PixelColor c) { y = TransformY(y); // Draw pixel when within range if ((x >= 0) && (x < visiblewidth) && (y >= 0) && (y < visibleheight)) pixels[y * width + x] = c; } // This draws a pixel normally public void DrawVertexSolid(int x, int y, int size, ref PixelColor c, ref PixelColor l, ref PixelColor d) { y = TransformY(y); int x1 = x - size; int x2 = x + size; int y1 = y - size; int y2 = y + size; // Do unchecked? if ((x1 >= 0) && (x2 < visiblewidth) && (y1 >= 0) && (y2 < visibleheight)) { // Filled square for (int yp = y1; yp <= y2; yp++) for (int xp = x1; xp <= x2; xp++) pixels[yp * width + xp] = c; // Vertical edges for (int yp = y1 + 1; yp <= y2 - 1; yp++) { pixels[yp * width + x1] = l; pixels[yp * width + x2] = d; } // Horizontal edges for (int xp = x1 + 1; xp <= x2 - 1; xp++) { pixels[y1 * width + xp] = l; pixels[y2 * width + xp] = d; } // Corners pixels[y2 * width + x2] = d; pixels[y1 * width + x1] = l; } /* else { // Filled square for(yp = y - size; yp <= y + size; yp++) for(xp = x - size; xp <= x + size; xp++) DrawPixelSolid(xp, yp, c); // Vertical edges for(yp = y - size + 1; yp <= y + size - 1; yp++) { DrawPixelSolid(x - size, yp, l); DrawPixelSolid(x + size, yp, d); } // Horizontal edges for(xp = x - size + 1; xp <= x + size - 1; xp++) { DrawPixelSolid(xp, y - size, l); DrawPixelSolid(xp, y + size, d); } // Corners DrawPixelSolid(x + size, y + size, d); DrawPixelSolid(x - size, y - size, l); } */ } // This draws a dotted grid line horizontally public void DrawGridLineH(int y, int x1, int x2, ref PixelColor c) { y = TransformY(y); int numpixels = visiblewidth >> 1; int offset = y & 0x01; int ywidth = y * width; x1 = General.Clamp(x1 >> 1, 0, numpixels - 1); x2 = General.Clamp(x2 >> 1, 0, numpixels - 1); if ((y >= 0) && (y < height)) { // Draw all pixels on this line for (int i = x1; i < x2; i++) pixels[ywidth + ((i << 1) | offset)] = c; } } // This draws a dotted grid line vertically public void DrawGridLineV(int x, int y1, int y2, ref PixelColor c) { y1 = TransformY(y1); y2 = TransformY(y2); int numpixels = visibleheight >> 1; int offset = x & 0x01; y1 = General.Clamp(y1 >> 1, 0, numpixels - 1); y2 = General.Clamp(y2 >> 1, 0, numpixels - 1); if ((x >= 0) && (x < width)) { // Draw all pixels on this line for (int i = y2; i < y1; i++) pixels[((i << 1) | offset) * width + x] = c; } } // This draws a pixel alpha blended public void DrawPixelAlpha(int x, int y, ref PixelColor c) { y = TransformY(y); fixed (PixelColor* pixels = this.pixels) { // Draw only when within range if ((x >= 0) && (x < visiblewidth) && (y >= 0) && (y < visibleheight)) { // Get the target pixel PixelColor* p = pixels + (y * width + x); // Not drawn on target yet? if (*(int*)p == 0) { // Simply apply color to pixel *p = c; } else { // Blend with pixel float a = c.a * 0.003921568627450980392156862745098f; if (p->a + c.a > 255) p->a = 255; else p->a += c.a; p->r = (byte)(p->r * (1f - a) + c.r * a); p->g = (byte)(p->g * (1f - a) + c.g * a); p->b = (byte)(p->b * (1f - a) + c.b * a); } } } } // This draws a line normally // See: http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm public void DrawLineSolid(int x1, int y1, int x2, int y2, ref PixelColor c, uint mask = 0xffffffff) { y1 = TransformY(y1); y2 = TransformY(y2); // Check if the line is outside the screen for sure. // This is quickly done by checking in which area both points are. When this // is above, below, right or left of the screen, then skip drawing the line. if (((x1 < 0) && (x2 < 0)) || ((x1 > visiblewidth) && (x2 > visiblewidth)) || ((y1 < 0) && (y2 < 0)) || ((y1 > visibleheight) && (y2 > visibleheight))) return; // Distance of the line int dx = x2 - x1; int dy = y2 - y1; // Positive (absolute) distance int dxabs = Math.Abs(dx); int dyabs = Math.Abs(dy); // Half distance int x = dyabs >> 1; int y = dxabs >> 1; // Direction int sdx = Math.Sign(dx); int sdy = Math.Sign(dy); // Start position int px = x1; int py = y1; // When the line is completely inside screen, // then do an unchecked draw, because all of its pixels are // guaranteed to be within the memory range if ((x1 >= 0) && (x2 >= 0) && (x1 < visiblewidth) && (x2 < visiblewidth) && (y1 >= 0) && (y2 >= 0) && (y1 < visibleheight) && (y2 < visibleheight)) { // Draw first pixel pixels[py * width + px] = c; // Check if the line is more horizontal than vertical if (dxabs >= dyabs) { for (int i = 0; i < dxabs; i++) { y += dyabs; if (y >= dxabs) { y -= dxabs; py += sdy; } px += sdx; // Draw pixel if ((mask & (1 << (i & 0x7))) != 0) { pixels[py * width + px] = c; } } } // Else the line is more vertical than horizontal else { for (int i = 0; i < dyabs; i++) { x += dxabs; if (x >= dyabs) { x -= dyabs; px += sdx; } py += sdy; // Draw pixel if ((mask & (1 << (i & 0x7))) != 0) { pixels[py * width + px] = c; } } } } else { // Draw first pixel if ((px >= 0) && (px < visiblewidth) && (py >= 0) && (py < visibleheight)) pixels[py * width + px] = c; // Check if the line is more horizontal than vertical if (dxabs >= dyabs) { for (int i = 0; i < dxabs; i++) { y += dyabs; if (y >= dxabs) { y -= dxabs; py += sdy; } px += sdx; // Draw pixel if ((mask & (1 << (i & 0x7))) != 0) { if ((px >= 0) && (px < visiblewidth) && (py >= 0) && (py < visibleheight)) pixels[py * width + px] = c; } } } // Else the line is more vertical than horizontal else { for (int i = 0; i < dyabs; i++) { x += dxabs; if (x >= dyabs) { x -= dyabs; px += sdx; } py += sdy; // Draw pixel if ((mask & (1 << (i & 0x7))) != 0) { if ((px >= 0) && (px < visiblewidth) && (py >= 0) && (py < visibleheight)) pixels[py * width + px] = c; } } } } } //mxd public void DrawLine3DFloor(int x1, int y1, int x2, int y2, ref PixelColor c, PixelColor c2) { Line2D line = new Line2D(x1, y1, x2, y2); double length = line.GetLength(); if (length < DASH_INTERVAL * 2) { DrawLineSolid(x1, y1, x2, y2, ref c2); } else { double d1 = DASH_INTERVAL / length; double d2 = 1.0f - d1; Vector2D p1 = line.GetCoordinatesAt(d1); Vector2D p2 = line.GetCoordinatesAt(d2); DrawLineSolid(x1, y1, (int)p1.x, (int)p1.y, ref c2); DrawLineSolid((int)p1.x, (int)p1.y, (int)p2.x, (int)p2.y, ref c); DrawLineSolid((int)p2.x, (int)p2.y, x2, y2, ref c2); } } #endregion #region ================== Drawing to rendertarget public void DrawContents(RenderDevice graphics) { // set pixels of texture // convert from pixelcolor to uint fixed (PixelColor* pixels = this.pixels) { uint* uintpixels = (uint*)pixels; uint* targetpixels = (uint*)graphics.MapPBO(Texture); for (int i = 0; i < this.pixels.Length; i++) *targetpixels++ = *uintpixels++; graphics.UnmapPBO(Texture); } } #endregion } }