UltimateZoneBuilder/Source/Core/Rendering/Renderer3D.cs
2024-02-24 22:09:57 +01:00

2011 lines
72 KiB
C#
Executable file

#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.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.GZBuilder.Data;
using CodeImp.DoomBuilder.GZBuilder.Models;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.VisualModes;
using CodeImp.DoomBuilder.GZBuilder;
using ColorMap = System.Drawing.Imaging.ColorMap;
#endregion
namespace CodeImp.DoomBuilder.Rendering
{
internal sealed class Renderer3D : Renderer, IRenderer3D
{
#region ================== Constants
private const float PROJ_NEAR_PLANE = 1f;
private const float FOG_RANGE = 0.9f;
private const int MAX_DYNLIGHTS_PER_SURFACE = 64;
#endregion
#region ================== Variables
// Matrices
private Matrix projection;
private Matrix view3d;
private Matrix billboard;
private Matrix view2d;
private Matrix world;
private Vector3D cameraposition;
private Vector3D cameravector;
private ShaderName shaderpass;
// Window size
private Size windowsize;
// Frustum
private ProjectedFrustum2D frustum;
// Thing cage
private bool renderthingcages;
//mxd
private VisualVertexHandle vertexhandle;
private int[] lightOffsets;
private Data.ColorMap classicLightingColorMap = null;
private Texture classicLightingColorMapTex = null;
// Slope handle
private VisualSlopeHandle visualslopehandle;
// Crosshair
private FlatVertex[] crosshairverts;
private bool crosshairbusy;
// Highlighting
private IVisualPickable highlighted;
private float highlightglow;
private float highlightglowinv;
private bool showselection;
private bool showhighlight;
//mxd. Solid geometry to be rendered. Must be sorted by sector.
private Dictionary<ImageData, List<VisualGeometry>> solidgeo;
//mxd. Masked geometry to be rendered. Must be sorted by sector.
private Dictionary<ImageData, List<VisualGeometry>> maskedgeo;
//mxd. Translucent geometry to be rendered. Must be sorted by camera distance.
private List<VisualGeometry> translucentgeo;
//mxd. Geometry to be rendered as skybox.
private List<VisualGeometry> skygeo;
//mxd. Solid things to be rendered (currently(?) there won't be any). Must be sorted by sector.
private Dictionary<ImageData, List<VisualThing>> solidthings;
//mxd. Masked things to be rendered. Must be sorted by sector.
private Dictionary<ImageData, List<VisualThing>> maskedthings;
//mxd. Translucent things to be rendered. Must be sorted by camera distance.
private List<VisualThing> translucentthings;
//mxd. Things with attached dynamic lights
private List<VisualThing> lightthings;
//mxd. Things, which should be rendered as models
private Dictionary<ModelData, List<VisualThing>> maskedmodelthings;
//mxd. Things, which should be rendered as translucent models
private List<VisualThing> translucentmodelthings;
//mxd. All things. Used to render thing cages
private List<VisualThing> allthings;
//mxd. Visual vertices
private List<VisualVertex> visualvertices;
// Visual slope handles
private ICollection<VisualSlope> visualslopehandles;
//mxd. Event lines
private List<Line3D> eventlines;
// FPS-related
private int fps = 0;
private System.Diagnostics.Stopwatch fpsWatch;
private TextLabel fpsLabel;
#endregion
#region ================== Properties
public ProjectedFrustum2D Frustum2D { get { return frustum; } }
public bool DrawThingCages { get { return renderthingcages; } set { renderthingcages = value; } }
public bool ShowSelection { get { return showselection; } set { showselection = value; } }
public bool ShowHighlight { get { return showhighlight; } set { showhighlight = value; } }
protected bool UseIndexedTexture
{
get { return General.Settings.ClassicRendering && !fullbrightness; }
}
#endregion
#region ================== Constructor / Disposer
// Constructor
internal Renderer3D(RenderDevice graphics) : base(graphics)
{
// Initialize
//CreateProjection(); // [ZZ] don't do undefined things once not even ready
CreateMatrices2D();
renderthingcages = true;
showselection = true;
showhighlight = true;
eventlines = new List<Line3D>(); //mxd
// Dummy frustum
frustum = new ProjectedFrustum2D(new Vector2D(), 0.0f, 0.0f, PROJ_NEAR_PLANE,
General.Settings.ViewDistance, (float)Angle2D.DegToRad(General.Settings.VisualFOV));
fpsLabel = new TextLabel();
fpsLabel.AlignX = TextAlignmentX.Left;
fpsLabel.AlignY = TextAlignmentY.Top;
fpsLabel.Text = "(FPS unavailable)";
fpsWatch = new System.Diagnostics.Stopwatch();
// We have no destructor
GC.SuppressFinalize(this);
}
// Disposer
public override void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Clean up
if(vertexhandle != null) vertexhandle.Dispose(); //mxd
if (visualslopehandle != null) visualslopehandle.Dispose();
// 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;
}
// This is called resets when the device is reset
// (when resized or display adapter was changed)
public override void ReloadResource()
{
CreateMatrices2D();
}
// This makes screen vertices for display
private void CreateCrosshairVerts(Size texturesize)
{
// Determine coordinates
float width = windowsize.Width;
float height = windowsize.Height;
RectangleF rect = new RectangleF((float)Math.Round((width - texturesize.Width) * 0.5f), (float)Math.Round((height - texturesize.Height) * 0.5f), texturesize.Width, texturesize.Height);
// Make vertices
crosshairverts = new FlatVertex[4];
crosshairverts[0].x = rect.Left;
crosshairverts[0].y = rect.Top;
crosshairverts[0].c = -1;
crosshairverts[1].x = rect.Right;
crosshairverts[1].y = rect.Top;
crosshairverts[1].c = -1;
crosshairverts[1].u = 1.0f;
crosshairverts[2].x = rect.Left;
crosshairverts[2].y = rect.Bottom;
crosshairverts[2].c = -1;
crosshairverts[2].v = 1.0f;
crosshairverts[3].x = rect.Right;
crosshairverts[3].y = rect.Bottom;
crosshairverts[3].c = -1;
crosshairverts[3].u = 1.0f;
crosshairverts[3].v = 1.0f;
}
#endregion
#region ================== Resources
//mxd
internal void UpdateVertexHandle()
{
if(vertexhandle != null)
{
vertexhandle.UnloadResource();
vertexhandle.ReloadResource();
}
}
internal void UpdateVisualSlopeHandle()
{
if (visualslopehandle != null)
{
visualslopehandle.UnloadResource();
visualslopehandle.ReloadResource();
}
}
#endregion
#region ================== Presentation
// This creates the projection
internal void CreateProjection()
{
// Calculate aspect
float screenheight = General.Map.Graphics.RenderTarget.ClientSize.Height * (General.Settings.DOOMStretchView ? General.Map.Data.InvertedVerticalViewStretch : 1.0f); //mxd
float aspect = General.Map.Graphics.RenderTarget.ClientSize.Width / screenheight;
// 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 = (float)Angle2D.DegToRad(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);
// Make the projection matrix
projection = Matrix.PerspectiveFov(fovy, aspect, PROJ_NEAR_PLANE, General.Settings.ViewDistance);
// We also need to re-create the 2D matrices, otherwise the corsshair will be distorted after the viewport is resized. See
// https://github.com/jewalky/UltimateDoomBuilder/issues/321
// and
// https://github.com/jewalky/UltimateDoomBuilder/issues/777
CreateMatrices2D();
crosshairverts = null;
}
// This creates matrices for a camera view
public void PositionAndLookAt(Vector3D pos, Vector3D lookat)
{
// Calculate delta vector
cameraposition = pos;
Vector3D delta = lookat - pos;
cameravector = delta.GetNormal();
double anglexy = delta.GetAngleXY();
double anglez = delta.GetAngleZ();
// Create frustum
frustum = new ProjectedFrustum2D(pos, (float)anglexy, (float)anglez, PROJ_NEAR_PLANE,
General.Settings.ViewDistance, (float)Angle2D.DegToRad(General.Settings.VisualFOV));
// Make the view matrix
view3d = Matrix.LookAt(RenderDevice.V3(pos), RenderDevice.V3(lookat), new Vector3f(0f, 0f, 1f));
// Make the billboard matrix
billboard = Matrix.RotationZ((float)(anglexy + Angle2D.PI));
}
// volte: Set the active colormap for classic lighting
public void SetClassicLightingColorMap(Data.ColorMap colormap)
{
if (colormap == classicLightingColorMap)
{
return;
}
classicLightingColorMap = colormap;
if (classicLightingColorMap == null)
{
classicLightingColorMapTex = null;
}
else
{
classicLightingColorMapTex = new Texture(graphics, classicLightingColorMap.CreateBitmap());
}
}
// This creates 2D view matrix
private void CreateMatrices2D()
{
windowsize = graphics.RenderTarget.ClientSize;
Matrix scaling = Matrix.Scaling((1f / windowsize.Width) * 2f, (1f / windowsize.Height) * -2f, 1f);
Matrix translate = Matrix.Translation(-(float)windowsize.Width * 0.5f, -(float)windowsize.Height * 0.5f, 0f);
view2d = translate * scaling;
}
#endregion
#region ================== Start / Finish
// This starts rendering
public bool Start()
{
if (General.Settings.ShowFPS && !fpsWatch.IsRunning)
fpsWatch.Start();
// Start drawing
graphics.StartRendering(true, General.Colors.Background.ToColorValue());
// Beginning renderstates
graphics.SetCullMode(Cull.None);
graphics.SetZEnable(false);
graphics.SetAlphaBlendEnable(false);
graphics.SetAlphaTestEnable(false);
graphics.SetSourceBlend(Blend.SourceAlpha);
graphics.SetDestinationBlend(Blend.InverseSourceAlpha);
graphics.SetUniform(UniformName.fogsettings, new Vector4f(-1.0f));
graphics.SetUniform(UniformName.fogcolor, General.Colors.Background.ToColorValue());
graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
graphics.SetUniform(UniformName.doomlightlevels, General.Map.Config.DoomLightLevels);
graphics.SetUniform(UniformName.highlightcolor, new Color4()); //mxd
TextureFilter texFilter = (!General.Settings.ClassicRendering && General.Settings.VisualBilinear) ? TextureFilter.Linear : TextureFilter.Nearest;
MipmapFilter mipFilter = General.Settings.ClassicRendering ? MipmapFilter.None : MipmapFilter.Linear;
float aniso = General.Settings.ClassicRendering ? 0 : General.Settings.FilterAnisotropy;
graphics.SetSamplerFilter(texFilter, texFilter, mipFilter, aniso);
// Texture addressing
graphics.SetSamplerState(TextureAddress.Wrap);
// Matrices
world = Matrix.Identity;
graphics.SetUniform(UniformName.projection, ref projection);
graphics.SetUniform(UniformName.world, ref world);
graphics.SetUniform(UniformName.view, ref view3d);
// Highlight
if(General.Settings.AnimateVisualSelection)
{
highlightglow = (float)Math.Sin(Clock.CurrentTime / 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 = ShaderName.world3d_fullbright;
}
else if (General.Settings.ClassicRendering)
{
shaderpass = ShaderName.world3d_classic;
}
else
{
shaderpass = ShaderName.world3d_main;
}
// Create crosshair vertices
if(crosshairverts == null)
CreateCrosshairVerts(new Size(General.Map.Data.Crosshair3D.Width, General.Map.Data.Crosshair3D.Height));
//mxd. Crate vertex handle
if(vertexhandle == null) vertexhandle = new VisualVertexHandle();
// Create slope handle
if (visualslopehandle == null) visualslopehandle = new VisualSlopeHandle();
// Ready
return true;
}
// This begins rendering world geometry
public void StartGeometry()
{
// Make collections
solidgeo = new Dictionary<ImageData, List<VisualGeometry>>(); //mxd
maskedgeo = new Dictionary<ImageData, List<VisualGeometry>>(); //mxd
translucentgeo = new List<VisualGeometry>(); //mxd
skygeo = new List<VisualGeometry>(); //mxd
solidthings = new Dictionary<ImageData, List<VisualThing>>(); //mxd
maskedthings = new Dictionary<ImageData, List<VisualThing>>(); //mxd
translucentthings = new List<VisualThing>(); //mxd
maskedmodelthings = new Dictionary<ModelData, List<VisualThing>>(); //mxd
translucentmodelthings = new List<VisualThing>(); //mxd
lightthings = new List<VisualThing>(); //mxd
allthings = new List<VisualThing>(); //mxd
}
// This ends rendering world geometry
public void FinishGeometry()
{
//mxd. Sort lights
if(General.Settings.GZDrawLightsMode != LightRenderMode.NONE && !fullbrightness && lightthings.Count > 0)
UpdateLights();
// Initial renderstates
graphics.SetCullMode(Cull.Clockwise);
graphics.SetZEnable(true);
graphics.SetZWriteEnable(true);
graphics.SetAlphaBlendEnable(false);
graphics.SetAlphaTestEnable(false);
graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
//mxd. SKY PASS
if (skygeo.Count > 0)
{
world = Matrix.Identity;
graphics.SetUniform(UniformName.world, ref world);
RenderSky(skygeo);
}
// SOLID PASS
world = Matrix.Identity;
graphics.SetUniform(UniformName.world, ref world);
RenderSinglePass(solidgeo, solidthings, lightthings);
//mxd. Render models, without backface culling
if(maskedmodelthings.Count > 0)
{
graphics.SetAlphaTestEnable(true);
graphics.SetCullMode(Cull.None);
RenderModels(false, lightthings);
graphics.SetCullMode(Cull.Clockwise);
}
// MASK PASS
if(maskedgeo.Count > 0 || maskedthings.Count > 0)
{
world = Matrix.Identity;
graphics.SetUniform(UniformName.world, ref world);
graphics.SetAlphaTestEnable(true);
RenderSinglePass(maskedgeo, maskedthings, lightthings);
}
// ALPHA AND ADDITIVE PASS
if(translucentgeo.Count > 0 || translucentthings.Count > 0)
{
world = Matrix.Identity;
graphics.SetUniform(UniformName.world, ref world);
graphics.SetAlphaBlendEnable(true);
graphics.SetAlphaTestEnable(false);
graphics.SetZWriteEnable(false);
graphics.SetSourceBlend(Blend.SourceAlpha);
RenderTranslucentPass(translucentgeo, translucentthings, lightthings);
}
//mxd. Render translucent models, with backface culling
if(translucentmodelthings.Count > 0)
{
graphics.SetAlphaBlendEnable(true);
graphics.SetAlphaTestEnable(false);
graphics.SetZWriteEnable(false);
graphics.SetSourceBlend(Blend.SourceAlpha);
RenderModels(true, lightthings);
}
// THING CAGES
if (renderthingcages)
{
world = Matrix.Identity;
graphics.SetUniform(UniformName.world, ref world);
RenderThingCages();
}
//mxd. Visual vertices
RenderVertices();
// Slope handles
if (General.Map.UDMF /* && General.Settings.ShowVisualSlopeHandles */)
RenderSlopeHandles();
//mxd. Event lines
if (General.Settings.GZShowEventLines) RenderArrows(eventlines);
// Remove references
graphics.SetTexture(null);
//mxd. Trash collections
solidgeo = null;
maskedgeo = null;
translucentgeo = null;
skygeo = null;
solidthings = null;
maskedthings = null;
translucentthings = null;
allthings = null;
lightthings = null;
maskedmodelthings = null;
translucentmodelthings = null;
visualvertices = null;
if (General.Settings.ShowFPS)
{
fps++;
if (fpsWatch.ElapsedMilliseconds > 1000)
{
fpsLabel.Text = string.Format("{0} FPS", fps);
fps = 0;
fpsWatch.Restart();
}
}
}
// [ZZ] black renderer magic here.
// todo maybe implement proper frustum culling eventually?
// Frustum2D.IntersectCircle doesn't seem to work here.
private bool CullLight(VisualThing t)
{
Vector3D lightToCamera = (cameraposition - t.CenterV3D).GetNormal();
double angdiff = Vector3D.DotProduct(lightToCamera, cameravector);
if (angdiff <= 0)
return true; // light in front of the camera. it's not negative because I don't want to calculate things twice and need the vector to point at camera.
// otherwise check light size: large lights might have center on the back, but radius in front of the camera.
Vector3D lightToCameraWithRadius = (cameraposition - (t.CenterV3D + lightToCamera * t.LightRadius)).GetNormal();
double angdiffWithRadius = Vector3D.DotProduct(lightToCameraWithRadius, cameravector);
if (angdiffWithRadius <= 0)
return true; // light's radius extension is in front of the camera.
return false;
}
//mxd
private void UpdateLights()
{
// Calculate distance to camera
foreach(VisualThing t in lightthings) t.CalculateCameraDistance(cameraposition);
// Sort by it, closer ones first
lightthings.Sort((t1, t2) => Math.Sign(t1.CameraDistance - t2.CameraDistance));
// Gather the closest
List<VisualThing> tl = new List<VisualThing>(lightthings.Count);
// Break on either end of things of max dynamic lights reached
for (int i = 0; i < lightthings.Count && tl.Count < General.Settings.GZMaxDynamicLights; i++)
{
// Make sure we can see this light at all
if (!CullLight(lightthings[i]))
continue;
tl.Add(lightthings[i]);
}
// Update the array
lightthings = tl;
// Sort things by light render style
lightthings.Sort((t1, t2) => Math.Sign(t1.LightType.LightRenderStyle - t2.LightType.LightRenderStyle));
lightOffsets = new int[4];
foreach(VisualThing t in lightthings)
{
//add light to apropriate array.
switch(t.LightType.LightRenderStyle)
{
case GZGeneral.LightRenderStyle.NORMAL:
case GZGeneral.LightRenderStyle.VAVOOM: lightOffsets[0]++; break;
case GZGeneral.LightRenderStyle.ADDITIVE: lightOffsets[2]++; break;
case GZGeneral.LightRenderStyle.SUBTRACTIVE: lightOffsets[3]++; break;
case GZGeneral.LightRenderStyle.LIGHTMAP: // Static lights look the same as attenuated lights
default: lightOffsets[1]++; break; // attenuated
}
}
}
//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.SetAlphaBlendEnable(true);
graphics.SetAlphaTestEnable(false);
graphics.SetZWriteEnable(false);
graphics.SetSourceBlend(Blend.SourceAlpha);
graphics.SetDestinationBlend(Blend.SourceAlpha);
graphics.SetShader(ShaderName.world3d_constant_color);
foreach(VisualThing t in allthings)
{
// Setup color
Color4 thingcolor;
if(t.Selected && showselection)
{
thingcolor = General.Colors.Selection3D.ToColorValue();
}
else
{
thingcolor = t.CageColor;
if(t != highlighted) thingcolor.Alpha = 0.6f;
}
graphics.SetUniform(UniformName.vertexColor, thingcolor);
//Render cage
graphics.SetVertexBuffer(t.CageBuffer);
graphics.Draw(PrimitiveType.LineList, 0, t.CageLength);
}
// Done
graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
}
//mxd
private void RenderVertices()
{
if(visualvertices == null) return;
graphics.SetAlphaBlendEnable(true);
graphics.SetAlphaTestEnable(false);
graphics.SetZWriteEnable(false);
graphics.SetSourceBlend(Blend.SourceAlpha);
graphics.SetDestinationBlend(Blend.SourceAlpha);
graphics.SetShader(ShaderName.world3d_constant_color);
foreach(VisualVertex v in visualvertices)
{
world = v.Position;
graphics.SetUniform(UniformName.world, ref world);
// Setup color
Color4 color;
if(v.Selected && showselection)
{
color = General.Colors.Selection3D.ToColorValue();
}
else
{
color = v.HaveHeightOffset ? General.Colors.InfoLine.ToColorValue() : General.Colors.Vertices.ToColorValue();
if(v != highlighted) color.Alpha = 0.6f;
}
graphics.SetUniform(UniformName.vertexColor, color);
//Commence drawing!!11
graphics.SetVertexBuffer(v.CeilingVertex ? vertexhandle.Upper : vertexhandle.Lower);
graphics.Draw(PrimitiveType.LineList, 0, 8);
}
// Done
graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
}
private void RenderSlopeHandles()
{
if (visualslopehandles == null || !showselection) return;
graphics.SetAlphaBlendEnable(true);
graphics.SetAlphaTestEnable(false);
graphics.SetZWriteEnable(false);
graphics.SetSourceBlend(Blend.SourceAlpha);
graphics.SetDestinationBlend(Blend.InverseSourceAlpha);
graphics.SetShader(ShaderName.world3d_slope_handle);
foreach (VisualSlope handle in visualslopehandles)
{
PixelColor color = General.Colors.Vertices;
if (handle.Pivot)
color = General.Colors.Guideline;
else if (handle.Selected)
color = General.Colors.Selection3D;
else if (handle == highlighted)
color = General.Colors.Highlight3D;
else if (handle.SmartPivot)
color = General.Colors.Vertices;
world = handle.Position;
graphics.SetUniform(UniformName.world, ref world);
graphics.SetUniform(UniformName.vertexColor, color.ToColorValue());
if (handle.Type == VisualSlopeType.Line)
{
graphics.SetUniform(UniformName.slopeHandleLength, (float)handle.Length);
graphics.SetVertexBuffer(visualslopehandle.LineGeometry);
graphics.Draw(PrimitiveType.TriangleList, 0, 2);
}
else if (handle.Type == VisualSlopeType.Vertex)
{
graphics.SetUniform(UniformName.slopeHandleLength, 1.0f);
graphics.SetVertexBuffer(visualslopehandle.VertexGeometry);
graphics.Draw(PrimitiveType.TriangleList, 0, 1);
}
}
// Done
graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
}
//mxd
private void RenderArrows(ICollection<Line3D> lines)
{
// Calculate required points count
if(lines.Count == 0) return;
int pointscount = 0;
foreach(Line3D line in lines) pointscount += (line.RenderArrowhead ? 6 : 2); // 4 extra points for the arrowhead
if(pointscount < 2) return;
//create vertices
WorldVertex[] verts = new WorldVertex[pointscount];
const float scaler = 20f;
pointscount = 0;
foreach(Line3D line in lines)
{
int color = line.Color.ToInt();
// Add regular points
verts[pointscount].x = (float)line.Start.x;
verts[pointscount].y = (float)line.Start.y;
verts[pointscount].z = (float)line.Start.z;
verts[pointscount].c = color;
pointscount++;
verts[pointscount].x = (float)line.End.x;
verts[pointscount].y = (float)line.End.y;
verts[pointscount].z = (float)line.End.z;
verts[pointscount].c = color;
pointscount++;
// Add arrowhead
if(line.RenderArrowhead)
{
double nz = line.GetDelta().GetNormal().z * scaler;
double angle = line.GetAngle();
Vector3D a1 = new Vector3D(line.End.x - scaler * Math.Sin(angle - 0.46f), line.End.y + scaler * Math.Cos(angle - 0.46f), line.End.z - nz);
Vector3D a2 = new Vector3D(line.End.x - scaler * Math.Sin(angle + 0.46f), line.End.y + scaler * Math.Cos(angle + 0.46f), line.End.z - nz);
verts[pointscount] = verts[pointscount - 1];
verts[pointscount + 1].x = (float)a1.x;
verts[pointscount + 1].y = (float)a1.y;
verts[pointscount + 1].z = (float)a1.z;
verts[pointscount + 1].c = color;
verts[pointscount + 2] = verts[pointscount - 1];
verts[pointscount + 3].x = (float)a2.x;
verts[pointscount + 3].y = (float)a2.y;
verts[pointscount + 3].z = (float)a2.z;
verts[pointscount + 3].c = color;
pointscount += 4;
}
}
VertexBuffer vb = new VertexBuffer();
graphics.SetBufferData(vb, verts);
//begin rendering
graphics.SetAlphaBlendEnable(true);
graphics.SetAlphaTestEnable(false);
graphics.SetZWriteEnable(false);
graphics.SetSourceBlend(Blend.SourceAlpha);
graphics.SetDestinationBlend(Blend.SourceAlpha);
graphics.SetShader(ShaderName.world3d_vertex_color);
world = Matrix.Identity;
graphics.SetUniform(UniformName.world, ref world);
//render
graphics.SetVertexBuffer(vb);
graphics.Draw(PrimitiveType.LineList, 0, pointscount / 2);
// Done
graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
vb.Dispose();
}
// This performs a single render pass
private void RenderSinglePass(Dictionary<ImageData, List<VisualGeometry>> geopass, Dictionary<ImageData, List<VisualThing>> thingspass, List<VisualThing> lights)
{
ImageData curtexture;
ShaderName currentshaderpass = shaderpass;
ShaderName highshaderpass = (ShaderName)(shaderpass + 2);
// Begin rendering with this shader
graphics.SetShader(shaderpass);
// Light data arrays
Vector4f[] lightColor = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector4f[] lightPosAndRadius = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector4f[] lightOrientation = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector2f[] light2Radius = new Vector2f[MAX_DYNLIGHTS_PER_SURFACE];
graphics.SetUniform(UniformName.lightsEnabled, lights.Count > 0);
graphics.SetUniform(UniformName.ignoreNormals, false);
bool hadlights = false;
// volte: Set textures for classic lighting
if (classicLightingColorMap != null)
{
graphics.SetUniform(UniformName.colormapSize, new Vector2i(classicLightingColorMapTex.Width, classicLightingColorMapTex.Height));
graphics.SetTexture(classicLightingColorMapTex, 1);
graphics.SetSamplerFilter(TextureFilter.Nearest, TextureFilter.Nearest, MipmapFilter.None, 0, 1);
graphics.SetSamplerState(TextureAddress.Clamp, 1);
}
// Render the geometry collected
foreach (KeyValuePair<ImageData, List<VisualGeometry>> group in geopass)
{
curtexture = group.Key;
// Apply texture
Texture texture = UseIndexedTexture ? curtexture.IndexedTexture : curtexture.Texture;
graphics.SetTexture(texture);
graphics.SetUniform(UniformName.drawPaletted, texture.UserData == ImageData.TEXTURE_INDEXED);
//mxd. Sort geometry by sector index
group.Value.Sort((g1, g2) => g1.Sector.Sector.FixedIndex - g2.Sector.Sector.FixedIndex);
// Go for all geometry that uses this texture
VisualSector sector = null;
foreach(VisualGeometry g in group.Value)
{
int lightIndex = 0;
foreach (VisualThing light in lights)
{
if (BoundingBoxesIntersect(g.BoundingBox, light.BoundingBox))
{
//t.LightType.LightRenderStyle
//Vector4f lightColor, lightPosAndRadius, lightOrientation, light2Radius;
lightColor[lightIndex] = light.LightColor.ToVector();
lightPosAndRadius[lightIndex] = new Vector4f(light.Center, light.LightRadius);
// set type of light
if (light.LightType.LightType == GZGeneral.LightType.SPOT)
{
lightOrientation[lightIndex] = new Vector4f(light.VectorLookAt, 1f);
light2Radius[lightIndex] = new Vector2f(CosDeg(light.LightSpotRadius1), CosDeg(light.LightSpotRadius2));
}
else lightOrientation[lightIndex].W = 0f;
lightIndex++;
if (lightIndex >= lightColor.Length)
break;
}
}
bool havelights = (lightIndex > 0);
for (int i = lightIndex; i < lightColor.Length; i++)
lightColor[i].W = 0;
if (havelights != hadlights || havelights)
{
graphics.SetUniform(UniformName.lightColor, lightColor);
if (havelights)
{
graphics.SetUniform(UniformName.lightPosAndRadius, lightPosAndRadius);
graphics.SetUniform(UniformName.lightOrientation, lightOrientation);
graphics.SetUniform(UniformName.light2Radius, light2Radius);
}
}
hadlights = havelights;
// Changing sector?
if(!object.ReferenceEquals(g.Sector, sector))
{
// Update the sector if needed
if(g.Sector.NeedsUpdateGeo) g.Sector.Update(graphics);
// 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.SetVertexBuffer(sector.GeometryBuffer);
}
else
{
sector = null;
}
}
graphics.SetUniform(UniformName.desaturation, 0.0f);
if (sector != null)
{
// Determine the shader pass we want to use for this object
ShaderName wantedshaderpass = (((g == highlighted) && showhighlight) || (g.Selected && showselection)) ? highshaderpass : shaderpass;
//mxd. Render fog?
if(General.Settings.GZDrawFog && !fullbrightness && !General.Settings.ClassicRendering && sector.Sector.FogMode != SectorFogMode.NONE)
wantedshaderpass += 8;
// Switch shader pass?
if(currentshaderpass != wantedshaderpass)
{
graphics.SetShader(wantedshaderpass);
currentshaderpass = wantedshaderpass;
//mxd. Set variables for fog rendering?
if(wantedshaderpass > ShaderName.world3d_p7)
{
graphics.SetUniform(UniformName.modelnormal, Matrix.Identity);
}
}
// volte: set sector light level for classic rendering mode
graphics.SetUniform(UniformName.sectorLightLevel, sector.Sector.Brightness);
//mxd. Set variables for fog rendering?
if(wantedshaderpass > ShaderName.world3d_p7)
{
graphics.SetUniform(UniformName.campos, new Vector4f((float)cameraposition.x, (float)cameraposition.y, (float)cameraposition.z, g.FogFactor));
graphics.SetUniform(UniformName.sectorfogcolor, sector.Sector.FogColor);
}
// Set the colors to use
graphics.SetUniform(UniformName.highlightcolor, CalculateHighlightColor((g == highlighted) && showhighlight, (g.Selected && showselection)));
// [ZZ] include desaturation factor
graphics.SetUniform(UniformName.desaturation, (float)sector.Sector.Desaturation);
// Skew
graphics.SetUniform(UniformName.skew, g.Skew);
// Render!
graphics.Draw(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles);
}
}
}
// Done with geometry, reset potentially lingering skew setting
graphics.SetUniform(UniformName.skew, new Vector2f(0.0f, 0.0f));
graphics.SetUniform(UniformName.lightsEnabled, false);
// Get things for this pass
if (thingspass.Count > 0)
{
// Texture addressing
graphics.SetSamplerState(TextureAddress.Clamp);
graphics.SetCullMode(Cull.None); //mxd. Disable backside culling, because otherwise sprites with positive ScaleY and negative ScaleX will be facing away from the camera...
Color4 vertexcolor = new Color4(); //mxd
// Render things collected
foreach(KeyValuePair<ImageData, List<VisualThing>> group in thingspass)
{
if(group.Key is UnknownImage) continue;
curtexture = group.Key;
// Apply texture
Texture texture = UseIndexedTexture ? curtexture.IndexedTexture : curtexture.Texture;
graphics.SetTexture(texture);
graphics.SetUniform(UniformName.drawPaletted, texture.UserData == ImageData.TEXTURE_INDEXED);
// Render all things with this texture
foreach(VisualThing t in group.Value)
{
// Update buffer if needed
t.Update();
//mxd. Check 3D distance. Check against MaxValue to save doing GetLenthSq if there's not DistanceCheck defined
if (t.Info.DistanceCheckSq < double.MaxValue)
{
t.CalculateCameraDistance(cameraposition);
if (t.CameraDistance > t.Info.DistanceCheckSq)
continue;
}
// Only do this sector when a vertexbuffer is created
if (t.GeometryBuffer != null)
{
// Determine the shader pass we want to use for this object
ShaderName 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 && !General.Settings.ClassicRendering && t.Thing.Sector != null && t.Thing.Sector.FogMode != SectorFogMode.NONE)
wantedshaderpass += 8;
//mxd. Create the matrix for positioning
world = CreateThingPositionMatrix(t);
//mxd. If current thing is light - set it's color to light color
if(t.LightType != null && t.LightType.LightInternal && t.LightType.LightType != GZGeneral.LightType.SUN && !fullbrightness && !General.Settings.ClassicRendering)
{
wantedshaderpass += 4; // Render using one of passes, which uses World3D.VertexColor
vertexcolor = t.LightColor;
}
//mxd. Check if Thing is affected by dynamic lights and set color accordingly
else if(General.Settings.GZDrawLightsMode != LightRenderMode.NONE && !fullbrightness && !General.Settings.ClassicRendering && lightthings.Count > 0)
{
Color4 litcolor = GetLitColorForThing(t);
if(litcolor.ToArgb() != 0)
{
wantedshaderpass += 4; // Render using one of passes, which uses World3D.VertexColor
vertexcolor = new Color4(t.VertexColor) + litcolor;
}
}
else
{
vertexcolor = new Color4();
}
// Switch shader pass?
if(currentshaderpass != wantedshaderpass)
{
graphics.SetShader(wantedshaderpass);
currentshaderpass = wantedshaderpass;
}
//mxd. Set variables for fog rendering?
if(wantedshaderpass > ShaderName.world3d_p7)
{
graphics.SetUniform(UniformName.modelnormal, Matrix.Identity);
graphics.SetUniform(UniformName.campos, new Vector4f((float)cameraposition.x, (float)cameraposition.y, (float)cameraposition.z, t.FogFactor));
}
// Set the colors to use
if (t.Thing.Sector != null)
{
graphics.SetUniform(UniformName.sectorLightLevel, t.Thing.Sector.Brightness);
graphics.SetUniform(UniformName.sectorfogcolor, t.Thing.Sector.FogColor);
}
graphics.SetUniform(UniformName.vertexColor, vertexcolor);
graphics.SetUniform(UniformName.highlightcolor, CalculateHighlightColor((t == highlighted) && showhighlight, (t.Selected && showselection)));
// [ZZ] check if we want stencil
graphics.SetUniform(UniformName.stencilColor, t.StencilColor.ToColorValue());
// [ZZ] apply desaturation
if (t.Thing.Sector != null)
graphics.SetUniform(UniformName.desaturation, (float)t.Thing.Sector.Desaturation);
else graphics.SetUniform(UniformName.desaturation, 0.0f);
// Apply changes
graphics.SetUniform(UniformName.world, world);
// Apply buffer
graphics.SetVertexBuffer(t.GeometryBuffer);
// Render!
graphics.Draw(PrimitiveType.TriangleList, 0, t.Triangles);
}
}
// [ZZ]
graphics.SetUniform(UniformName.stencilColor, new Color4(1f, 1f, 1f, 0f));
}
// Texture addressing
graphics.SetSamplerState(TextureAddress.Wrap);
graphics.SetCullMode(Cull.Clockwise); //mxd
}
}
//mxd
private void RenderTranslucentPass(List<VisualGeometry> geopass, List<VisualThing> thingspass, List<VisualThing> lights)
{
ShaderName currentshaderpass = shaderpass;
ShaderName highshaderpass = (ShaderName)(shaderpass + 2);
// Sort geometry by camera distance. First vertex of the BoundingBox is it's center
geopass.Sort(delegate(VisualGeometry vg1, VisualGeometry vg2)
{
if (vg1 == vg2)
return 0;
double dist1, dist2;
Vector3D cameraPos = General.Map.VisualCamera.Position;
Vector2D cameraPos2 = new Vector2D(cameraPos);
// if one of the things being compared is a plane, use easier formula. (3d floor compatibility)
if (vg1.GeometryType == VisualGeometryType.FLOOR || vg1.GeometryType == VisualGeometryType.CEILING ||
vg2.GeometryType == VisualGeometryType.FLOOR || vg2.GeometryType == VisualGeometryType.CEILING)
{
// more magic
dist1 = Math.Abs(vg1.BoundingBox[0].z - cameraPos.z);
dist2 = Math.Abs(vg2.BoundingBox[0].z - cameraPos.z);
}
else
{
dist1 = (General.Map.VisualCamera.Position - vg1.BoundingBox[0]).GetLengthSq();
dist2 = (General.Map.VisualCamera.Position - vg2.BoundingBox[0]).GetLengthSq();
}
return (int)(dist2 - dist1);
});
ImageData curtexture;
VisualSector sector = null;
RenderPass currentpass = RenderPass.Solid;
long curtexturename = 0;
float fogfactor = -1;
// Begin rendering with this shader
graphics.SetShader(shaderpass);
// Light data arrays
Vector4f[] lightColor = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector4f[] lightPosAndRadius = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector4f[] lightOrientation = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector2f[] light2Radius = new Vector2f[MAX_DYNLIGHTS_PER_SURFACE];
graphics.SetUniform(UniformName.lightsEnabled, lights.Count > 0);
graphics.SetUniform(UniformName.ignoreNormals, false);
bool hadlights = false;
// Go for all geometry
foreach (VisualGeometry g in geopass)
{
int lightIndex = 0;
foreach (VisualThing light in lights)
{
if (BoundingBoxesIntersect(g.BoundingBox, light.BoundingBox))
{
//t.LightType.LightRenderStyle
//Vector4f lightColor, lightPosAndRadius, lightOrientation, light2Radius;
lightColor[lightIndex] = light.LightColor.ToVector();
lightPosAndRadius[lightIndex] = new Vector4f(light.Center, light.LightRadius);
// set type of light
if (light.LightType.LightType == GZGeneral.LightType.SPOT)
{
lightOrientation[lightIndex] = new Vector4f(light.VectorLookAt, 1f);
light2Radius[lightIndex] = new Vector2f(CosDeg(light.LightSpotRadius1), CosDeg(light.LightSpotRadius2));
}
else lightOrientation[lightIndex].W = 0f;
lightIndex++;
if (lightIndex >= lightColor.Length)
break;
}
}
bool havelights = (lightIndex > 0);
for (int i = lightIndex; i < lightColor.Length; i++)
lightColor[i].W = 0;
if (havelights != hadlights || havelights)
{
graphics.SetUniform(UniformName.lightColor, lightColor);
if (havelights)
{
graphics.SetUniform(UniformName.lightPosAndRadius, lightPosAndRadius);
graphics.SetUniform(UniformName.lightOrientation, lightOrientation);
graphics.SetUniform(UniformName.light2Radius, light2Radius);
}
}
hadlights = havelights;
// Change blend mode?
if (g.RenderPass != currentpass)
{
switch(g.RenderPass)
{
case RenderPass.Additive:
graphics.SetDestinationBlend(Blend.One);
break;
case RenderPass.Alpha:
graphics.SetDestinationBlend(Blend.InverseSourceAlpha);
break;
}
currentpass = g.RenderPass;
}
// Change texture?
if(g.Texture.LongName != curtexturename)
{
curtexture = g.Texture;
// Apply texture
Texture texture = UseIndexedTexture ? curtexture.IndexedTexture : curtexture.Texture;
graphics.SetTexture(texture);
graphics.SetUniform(UniformName.drawPaletted, texture.UserData == ImageData.TEXTURE_INDEXED);
curtexturename = g.Texture.LongName;
}
// Changing sector?
if(!object.ReferenceEquals(g.Sector, sector))
{
// Update the sector if needed
if(g.Sector.NeedsUpdateGeo) g.Sector.Update(graphics);
// 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.SetVertexBuffer(sector.GeometryBuffer);
}
else
{
sector = null;
}
}
if (sector != null)
{
// Determine the shader pass we want to use for this object
ShaderName wantedshaderpass = (((g == highlighted) && showhighlight) || (g.Selected && showselection)) ? highshaderpass : shaderpass;
//mxd. Render fog?
if (General.Settings.GZDrawFog && !fullbrightness && !General.Settings.ClassicRendering && sector.Sector.FogMode != SectorFogMode.NONE)
wantedshaderpass += 8;
// Switch shader pass?
if (currentshaderpass != wantedshaderpass)
{
graphics.SetShader(wantedshaderpass);
currentshaderpass = wantedshaderpass;
//mxd. Set variables for fog rendering?
if (wantedshaderpass > ShaderName.world3d_p7)
{
graphics.SetUniform(UniformName.modelnormal, Matrix.Identity);
}
}
// Set variables for fog rendering?
if (wantedshaderpass > ShaderName.world3d_p7 && g.FogFactor != fogfactor)
{
graphics.SetUniform(UniformName.campos, new Vector4f((float)cameraposition.x, (float)cameraposition.y, (float)cameraposition.z, g.FogFactor));
fogfactor = g.FogFactor;
}
//
graphics.SetUniform(UniformName.desaturation, (float)sector.Sector.Desaturation);
// Skew
graphics.SetUniform(UniformName.skew, g.Skew);
// Set the colors to use
graphics.SetUniform(UniformName.sectorfogcolor, sector.Sector.FogColor);
graphics.SetUniform(UniformName.highlightcolor, CalculateHighlightColor((g == highlighted) && showhighlight, (g.Selected && showselection)));
// Render!
graphics.Draw(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles);
}
else graphics.SetUniform(UniformName.desaturation, 0.0f);
}
// Done with geometry, reset potentially lingering skew setting
graphics.SetUniform(UniformName.skew, new Vector2f(0.0f, 0.0f));
graphics.SetUniform(UniformName.lightsEnabled, false);
// Get things for this pass
if (thingspass.Count > 0)
{
// Texture addressing
graphics.SetSamplerState(TextureAddress.Clamp);
graphics.SetCullMode(Cull.None); //mxd. Disable backside culling, because otherwise sprites with positive ScaleY and negative ScaleX will be facing away from the camera...
// Sort geometry by camera distance. First vertex of the BoundingBox is it's center
thingspass.Sort(delegate(VisualThing vt1, VisualThing vt2)
{
if(vt1 == vt2) return 0;
return (int)((General.Map.VisualCamera.Position - vt2.BoundingBox[0]).GetLengthSq()
- (General.Map.VisualCamera.Position - vt1.BoundingBox[0]).GetLengthSq());
});
// Reset vars
currentpass = RenderPass.Solid;
curtexturename = 0;
Color4 vertexcolor = new Color4();
fogfactor = -1;
// Render things collected
foreach(VisualThing t in thingspass)
{
// Update buffer if needed
t.Update();
//mxd. Check 3D distance. Check against MaxValue to save doing GetLenthSq if there's not DistanceCheck defined
if (t.Info.DistanceCheckSq < double.MaxValue)
{
t.CalculateCameraDistance(cameraposition);
if (t.CameraDistance > t.Info.DistanceCheckSq)
continue;
}
t.UpdateSpriteFrame(); // Set correct texture, geobuffer and triangles count
if(t.Texture is UnknownImage) continue;
// Change blend mode?
if(t.RenderPass != currentpass)
{
switch(t.RenderPass)
{
case RenderPass.Additive:
graphics.SetDestinationBlend(Blend.One);
break;
case RenderPass.Alpha:
graphics.SetDestinationBlend(Blend.InverseSourceAlpha);
break;
}
currentpass = t.RenderPass;
}
// Change texture?
if(t.Texture.LongName != curtexturename)
{
curtexture = t.Texture;
// Apply texture
Texture texture = UseIndexedTexture ? curtexture.IndexedTexture : curtexture.Texture;
graphics.SetTexture(texture);
graphics.SetUniform(UniformName.drawPaletted, texture.UserData == ImageData.TEXTURE_INDEXED);
curtexturename = t.Texture.LongName;
}
// Only do this sector when a vertexbuffer is created
if(t.GeometryBuffer != null)
{
// Determine the shader pass we want to use for this object
ShaderName 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 && !General.Settings.ClassicRendering && t.Thing.Sector != null && t.Thing.Sector.FogMode != SectorFogMode.NONE)
wantedshaderpass += 8;
//mxd. Create the matrix for positioning
world = CreateThingPositionMatrix(t);
//mxd. If current thing is light - set it's color to light color
if(t.LightType != null && t.LightType.LightInternal && !fullbrightness && !General.Settings.ClassicRendering)
{
wantedshaderpass += 4; // Render using one of passes, which uses World3D.VertexColor
vertexcolor = t.LightColor;
}
//mxd. Check if Thing is affected by dynamic lights and set color accordingly
else if(General.Settings.GZDrawLightsMode != LightRenderMode.NONE && !fullbrightness && !General.Settings.ClassicRendering && lightthings.Count > 0)
{
Color4 litcolor = GetLitColorForThing(t);
if(litcolor.ToArgb() != 0)
{
wantedshaderpass += 4; // Render using one of passes, which uses World3D.VertexColor
vertexcolor = new Color4(t.VertexColor) + litcolor;
}
}
else
{
vertexcolor = new Color4();
}
// Switch shader pass?
if(currentshaderpass != wantedshaderpass)
{
graphics.SetShader(wantedshaderpass);
currentshaderpass = wantedshaderpass;
}
//mxd. Set variables for fog rendering?
if(wantedshaderpass > ShaderName.world3d_p7)
{
graphics.SetUniform(UniformName.modelnormal, Matrix.Identity);
if (t.FogFactor != fogfactor)
{
graphics.SetUniform(UniformName.campos, new Vector4f((float)cameraposition.x, (float)cameraposition.y, (float)cameraposition.z, t.FogFactor));
fogfactor = t.FogFactor;
}
}
// Set the colors to use
graphics.SetUniform(UniformName.sectorfogcolor, t.Thing.Sector.FogColor);
graphics.SetUniform(UniformName.vertexColor, vertexcolor);
graphics.SetUniform(UniformName.highlightcolor, CalculateHighlightColor((t == highlighted) && showhighlight, (t.Selected && showselection)));
// [ZZ] check if we want stencil
graphics.SetUniform(UniformName.stencilColor, t.StencilColor.ToColorValue());
//
graphics.SetUniform(UniformName.desaturation, (float)t.Thing.Sector.Desaturation);
// Apply changes
graphics.SetUniform(UniformName.world, world);
// Apply buffer
graphics.SetVertexBuffer(t.GeometryBuffer);
// Render!
graphics.Draw(PrimitiveType.TriangleList, 0, t.Triangles);
}
}
// [ZZ] check if we want stencil
graphics.SetUniform(UniformName.stencilColor, new Color4(1f, 1f, 1f, 0f));
// Texture addressing
graphics.SetSamplerState(TextureAddress.Wrap);
graphics.SetCullMode(Cull.Clockwise); //mxd
}
}
//mxd
private Matrix CreateThingPositionMatrix(VisualThing t)
{
// Use normal ThingRenderMode when model rendering is disabled for this thing
ThingRenderMode rendermode = t.Thing.RenderMode;
if((t.Thing.RenderMode == ThingRenderMode.MODEL || t.Thing.RenderMode == ThingRenderMode.VOXEL) &&
(General.Settings.GZDrawModelsMode == ModelRenderMode.NONE ||
(General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && !t.Selected)))
{
rendermode = ThingRenderMode.NORMAL;
}
// Create the matrix for positioning
switch(rendermode)
{
case ThingRenderMode.NORMAL:
if(t.Info.XYBillboard) // Apply billboarding?
{
return Matrix.Translation(0f, 0f, -t.LocalCenterZ)
* Matrix.RotationX((float)(Angle2D.PI - General.Map.VisualCamera.AngleZ))
* Matrix.Translation(0f, 0f, t.LocalCenterZ)
* billboard
* t.Position;
}
return billboard * t.Position;
case ThingRenderMode.FLATSPRITE:
case ThingRenderMode.WALLSPRITE:
case ThingRenderMode.MODEL:
case ThingRenderMode.VOXEL:
return t.Position;
default: throw new NotImplementedException("Unknown ThingRenderMode");
}
}
private float CosDeg(float angle)
{
return (float)Math.Cos(Angle2D.DegToRad(angle));
}
//mxd. Render models
private void RenderModels(bool trans, List<VisualThing> lights)
{
ShaderName shaderpass = (fullbrightness ? ShaderName.world3d_fullbright : ShaderName.world3d_main_vertexcolor);
ShaderName currentshaderpass = shaderpass;
ShaderName highshaderpass = (ShaderName)(shaderpass + 2);
RenderPass currentpass = RenderPass.Solid;
// Begin rendering with this shader
graphics.SetShader(currentshaderpass);
// Light data arrays
Vector4f[] lightColor = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector4f[] lightPosAndRadius = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector4f[] lightOrientation = new Vector4f[MAX_DYNLIGHTS_PER_SURFACE];
Vector2f[] light2Radius = new Vector2f[MAX_DYNLIGHTS_PER_SURFACE];
graphics.SetUniform(UniformName.lightsEnabled, lights.Count > 0);
graphics.SetUniform(UniformName.ignoreNormals, true);
bool hadlights = false;
List<VisualThing> things;
if (trans)
{
// Sort models by camera distance. First vertex of the BoundingBox is it's center
translucentmodelthings.Sort((vt1, vt2) => (int)((General.Map.VisualCamera.Position - vt2.BoundingBox[0]).GetLengthSq()
- (General.Map.VisualCamera.Position - vt1.BoundingBox[0]).GetLengthSq()));
things = translucentmodelthings;
}
else
{
things = new List<VisualThing>();
foreach (KeyValuePair<ModelData, List<VisualThing>> group in maskedmodelthings)
foreach (VisualThing t in group.Value)
things.Add(t);
}
foreach(VisualThing t in things)
{
if (trans)
{
// Change blend mode?
if (t.RenderPass != currentpass)
{
switch (t.RenderPass)
{
case RenderPass.Additive:
graphics.SetDestinationBlend(Blend.One);
break;
case RenderPass.Alpha:
graphics.SetDestinationBlend(Blend.InverseSourceAlpha);
break;
}
currentpass = t.RenderPass;
}
}
// Update buffer if needed
t.Update();
// Check 3D distance. Check against MaxValue to save doing GetLenthSq if there's not DistanceCheck defined
if (t.Info.DistanceCheckSq < double.MaxValue)
{
t.CalculateCameraDistance(cameraposition);
if (t.CameraDistance > t.Info.DistanceCheckSq)
continue;
}
Color4 vertexcolor = new Color4(t.VertexColor);
// Check if model is affected by dynamic lights and set color accordingly
graphics.SetUniform(UniformName.vertexColor, vertexcolor);
// Determine the shader pass we want to use for this object
ShaderName wantedshaderpass = ((((t == highlighted) && showhighlight) || (t.Selected && showselection)) ? highshaderpass : shaderpass);
// If fog is enagled, switch to shader, which calculates it
if (General.Settings.GZDrawFog && !fullbrightness && !General.Settings.ClassicRendering && t.Thing.Sector != null && t.Thing.Sector.FogMode != SectorFogMode.NONE)
wantedshaderpass += 8;
// Switch shader pass?
if (currentshaderpass != wantedshaderpass)
{
graphics.SetShader(wantedshaderpass);
currentshaderpass = wantedshaderpass;
}
// Set the colors to use
graphics.SetUniform(UniformName.highlightcolor, CalculateHighlightColor((t == highlighted) && showhighlight, (t.Selected && showselection)));
// Create the matrix for positioning / rotation
double sx = t.Thing.ScaleX * t.Thing.ActorScale.Width;
double sy = t.Thing.ScaleY * t.Thing.ActorScale.Height;
Matrix modelscale = Matrix.Scaling((float)sx, (float)sx, (float)sy);
Matrix modelrotation = Matrix.RotationY((float)-t.Thing.RollRad) * Matrix.RotationX((float)-t.Thing.PitchRad) * Matrix.RotationZ((float)t.Thing.Angle);
if(General.Map.Data.ModeldefEntries[t.Thing.Type].UseRotationCenter)
world = General.Map.Data.ModeldefEntries[t.Thing.Type].Transform * modelscale * Matrix.Translation(-General.Map.Data.ModeldefEntries[t.Thing.Type].RotationCenter) * modelrotation * Matrix.Translation(General.Map.Data.ModeldefEntries[t.Thing.Type].RotationCenter) * t.Position;
else
world = General.Map.Data.ModeldefEntries[t.Thing.Type].Transform * modelscale * modelrotation * t.Position;
graphics.SetUniform(UniformName.world, world);
// Set variables for fog rendering
if (wantedshaderpass > ShaderName.world3d_p7)
{
// this is not right...
graphics.SetUniform(UniformName.modelnormal, General.Map.Data.ModeldefEntries[t.Thing.Type].TransformRotation * modelrotation);
if (t.Thing.Sector != null) graphics.SetUniform(UniformName.sectorfogcolor, t.Thing.Sector.FogColor);
graphics.SetUniform(UniformName.campos, new Vector4f((float)cameraposition.x, (float)cameraposition.y, (float)cameraposition.z, t.FogFactor));
}
if (t.Thing.Sector != null)
graphics.SetUniform(UniformName.desaturation, (float)t.Thing.Sector.Desaturation);
else graphics.SetUniform(UniformName.desaturation, 0.0f);
int lightIndex = 0;
foreach (VisualThing light in lights)
{
if (BoundingBoxesIntersect(t.BoundingBox, light.BoundingBox))
{
//t.LightType.LightRenderStyle
//Vector4f lightColor, lightPosAndRadius, lightOrientation, light2Radius;
lightColor[lightIndex] = light.LightColor.ToVector();
lightPosAndRadius[lightIndex] = new Vector4f(light.Center, light.LightRadius);
// set type of light
if (light.LightType.LightType == GZGeneral.LightType.SPOT)
{
lightOrientation[lightIndex] = new Vector4f(light.VectorLookAt, 1f);
light2Radius[lightIndex] = new Vector2f(CosDeg(light.LightSpotRadius1), CosDeg(light.LightSpotRadius2));
}
else lightOrientation[lightIndex].W = 0f;
lightIndex++;
if (lightIndex >= lightColor.Length)
break;
}
}
bool havelights = (lightIndex > 0);
for (int i = lightIndex; i < lightColor.Length; i++)
lightColor[i].W = 0;
if (hadlights != havelights || havelights)
{
graphics.SetUniform(UniformName.lightColor, lightColor);
if (havelights)
{
graphics.SetUniform(UniformName.lightPosAndRadius, lightPosAndRadius);
graphics.SetUniform(UniformName.lightOrientation, lightOrientation);
graphics.SetUniform(UniformName.light2Radius, light2Radius);
}
}
hadlights = havelights;
GZModel model = General.Map.Data.ModeldefEntries[t.Thing.Type].Model;
for (int j = 0; j < model.Meshes.Count; j++)
{
graphics.SetTexture(model.Textures[j]);
// Render!
model.Meshes[j].Draw(graphics);
}
}
graphics.SetUniform(UniformName.lightsEnabled, false);
}
//mxd
private void RenderSky(IEnumerable<VisualGeometry> geo)
{
VisualSector sector = null;
// Set render settings
graphics.SetShader(ShaderName.world3d_skybox);
graphics.SetTexture(General.Map.Data.SkyBox);
graphics.SetUniform(UniformName.campos, new Vector4f((float)cameraposition.x, (float)cameraposition.y, (float)cameraposition.z, 0f));
foreach(VisualGeometry g in geo)
{
// Changing sector?
if(!object.ReferenceEquals(g.Sector, sector))
{
// Update the sector if needed
if(g.Sector.NeedsUpdateGeo) g.Sector.Update(graphics);
// 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.SetVertexBuffer(sector.GeometryBuffer);
}
else
{
sector = null;
}
}
if(sector != null)
{
graphics.SetUniform(UniformName.highlightcolor, CalculateHighlightColor((g == highlighted) && showhighlight, (g.Selected && showselection)));
graphics.Draw(PrimitiveType.TriangleList, g.VertexOffset, g.Triangles);
}
}
}
// [ZZ] this is copied from GZDoom
private float Smoothstep(float edge0, float edge1, float x)
{
double t = Math.Min(Math.Max((x - edge0) / (edge1 - edge0), 0.0), 1.0);
return (float)(t * t * (3.0 - 2.0 * t));
}
//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();
foreach(VisualThing lt in lightthings)
{
// Don't light self
if(General.Map.Data.GldefsEntries.ContainsKey(t.Thing.Type) && General.Map.Data.GldefsEntries[t.Thing.Type].DontLightSelf && t.Thing.Index == lt.Thing.Index)
continue;
float distSquared = Vector3f.DistanceSquared(lt.Center, t.Center);
float radiusSquared = lt.LightRadius * lt.LightRadius;
if(distSquared < radiusSquared)
{
int sign = (lt.LightType.LightRenderStyle == GZGeneral.LightRenderStyle.SUBTRACTIVE ? -1 : 1);
Vector3f L = (t.Center - lt.Center);
float dist = L.Length();
float scaler = 1 - dist / lt.LightRadius * lt.LightColor.Alpha;
if (lt.LightType.LightType == GZGeneral.LightType.SPOT)
{
Vector3f lookAt = lt.VectorLookAt;
L.Normalize();
float cosDir = Vector3f.Dot(-L, lookAt);
scaler *= (float)Smoothstep(CosDeg(lt.LightSpotRadius2), CosDeg(lt.LightSpotRadius1), cosDir);
}
if (scaler > 0)
{
litColor.Red += lt.LightColor.Red * scaler * sign;
litColor.Green += lt.LightColor.Green * scaler * sign;
litColor.Blue += lt.LightColor.Blue * scaler * sign;
}
}
}
return litColor;
}
// This calculates the highlight/selection color
private Color4 CalculateHighlightColor(bool ishighlighted, bool isselected)
{
if(!ishighlighted && !isselected) return new Color4(); //mxd
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)
{
if(g.RenderAsSky && General.Settings.GZDrawSky)
{
skygeo.Add(g);
}
else
{
switch(g.RenderPass)
{
case RenderPass.Solid:
if(!solidgeo.ContainsKey(g.Texture))
solidgeo.Add(g.Texture, new List<VisualGeometry>());
solidgeo[g.Texture].Add(g);
break;
case RenderPass.Mask:
if(!maskedgeo.ContainsKey(g.Texture))
maskedgeo.Add(g.Texture, new List<VisualGeometry>());
maskedgeo[g.Texture].Add(g);
break;
case RenderPass.Additive:
case RenderPass.Alpha:
translucentgeo.Add(g);
break;
default:
throw new NotImplementedException("Geometry rendering of " + g.RenderPass + " render pass is not implemented!");
}
}
}
}
// This collects a visual sector's geometry for rendering
public void AddThingGeometry(VisualThing t)
{
// The thing might have changed, especially when doing realtime editing from the edit thing dialog, so update it if necessary
t.Update();
//mxd. Gather lights
if (General.Settings.GZDrawLightsMode != LightRenderMode.NONE && !fullbrightness && t.LightType != null)
{
t.UpdateLightRadius();
if (t.LightRadius > 0)
{
if (t.LightType != null && t.LightType.LightAnimated)
t.UpdateBoundingBox();
lightthings.Add(t);
}
}
//mxd. Gather models
if((t.Thing.RenderMode == ThingRenderMode.MODEL || t.Thing.RenderMode == ThingRenderMode.VOXEL) &&
(General.Settings.GZDrawModelsMode == ModelRenderMode.ALL ||
General.Settings.GZDrawModelsMode == ModelRenderMode.ACTIVE_THINGS_FILTER ||
(General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && t.Selected)))
{
if (t.RenderPass == RenderPass.Mask ||
t.RenderPass == RenderPass.Solid ||
(t.RenderPass == RenderPass.Alpha && (t.VertexColor & 0xFF000000) == 0xFF000000))
{
ModelData mde = General.Map.Data.ModeldefEntries[t.Thing.Type];
if (!maskedmodelthings.ContainsKey(mde)) maskedmodelthings.Add(mde, new List<VisualThing>());
maskedmodelthings[mde].Add(t);
}
else if (t.RenderPass == RenderPass.Alpha || t.RenderPass == RenderPass.Additive)
{
translucentmodelthings.Add(t);
}
else
{
throw new NotImplementedException("Thing model rendering of " + t.RenderPass + " render pass is not implemented!");
}
}
// Gather regular things
else
{
//mxd. Set correct texture, geobuffer and triangles count
t.UpdateSpriteFrame();
//Must have a texture!
if(t.Texture != null)
{
//mxd
switch(t.RenderPass)
{
case RenderPass.Solid:
if(!solidthings.ContainsKey(t.Texture)) solidthings.Add(t.Texture, new List<VisualThing>());
solidthings[t.Texture].Add(t);
break;
case RenderPass.Mask:
if(!maskedthings.ContainsKey(t.Texture)) maskedthings.Add(t.Texture, new List<VisualThing>());
maskedthings[t.Texture].Add(t);
break;
case RenderPass.Additive:
case RenderPass.Alpha:
translucentthings.Add(t);
break;
default:
throw new NotImplementedException("Thing rendering of " + t.RenderPass + " render pass is not implemented!");
}
}
}
//mxd. Add to the plain list
allthings.Add(t);
}
//mxd
public void SetVisualVertices(List<VisualVertex> verts) { visualvertices = verts; }
public void SetVisualSlopeHandles(ICollection<VisualSlope> handles) { visualslopehandles = handles; }
//mxd
public void SetEventLines(List<Line3D> lines) { eventlines = lines; }
//mxd
private static bool BoundingBoxesIntersect(Vector3D[] bbox1, Vector3D[] bbox2)
{
Vector3D dist = bbox1[0] - bbox2[0];
Vector3D halfSize1 = bbox1[0] - bbox1[1];
Vector3D halfSize2 = bbox2[0] - bbox2[1];
return (halfSize1.x + halfSize2.x >= Math.Abs(dist.x) && halfSize1.y + halfSize2.y >= Math.Abs(dist.y) && halfSize1.z + halfSize2.z >= Math.Abs(dist.z));
}
// This renders the crosshair
public void RenderCrosshair()
{
//mxd
world = Matrix.Identity;
graphics.SetUniform(UniformName.world, world);
// Set renderstates
graphics.SetCullMode(Cull.None);
graphics.SetZEnable(false);
graphics.SetAlphaBlendEnable(true);
graphics.SetAlphaTestEnable(false);
graphics.SetSourceBlend(Blend.SourceAlpha);
graphics.SetDestinationBlend(Blend.InverseSourceAlpha);
graphics.SetShader(ShaderName.display2d_normal);
graphics.SetUniform(UniformName.projection, world * view2d);
graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
graphics.SetUniform(UniformName.rendersettings, new Vector4f(1.0f, 1.0f, 0.0f, 1.0f));
graphics.SetSamplerFilter(General.Settings.VisualBilinear ? TextureFilter.Linear : TextureFilter.Nearest);
// Texture
if (crosshairbusy)
{
graphics.SetTexture(General.Map.Data.CrosshairBusy3D.Texture);
}
else
{
graphics.SetTexture(General.Map.Data.Crosshair3D.Texture);
}
// Draw
graphics.Draw(PrimitiveType.TriangleStrip, 0, 2, crosshairverts);
if (General.Settings.ShowFPS)
General.Map.Renderer2D.RenderText(fpsLabel);
}
// This switches fog on and off
public void SetFogMode(bool usefog)
{
if (usefog)
{
graphics.SetUniform(UniformName.fogsettings, new Vector4f(General.Settings.ViewDistance * FOG_RANGE, General.Settings.ViewDistance, 0.0f, 0.0f));
}
else
{
graphics.SetUniform(UniformName.fogsettings, new Vector4f(-1.0f));
}
}
// This siwtches crosshair busy icon on and off
public void SetCrosshairBusy(bool busy)
{
crosshairbusy = busy;
}
#endregion
}
}