diff --git a/assets/Shaders/default.frag b/assets/Shaders/default.frag index 1d0a2e9e66..d9d7fba73c 100644 --- a/assets/Shaders/default.frag +++ b/assets/Shaders/default.frag @@ -66,6 +66,25 @@ struct Light }; uniform Light uLight; +struct DynamicLight { + int type; + vec3 position; + vec3 direction; + vec4 color; + float range; + float rangeSquared; + float spotCutoff; + float power; + float exposure; + int isNormalize; + float radius; + int softFalloff; + float softness; + vec2 areaSize; +}; +uniform int uDynamicLightCount; +uniform DynamicLight uDynamicLights[16]; + // Inputs from vertex shader in vec3 vNormal; in vec4 vPosLightSpace0; @@ -212,17 +231,7 @@ void main(void) // Multiply material alpha by it's opacity finalColor.a *= uOpacity; - /* - * NOTES: - * Unused alpha functions must not be added to the shader - * This has a nasty affect on framerates - * - * A switch case block is also ~30% slower than the else-if - * - * Numbers used are those from the GL.AlphaFunction enum to allow - * for direct casts - */ - if(uAlphaTest.x == 513) // Less + if(uAlphaTest.x == float(ALPHA_LESS)) // Less { if(finalColor.a >= uAlphaTest.y) { @@ -252,10 +261,118 @@ void main(void) */ float shadow = CalculateShadowFactor(); + vec3 dynamicLightSum = vec3(0.0); + if ((uMaterialFlags & 1) == 0 && (uMaterialFlags & 4) == 0 && uDynamicLightCount > 0) + { + // Precalculated constants for faster lighting calculations + const float ONE_OVER_FOUR_PI = 0.0795774715; // 1.0 / (4.0 * PI) + const float TWO_PI = 6.283185307; // 2.0 * PI + const float FOUR_PI = 12.566370614; // 4.0 * PI + vec3 N = normalize(vNormal); + for (int i = 0; i < uDynamicLightCount; i++) + { + vec3 toLight = uDynamicLights[i].position - oViewPos.xyz; + float d2 = dot(toLight, toLight); + if (d2 <= uDynamicLights[i].rangeSquared) + { + float d = sqrt(d2); + + // 1. Calculate Intensity: Power in Watts, Exposure + float intensity = uDynamicLights[i].power * exp2(uDynamicLights[i].exposure); + vec3 lightColor = uDynamicLights[i].color.rgb * intensity; + + // 2. Attenuation: SoftFalloff, Radius + float denom = d2 + uDynamicLights[i].radius * uDynamicLights[i].radius; + float att = 1.0 / max(denom, 0.0001); + if (uDynamicLights[i].softFalloff != 0) + { + att *= clamp((uDynamicLights[i].range - d) / max(0.001, uDynamicLights[i].range * 0.2), 0.0, 1.0); + } + + if (uDynamicLights[i].type == 2) + { + // AREA LIGHT + // Construct orthonormal coordinate basis from the light normal (direction) + vec3 normal = normalize(uDynamicLights[i].direction); + vec3 right = normalize(cross(normal, abs(normal.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, 0.0, 1.0))); + vec3 up = cross(right, normal); + + vec2 halfSize = uDynamicLights[i].areaSize * 0.5; + + // Calculate the four vertices of the rectangular light in view space + vec3 P[4]; + P[0] = uDynamicLights[i].position - right * halfSize.x - up * halfSize.y; + P[1] = uDynamicLights[i].position + right * halfSize.x - up * halfSize.y; + P[2] = uDynamicLights[i].position + right * halfSize.x + up * halfSize.y; + P[3] = uDynamicLights[i].position - right * halfSize.x + up * halfSize.y; + + // Integrate cosine-weighted illumination analytically using projected solid angle (boundary integration) + float irradiance = 0.0; + vec3 p[4]; + for (int j = 0; j < 4; j++) + { + p[j] = normalize(P[j] - oViewPos.xyz); + } + + for (int j = 0; j < 4; j++) + { + int next = (j + 1) % 4; + vec3 p1 = p[j]; + vec3 p2 = p[next]; + + float cosTheta = clamp(dot(p1, p2), -1.0, 1.0); + float theta = acos(cosTheta); + + vec3 crossP = cross(p1, p2); + float len = length(crossP); + if (len > 0.0001) + { + vec3 g = crossP / len; + irradiance += theta * dot(g, N); + } + } + irradiance = max(irradiance / TWO_PI, 0.0); + + // Double-sided / backface check (light only emits forward) + float frontFacing = clamp(dot(N, normalize(toLight)), 0.0, 1.0); + irradiance *= frontFacing; + + dynamicLightSum += lightColor * irradiance * att; + } + else + { + // POINT/SPOT LIGHT + vec3 L = toLight / d; + float solidAngle = TWO_PI * (1.0 - uDynamicLights[i].spotCutoff); + bool normalizeSpot = (uDynamicLights[i].type == 1 && uDynamicLights[i].isNormalize != 0); + float normalizedIntensity = intensity / (normalizeSpot ? max(solidAngle, 0.0001) : FOUR_PI); + vec3 normalizedLightColor = uDynamicLights[i].color.rgb * normalizedIntensity; + + // Spot Cone Attenuation (Branchless) + vec3 lightToFrag = -L; + float spotDot = dot(lightToFrag, uDynamicLights[i].direction); + float outerCutoff = uDynamicLights[i].spotCutoff; + + float softnessFactor = clamp(uDynamicLights[i].softness, 0.0, 1.0); + float innerCutoff = mix(1.0, outerCutoff, 1.0 - softnessFactor); + + float intensityFactor = clamp((spotDot - outerCutoff) / max(innerCutoff - outerCutoff, 0.0001), 0.0, 1.0); + float spotAtt = smoothstep(0.0, 1.0, intensityFactor) * step(outerCutoff, spotDot); + + att *= mix(1.0, spotAtt, float(uDynamicLights[i].type == 1)); + + float nDotL = abs(dot(N, L)); + dynamicLightSum += normalizedLightColor * nDotL * att; + } + } + } + } + if ((uMaterialFlags & 1) == 0 && (uMaterialFlags & 4) == 0) { - // Material is not emissive, apply shadow to the light factor - finalColor.rgb *= (oLightResult.rgb * shadow); + // Material is not emissive, apply shadow to the light factor and add dynamic lights + vec3 totalLight = oLightResult.rgb * shadow + dynamicLightSum; + finalColor.rgb *= totalLight; finalColor.a *= oLightResult.a; } else diff --git a/source/LibRender2/BaseRenderer.cs b/source/LibRender2/BaseRenderer.cs index 57f4ca9510..71d90303e2 100644 --- a/source/LibRender2/BaseRenderer.cs +++ b/source/LibRender2/BaseRenderer.cs @@ -156,6 +156,9 @@ public Vector2 ScaleFactor protected internal AbstractShader CurrentShader; public Shader DefaultShader; + + public List ActiveSceneLights = new List(); + public List TempObjectLights = new List(); /// Manages the Cascaded Shadow Mapping (CSM) system. public Shadows Shadows; @@ -1168,6 +1171,117 @@ public void ResetShader(Shader shader) shader.SetTexture(0); shader.SetBrightness(1.0f); shader.SetOpacity(1.0f); + shader.SetDynamicLights(new List(), Matrix4D.Identity, 0); + } + + private static bool logged = false; + + private void UpdateLightsForObject(Shader shader, ObjectState state) + { + if (ActiveSceneLights.Count <= currentOptions.DynamicLightLimit) + { + return; + } + + Vector3 objPos = new Vector3(state.ModelMatrix.Row3.X, state.ModelMatrix.Row3.Y, state.ModelMatrix.Row3.Z); + + TempObjectLights.Clear(); + TempObjectLights.AddRange(ActiveSceneLights); + TempObjectLights.Sort((a, b) => + { + double distA = (a.Position - objPos).NormSquared(); + double distB = (b.Position - objPos).NormSquared(); + return distA.CompareTo(distB); + }); + + shader.SetDynamicLights(TempObjectLights, CurrentViewMatrix, currentOptions.DynamicLightLimit); + } + + public void UpdateActiveLights(Shader shader) + { + if (shader == null) + { + return; + } + ActiveSceneLights.Clear(); + Vector3 cameraPos = Camera.AbsolutePosition; + Matrix4D lightViewMatrix = Camera.TranslationMatrix * CurrentViewMatrix; + + var halfHFOV = Camera.HorizontalViewingAngle * 0.5; + var halfVFOV = Camera.VerticalViewingAngle * 0.5; + var tanH = Math.Tan(halfHFOV); + var tanV = Math.Tan(halfVFOV); + var viewDistance = currentOptions.ViewingDistance; + + bool IsLightInFrustum(SceneLight light) + { + var dist = (light.Position - cameraPos).Norm(); + if (dist - light.Range > viewDistance) return false; + + Vector3 viewPos = light.Position; + viewPos.Transform(lightViewMatrix, false); + + if (viewPos.Z - light.Range > 0) return false; + + var xLimit = -viewPos.Z * tanH + light.Range; + if (Math.Abs(viewPos.X) > xLimit) return false; + + var yLimit = -viewPos.Z * tanV + light.Range; + if (Math.Abs(viewPos.Y) > yLimit) return false; + + return true; + } + + lock (VisibleObjects.LockObject) + { + foreach (var kvp in VisibleObjects.Objects) + { + ObjectState obj = kvp.Key; + if (obj.Lights != null && obj.Lights.Count > 0) + { + for (int j = 0; j < obj.Lights.Count; j++) + { + SceneLight light = obj.Lights[j]; + if (IsLightInFrustum(light)) + { + ActiveSceneLights.Add(light); + } + } + } + else if (obj.Light != null) + { + if (IsLightInFrustum(obj.Light)) + { + ActiveSceneLights.Add(obj.Light); + } + } + } + } + // Sort by squared distance to camera + ActiveSceneLights.Sort((a, b) => + { + double distA = (a.Position - cameraPos).NormSquared(); + double distB = (b.Position - cameraPos).NormSquared(); + return distA.CompareTo(distB); + }); + + if (ActiveSceneLights.Count > 0 && !logged) + { + logged = true; + for (int i = 0; i < ActiveSceneLights.Count; i++) + { + SceneLight light = ActiveSceneLights[i]; + Vector3 viewPos = light.Position; + viewPos.Transform(lightViewMatrix, false); + Vector3 viewDir = light.Direction; + viewDir.Transform(CurrentViewMatrix, true); + viewDir.Normalize(); + System.Console.WriteLine( + $"[LIGHT_LOG] CamPos={cameraPos}, LightPos={light.Position}, ViewPos={viewPos}, LightDir={light.Direction}, ViewDir={viewDir}"); + } + } + + shader.SetDynamicLights(ActiveSceneLights, CurrentViewMatrix, currentOptions.DynamicLightLimit); shader.SetObjectIndex(0); shader.SetAlphaTest(false); } @@ -1319,6 +1433,7 @@ public void RenderFace(Shader shader, ObjectState state, MeshFace face, bool deb lastModelMatrix = state.ModelMatrix * Camera.TranslationMatrix; lastModelViewMatrix = lastModelMatrix * CurrentViewMatrix; sendToShader = true; + UpdateLightsForObject(shader, state); } if (state.Prototype.Mesh.Vertices.Length < 1) @@ -1856,6 +1971,145 @@ public void SetWindowSize(int width, int height) } } + public void DrawLightVisuals() + { + bool anyVisual = false; + for (int i = 0; i < ActiveSceneLights.Count; i++) + { + if (ActiveSceneLights[i].Visual) + { + anyVisual = true; + break; + } + } + if (!anyVisual) return; + + ResetOpenGlState(); + if (AvailableNewRenderer) + { + CurrentShader.Deactivate(); + } + + unsafe + { + GL.MatrixMode(MatrixMode.Projection); + GL.PushMatrix(); + fixed (double* matrixPointer = &CurrentProjectionMatrix.Row0.X) + { + GL.LoadMatrix(matrixPointer); + } + + GL.MatrixMode(MatrixMode.Modelview); + GL.PushMatrix(); + fixed (double* matrixPointer = &CurrentViewMatrix.Row0.X) + { + GL.LoadMatrix(matrixPointer); + } + + Matrix4D m = Camera.TranslationMatrix; + double* matrixPointer2 = &m.Row0.X; + GL.MultMatrix(matrixPointer2); + } + + GL.Disable(EnableCap.Texture2D); + GL.Disable(EnableCap.Lighting); + GL.Disable(EnableCap.DepthTest); + + for (int i = 0; i < ActiveSceneLights.Count; i++) + { + SceneLight light = ActiveSceneLights[i]; + if (!light.Visual) continue; + + // Draw a small cross at light position + GL.Begin(PrimitiveType.Lines); + GL.Color4(light.Color.R, light.Color.G, light.Color.B, 1.0f); + + Vector3 pos = light.Position; + double size = 0.5; + GL.Vertex3(pos.X - size, pos.Y, pos.Z); + GL.Vertex3(pos.X + size, pos.Y, pos.Z); + + GL.Vertex3(pos.X, pos.Y - size, pos.Z); + GL.Vertex3(pos.X, pos.Y + size, pos.Z); + + GL.Vertex3(pos.X, pos.Y, pos.Z - size); + GL.Vertex3(pos.X, pos.Y, pos.Z + size); + + if (light.Type == SceneLightType.Spot) + { + Vector3 dir = light.Direction; + Vector3 target = pos + dir * light.Range; + + // Draw central direction line + GL.Vertex3(pos.X, pos.Y, pos.Z); + GL.Vertex3(target.X, target.Y, target.Z); + + // Draw cone base outline + double cutoffAngle = Math.Acos(light.SpotCutoff); + double radius = light.Range * Math.Tan(cutoffAngle); + + Vector3 right = Vector3.Cross(dir, Vector3.Up); + if (right.NormSquared() < 0.001) + { + right = Vector3.Cross(dir, Vector3.Right); + } + right.Normalize(); + Vector3 up = Vector3.Cross(right, dir); + up.Normalize(); + + int segments = 8; + Vector3 lastConePoint = Vector3.Zero; + for (int j = 0; j <= segments; j++) + { + double angle = (j * 2.0 * Math.PI) / segments; + Vector3 conePoint = target + (right * Math.Cos(angle) + up * Math.Sin(angle)) * radius; + + GL.Vertex3(pos.X, pos.Y, pos.Z); + GL.Vertex3(conePoint.X, conePoint.Y, conePoint.Z); + + if (j > 0) + { + GL.Vertex3(lastConePoint.X, lastConePoint.Y, lastConePoint.Z); + GL.Vertex3(conePoint.X, conePoint.Y, conePoint.Z); + } + lastConePoint = conePoint; + } + } + else if (light.Type == SceneLightType.Point) + { + int segments = 16; + for (int plane = 0; plane < 3; plane++) + { + Vector3 lastPt = Vector3.Zero; + for (int j = 0; j <= segments; j++) + { + double angle = (j * 2.0 * Math.PI) / segments; + double dx = light.Range * Math.Cos(angle); + double dy = light.Range * Math.Sin(angle); + + Vector3 pt = pos; + if (plane == 0) { pt.X += dx; pt.Y += dy; } + else if (plane == 1) { pt.Y += dx; pt.Z += dy; } + else { pt.X += dx; pt.Z += dy; } + + if (j > 0) + { + GL.Vertex3(lastPt.X, lastPt.Y, lastPt.Z); + GL.Vertex3(pt.X, pt.Y, pt.Z); + } + lastPt = pt; + } + } + } + GL.End(); + } + + GL.PopMatrix(); + GL.MatrixMode(MatrixMode.Projection); + GL.PopMatrix(); + GL.Enable(EnableCap.DepthTest); + } + public ConcurrentQueue RenderThreadJobs; /// This method is used during loading to run commands requiring an OpenGL context in the main render loop diff --git a/source/LibRender2/Shaders/Shader.cs b/source/LibRender2/Shaders/Shader.cs index d2a97bd39f..c6e261a0b4 100644 --- a/source/LibRender2/Shaders/Shader.cs +++ b/source/LibRender2/Shaders/Shader.cs @@ -22,6 +22,7 @@ //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +using System.Collections.Generic; using LibRender2.Fogs; using OpenBveApi.Colors; using OpenBveApi.Math; @@ -67,6 +68,21 @@ public class Shader : AbstractShader private readonly int uLightSpaceMatrix3Location; private readonly int uModelMatrixLocation; private readonly int uCurrentViewMatrixLocation; + private readonly int uDynamicLightCountLocation; + private readonly int[] uDynamicLightTypeLocation = new int[16]; + private readonly int[] uDynamicLightPositionLocation = new int[16]; + private readonly int[] uDynamicLightDirectionLocation = new int[16]; + private readonly int[] uDynamicLightColorLocation = new int[16]; + private readonly int[] uDynamicLightRangeLocation = new int[16]; + private readonly int[] uDynamicLightRangeSquaredLocation = new int[16]; + private readonly int[] uDynamicLightSpotCutoffLocation = new int[16]; + private readonly int[] uDynamicLightPowerLocation = new int[16]; + private readonly int[] uDynamicLightExposureLocation = new int[16]; + private readonly int[] uDynamicLightNormalizeLocation = new int[16]; + private readonly int[] uDynamicLightRadiusLocation = new int[16]; + private readonly int[] uDynamicLightSoftFalloffLocation = new int[16]; + private readonly int[] uDynamicLightSoftnessLocation = new int[16]; + private readonly int[] uDynamicLightAreaSizeLocation = new int[16]; /// @@ -103,6 +119,24 @@ public Shader(BaseRenderer Renderer, string vertexShaderName, string fragmentSha uLightSpaceMatrix3Location = GL.GetUniformLocation(Handle, "uLightSpaceMatrix3"); uModelMatrixLocation = GL.GetUniformLocation(Handle, "uModelMatrix"); uCurrentViewMatrixLocation = GL.GetUniformLocation(Handle, "uCurrentViewMatrix"); + uDynamicLightCountLocation = GL.GetUniformLocation(Handle, "uDynamicLightCount"); + for (int i = 0; i < 16; i++) + { + uDynamicLightTypeLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].type"); + uDynamicLightPositionLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].position"); + uDynamicLightDirectionLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].direction"); + uDynamicLightColorLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].color"); + uDynamicLightRangeLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].range"); + uDynamicLightRangeSquaredLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].rangeSquared"); + uDynamicLightSpotCutoffLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].spotCutoff"); + uDynamicLightPowerLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].power"); + uDynamicLightExposureLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].exposure"); + uDynamicLightNormalizeLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].isNormalize"); + uDynamicLightRadiusLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].radius"); + uDynamicLightSoftFalloffLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].softFalloff"); + uDynamicLightSoftnessLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].softness"); + uDynamicLightAreaSizeLocation[i] = GL.GetUniformLocation(Handle, $"uDynamicLights[{i}].areaSize"); + } VertexLayout = GetVertexLayout(); UniformLayout = GetUniformLayout(); @@ -537,6 +571,68 @@ private static float[] Matrix4DToFloatArray(OpenBveApi.Math.Matrix4D m) }; } + private readonly List lastBoundLights = new List(); + private Matrix4D lastViewMatrix = Matrix4D.Identity; + + public void SetDynamicLights(List lights, Matrix4D viewMatrix, int maxLimit) + { + int count = System.Math.Min(lights.Count, maxLimit); + + bool identical = lastBoundLights.Count == count && lastViewMatrix == viewMatrix; + if (identical) + { + for (int i = 0; i < count; i++) + { + if (lastBoundLights[i] != lights[i]) + { + identical = false; + break; + } + } + } + + if (identical) + { + return; + } + + lastBoundLights.Clear(); + for (int i = 0; i < count; i++) + { + lastBoundLights.Add(lights[i]); + } + lastViewMatrix = viewMatrix; + + GL.ProgramUniform1(Handle, uDynamicLightCountLocation, count); + for (int i = 0; i < count; i++) + { + SceneLight light = lights[i]; + + Matrix4D lightViewMatrix = Renderer.Camera != null ? Renderer.Camera.TranslationMatrix * viewMatrix : viewMatrix; + Vector3 viewPos = light.Position; + viewPos.Transform(lightViewMatrix, false); + + Vector3 viewDir = light.Direction; + viewDir.Transform(viewMatrix, true); + viewDir.Normalize(); + + GL.ProgramUniform1(Handle, uDynamicLightTypeLocation[i], (int)light.Type); + GL.ProgramUniform3(Handle, uDynamicLightPositionLocation[i], (float)viewPos.X, (float)viewPos.Y, (float)viewPos.Z); + GL.ProgramUniform3(Handle, uDynamicLightDirectionLocation[i], (float)viewDir.X, (float)viewDir.Y, (float)viewDir.Z); + GL.ProgramUniform4(Handle, uDynamicLightColorLocation[i], light.Color.R, light.Color.G, light.Color.B, light.Color.A); + GL.ProgramUniform1(Handle, uDynamicLightRangeLocation[i], light.Range); + GL.ProgramUniform1(Handle, uDynamicLightRangeSquaredLocation[i], light.RangeSquared); + GL.ProgramUniform1(Handle, uDynamicLightSpotCutoffLocation[i], light.SpotCutoff); + GL.ProgramUniform1(Handle, uDynamicLightPowerLocation[i], light.Power); + GL.ProgramUniform1(Handle, uDynamicLightExposureLocation[i], light.Exposure); + GL.ProgramUniform1(Handle, uDynamicLightNormalizeLocation[i], light.NormalizeCone ? 1 : 0); + GL.ProgramUniform1(Handle, uDynamicLightRadiusLocation[i], light.Radius); + GL.ProgramUniform1(Handle, uDynamicLightSoftFalloffLocation[i], light.SoftFalloff ? 1 : 0); + GL.ProgramUniform1(Handle, uDynamicLightSoftnessLocation[i], light.Softness); + GL.ProgramUniform2(Handle, uDynamicLightAreaSizeLocation[i], (float)light.AreaSize.X, (float)light.AreaSize.Y); + } + } + #endregion } } diff --git a/source/ObjectViewer/Graphics/NewRendererS.cs b/source/ObjectViewer/Graphics/NewRendererS.cs index 86d1b1b41f..c83aa8c474 100644 --- a/source/ObjectViewer/Graphics/NewRendererS.cs +++ b/source/ObjectViewer/Graphics/NewRendererS.cs @@ -160,6 +160,11 @@ internal void RenderScene(double timeElapsed) DefaultShader.SetLightDiffuse(Lighting.OptionDiffuseColor); DefaultShader.SetLightSpecular(Lighting.OptionSpecularColor); DefaultShader.SetLightModel(Lighting.LightModel); + UpdateActiveLights(DefaultShader); + } + else + { + DefaultShader.SetDynamicLights(new List(), CurrentViewMatrix, 0); } DefaultShader.SetTexture(0); DefaultShader.SetCurrentProjectionMatrix(CurrentProjectionMatrix); @@ -241,6 +246,7 @@ internal void RenderScene(double timeElapsed) lastVAO = -1; } + DrawLightVisuals(); // render overlays ResetOpenGlState(); OptionLighting = false; diff --git a/source/OpenBVE/Graphics/NewRenderer.cs b/source/OpenBVE/Graphics/NewRenderer.cs index 7ae9dca3e5..b6eeefa901 100644 --- a/source/OpenBVE/Graphics/NewRenderer.cs +++ b/source/OpenBVE/Graphics/NewRenderer.cs @@ -232,6 +232,11 @@ internal void RenderScene(double TimeElapsed, double RealTimeElapsed) DefaultShader.SetLightDiffuse(Lighting.OptionDiffuseColor); DefaultShader.SetLightSpecular(Lighting.OptionSpecularColor); DefaultShader.SetLightModel(Lighting.LightModel); + UpdateActiveLights(DefaultShader); + } + else + { + DefaultShader.SetDynamicLights(new List(), CurrentViewMatrix, 0); } Fog.Set(); DefaultShader.SetTexture(0); @@ -502,6 +507,7 @@ internal void RenderScene(double TimeElapsed, double RealTimeElapsed) OptionLighting = false; Touch.RenderScene(); + DrawLightVisuals(); // render overlays ResetOpenGlState(); UnsetAlphaFunc(); diff --git a/source/OpenBVE/System/Options.cs b/source/OpenBVE/System/Options.cs index db2bd96ce3..09f53a15a9 100644 --- a/source/OpenBVE/System/Options.cs +++ b/source/OpenBVE/System/Options.cs @@ -326,6 +326,7 @@ public override void Save(string fileName) Builder.AppendLine("shadowbias = " + ShadowBias.ToString(Culture)); Builder.AppendLine("shadownormalbias = " + ShadowNormalBias.ToString(Culture)); Builder.AppendLine("shadowfiltercascades = " + (ShadowFilterCascades ? "true" : "false")); + Builder.AppendLine("dynamiclightlimit = " + DynamicLightLimit.ToString(Culture)); Builder.AppendLine("fpslimit = " + FPSLimit.ToString(Culture)); Builder.AppendLine(); Builder.AppendLine("[objectOptimization]"); @@ -540,6 +541,9 @@ internal static void LoadOptions() block.TryGetValue(OptionsKey.ShadowNormalBias, ref CurrentOptions.ShadowNormalBias); if (CurrentOptions.ShadowNormalBias < 0.0) CurrentOptions.ShadowNormalBias = 0.0; block.GetValue(OptionsKey.ShadowFilterCascades, out Interface.CurrentOptions.ShadowFilterCascades); + block.TryGetValue(OptionsKey.DynamicLightLimit, ref CurrentOptions.DynamicLightLimit); + if (CurrentOptions.DynamicLightLimit < 0) CurrentOptions.DynamicLightLimit = 0; + if (CurrentOptions.DynamicLightLimit > 16) CurrentOptions.DynamicLightLimit = 16; block.GetValue(OptionsKey.FPSLimit, out CurrentOptions.FPSLimit); if (CurrentOptions.FPSLimit < 0) { diff --git a/source/OpenBVE/UserInterface/formMain.cs b/source/OpenBVE/UserInterface/formMain.cs index f48c5853a9..c27545daa5 100644 --- a/source/OpenBVE/UserInterface/formMain.cs +++ b/source/OpenBVE/UserInterface/formMain.cs @@ -66,6 +66,8 @@ internal static LaunchParameters ShowMainDialog(LaunchParameters initial) private Image GamepadImage; private Image XboxImage; private Image ZukiImage; + private NumericUpDown updownDynamicLightLimit; + private Label labelDynamicLightLimit; // ==== // form @@ -120,6 +122,41 @@ private void formMain_Load(object sender, EventArgs e) radioButtonPackages.AutoSize = false; radioButtonPackages.Size = new Size(buttonClose.Width, buttonClose.Height); radioButtonPackages.TextAlign = ContentAlignment.MiddleCenter; + // Shift controls in panelOptionsRight for dynamic light limit option + { + int shift = 28; + groupboxSound.Height += shift; + foreach (Control c in panelOptionsRight.Controls) + { + if (c != groupboxSound && c.Top >= groupboxSound.Top) + { + c.Top += shift; + } + } + } + + // Create updownDynamicLightLimit + updownDynamicLightLimit = new NumericUpDown(); + updownDynamicLightLimit.Name = "updownDynamicLightLimit"; + updownDynamicLightLimit.Anchor = AnchorStyles.Top | AnchorStyles.Right; + updownDynamicLightLimit.Location = new Point(updownSoundNumber.Left, updownSoundNumber.Top + 28); + updownDynamicLightLimit.Size = updownSoundNumber.Size; + updownDynamicLightLimit.Minimum = 0; + updownDynamicLightLimit.Maximum = 16; + updownDynamicLightLimit.Value = Interface.CurrentOptions.DynamicLightLimit; + + // Create labelDynamicLightLimit + labelDynamicLightLimit = new Label(); + labelDynamicLightLimit.Name = "labelDynamicLightLimit"; + labelDynamicLightLimit.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + labelDynamicLightLimit.Location = new Point(labelSoundNumber.Left, labelSoundNumber.Top + 28); + labelDynamicLightLimit.Size = labelSoundNumber.Size; + labelDynamicLightLimit.Text = "Max dynamic lights:"; + labelDynamicLightLimit.TextAlign = ContentAlignment.TopRight; + + groupboxSound.Controls.Add(updownDynamicLightLimit); + groupboxSound.Controls.Add(labelDynamicLightLimit); + // options Interface.LoadLogs(); { @@ -519,6 +556,7 @@ private void formMain_Load(object sender, EventArgs e) trackbarJoystickAxisThreshold.Value = b; } updownSoundNumber.Value = Interface.CurrentOptions.SoundNumber; + updownDynamicLightLimit.Value = Interface.CurrentOptions.DynamicLightLimit; checkboxWarningMessages.Checked = Interface.CurrentOptions.ShowWarningMessages; checkboxErrorMessages.Checked = Interface.CurrentOptions.ShowErrorMessages; comboBoxCompressionFormat.SelectedIndex = (int)Interface.CurrentOptions.packageCompressionType; @@ -796,6 +834,11 @@ private void ApplyLanguage() //Sound groupboxSound.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","misc_sound"}); labelSoundNumber.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","misc_sound_number"}); + labelDynamicLightLimit.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","misc_sound_dynamiclights"}); + if (labelDynamicLightLimit.Text == "misc_sound_dynamiclights") + { + labelDynamicLightLimit.Text = "Max dynamic lights:"; + } //Verbosity groupboxVerbosity.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","verbosity"}); checkboxWarningMessages.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","verbosity_warningmessages"}); @@ -1271,6 +1314,7 @@ private void formMain_FormClosing() Interface.CurrentOptions.AllowAxisEB = checkBoxEBAxis.Checked; Interface.CurrentOptions.JoystickAxisThreshold = (trackbarJoystickAxisThreshold.Value - (double)trackbarJoystickAxisThreshold.Minimum) / (trackbarJoystickAxisThreshold.Maximum - trackbarJoystickAxisThreshold.Minimum); Interface.CurrentOptions.SoundNumber = (int)Math.Round(updownSoundNumber.Value); + Interface.CurrentOptions.DynamicLightLimit = (int)Math.Round(updownDynamicLightLimit.Value); Interface.CurrentOptions.ShowWarningMessages = checkboxWarningMessages.Checked; Interface.CurrentOptions.ShowErrorMessages = checkboxErrorMessages.Checked; Interface.CurrentOptions.RouteFolder = textboxRouteFolder.Text; diff --git a/source/OpenBveApi/Objects/ObjectState.cs b/source/OpenBveApi/Objects/ObjectState.cs index bfac89f3d1..d316dd4880 100644 --- a/source/OpenBveApi/Objects/ObjectState.cs +++ b/source/OpenBveApi/Objects/ObjectState.cs @@ -1,4 +1,4 @@ -using System; +using System; using OpenBveApi.Math; namespace OpenBveApi.Objects @@ -77,6 +77,10 @@ public Matrix4D ModelMatrix private bool updateModelMatrix; /// The texture translation matrix to be applied public Matrix4D TextureTranslation; + /// The active scene light source for this object instance + public SceneLight Light; + /// The active scene light sources for this object instance + public System.Collections.Generic.List Lights = new System.Collections.Generic.List(); /// The starting track position, for static objects only. public float StartingDistance; /// The ending track position, for static objects only. @@ -104,7 +108,13 @@ public ObjectState(StaticObject prototype) : this() /// Clones this ObjectState public object Clone() { - return MemberwiseClone(); + ObjectState cloned = (ObjectState)MemberwiseClone(); + cloned.Lights = new System.Collections.Generic.List(); + foreach (var l in this.Lights) + { + cloned.Lights.Add(l.Clone()); + } + return cloned; } /// Reverses this ObjectState diff --git a/source/OpenBveApi/Objects/ObjectTypes/AnimatedObject/AnimatedObject.cs b/source/OpenBveApi/Objects/ObjectTypes/AnimatedObject/AnimatedObject.cs index e2afba0157..877906f95f 100644 --- a/source/OpenBveApi/Objects/ObjectTypes/AnimatedObject/AnimatedObject.cs +++ b/source/OpenBveApi/Objects/ObjectTypes/AnimatedObject/AnimatedObject.cs @@ -113,6 +113,10 @@ public class AnimatedObject public AnimationScript ColorFunction; /// Array of colors for ColorFunction public Color24[] Colors; + /// The dynamic light source associated with this object + public SceneLight Light; + /// The dynamic light sources associated with this object + public System.Collections.Generic.List Lights = new System.Collections.Generic.List(); /// Creates a new animated object public AnimatedObject(HostInterface host, string fileName = "") @@ -170,7 +174,9 @@ public AnimatedObject Clone() LEDInitialAngle = this.LEDInitialAngle, LEDLastAngle = this.LEDLastAngle, SectionIndex = this.SectionIndex, - IsPartOfTrain = false // will be set by the CarSection load if appropriate + IsPartOfTrain = false, // will be set by the CarSection load if appropriate + Light = this.Light?.Clone(), + Lights = this.Lights.Select(x => x.Clone()).ToList() }; if (this.LEDVectors != null) @@ -209,6 +215,7 @@ public bool IsFreeOfFunctions() if (this.TextureShiftXFunction != null | this.TextureShiftYFunction != null) return false; if (this.LEDFunction != null) return false; if (this.TrackFollowerFunction != null) return false; + if (this.Light != null || this.Lights.Count > 0) return false; return true; } @@ -731,7 +738,7 @@ public void Update(AbstractTrain Train, int CarIndex, double TrackPosition, Vect internalObject.WorldPosition = Position; } - if (ColorFunction != null && Colors != null) + if (ColorFunction != null && Colors != null && internalObject.Prototype != null && internalObject.Prototype.Mesh != null && internalObject.Prototype.Mesh.Materials != null && internalObject.Prototype.Mesh.Materials.Length > 0) { int color = (int)ColorFunction.LastResult; if (UpdateFunctions) @@ -740,6 +747,110 @@ public void Update(AbstractTrain Train, int CarIndex, double TrackPosition, Vect } internalObject.Prototype.Mesh.Materials[0].Color = Colors[color]; } + + if (this.Light != null || this.Lights.Count > 0) + { + Matrix4D lightMatrix = Matrix4D.Identity; + if (rotateX) + { + lightMatrix *= Matrix4D.CreateFromAxisAngle(new Vector3(RotateXDirection.X, RotateXDirection.Y, -RotateXDirection.Z), 2.0 * System.Math.PI - radianX); + } + if (rotateY) + { + lightMatrix *= Matrix4D.CreateFromAxisAngle(new Vector3(RotateYDirection.X, RotateYDirection.Y, -RotateYDirection.Z), 2.0 * System.Math.PI - radianY); + } + if (rotateZ) + { + lightMatrix *= Matrix4D.CreateFromAxisAngle(new Vector3(RotateZDirection.X, RotateZDirection.Y, -RotateZDirection.Z), 2.0 * System.Math.PI - radianZ); + } + lightMatrix *= (Matrix4D)new Transformation(Direction, Up, Side); + lightMatrix *= Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z); + + // Process Lights list + if (internalObject.Lights == null) + { + internalObject.Lights = new System.Collections.Generic.List(); + } + while (internalObject.Lights.Count < this.Lights.Count) + { + internalObject.Lights.Add(new SceneLight()); + } + while (internalObject.Lights.Count > this.Lights.Count) + { + internalObject.Lights.RemoveAt(internalObject.Lights.Count - 1); + } + + for (int j = 0; j < this.Lights.Count; j++) + { + SceneLight light = this.Lights[j]; + SceneLight internalLight = internalObject.Lights[j]; + internalLight.Type = light.Type; + internalLight.Color = light.Color; + internalLight.Range = light.Range; + internalLight.RangeSquared = light.RangeSquared; + internalLight.SpotCutoff = light.SpotCutoff; + internalLight.Visual = light.Visual; + internalLight.Power = light.Power; + internalLight.Exposure = light.Exposure; + internalLight.NormalizeCone = light.NormalizeCone; + internalLight.Radius = light.Radius; + internalLight.SoftFalloff = light.SoftFalloff; + internalLight.Angle = light.Angle; + internalLight.Softness = light.Softness; + internalLight.ShowCone = light.ShowCone; + + Vector3 pos = new Vector3(light.Position.X, light.Position.Y, -light.Position.Z); + pos.Transform(lightMatrix, false); + internalLight.Position = pos; + + Vector3 dir = new Vector3(light.Direction.X, light.Direction.Y, -light.Direction.Z); + dir.Transform(lightMatrix, true); + dir.Normalize(); + internalLight.Direction = dir; + } + + // Process single Light (compatibility) + if (this.Light != null) + { + if (internalObject.Light == null) + { + internalObject.Light = new SceneLight(); + } + internalObject.Light.Type = this.Light.Type; + internalObject.Light.Color = this.Light.Color; + internalObject.Light.Range = this.Light.Range; + internalObject.Light.RangeSquared = this.Light.RangeSquared; + internalObject.Light.SpotCutoff = this.Light.SpotCutoff; + internalObject.Light.Visual = this.Light.Visual; + internalObject.Light.Power = this.Light.Power; + internalObject.Light.Exposure = this.Light.Exposure; + internalObject.Light.NormalizeCone = this.Light.NormalizeCone; + internalObject.Light.Radius = this.Light.Radius; + internalObject.Light.SoftFalloff = this.Light.SoftFalloff; + internalObject.Light.Angle = this.Light.Angle; + internalObject.Light.Softness = this.Light.Softness; + internalObject.Light.ShowCone = this.Light.ShowCone; + + Vector3 pos = new Vector3(this.Light.Position.X, this.Light.Position.Y, -this.Light.Position.Z); + pos.Transform(lightMatrix, false); + internalObject.Light.Position = pos; + + Vector3 dir = new Vector3(this.Light.Direction.X, this.Light.Direction.Y, -this.Light.Direction.Z); + dir.Transform(lightMatrix, true); + dir.Normalize(); + internalObject.Light.Direction = dir; + } + else + { + internalObject.Light = internalObject.Lights.Count > 0 ? internalObject.Lights[0] : null; + } + } + else + { + internalObject.Light = null; + internalObject.Lights = new System.Collections.Generic.List(); + } + // visibility changed // TouchElement is handled by another function. if (!IsTouch) @@ -807,7 +918,21 @@ public void CreateObject(Vector3 Position, Transformation WorldTransformation, T } } - currentObject.Radius = System.Math.Sqrt(r); + double radius = System.Math.Sqrt(r); + if (currentObject.Object.Light != null || currentObject.Object.Lights.Count > 0) + { + double maxLightRange = 25.0; + if (currentObject.Object.Light != null) + { + maxLightRange = System.Math.Max(maxLightRange, currentObject.Object.Light.Range); + } + for (int k = 0; k < currentObject.Object.Lights.Count; k++) + { + maxLightRange = System.Math.Max(maxLightRange, currentObject.Object.Lights[k].Range); + } + radius = System.Math.Max(radius, maxLightRange); + } + currentObject.Radius = radius; currentObject.Visible = false; currentObject.Object.Initialize(0, ObjectType.Dynamic, false); currentHost.AnimatedWorldObjects[a] = currentObject; @@ -846,7 +971,21 @@ public void CreateObject(Vector3 Position, Transformation WorldTransformation, T } } - currentObject.Radius = System.Math.Sqrt(r); + double radius = System.Math.Sqrt(r); + if (currentObject.Object.Light != null || currentObject.Object.Lights.Count > 0) + { + double maxLightRange = 25.0; + if (currentObject.Object.Light != null) + { + maxLightRange = System.Math.Max(maxLightRange, currentObject.Object.Light.Range); + } + for (int k = 0; k < currentObject.Object.Lights.Count; k++) + { + maxLightRange = System.Math.Max(maxLightRange, currentObject.Object.Lights[k].Range); + } + radius = System.Math.Max(radius, maxLightRange); + } + currentObject.Radius = radius; currentObject.Visible = false; currentObject.Object.Initialize(0, ObjectType.Dynamic, false); currentHost.AnimatedWorldObjects[a] = currentObject; @@ -871,6 +1010,20 @@ public void Reverse(bool interior = false) t.Row3.Z *= -1.0f; state.Translation = t; } + if (Light != null) + { + Light.Position.X *= -1.0; + Light.Position.Z *= -1.0; + Light.Direction.X *= -1.0; + Light.Direction.Z *= -1.0; + } + for (int j = 0; j < Lights.Count; j++) + { + Lights[j].Position.X *= -1.0; + Lights[j].Position.Z *= -1.0; + Lights[j].Direction.X *= -1.0; + Lights[j].Direction.Z *= -1.0; + } TranslateXDirection.X *= -1.0; TranslateXDirection.Z *= -1.0; TranslateYDirection.X *= -1.0; diff --git a/source/OpenBveApi/Objects/SceneLight.cs b/source/OpenBveApi/Objects/SceneLight.cs new file mode 100644 index 0000000000..67a695f28f --- /dev/null +++ b/source/OpenBveApi/Objects/SceneLight.cs @@ -0,0 +1,102 @@ +using OpenBveApi.Colors; +using OpenBveApi.Math; + +namespace OpenBveApi.Objects +{ + /// The type of the dynamic light source + public enum SceneLightType + { + /// Point light source emitting light in all directions + Point = 0, + /// Spot light source emitting a cone of light in a specific direction + Spot = 1, + /// Area light source emitting light from a 2D surface + Area = 2 + } + + /// Represents a dynamic light source in the rendering scene + public class SceneLight + { + /// The light source type + public SceneLightType Type; + /// Position in world coordinates or relative to parent object + public Vector3 Position; + /// Direction vector for spot lights + public Vector3 Direction; + /// Light color + public Color128 Color; + /// Maximum range of the light in meters + public float Range; + /// Squared range of the light + public float RangeSquared; + /// Spotlight cutoff cosine (pre-calculated on CPU) + public float SpotCutoff; + /// Whether to show a visual helper for this light + public bool Visual; + /// Power of the light in Watts + public float Power; + /// Exposure multiplier + public float Exposure; + /// Whether to normalize power + public bool NormalizeCone; + /// Source radius (size) of the light source + public float Radius; + /// Whether to use soft falloff + public bool SoftFalloff; + /// Spot angle in degrees + public float Angle; + /// Spot softness factor (0 to 1) + public float Softness; + /// Whether to show the cone helper + public bool ShowCone; + /// Width and Height of the area light source + public Vector2 AreaSize; + + /// Creates a default scene light + public SceneLight() + { + Type = SceneLightType.Point; + Position = Vector3.Zero; + Direction = Vector3.Forward; + Color = Color128.White; + Range = 10.0f; + RangeSquared = 100.0f; + SpotCutoff = 0.5f; // cos(45 deg) + Visual = false; + Power = 12.5663706f; // 4 * PI (so default multiplier is 1.0) + Exposure = 0.0f; + NormalizeCone = true; + Radius = 0.0f; + SoftFalloff = true; + Angle = 45.0f; + Softness = 1.0f; + ShowCone = true; + AreaSize = new Vector2(1.0f, 1.0f); + } + + /// Clones the light source + public SceneLight Clone() + { + return new SceneLight + { + Type = this.Type, + Position = this.Position, + Direction = this.Direction, + Color = this.Color, + Range = this.Range, + RangeSquared = this.RangeSquared, + SpotCutoff = this.SpotCutoff, + Visual = this.Visual, + Power = this.Power, + Exposure = this.Exposure, + NormalizeCone = this.NormalizeCone, + Radius = this.Radius, + SoftFalloff = this.SoftFalloff, + Angle = this.Angle, + Softness = this.Softness, + ShowCone = this.ShowCone, + AreaSize = this.AreaSize + }; + } + } +} diff --git a/source/OpenBveApi/OpenBveApi.csproj b/source/OpenBveApi/OpenBveApi.csproj index c75e05d797..808593dee5 100644 --- a/source/OpenBveApi/OpenBveApi.csproj +++ b/source/OpenBveApi/OpenBveApi.csproj @@ -155,6 +155,7 @@ + diff --git a/source/OpenBveApi/System/BaseOptions.OptionsKey.cs b/source/OpenBveApi/System/BaseOptions.OptionsKey.cs index 9d93fac265..c813c2ddbd 100644 --- a/source/OpenBveApi/System/BaseOptions.OptionsKey.cs +++ b/source/OpenBveApi/System/BaseOptions.OptionsKey.cs @@ -57,6 +57,7 @@ public enum OptionsKey ShadowBias, ShadowNormalBias, ShadowFilterCascades, + DynamicLightLimit, LightAzimuth, LightElevation, diff --git a/source/OpenBveApi/System/BaseOptions.cs b/source/OpenBveApi/System/BaseOptions.cs index 135f660222..dd119eece5 100644 --- a/source/OpenBveApi/System/BaseOptions.cs +++ b/source/OpenBveApi/System/BaseOptions.cs @@ -96,6 +96,8 @@ public abstract class BaseOptions public double ShadowNormalBias = 2.0; /// Whether to filter shadow casters per cascade to improve performance. public bool ShadowFilterCascades = true; + /// The maximum number of active dynamic lights + public int DynamicLightLimit = 8; /// The sun azimuth in degrees diff --git a/source/Plugins/Object.Animated/Enums/AnimatedKey.cs b/source/Plugins/Object.Animated/Enums/AnimatedKey.cs index da21b9775f..6c41e9c424 100644 --- a/source/Plugins/Object.Animated/Enums/AnimatedKey.cs +++ b/source/Plugins/Object.Animated/Enums/AnimatedKey.cs @@ -1,4 +1,4 @@ -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace Plugin { /// The available keys found in an animated file @@ -63,6 +63,20 @@ internal enum AnimatedKey ScaleYScript, ScaleZFunction, ScaleZScript, - StateScript + StateScript, + Type, + Direction, + Color, + Range, + Visual, + Power, + Exposure, + Normalize, + SoftFalloff, + Angle, + Softness, + ShowCone, + Shadow, + Size } } diff --git a/source/Plugins/Object.Animated/Enums/AnimatedSection.cs b/source/Plugins/Object.Animated/Enums/AnimatedSection.cs index 7cd076c285..785e4092c0 100644 --- a/source/Plugins/Object.Animated/Enums/AnimatedSection.cs +++ b/source/Plugins/Object.Animated/Enums/AnimatedSection.cs @@ -1,4 +1,4 @@ -namespace Plugin +namespace Plugin { /// The available sections in an animated file internal enum AnimatedSection @@ -7,6 +7,7 @@ internal enum AnimatedSection Include, Object, Sound, - StateChangeSound + StateChangeSound, + Light } } diff --git a/source/Plugins/Object.Animated/Plugin.Parser.cs b/source/Plugins/Object.Animated/Plugin.Parser.cs index c2104658df..b6acb02267 100644 --- a/source/Plugins/Object.Animated/Plugin.Parser.cs +++ b/source/Plugins/Object.Animated/Plugin.Parser.cs @@ -1,4 +1,5 @@ using Formats.OpenBve; +using OpenBveApi.Colors; using OpenBveApi.FunctionScripting; using OpenBveApi.Interface; using OpenBveApi.Math; @@ -367,6 +368,137 @@ private static AnimatedObjectCollection ReadObject(string FileName, System.Text. } } + break; + case AnimatedSection.Light: + { + if (Result.Objects.Length == ObjectCount) + { + Array.Resize(ref Result.Objects, Result.Objects.Length << 1); + } + AnimatedObject animatedObj = new AnimatedObject(currentHost, FileName) + { + CurrentState = 0, + States = new ObjectState[] { new ObjectState() }, + TranslateXDirection = Vector3.Right, + TranslateYDirection = Vector3.Down, + TranslateZDirection = Vector3.Forward, + RotateXDirection = Vector3.Right, + RotateYDirection = Vector3.Down, + RotateZDirection = Vector3.Forward, + TextureShiftXDirection = Vector2.Right, + TextureShiftYDirection = Vector2.Down, + RefreshRate = 0.0, + }; + animatedObj.States[0].Prototype = new StaticObject(currentHost); + animatedObj.States[0].Prototype.Dynamic = true; + animatedObj.States[0].Translation = Matrix4D.Identity; + + SceneLight light = new SceneLight(); + + if (Block.GetEnumValue(AnimatedKey.Type, out SceneLightType type)) + { + light.Type = type; + } + + Block.TryGetVector3(AnimatedKey.Position, ',', ref light.Position); + Block.TryGetVector3(AnimatedKey.Direction, ',', ref light.Direction); + light.Direction.Normalize(); + + Color32 color32 = Color32.White; + if (Block.TryGetColor32(AnimatedKey.Color, ref color32)) + { + light.Color = new Color128(color32.R / 255.0f, color32.G / 255.0f, color32.B / 255.0f, color32.A / 255.0f); + } + + double range = 10.0; + if (Block.TryGetValue(AnimatedKey.Range, ref range, NumberRange.Positive)) + { + light.Range = (float)range; + light.RangeSquared = light.Range * light.Range; + } + + bool visual = false; + if (Block.TryGetValue(AnimatedKey.Visual, ref visual)) + { + light.Visual = visual; + light.ShowCone = visual; + } + + double power = 12.5663706; + if (Block.TryGetValue(AnimatedKey.Power, ref power, NumberRange.Positive)) + { + light.Power = (float)power; + } + + double exposure = 0.0; + if (Block.TryGetValue(AnimatedKey.Exposure, ref exposure)) + { + light.Exposure = (float)exposure; + } + + if (Block.GetValue(AnimatedKey.Normalize, out string normStr)) + { + string s = normStr.ToLowerInvariant().Trim(); + light.NormalizeCone = (s == "1" || s == "true"); + } + + double radius = 0.0; + if (Block.TryGetValue(AnimatedKey.Radius, ref radius, NumberRange.NonNegative)) + { + light.Radius = (float)radius; + } + + if (Block.GetValue(AnimatedKey.SoftFalloff, out string sfStr)) + { + string s = sfStr.ToLowerInvariant().Trim(); + light.SoftFalloff = (s == "1" || s == "true"); + } + + double angle = 45.0; + if (Block.TryGetValue(AnimatedKey.Angle, ref angle, NumberRange.Positive)) + { + light.Angle = (float)angle; + light.SpotCutoff = (float)System.Math.Cos((angle / 2.0) * System.Math.PI / 180.0); + } + + double softness = 1.0; + if (Block.TryGetValue(AnimatedKey.Softness, ref softness, NumberRange.NonNegative)) + { + light.Softness = (float)softness; + } + + if (Block.GetValue(AnimatedKey.ShowCone, out string scStr)) + { + string s = scStr.ToLowerInvariant().Trim(); + light.ShowCone = (s == "1" || s == "true"); + light.Visual = light.ShowCone; + } + + Block.TryGetVector2(AnimatedKey.Size, ',', ref light.AreaSize); + + animatedObj.Lights.Add(light); + animatedObj.Light = light; + + // Parse animation functions for the light object itself + Block.GetFunctionScript(new[] { AnimatedKey.RotateXFunction, AnimatedKey.RotateXFunctionRPN, AnimatedKey.RotateXScript }, Folder, out animatedObj.RotateXFunction); + Block.GetFunctionScript(new[] { AnimatedKey.RotateYFunction, AnimatedKey.RotateYFunctionRPN, AnimatedKey.RotateYScript }, Folder, out animatedObj.RotateYFunction); + Block.GetFunctionScript(new[] { AnimatedKey.RotateZFunction, AnimatedKey.RotateZFunctionRPN, AnimatedKey.RotateZScript }, Folder, out animatedObj.RotateZFunction); + Block.GetFunctionScript(new[] { AnimatedKey.TranslateXFunction, AnimatedKey.TranslateXFunctionRPN, AnimatedKey.TranslateXScript }, Folder, out animatedObj.TranslateXFunction); + Block.GetFunctionScript(new[] { AnimatedKey.TranslateYFunction, AnimatedKey.TranslateYFunctionRPN, AnimatedKey.TranslateYScript }, Folder, out animatedObj.TranslateYFunction); + Block.GetFunctionScript(new[] { AnimatedKey.TranslateZFunction, AnimatedKey.TranslateZFunctionRPN, AnimatedKey.TranslateZScript }, Folder, out animatedObj.TranslateZFunction); + Block.TryGetVector3(AnimatedKey.TranslateXDirection, ',', ref animatedObj.TranslateXDirection); + Block.TryGetVector3(AnimatedKey.TranslateYDirection, ',', ref animatedObj.TranslateYDirection); + Block.TryGetVector3(AnimatedKey.TranslateZDirection, ',', ref animatedObj.TranslateZDirection); + Block.TryGetVector3(AnimatedKey.RotateXDirection, ',', ref animatedObj.RotateXDirection); + Block.TryGetVector3(AnimatedKey.RotateYDirection, ',', ref animatedObj.RotateYDirection); + Block.TryGetVector3(AnimatedKey.RotateZDirection, ',', ref animatedObj.RotateZDirection); + Block.GetDamping(AnimatedKey.RotateXDamping, ',', out animatedObj.RotateXDamping); + Block.GetDamping(AnimatedKey.RotateYDamping, ',', out animatedObj.RotateYDamping); + Block.GetDamping(AnimatedKey.RotateZDamping, ',', out animatedObj.RotateZDamping); + + Result.Objects[ObjectCount] = animatedObj; + ObjectCount++; + } break; } Block.ReportErrors(); diff --git a/source/RouteViewer/NewRendererR.cs b/source/RouteViewer/NewRendererR.cs index 7a54a7115e..0bcae08111 100644 --- a/source/RouteViewer/NewRendererR.cs +++ b/source/RouteViewer/NewRendererR.cs @@ -195,6 +195,11 @@ internal void RenderScene(double timeElapsed) DefaultShader.SetLightDiffuse(Lighting.OptionDiffuseColor); DefaultShader.SetLightSpecular(Lighting.OptionSpecularColor); DefaultShader.SetLightModel(Lighting.LightModel); + UpdateActiveLights(DefaultShader); + } + else + { + DefaultShader.SetDynamicLights(new List(), CurrentViewMatrix, 0); } Fog.Set(); DefaultShader.SetTexture(0); @@ -340,6 +345,7 @@ internal void RenderScene(double timeElapsed) } // render overlays + DrawLightVisuals(); if (AvailableNewRenderer) { DefaultShader.Deactivate();