#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 System.Collections; using System.Collections.Generic; using System.Globalization; using System.Text; using System.Windows.Forms; using System.IO; using System.Reflection; using System.Drawing; using System.ComponentModel; using SlimDX; using CodeImp.DoomBuilder.Geometry; using SlimDX.Direct3D9; using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.VisualModes; using CodeImp.DoomBuilder.Map; //mxd using CodeImp.DoomBuilder.GZBuilder.MD3; using CodeImp.DoomBuilder.GZBuilder.Data; using CodeImp.DoomBuilder.GZBuilder.Geometry; using CodeImp.DoomBuilder.GZBuilder.Rendering; #endregion namespace CodeImp.DoomBuilder.Rendering { internal sealed class Renderer3D : Renderer, IRenderer3D { #region ================== Constants private const int RENDER_PASSES = 4; private const float PROJ_NEAR_PLANE = 1f; private const float CROSSHAIR_SCALE = 0.06f; private const float FOG_RANGE = 0.9f; #endregion #region ================== Variables // Matrices private Matrix projection; private Matrix view3d; private Matrix billboard; private Matrix worldviewproj; private Matrix view2d; private Matrix world; private Vector3D cameraposition; private int shaderpass; // Options private bool fullbrightness; // Window size private Size windowsize; // Frustum private ProjectedFrustum2D frustum; // Thing cage private bool renderthingcages; //mxd private ThingBoundingBox bbox; private VisualVertexHandle vertexHandle; private SizelessVisualThingCage sizelessThingHandle; private List thingsWithLight; private int[] lightOffsets; private Dictionary> litGeometry; private Dictionary> thingsWithModel; // Crosshair private FlatVertex[] crosshairverts; private bool crosshairbusy; // Highlighting private IVisualPickable highlighted; private float highlightglow; private float highlightglowinv; private ColorImage highlightimage; private ColorImage selectionimage; private bool showselection; private bool showhighlight; // Geometry to be rendered. // Each Dictionary in the array is a render pass. // Each BinaryHeap in the Dictionary contains all geometry that needs // to be rendered with the associated ImageData. // The BinaryHeap sorts the geometry by sector to minimize stream switchs. private Dictionary>[] geometry; // Things to be rendered. // Each Dictionary in the array is a render pass. // Each VisualThing is inserted in the Dictionary by their texture image. private Dictionary>[] things; // Things to be rendered, sorted by distance from camera private BinaryHeap thingsbydistance; //mxd. Visual vertices private VisualVertex[] visualvertices; #endregion #region ================== Properties public ProjectedFrustum2D Frustum2D { get { return frustum; } } public bool DrawThingCages { get { return renderthingcages; } set { renderthingcages = value; } } public bool FullBrightness { get { return fullbrightness; } set { fullbrightness = value; } } public bool ShowSelection { get { return showselection; } set { showselection = value; } } public bool ShowHighlight { get { return showhighlight; } set { showhighlight = value; } } #endregion #region ================== Constructor / Disposer // Constructor internal Renderer3D(D3DDevice graphics) : base(graphics) { // Initialize CreateProjection(); CreateMatrices2D(); SetupHelperObjects(); SetupTextures(); renderthingcages = true; showselection = true; showhighlight = true; // Dummy frustum frustum = new ProjectedFrustum2D(new Vector2D(), 0.0f, 0.0f, PROJ_NEAR_PLANE, General.Settings.ViewDistance, Angle2D.DegToRad((float)General.Settings.VisualFOV)); // We have no destructor GC.SuppressFinalize(this); } // Disposer internal override void Dispose() { // Not already disposed? if(!isdisposed) { // Clean up //mxd if(bbox != null) bbox.Dispose(); if(vertexHandle != null) vertexHandle.Dispose(); if(sizelessThingHandle != null) sizelessThingHandle.Dispose(); if(selectionimage != null) selectionimage.Dispose(); if(highlightimage != null) highlightimage.Dispose(); //mxd bbox = null; vertexHandle = null; sizelessThingHandle = null; selectionimage = null; highlightimage = null; // Done base.Dispose(); } } #endregion #region ================== Management // This is called before a device is reset // (when resized or display adapter was changed) public override void UnloadResource() { crosshairverts = null; //mxd if(bbox != null) bbox.Dispose(); if(vertexHandle != null) vertexHandle.Dispose(); if(sizelessThingHandle != null) sizelessThingHandle.Dispose(); if(selectionimage != null) selectionimage.Dispose(); if(highlightimage != null) highlightimage.Dispose(); //mxd vertexHandle = null; bbox = null; sizelessThingHandle = null; selectionimage = null; highlightimage = null; } // This is called resets when the device is reset // (when resized or display adapter was changed) public override void ReloadResource() { CreateMatrices2D(); SetupHelperObjects(); SetupTextures(); } // This makes screen vertices for display private void CreateCrosshairVerts(Size texturesize) { // Determine coordinates float width = (float)windowsize.Width; float height = (float)windowsize.Height; float size = (float)height * CROSSHAIR_SCALE; RectangleF rect = new RectangleF((width - size) / 2, (height - size) / 2, size, size); // Make vertices crosshairverts = new FlatVertex[4]; crosshairverts[0].x = rect.Left; crosshairverts[0].y = rect.Top; crosshairverts[0].c = -1; crosshairverts[0].u = 1f / texturesize.Width; crosshairverts[0].v = 1f / texturesize.Height; crosshairverts[1].x = rect.Right; crosshairverts[1].y = rect.Top; crosshairverts[1].c = -1; crosshairverts[1].u = 1f - 1f / texturesize.Width; crosshairverts[1].v = 1f / texturesize.Height; crosshairverts[2].x = rect.Left; crosshairverts[2].y = rect.Bottom; crosshairverts[2].c = -1; crosshairverts[2].u = 1f / texturesize.Width; crosshairverts[2].v = 1f - 1f / texturesize.Height; crosshairverts[3].x = rect.Right; crosshairverts[3].y = rect.Bottom; crosshairverts[3].c = -1; crosshairverts[3].u = 1f - 1f / texturesize.Width; crosshairverts[3].v = 1f - 1f / texturesize.Height; } #endregion #region ================== Resources // This loads the textures for highlight and selection if we need them private void SetupTextures() { if(!graphics.Shaders.Enabled) { highlightimage = new ColorImage(General.Colors.Highlight, 32, 32); highlightimage.LoadImage(); highlightimage.CreateTexture(); selectionimage = new ColorImage(General.Colors.Selection, 32, 32); selectionimage.LoadImage(); selectionimage.CreateTexture(); } } //mxd private void SetupHelperObjects() { bbox = new ThingBoundingBox(graphics.Device); vertexHandle = new VisualVertexHandle(graphics.Device); sizelessThingHandle = new SizelessVisualThingCage(graphics.Device); } #endregion #region ================== Presentation // This creates the projection internal void CreateProjection() { // Calculate aspect float aspect = (float)General.Map.Graphics.RenderTarget.ClientSize.Width / (float)General.Map.Graphics.RenderTarget.ClientSize.Height; // The DirectX PerspectiveFovRH matrix method calculates the scaling in X and Y as follows: // yscale = 1 / tan(fovY / 2) // xscale = yscale / aspect // The fov specified in the method is the FOV over Y, but we want the user to specify the FOV // over X, so calculate what it would be over Y first; float fov = Angle2D.DegToRad((float)General.Settings.VisualFOV); float reversefov = 1.0f / (float)Math.Tan(fov / 2.0f); float reversefovy = reversefov * aspect; float fovy = (float)Math.Atan(1.0f / reversefovy) * 2.0f; // Make the projection matrix projection = Matrix.PerspectiveFovRH(fovy, aspect, PROJ_NEAR_PLANE, General.Settings.ViewDistance); // Apply matrices ApplyMatrices3D(); } // This creates matrices for a camera view public void PositionAndLookAt(Vector3D pos, Vector3D lookat) { Vector3D delta; float anglexy, anglez; // Calculate delta vector cameraposition = pos; delta = lookat - pos; anglexy = delta.GetAngleXY(); anglez = delta.GetAngleZ(); // Create frustum frustum = new ProjectedFrustum2D(pos, anglexy, anglez, PROJ_NEAR_PLANE, General.Settings.ViewDistance, Angle2D.DegToRad((float)General.Settings.VisualFOV)); // Make the view matrix view3d = Matrix.LookAtRH(D3DDevice.V3(pos), D3DDevice.V3(lookat), new Vector3(0f, 0f, 1f)); // Make the billboard matrix billboard = Matrix.RotationZ(anglexy + Angle2D.PI); } // This creates 2D view matrix private void CreateMatrices2D() { windowsize = graphics.RenderTarget.ClientSize; Matrix scaling = Matrix.Scaling((1f / (float)windowsize.Width) * 2f, (1f / (float)windowsize.Height) * -2f, 1f); Matrix translate = Matrix.Translation(-(float)windowsize.Width * 0.5f, -(float)windowsize.Height * 0.5f, 0f); view2d = Matrix.Multiply(translate, scaling); } // This applies the matrices private void ApplyMatrices3D() { worldviewproj = world * view3d * projection; graphics.Shaders.World3D.WorldViewProj = worldviewproj; graphics.Device.SetTransform(TransformState.World, world); graphics.Device.SetTransform(TransformState.Projection, projection); graphics.Device.SetTransform(TransformState.View, view3d); } // This sets the appropriate view matrix public void ApplyMatrices2D() { graphics.Device.SetTransform(TransformState.World, world); graphics.Device.SetTransform(TransformState.Projection, Matrix.Identity); graphics.Device.SetTransform(TransformState.View, view2d); } #endregion #region ================== Start / Finish // This starts rendering public bool Start() { // Start drawing if(graphics.StartRendering(true, General.Colors.Background.ToColorValue(), graphics.BackBuffer, graphics.DepthBuffer)) { // Beginning renderstates graphics.Device.SetRenderState(RenderState.CullMode, Cull.None); graphics.Device.SetRenderState(RenderState.ZEnable, false); graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, false); graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false); graphics.Device.SetRenderState(RenderState.SourceBlend, Blend.SourceAlpha); graphics.Device.SetRenderState(RenderState.DestinationBlend, Blend.InverseSourceAlpha); graphics.Device.SetRenderState(RenderState.FogEnable, false); graphics.Device.SetRenderState(RenderState.FogDensity, 1.0f); graphics.Device.SetRenderState(RenderState.FogColor, General.Colors.Background.ToInt()); graphics.Device.SetRenderState(RenderState.FogStart, General.Settings.ViewDistance * FOG_RANGE); graphics.Device.SetRenderState(RenderState.FogEnd, General.Settings.ViewDistance); graphics.Device.SetRenderState(RenderState.FogTableMode, FogMode.Linear); graphics.Device.SetRenderState(RenderState.RangeFogEnable, false); graphics.Device.SetRenderState(RenderState.TextureFactor, -1); graphics.Shaders.World3D.SetModulateColor(-1); graphics.Shaders.World3D.SetHighlightColor(0); // Texture addressing graphics.Device.SetSamplerState(0, SamplerState.AddressU, TextureAddress.Wrap); graphics.Device.SetSamplerState(0, SamplerState.AddressV, TextureAddress.Wrap); graphics.Device.SetSamplerState(0, SamplerState.AddressW, TextureAddress.Wrap); // Matrices world = Matrix.Identity; ApplyMatrices3D(); // Highlight if(General.Settings.AnimateVisualSelection) { float time = General.Clock.CurrentTime; highlightglow = (float)Math.Sin(time / 100.0f) * 0.1f + 0.4f; //mxd. WHY?! //highlightglowinv = -(float)Math.Sin(time / 100.0f) * 0.1f + 0.4f; highlightglowinv = -highlightglow + 0.8f; } else { highlightglow = 0.4f; highlightglowinv = 0.3f; } // Determine shader pass to use if(fullbrightness) shaderpass = 1; else shaderpass = 0; // Create crosshair vertices if(crosshairverts == null) CreateCrosshairVerts(new Size(General.Map.Data.Crosshair3D.Width, General.Map.Data.Crosshair3D.Height)); // Ready return true; } else { // Can't render now return false; } } // This begins rendering world geometry public void StartGeometry() { // Make collection geometry = new Dictionary>[RENDER_PASSES]; things = new Dictionary>[RENDER_PASSES]; thingsbydistance = new BinaryHeap(); //mxd thingsWithModel = new Dictionary>(); litGeometry = new Dictionary>(); for(int i = 0; i < RENDER_PASSES; i++) { geometry[i] = new Dictionary>(); things[i] = new Dictionary>(); } //mxd if (General.Settings.GZDrawLights) { thingsWithLight = new List(); } } // This ends rendering world geometry public void FinishGeometry() { //mxd. sort lights if (General.Settings.GZDrawLights && !fullbrightness && thingsWithLight.Count > 0) updateLights(); // Initial renderstates graphics.Device.SetRenderState(RenderState.CullMode, Cull.Counterclockwise); graphics.Device.SetRenderState(RenderState.ZEnable, true); graphics.Device.SetRenderState(RenderState.ZWriteEnable, true); graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, false); graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false); graphics.Device.SetRenderState(RenderState.TextureFactor, -1); graphics.Shaders.World3D.Begin(); // SOLID PASS world = Matrix.Identity; ApplyMatrices3D(); RenderSinglePass((int)RenderPass.Solid); //mxd. Render models, without culling. The way it's done in GZDoom... Fixes Rendering of things like grass and models with negative Scale graphics.Device.SetRenderState(RenderState.AlphaTestEnable, true); graphics.Device.SetRenderState(RenderState.CullMode, Cull.None); RenderModels(); graphics.Device.SetRenderState(RenderState.CullMode, Cull.Counterclockwise); // MASK PASS world = Matrix.Identity; ApplyMatrices3D(); RenderSinglePass((int)RenderPass.Mask); // ALPHA PASS world = Matrix.Identity; ApplyMatrices3D(); graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, true); graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false); graphics.Device.SetRenderState(RenderState.ZWriteEnable, false); graphics.Device.SetRenderState(RenderState.SourceBlend, Blend.SourceAlpha); graphics.Device.SetRenderState(RenderState.DestinationBlend, Blend.InverseSourceAlpha); RenderSinglePass((int)RenderPass.Alpha); // THINGS if(renderthingcages) RenderThingCages(); //mxd. Visual vertices RenderVertices(); //mxd. LINKS if (General.Settings.GZShowEventLines) { //mxd. gather links List lines = GZBuilder.Data.LinksCollector.GetThingLinks(thingsbydistance); if(lines.Count > 0) { List normalLines = new List(); List activatorLines = new List(); foreach(Line3D l in lines){ if(l.LineType == Line3DType.DEFAULT) normalLines.Add(l); else activatorLines.Add(l); } if(normalLines.Count > 0) renderArrows(normalLines, General.Colors.InfoLine.ToColorValue()); if(activatorLines.Count > 0) renderArrows(activatorLines, General.Colors.Selection3D.ToColorValue()); } } // ADDITIVE PASS world = Matrix.Identity; ApplyMatrices3D(); graphics.Device.SetRenderState(RenderState.DestinationBlend, Blend.One); RenderSinglePass((int)RenderPass.Additive); //mxd. LIGHT PASS if (General.Settings.GZDrawLights && !fullbrightness && thingsWithLight.Count > 0 && litGeometry.Count > 0) { RenderLights(litGeometry, thingsWithLight); } // Remove references graphics.Shaders.World3D.Texture1 = null; // Done graphics.Shaders.World3D.End(); geometry = null; visualvertices = null;//mxd } //mxd private void updateLights() { thingsWithLight.Sort(sortThingsByCameraDistance); if (thingsWithLight.Count > General.Settings.GZMaxDynamicLights) { List tl = new List(); for (int i = 0; i < General.Settings.GZMaxDynamicLights; i++) tl.Add(thingsWithLight[i]); thingsWithLight = tl; } thingsWithLight.Sort(sortThingsByLightRenderStyle); lightOffsets = new int[3]; foreach (VisualThing t in thingsWithLight) { //add light to apropriate array. if (t.LightRenderStyle == (int)GZDoomLightRenderStyle.NORMAL || t.LightRenderStyle == (int)GZDoomLightRenderStyle.VAVOOM) lightOffsets[0]++; else if (t.LightRenderStyle == (int)GZDoomLightRenderStyle.ADDITIVE) lightOffsets[1]++; else lightOffsets[2]++; } } //mxd private int sortThingsByCameraDistance(VisualThing t1, VisualThing t2) { if (t1.CameraDistance3D == t2.CameraDistance3D) return 0; if (t1.CameraDistance3D > t2.CameraDistance3D) return 1; return -1; } //mxd private int sortThingsByLightRenderStyle(VisualThing t1, VisualThing t2) { if (t1.LightRenderStyle == t2.LightRenderStyle) return 0; if (t1.LightRenderStyle > t2.LightRenderStyle) return 1; return -1; } //mxd. //I never particularly liked old ThingCages, so I wrote this instead. //It should render faster and it has fancy arrow! :) private void RenderThingCages() { graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, true); graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false); graphics.Device.SetRenderState(RenderState.ZWriteEnable, false); graphics.Device.SetRenderState(RenderState.SourceBlend, Blend.SourceAlpha); graphics.Device.SetRenderState(RenderState.DestinationBlend, Blend.SourceAlpha); graphics.Shaders.World3D.BeginPass(16); foreach (VisualThing t in thingsbydistance) { // Setup matrix world = Matrix.Multiply(t.CageScales, t.Position); ApplyMatrices3D(); // Setup color Color4 thingColor = new Color4(); if (t.Selected && showselection) { thingColor = General.Colors.Selection3D.ToColorValue(); } else { thingColor = t.Thing.Color.ToColorValue(); if (t != highlighted) thingColor.Alpha = 0.6f; } graphics.Shaders.World3D.VertexColor = thingColor; //Render cage graphics.Shaders.World3D.ApplySettings(); if(t.Sizeless) { graphics.Device.SetStreamSource(0, sizelessThingHandle.Shape, 0, WorldVertex.Stride); graphics.Device.DrawPrimitives(PrimitiveType.LineList, 0, 3); } else { graphics.Device.SetStreamSource(0, bbox.Cage, 0, WorldVertex.Stride); graphics.Device.DrawPrimitives(PrimitiveType.LineList, 0, 12); } //and arrow float sx = t.CageScales.M11; Matrix arrowScaler = Matrix.Scaling(sx, sx, sx); //scale arrow evenly based on thing width\depth if(t.Sizeless) { world = Matrix.Multiply(arrowScaler, t.Position); } else { world = Matrix.Multiply(arrowScaler, t.Position * Matrix.Translation(0.0f, 0.0f, t.CageScales.M33 / 2)); } Matrix rot = Matrix.RotationZ(t.Thing.Angle - Angle2D.PI / 2); world = Matrix.Multiply(rot, world); ApplyMatrices3D(); graphics.Shaders.World3D.ApplySettings(); graphics.Device.SetStreamSource(0, bbox.Arrow, 0, WorldVertex.Stride); graphics.Device.DrawPrimitives(PrimitiveType.LineList, 0, 5); } // Done graphics.Shaders.World3D.EndPass(); graphics.Shaders.World3D.SetModulateColor(-1); graphics.Device.SetRenderState(RenderState.TextureFactor, -1); } //mxd private void RenderVertices() { if(visualvertices == null) return; graphics.Shaders.World3D.BeginPass(16); foreach(VisualVertex v in visualvertices) { world = v.Position; ApplyMatrices3D(); // Setup color Color4 color = new Color4(); if(v.Selected && showselection) { color = General.Colors.Selection3D.ToColorValue(); } else { color = General.Colors.InfoLine.ToColorValue(); if(v != highlighted) color.Alpha = 0.6f; } graphics.Shaders.World3D.VertexColor = color; //Commence drawing!!11 graphics.Shaders.World3D.ApplySettings(); graphics.Device.SetStreamSource(0, v.CeilingVertex ? vertexHandle.Upper : vertexHandle.Lower, 0, WorldVertex.Stride); graphics.Device.DrawPrimitives(PrimitiveType.LineList, 0, 16); } // Done graphics.Shaders.World3D.EndPass(); } //mxd private void renderArrows(List lines, Color4 color) { //create vertices WorldVertex[] verts = new WorldVertex[lines.Count * 6]; float scaler = 20f; for (int i = 0; i < lines.Count; i++) { WorldVertex endPoint = new WorldVertex(lines[i].v2); float nz = lines[i].GetDelta().GetNormal().z * scaler; float angle = lines[i].GetAngle(); verts[i * 6] = new WorldVertex(lines[i].v1); verts[i * 6 + 1] = endPoint; verts[i * 6 + 2] = endPoint; verts[i * 6 + 3] = new WorldVertex(new Vector3D(lines[i].v2.x - scaler * (float)Math.Sin(angle - 0.46f), lines[i].v2.y + scaler * (float)Math.Cos(angle - 0.46f), lines[i].v2.z - nz)); verts[i * 6 + 4] = endPoint; verts[i * 6 + 5] = new WorldVertex(new Vector3D(lines[i].v2.x - scaler * (float)Math.Sin(angle + 0.46f), lines[i].v2.y + scaler * (float)Math.Cos(angle + 0.46f), lines[i].v2.z - nz)); } VertexBuffer vb = new VertexBuffer(General.Map.Graphics.Device, WorldVertex.Stride * verts.Length, Usage.WriteOnly | Usage.Dynamic, VertexFormat.None, Pool.Default); DataStream s = vb.Lock(0, WorldVertex.Stride * verts.Length, LockFlags.Discard); s.WriteRange(verts); vb.Unlock(); s.Dispose(); //begin rendering graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, true); graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false); graphics.Device.SetRenderState(RenderState.ZWriteEnable, false); graphics.Device.SetRenderState(RenderState.SourceBlend, Blend.SourceAlpha); graphics.Device.SetRenderState(RenderState.DestinationBlend, Blend.SourceAlpha); graphics.Shaders.World3D.BeginPass(16); world = Matrix.Identity; ApplyMatrices3D(); // Setup color graphics.Shaders.World3D.VertexColor = color; //render graphics.Shaders.World3D.ApplySettings(); graphics.Device.SetStreamSource(0, vb, 0, WorldVertex.Stride); graphics.Device.DrawPrimitives(PrimitiveType.LineList, 0, lines.Count * 3); // Done graphics.Shaders.World3D.EndPass(); graphics.Shaders.World3D.SetModulateColor(-1); graphics.Device.SetRenderState(RenderState.TextureFactor, -1); vb.Dispose(); } // This performs a single render pass private void RenderSinglePass(int pass) { Color4 fogColor; int currentshaderpass = shaderpass; int highshaderpass = shaderpass + 2; // Get geometry for this pass Dictionary> geopass = geometry[pass]; // Begin rendering with this shader graphics.Shaders.World3D.BeginPass(shaderpass); // Render the geometry collected foreach(KeyValuePair> group in geopass) { ImageData curtexture; // What texture to use? if(group.Key is UnknownImage) curtexture = General.Map.Data.UnknownTexture3D; else if((group.Key != null) && group.Key.IsImageLoaded && !group.Key.IsDisposed) curtexture = group.Key; else curtexture = General.Map.Data.Hourglass3D; // Create Direct3D texture if still needed if((curtexture.Texture == null) || curtexture.Texture.Disposed) curtexture.CreateTexture(); // Apply texture if(!graphics.Shaders.Enabled) graphics.Device.SetTexture(0, curtexture.Texture); graphics.Shaders.World3D.Texture1 = curtexture.Texture; // Go for all geometry that uses this texture VisualSector sector = null; foreach(VisualGeometry g in group.Value) { // Changing sector? if(!object.ReferenceEquals(g.Sector, sector)) { // Update the sector if needed if(g.Sector.NeedsUpdateGeo) g.Sector.Update(); // Only do this sector when a vertexbuffer is created //mxd. no Map means that sector was deleted recently, I suppose if (g.Sector.GeometryBuffer != null && g.Sector.Sector.Map != null) { // Change current sector sector = g.Sector; // Set stream source graphics.Device.SetStreamSource(0, sector.GeometryBuffer, 0, WorldVertex.Stride); } else { sector = null; } } if (sector != null) { // Determine the shader pass we want to use for this object int wantedshaderpass = (((g == highlighted) && showhighlight) || (g.Selected && showselection)) ? highshaderpass : shaderpass; //mxd if (General.Settings.GZDrawFog && !fullbrightness && sector.Sector.Brightness < 248) wantedshaderpass += 8; //mxd. Seems that lines rendered with RenderPass.Alpha or RenderPass.Additive aren't affected by dynamic lights in GZDoom if (g.RenderPass != RenderPass.Alpha && g.RenderPass != RenderPass.Additive && General.Settings.GZDrawLights && !fullbrightness && thingsWithLight.Count > 0) { if (curtexture.Texture != null) { if (!litGeometry.ContainsKey(curtexture.Texture)) litGeometry[curtexture.Texture] = new List(); litGeometry[curtexture.Texture].Add(g); } } // Switch shader pass? if(currentshaderpass != wantedshaderpass) { graphics.Shaders.World3D.EndPass(); graphics.Shaders.World3D.BeginPass(wantedshaderpass); currentshaderpass = wantedshaderpass; } // Set the colors to use if(!graphics.Shaders.Enabled) { graphics.Device.SetTexture(2, (g.Selected && showselection) ? selectionimage.Texture : null); graphics.Device.SetTexture(3, ((g == highlighted) && showhighlight) ? highlightimage.Texture : null); } else { //mxd. set variables for fog rendering if (wantedshaderpass > 7) { graphics.Shaders.World3D.World = world; bool sectorHasFogColor = getFogColor(sector.Sector, out fogColor); graphics.Shaders.World3D.LightColor = fogColor; graphics.Shaders.World3D.CameraPosition = new Vector4(cameraposition.x, cameraposition.y, cameraposition.z, getFogEnd(sector.Sector, sectorHasFogColor)); } graphics.Shaders.World3D.SetHighlightColor(CalculateHighlightColor((g == highlighted) && showhighlight, (g.Selected && showselection)).ToArgb()); graphics.Shaders.World3D.ApplySettings(); } // Render! graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles); } } } // Get things for this pass Dictionary> thingspass = things[pass]; if(thingspass.Count > 0) { // Texture addressing graphics.Device.SetSamplerState(0, SamplerState.AddressU, TextureAddress.Clamp); graphics.Device.SetSamplerState(0, SamplerState.AddressV, TextureAddress.Clamp); graphics.Device.SetSamplerState(0, SamplerState.AddressW, TextureAddress.Clamp); // Render things collected foreach(KeyValuePair> group in thingspass) { ImageData curtexture; if(!(group.Key is UnknownImage)) { // What texture to use? if((group.Key != null) && group.Key.IsImageLoaded && !group.Key.IsDisposed) curtexture = group.Key; else curtexture = General.Map.Data.Hourglass3D; // Create Direct3D texture if still needed if((curtexture.Texture == null) || curtexture.Texture.Disposed) curtexture.CreateTexture(); // Apply texture if(!graphics.Shaders.Enabled) graphics.Device.SetTexture(0, curtexture.Texture); graphics.Shaders.World3D.Texture1 = curtexture.Texture; // Render all things with this texture foreach(VisualThing t in group.Value) { //mxd if (General.Settings.GZDrawModels && (!General.Settings.GZDrawSelectedModelsOnly || t.Selected) && t.Thing.IsModel) continue; // Update buffer if needed t.Update(); // Only do this sector when a vertexbuffer is created if (t.GeometryBuffer != null) { // Determine the shader pass we want to use for this object int wantedshaderpass = (((t == highlighted) && showhighlight) || (t.Selected && showselection)) ? highshaderpass : shaderpass; //mxd. if fog is enagled, switch to shader, which calculates it if (General.Settings.GZDrawFog && !fullbrightness && t.Thing.Sector != null && t.Thing.Sector.Brightness < 248) wantedshaderpass += 8; //mxd. if current thing is light - set it's color to light color if (Array.IndexOf(GZBuilder.GZGeneral.GZ_LIGHTS, t.Thing.Type) != -1 && !fullbrightness) { wantedshaderpass += 4; //render using one of passes, which uses World3D.VertexColor graphics.Shaders.World3D.VertexColor = t.LightColor; //mxd. check if Thing is affected by dynamic lights and set color accordingly } else if (General.Settings.GZDrawLights && !fullbrightness && thingsWithLight.Count > 0) { Color4 litColor = getLitColorForThing(t); if (litColor.ToArgb() != 0) { wantedshaderpass += 4; //render using one of passes, which uses World3D.VertexColor graphics.Shaders.World3D.VertexColor = new Color4(t.VertexColor) + litColor; } } // Switch shader pass? if (currentshaderpass != wantedshaderpass) { graphics.Shaders.World3D.EndPass(); graphics.Shaders.World3D.BeginPass(wantedshaderpass); currentshaderpass = wantedshaderpass; } // Set the colors to use if (!graphics.Shaders.Enabled) { graphics.Device.SetTexture(2, (t.Selected && showselection) ? selectionimage.Texture : null); graphics.Device.SetTexture(3, ((t == highlighted) && showhighlight) ? highlightimage.Texture : null); } else { graphics.Shaders.World3D.SetHighlightColor(CalculateHighlightColor((t == highlighted) && showhighlight, (t.Selected && showselection)).ToArgb()); } // Create the matrix for positioning / rotation world = t.Orientation; if (t.Billboard) world = Matrix.Multiply(world, billboard); world = Matrix.Multiply(world, t.Position); ApplyMatrices3D(); //mxd. set variables for fog rendering if (wantedshaderpass > 7) { graphics.Shaders.World3D.World = world; bool sectorHasFogColor = getFogColor(t.Thing.Sector, out fogColor); graphics.Shaders.World3D.LightColor = fogColor; graphics.Shaders.World3D.CameraPosition = new Vector4(cameraposition.x, cameraposition.y, cameraposition.z, getFogEnd(t.Thing.Sector, sectorHasFogColor)); } graphics.Shaders.World3D.ApplySettings(); // Apply buffer graphics.Device.SetStreamSource(0, t.GeometryBuffer, 0, WorldVertex.Stride); // Render! graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, 0, t.Triangles); } } } } // Texture addressing graphics.Device.SetSamplerState(0, SamplerState.AddressU, TextureAddress.Wrap); graphics.Device.SetSamplerState(0, SamplerState.AddressV, TextureAddress.Wrap); graphics.Device.SetSamplerState(0, SamplerState.AddressW, TextureAddress.Wrap); } // Done rendering with this shader graphics.Shaders.World3D.EndPass(); } //mxd. Dynamic lights pass! private void RenderLights(Dictionary> geometry_to_lit, List lights) { graphics.Shaders.World3D.World = Matrix.Identity; graphics.Shaders.World3D.BeginPass(17); int i, count; Vector4 lpr; graphics.Device.SetRenderState(RenderState.SourceBlend, Blend.One); graphics.Device.SetRenderState(RenderState.DestinationBlend, Blend.BlendFactor); foreach (KeyValuePair> group in geometry_to_lit) { graphics.Shaders.World3D.Texture1 = group.Key; foreach (VisualGeometry g in group.Value) { i = 0; count = 0; graphics.Device.SetStreamSource(0, g.Sector.GeometryBuffer, 0, WorldVertex.Stride); //normal lights count = lightOffsets[0]; if (lightOffsets[0] > 0) { graphics.Device.SetRenderState(RenderState.BlendOperation, BlendOperation.Add); for (i = 0; i < count; i++) { if (checkBBoxIntersection(g.BoundingBox, lights[i].BoundingBox)) { lpr = new Vector4(lights[i].Center, lights[i].LightRadius); if (lpr.W == 0) continue; graphics.Shaders.World3D.LightColor = lights[i].LightColor; graphics.Shaders.World3D.LightPositionAndRadius = lpr; graphics.Shaders.World3D.ApplySettings(); graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles); } } } //additive lights. if (lightOffsets[1] > 0) { count += lightOffsets[1]; graphics.Device.SetRenderState(RenderState.BlendOperation, BlendOperation.Add); for (i = lightOffsets[0]; i < count; i++) { if (checkBBoxIntersection(g.BoundingBox, lights[i].BoundingBox)) { lpr = new Vector4(lights[i].Center, lights[i].LightRadius); if (lpr.W == 0) continue; graphics.Shaders.World3D.LightColor = lights[i].LightColor; graphics.Shaders.World3D.LightPositionAndRadius = lpr; graphics.Shaders.World3D.ApplySettings(); graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles); } } } //negative lights if (lightOffsets[2] > 0) { count += lightOffsets[2]; graphics.Device.SetRenderState(RenderState.BlendOperation, BlendOperation.ReverseSubtract); for (i = lightOffsets[0] + lightOffsets[1]; i < count; i++) { if (checkBBoxIntersection(g.BoundingBox, lights[i].BoundingBox)) { lpr = new Vector4(lights[i].Center, lights[i].LightRadius); if (lpr.W == 0) continue; Color4 lc = lights[i].LightColor; graphics.Shaders.World3D.LightColor = new Color4(lc.Alpha, (lc.Green + lc.Blue) / 2, (lc.Red + lc.Blue) / 2, (lc.Green + lc.Red) / 2); graphics.Shaders.World3D.LightPositionAndRadius = lpr; graphics.Shaders.World3D.ApplySettings(); graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles); } } } } } graphics.Shaders.World3D.EndPass(); graphics.Device.SetRenderState(RenderState.BlendOperation, BlendOperation.Add); } //mxd. render models private void RenderModels() { Color4 fogColor; int shaderpass = fullbrightness ? 1 : 4; int currentshaderpass = shaderpass; int highshaderpass = shaderpass + 2; // Begin rendering with this shader graphics.Shaders.World3D.BeginPass(currentshaderpass); foreach (KeyValuePair> group in thingsWithModel) { foreach (VisualThing t in group.Value) { t.Update(); Color4 vertexColor = new Color4(t.VertexColor); vertexColor.Alpha = 1.0f; //check if model is affected by dynamic lights and set color accordingly if (General.Settings.GZDrawLights && !fullbrightness && thingsWithLight.Count > 0) { Color4 litColor = getLitColorForThing(t); graphics.Shaders.World3D.VertexColor = vertexColor + litColor; } else { graphics.Shaders.World3D.VertexColor = vertexColor; } // Determine the shader pass we want to use for this object int wantedshaderpass = ((((t == highlighted) && showhighlight) || (t.Selected && showselection)) ? highshaderpass : shaderpass); //mxd. if fog is enagled, switch to shader, which calculates it if (General.Settings.GZDrawFog && !fullbrightness && t.Thing.Sector != null && t.Thing.Sector.Brightness < 248) wantedshaderpass += 8; // Switch shader pass? if (currentshaderpass != wantedshaderpass) { graphics.Shaders.World3D.EndPass(); graphics.Shaders.World3D.BeginPass(wantedshaderpass); currentshaderpass = wantedshaderpass; } // Set the colors to use if (!graphics.Shaders.Enabled) { graphics.Device.SetTexture(2, (t.Selected && showselection) ? selectionimage.Texture : null); graphics.Device.SetTexture(3, ((t == highlighted) && showhighlight) ? highlightimage.Texture : null); } else { graphics.Shaders.World3D.SetHighlightColor(CalculateHighlightColor((t == highlighted) && showhighlight, (t.Selected && showselection)).ToArgb()); } // Create the matrix for positioning / rotation world = Matrix.Multiply(t.Orientation, Matrix.RotationZ(t.Thing.Angle)); world = Matrix.Multiply(world, t.Position); ApplyMatrices3D(); //mxd. set variables for fog rendering if (wantedshaderpass > 7) { graphics.Shaders.World3D.World = world; bool sectorHasFogColor = getFogColor(t.Thing.Sector, out fogColor); graphics.Shaders.World3D.LightColor = fogColor; graphics.Shaders.World3D.CameraPosition = new Vector4(cameraposition.x, cameraposition.y, cameraposition.z, getFogEnd(t.Thing.Sector, sectorHasFogColor)); } for(int i = 0; i < group.Key.Model.Meshes.Count; i++) { if (!graphics.Shaders.Enabled) graphics.Device.SetTexture(0, group.Key.Model.Textures[i]); graphics.Shaders.World3D.Texture1 = group.Key.Model.Textures[i]; graphics.Shaders.World3D.ApplySettings(); // Render! group.Key.Model.Meshes[i].DrawSubset(0); } } } graphics.Shaders.World3D.EndPass(); } //mxd. This gets color from dynamic lights based on distance to thing. //thing position must be in absolute cordinates //(thing.Position.Z value is relative to floor of the sector the thing is in) private Color4 getLitColorForThing(VisualThing t) { Color4 litColor = new Color4(); float radius, radiusSquared, distSquared, scaler; int sign; for (int i = 0; i < thingsWithLight.Count; i++ ) { //don't light self if (General.Map.Data.GldefsEntries.ContainsKey(t.Thing.Type) && General.Map.Data.GldefsEntries[t.Thing.Type].DontLightSelf && t.Thing.Index == thingsWithLight[i].Thing.Index) { continue; } distSquared = Vector3.DistanceSquared(thingsWithLight[i].Center, t.PositionV3); radius = thingsWithLight[i].LightRadius; radiusSquared = radius * radius; if (distSquared < radiusSquared) { sign = thingsWithLight[i].LightRenderStyle == (int)GZDoomLightRenderStyle.NEGATIVE ? -1 : 1; scaler = 1 - distSquared / radiusSquared * thingsWithLight[i].LightColor.Alpha; litColor.Red += thingsWithLight[i].LightColor.Red * scaler * sign; litColor.Green += thingsWithLight[i].LightColor.Green * scaler * sign; litColor.Blue += thingsWithLight[i].LightColor.Blue * scaler * sign; } } return litColor; } //mxd. This returns distance, at which fog color completely replaces texture color for given sector private float getFogEnd(Sector s, bool sectorHasFogColor) { float brightness = (float)Math.Max(30, s.Brightness); if (sectorHasFogColor) { return brightness * 11.0f; } return (float)Math.Pow(2.0f, brightness / 11.0f); } //mxd. returns true if sector has fog color private bool getFogColor(Sector sector, out Color4 color) { if (General.Map.UDMF && sector.Fields.ContainsKey("fadecolor")) { color = new Color4((int)sector.Fields["fadecolor"].Value); return true; } else if (General.Map.Data.MapInfo.HasOutsideFogColor && sector.CeilTexture == General.Map.Config.SkyFlatName) { color = General.Map.Data.MapInfo.OutsideFogColor; return true; } else if (General.Map.Data.MapInfo.HasFadeColor) { color = General.Map.Data.MapInfo.FadeColor; return true; } color = new Color4(); //black return false; } // This calculates the highlight/selection color public Color4 CalculateHighlightColor(bool ishighlighted, bool isselected) { Color4 highlightcolor = isselected ? General.Colors.Selection.ToColorValue() : General.Colors.Highlight.ToColorValue(); highlightcolor.Alpha = ishighlighted ? highlightglowinv : highlightglow; return highlightcolor; } // This finishes rendering public void Finish() { General.Plugins.OnPresentDisplayBegin(); // Done graphics.FinishRendering(); graphics.Present(); highlighted = null; } #endregion #region ================== Rendering // This sets the highlighted object for the rendering public void SetHighlightedObject(IVisualPickable obj) { highlighted = obj; } // This collects a visual sector's geometry for rendering public void AddSectorGeometry(VisualGeometry g) { // Must have a texture and vertices if((g.Texture != null) && (g.Triangles > 0)) { // Texture group not yet collected? if(!geometry[g.RenderPassInt].ContainsKey(g.Texture)) { // Create texture group geometry[g.RenderPassInt].Add(g.Texture, new BinaryHeap()); } // Add geometry to texture group geometry[g.RenderPassInt][g.Texture].Add(g); } } // This collects a visual sector's geometry for rendering public void AddThingGeometry(VisualThing t) { //mxd. gater lights if (General.Settings.GZDrawLights && !fullbrightness && t.LightType != -1) { t.UpdateLightRadius(); if (t.LightRadius > 0) { t.CalculateCameraDistance3D(D3DDevice.V3(cameraposition)); //t.CameraDistance3D is actually squared distance, hence (t.LightRadius * t.LightRadius) if (t.CameraDistance3D < (t.LightRadius * t.LightRadius) || isThingOnScreen(t.BoundingBox)) { //always render light if camera is within it's radius if (Array.IndexOf(GZBuilder.GZGeneral.GZ_ANIMATED_LIGHT_TYPES, t.LightType) != -1) t.UpdateBoundingBox(); thingsWithLight.Add(t); } } } if (!isThingOnScreen(t.BoundingBox)) { return; } //mxd. gather models if (General.Settings.GZDrawModels && (!General.Settings.GZDrawSelectedModelsOnly || t.Selected) && t.Thing.IsModel) { ModeldefEntry mde = General.Map.Data.ModeldefEntries[t.Thing.Type]; if (!thingsWithModel.ContainsKey(mde)) thingsWithModel.Add(mde, new List()); thingsWithModel[mde].Add(t); } // Make sure the distance to camera is calculated t.CalculateCameraDistance(cameraposition); thingsbydistance.Add(t); // Must have a texture! if(t.Texture != null) { // Texture group not yet collected? if(!things[t.RenderPassInt].ContainsKey(t.Texture)) { // Create texture group things[t.RenderPassInt].Add(t.Texture, new List()); } // Add geometry to texture group things[t.RenderPassInt][t.Texture].Add(t); } } //mxd public void AddVisualVertices(VisualVertex[] verts) { visualvertices = verts; } //mxd private bool isThingOnScreen(Vector3[] bbox) { Vector3D camNormal = Vector3D.FromAngleXYZ(General.Map.VisualCamera.AngleXY, General.Map.VisualCamera.AngleZ); Vector3D thingNormal = D3DDevice.V3D(bbox[0]) - cameraposition; //bbox[0] is always thing center if (Vector3D.DotProduct(camNormal, thingNormal) < 0) return false; //behind camera plane int len = bbox.Length; Vector3 screenPos; int behindCount = 0; int leftCount = 0; int rightCount = 0; int topCount = 0; int bottomCount = 0; for (int i = 0; i < len; i++) { //check visibility screenPos = Vector3.Project(bbox[i], 0, 0, 1, 1, PROJ_NEAR_PLANE, General.Settings.ViewDistance, worldviewproj); if (screenPos.X > 0 && screenPos.X < 1 && screenPos.Y > 0 && screenPos.Y < 1) return true; if (screenPos.Z < 0) behindCount++; if (screenPos.X < 0) leftCount++; else if (screenPos.X > 1) rightCount++; if (screenPos.Y < 0) topCount++; else if (screenPos.Y > 1) bottomCount++; } bool notOnScreen = (behindCount == len || leftCount == len || rightCount == len || topCount == len || bottomCount == len); return !notOnScreen; } //mxd private static bool checkBBoxIntersection(Vector3[] bbox1, Vector3[] bbox2) { Vector3 dist = bbox1[0] - bbox2[0]; Vector3 halfSize1 = bbox1[0] - bbox1[1]; Vector3 halfSize2 = bbox2[0] - bbox2[1]; if (halfSize1.X + halfSize2.X >= Math.Abs(dist.X) && halfSize1.Y + halfSize2.Y >= Math.Abs(dist.Y) && halfSize1.Z + halfSize2.Z >= Math.Abs(dist.Z)) return true; return false; } // This renders the crosshair public void RenderCrosshair() { // Set renderstates graphics.Device.SetRenderState(RenderState.CullMode, Cull.None); graphics.Device.SetRenderState(RenderState.ZEnable, false); graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, true); graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false); graphics.Device.SetRenderState(RenderState.SourceBlend, Blend.SourceAlpha); graphics.Device.SetRenderState(RenderState.DestinationBlend, Blend.InverseSourceAlpha); graphics.Device.SetRenderState(RenderState.TextureFactor, -1); graphics.Device.SetTransform(TransformState.World, Matrix.Identity); graphics.Device.SetTransform(TransformState.Projection, Matrix.Identity); ApplyMatrices2D(); // Texture if(crosshairbusy) { if(General.Map.Data.CrosshairBusy3D.Texture == null) General.Map.Data.CrosshairBusy3D.CreateTexture(); graphics.Device.SetTexture(0, General.Map.Data.CrosshairBusy3D.Texture); graphics.Shaders.Display2D.Texture1 = General.Map.Data.CrosshairBusy3D.Texture; } else { if(General.Map.Data.Crosshair3D.Texture == null) General.Map.Data.Crosshair3D.CreateTexture(); graphics.Device.SetTexture(0, General.Map.Data.Crosshair3D.Texture); graphics.Shaders.Display2D.Texture1 = General.Map.Data.Crosshair3D.Texture; } // Draw graphics.Shaders.Display2D.Begin(); graphics.Shaders.Display2D.SetSettings(1.0f, 1.0f, 0.0f, 1.0f, true); graphics.Shaders.Display2D.BeginPass(1); graphics.Device.DrawUserPrimitives(PrimitiveType.TriangleStrip, 0, 2, crosshairverts); graphics.Shaders.Display2D.EndPass(); graphics.Shaders.Display2D.End(); } // This switches fog on and off public void SetFogMode(bool usefog) { graphics.Device.SetRenderState(RenderState.FogEnable, usefog); } // This siwtches crosshair busy icon on and off public void SetCrosshairBusy(bool busy) { crosshairbusy = busy; } //mxd. dbg //private int lastTick, lastFrameRate, frameRate; /*private int calculateFrameRate() { if (System.Environment.TickCount - lastTick >= 1000) { lastFrameRate = frameRate; frameRate = 0; lastTick = System.Environment.TickCount; } frameRate++; return lastFrameRate; }*/ #endregion } }