diff --git a/assets/Languages/en-US.xlf b/assets/Languages/en-US.xlf index 5d47dc075..da29a4428 100755 --- a/assets/Languages/en-US.xlf +++ b/assets/Languages/en-US.xlf @@ -123,6 +123,9 @@ Please check your fullscreen resolution settings. + + Please first select a route file to begin. + The selected route is corrupt: \r\n WithTrack section missing. @@ -209,6 +212,18 @@ Train + + Choose Train... + + + Do you wish to use the default train? + + + No + + + Yes + Selection diff --git a/assets/Menu/icon_disk.png b/assets/Menu/icon_disk.png index 3be9af00d..2213b4d37 100644 Binary files a/assets/Menu/icon_disk.png and b/assets/Menu/icon_disk.png differ diff --git a/assets/Menu/icon_folder.png b/assets/Menu/icon_folder.png index a567e6577..d6302b49e 100644 Binary files a/assets/Menu/icon_folder.png and b/assets/Menu/icon_folder.png differ diff --git a/assets/Menu/icon_route.png b/assets/Menu/icon_route.png index 3b02402d9..8763b297d 100644 Binary files a/assets/Menu/icon_route.png and b/assets/Menu/icon_route.png differ diff --git a/assets/Menu/icon_train.png b/assets/Menu/icon_train.png index 003902f51..bcfec4fa8 100644 Binary files a/assets/Menu/icon_train.png and b/assets/Menu/icon_train.png differ diff --git a/assets/Menu/please_select.png b/assets/Menu/please_select.png new file mode 100644 index 000000000..c749bbae3 Binary files /dev/null and b/assets/Menu/please_select.png differ diff --git a/assets/Shaders/default.frag b/assets/Shaders/default.frag index b386209e4..8b0ec73c8 100644 --- a/assets/Shaders/default.frag +++ b/assets/Shaders/default.frag @@ -1,11 +1,10 @@ -#version 150 - +#version 150 core in vec4 oViewPos; in vec2 oUv; in vec4 oColor; in vec4 oLightResult; - -uniform bool uIsTexture; +uniform int uAlphaFunction; +uniform float uAlphaComparison; uniform sampler2D uTexture; uniform int uMaterialFlags; uniform float uBrightness; @@ -16,14 +15,12 @@ uniform float uFogEnd; uniform vec3 uFogColor; uniform float uFogDensity; uniform bool uFogIsLinear; +out vec4 fragColor; void main(void) { - vec4 finalColor = vec4(oColor.rgb, 1.0); - if(uIsTexture) - { - finalColor *= texture2D(uTexture, oUv); - } + vec4 finalColor = vec4(oColor.rgb, 1.0) * texture(uTexture, oUv); + if((uMaterialFlags & 1) == 0 && (uMaterialFlags & 4) == 0) { //Material is not emissive and lighting is enabled, so multiply by brightness @@ -33,6 +30,38 @@ void main(void) //Apply the lighting results *after* the final color has been calculated finalColor *= oLightResult; + /* + * 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(uAlphaFunction == 513) // Less + { + if(finalColor.a >= uAlphaComparison) + { + discard; + } + } + else if(uAlphaFunction == 514) // Equal + { + if(!(abs(finalColor.a - 1.0) < 0.00001)) + { + discard; + } + } + else if(uAlphaFunction == 516) // Greater + { + if(finalColor.a <= uAlphaComparison) + { + discard; + } + } + // Fog float fogFactor = 1.0; @@ -48,5 +77,5 @@ void main(void) } } - gl_FragData[0] = vec4(mix(uFogColor, finalColor.rgb, fogFactor), finalColor.a); + fragColor = vec4(mix(uFogColor, finalColor.rgb, fogFactor), finalColor.a); } diff --git a/assets/Shaders/default.vert b/assets/Shaders/default.vert index 8727bcced..80981c2d1 100644 --- a/assets/Shaders/default.vert +++ b/assets/Shaders/default.vert @@ -1,4 +1,4 @@ -#version 150 +#version 150 core precision highp int; struct Light diff --git a/assets/Shaders/picking.frag b/assets/Shaders/picking.frag index 5a4269433..bafa4e2e3 100644 --- a/assets/Shaders/picking.frag +++ b/assets/Shaders/picking.frag @@ -1,8 +1,8 @@ -#version 150 - +#version 150 core uniform int uObjectIndex; +out vec4 fragColor; void main(void) { - gl_FragData[0].r = float(uObjectIndex); + fragColor.r = float(uObjectIndex); } diff --git a/assets/Shaders/rectangle.frag b/assets/Shaders/rectangle.frag new file mode 100644 index 000000000..efb5f2473 --- /dev/null +++ b/assets/Shaders/rectangle.frag @@ -0,0 +1,50 @@ +#version 150 core +in vec2 textureCoord; +uniform int uAlphaFunction; +uniform float uAlphaComparison; +uniform bool uRectangleHasColour; +uniform vec4 uColor; +uniform sampler2D uTexture; +out vec4 fragColor; + +void main(void) +{ + vec4 textureColour = texture(uTexture, textureCoord); + + vec4 finalColor = textureColour * uColor; + + /* + * 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(uAlphaFunction == 513) // Less + { + if(finalColor.a >= uAlphaComparison) + { + discard; + } + } + else if(uAlphaFunction == 514) // Equal + { + if(!(abs(finalColor.a - 1.0) < 0.00001)) + { + discard; + } + } + else if(uAlphaFunction == 516) // Greater + { + if(finalColor.a <= uAlphaComparison) + { + discard; + } + } + + fragColor = finalColor; + +} diff --git a/assets/Shaders/rectangle.vert b/assets/Shaders/rectangle.vert new file mode 100644 index 000000000..49ece19d9 --- /dev/null +++ b/assets/Shaders/rectangle.vert @@ -0,0 +1,44 @@ +#version 150 core +uniform mat4 uCurrentProjectionMatrix; +uniform mat4 uCurrentModelViewMatrix; +uniform vec2 uPoint; +uniform vec2 uSize; +uniform vec2 uCoordinates; +out vec2 textureCoord; +vec4 viewPos = vec4(0,0,0,0); + +void main() +{ + if(gl_VertexID == 0) + { + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y, 0), 1.0); + textureCoord = vec2(0,0); + } + else if (gl_VertexID == 1) + { + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y, 0), 1.0); + textureCoord = vec2(uCoordinates.x,0); + } + else if (gl_VertexID == 2) + { + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y + uSize.y, 0), 1.0); + textureCoord = uCoordinates; + } + else if (gl_VertexID == 3) + { + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y, 0), 1.0); + textureCoord = vec2(0,0); + } + else if (gl_VertexID == 4) + { + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y + uSize.y, 0), 1.0); + textureCoord = vec2(0, uCoordinates.y); + } + else if (gl_VertexID == 5) + { + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y + uSize.y, 0), 1.0); + textureCoord = uCoordinates; + } + + gl_Position = uCurrentProjectionMatrix * viewPos; +} diff --git a/assets/Shaders/text.vert b/assets/Shaders/text.vert new file mode 100644 index 000000000..8e64a5de8 --- /dev/null +++ b/assets/Shaders/text.vert @@ -0,0 +1,41 @@ +#version 150 core + +uniform mat4 uCurrentProjectionMatrix; +uniform mat4 uCurrentModelViewMatrix; +uniform vec2 uPoint; +uniform vec2 uSize; +uniform vec4 uAtlasLocation; +out vec2 textureCoord; +vec4 viewPos = vec4(0,0,0,0); + +void main() +{ + switch(gl_VertexID) + { + case 0: + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y, 0), 1.0); + textureCoord = vec2(uAtlasLocation.x, uAtlasLocation.y); + break; + case 1: + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y, 0), 1.0); + textureCoord = vec2(uAtlasLocation.x + uAtlasLocation.z, uAtlasLocation.y); + break; + case 2: + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y + uSize.y, 0), 1.0); + textureCoord = vec2(uAtlasLocation.x + uAtlasLocation.z, uAtlasLocation.y + uAtlasLocation.w); + break; + case 3: + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y, 0), 1.0); + textureCoord = vec2(uAtlasLocation.x, uAtlasLocation.y); + break; + case 4: + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x, uPoint.y + uSize.y, 0), 1.0); + textureCoord = vec2(uAtlasLocation.x, uAtlasLocation.y + uAtlasLocation.w); + break; + case 5: + viewPos = uCurrentModelViewMatrix * vec4(vec3(uPoint.x + uSize.x, uPoint.y + uSize.y, 0), 1.0); + textureCoord = vec2(uAtlasLocation.x + uAtlasLocation.z, uAtlasLocation.y + uAtlasLocation.w); + break; + } + gl_Position = uCurrentProjectionMatrix * viewPos; +} diff --git a/installers/mac/MacBundle.tgz b/installers/mac/MacBundle.tgz index 18633f9da..2e117d61a 100644 Binary files a/installers/mac/MacBundle.tgz and b/installers/mac/MacBundle.tgz differ diff --git a/source/LibRender2/Backgrounds/Background.cs b/source/LibRender2/Backgrounds/Background.cs index b90a3dca5..b26b3fd98 100644 --- a/source/LibRender2/Backgrounds/Background.cs +++ b/source/LibRender2/Backgrounds/Background.cs @@ -1,4 +1,4 @@ -using System; +using System; using OpenBveApi.Math; using OpenBveApi.Objects; using OpenBveApi.Routes; @@ -133,8 +133,6 @@ private void RenderStaticBackgroundRetained(StaticBackground data, float alpha, if (data.Texture != null && renderer.currentHost.LoadTexture(data.Texture, OpenGlTextureWrapMode.RepeatClamp)) { renderer.LastBoundTexture = data.Texture.OpenGlTextures[(int)OpenGlTextureWrapMode.RepeatClamp]; - GL.Enable(EnableCap.Texture2D); - if (alpha == 1.0f) { GL.Disable(EnableCap.Blend); @@ -164,7 +162,6 @@ private void RenderStaticBackgroundRetained(StaticBackground data, float alpha, } // texture - renderer.DefaultShader.SetIsTexture(true); GL.BindTexture(TextureTarget.Texture2D, data.Texture.OpenGlTextures[(int)OpenGlTextureWrapMode.RepeatClamp].Name); renderer.LastBoundTexture = null; @@ -178,15 +175,10 @@ private void RenderStaticBackgroundRetained(StaticBackground data, float alpha, VertexArrayObject VAO = (VertexArrayObject)data.VAO; VAO.Bind(); renderer.lastVAO = VAO.handle; - for (int i = 0; i + 9 < 32 * 10; i += 10) + for (int i = 0; i + 11 < 32 * 12; i += 12) { - VAO.Draw(PrimitiveType.Quads, i, 4); - VAO.Draw(PrimitiveType.Triangles, i + 4, 3); - VAO.Draw(PrimitiveType.Triangles, i + 7, 3); + VAO.Draw(PrimitiveType.Triangles, i, 12); } - renderer.DefaultShader.Deactivate(); - - GL.Disable(EnableCap.Texture2D); renderer.RestoreBlendFunc(); } } @@ -373,11 +365,6 @@ private void RenderBackgroundObject(BackgroundObject data) renderer.RenderFaceImmediateMode(new ObjectState(data.Object), face, Matrix4D.NoTransformation, Matrix4D.Scale(1.0) * renderer.CurrentViewMatrix); } } - - if (renderer.AvailableNewRenderer) - { - renderer.DefaultShader.Deactivate(); - } } } } diff --git a/source/LibRender2/BaseRenderer.cs b/source/LibRender2/BaseRenderer.cs index 23c1dfe94..da29e3927 100644 --- a/source/LibRender2/BaseRenderer.cs +++ b/source/LibRender2/BaseRenderer.cs @@ -13,11 +13,11 @@ using LibRender2.Screens; using LibRender2.Shaders; using LibRender2.Text; -using LibRender2.Texts; using LibRender2.Textures; using LibRender2.Viewports; using OpenBveApi; using OpenBveApi.Colors; +using OpenBveApi.FileSystem; using OpenBveApi.Hosts; using OpenBveApi.Interface; using OpenBveApi.Math; @@ -41,6 +41,8 @@ public abstract class BaseRenderer /// The callback to the host application internal HostInterface currentHost; + /// The host filesystem + internal FileSystem fileSystem; /// Holds a reference to the current options internal BaseOptions currentOptions; @@ -54,6 +56,8 @@ public abstract class BaseRenderer protected int ObjectsSortedByStartPointer; protected int ObjectsSortedByEndPointer; protected double LastUpdatedTrackPosition; + /// A dummy VAO used when working with procedural data within the shader + public VertexArrayObject dummyVao; public Screen Screen; @@ -115,7 +119,7 @@ public InterfaceType PreviousInterface #pragma warning restore 0219 /// The current shader in use - internal Shader CurrentShader; + protected internal Shader CurrentShader; public Shader DefaultShader; @@ -194,6 +198,45 @@ public bool ForceLegacyOpenGL set; } + protected internal Texture _programLogo; + + protected internal Texture whitePixel; + + private bool logoError; + + /// Gets the current program logo + public Texture ProgramLogo + { + get + { + if (_programLogo != null || logoError) + { + return _programLogo; + } + try + { + if (Screen.Width > 1024) + { + currentHost.RegisterTexture(Path.CombineFile(fileSystem.GetDataFolder("In-game"), "logo_1024.png"), new TextureParameters(null, null), out _programLogo, true); + } + else if (Screen.Width > 512) + { + currentHost.RegisterTexture(Path.CombineFile(fileSystem.GetDataFolder("In-game"), "logo_512.png"), new TextureParameters(null, null), out _programLogo, true); + } + else + { + currentHost.RegisterTexture(Path.CombineFile(fileSystem.GetDataFolder("In-game"), "logo_256.png"), new TextureParameters(null, null), out _programLogo, true); + } + } + catch + { + _programLogo = null; + logoError = true; + } + return _programLogo; + } + } + /* * List of VBO and IBO to delete on the next frame pass * This needs to be done here as opposed to in the finalizer @@ -219,10 +262,11 @@ protected BaseRenderer() /// /// Call this once to initialise the renderer /// - public virtual void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions) + public virtual void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions, FileSystem FileSystem) { currentHost = CurrentHost; currentOptions = CurrentOptions; + fileSystem = FileSystem; try { @@ -236,6 +280,7 @@ public virtual void Initialize(HostInterface CurrentHost, BaseOptions CurrentOpt DefaultShader.SetMaterialSpecular(Color32.White); lastColor = Color32.White; DefaultShader.Deactivate(); + dummyVao = new VertexArrayObject(); } catch { @@ -246,7 +291,7 @@ public virtual void Initialize(HostInterface CurrentHost, BaseOptions CurrentOpt Background = new Background(this); Fog = new Fog(); - OpenGlString = new OpenGlString(this); + OpenGlString = new OpenGlString(this); //text shader shares the rectangle fragment shader TextureManager = new TextureManager(currentHost, this); Cube = new Cube(this); Rectangle = new Rectangle(this); @@ -257,7 +302,7 @@ public virtual void Initialize(HostInterface CurrentHost, BaseOptions CurrentOpt StaticObjectStates = new List(); DynamicObjectStates = new List(); VisibleObjects = new VisibleObjectLibrary(currentHost, Camera, currentOptions, this); - + whitePixel = new Texture(new Texture(1, 1, 32, new byte[] {255, 255, 255, 255}, null)); GL.ClearColor(0.67f, 0.67f, 0.67f, 1.0f); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.Enable(EnableCap.DepthTest); @@ -271,11 +316,14 @@ public virtual void Initialize(HostInterface CurrentHost, BaseOptions CurrentOpt GL.Hint(HintTarget.GenerateMipmapHint, HintMode.Nicest); GL.Enable(EnableCap.CullFace); GL.CullFace(CullFaceMode.Front); - GL.Disable(EnableCap.Texture2D); GL.Disable(EnableCap.Dither); GL.Disable(EnableCap.Lighting); GL.Disable(EnableCap.Fog); - GL.Fog(FogParameter.FogMode, (int)FogMode.Linear); + if (!AvailableNewRenderer) + { + GL.Disable(EnableCap.Texture2D); + GL.Fog(FogParameter.FogMode, (int)FogMode.Linear); + } } /// Performs cleanup of disposed resources @@ -316,9 +364,13 @@ public void ReleaseResources() public virtual void ResetOpenGlState() { GL.Enable(EnableCap.CullFace); - GL.Disable(EnableCap.Lighting); - GL.Disable(EnableCap.Fog); - GL.Disable(EnableCap.Texture2D); + if (!AvailableNewRenderer) + { + GL.Disable(EnableCap.Lighting); + GL.Disable(EnableCap.Fog); + GL.Disable(EnableCap.Texture2D); + } + SetBlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); UnsetBlendFunc(); GL.Enable(EnableCap.DepthTest); @@ -364,7 +416,7 @@ public void Reset() currentHost.StaticObjectCache.Clear(); TextureManager.UnloadAllTextures(); - Initialize(currentHost, currentOptions); + Initialize(currentHost, currentOptions, fileSystem); } public int CreateStaticObject(StaticObject Prototype, Vector3 Position, Transformation WorldTransformation, Transformation LocalTransformation, ObjectDisposalMode AccurateObjectDisposal, double AccurateObjectDisposalZOffset, double StartingDistance, double EndingDistance, double BlockLength, double TrackPosition, double Brightness) @@ -690,10 +742,18 @@ public void DetermineMaxAFLevel() currentOptions.AnisotropicFilteringMaximum = 16; return; } + string[] Extensions; try { Extensions = GL.GetString(StringName.Extensions).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + ErrorCode error = GL.GetError(); + if (error == ErrorCode.InvalidEnum) + { + // Doing this on a forward compatible GL context fails with invalid enum + currentOptions.AnisotropicFilteringMaximum = 16; + return; + } } catch { @@ -793,11 +853,12 @@ public void ResetShader(Shader Shader) lastColor = Color32.White; Shader.SetMaterialShininess(1.0f); Shader.SetIsFog(false); - Shader.SetIsTexture(false); + Shader.DisableTexturing(); Shader.SetTexture(0); Shader.SetBrightness(1.0f); Shader.SetOpacity(1.0f); Shader.SetObjectIndex(0); + Shader.SetAlphaTest(false); } public void SetBlendFunc() @@ -847,15 +908,32 @@ public void SetAlphaFunc(AlphaFunction Comparison, float Value) alphaTestEnabled = true; alphaFuncComparison = Comparison; alphaFuncValue = Value; - GL.Enable(EnableCap.AlphaTest); - GL.AlphaFunc(Comparison, Value); + if (AvailableNewRenderer) + { + CurrentShader.SetAlphaTest(true); + CurrentShader.SetAlphaFunction(Comparison, Value); + } + else + { + GL.Enable(EnableCap.AlphaTest); + GL.AlphaFunc(Comparison, Value); + } + } /// Disables OpenGL alpha testing public void UnsetAlphaFunc() { alphaTestEnabled = false; - GL.Disable(EnableCap.AlphaTest); + if (AvailableNewRenderer) + { + CurrentShader.SetAlphaTest(false); + } + else + { + GL.Disable(EnableCap.AlphaTest); + } + } /// Restores the OpenGL alpha function to it's previous state @@ -863,15 +941,38 @@ public void RestoreAlphaFunc() { if (alphaTestEnabled) { - GL.Enable(EnableCap.AlphaTest); - GL.AlphaFunc(alphaFuncComparison, alphaFuncValue); + if (AvailableNewRenderer) + { + CurrentShader.SetAlphaTest(true); + CurrentShader.SetAlphaFunction(alphaFuncComparison, alphaFuncValue); + } + else + { + GL.Enable(EnableCap.AlphaTest); + GL.AlphaFunc(alphaFuncComparison, alphaFuncValue); + } + } else { - GL.Disable(EnableCap.AlphaTest); + if (AvailableNewRenderer) + { + CurrentShader.SetAlphaTest(false); + } + else + { + GL.Disable(EnableCap.AlphaTest); + } } } + + // Cached object state and matricies for shader drawing + private ObjectState lastObjectState; + private Matrix4D lastModelMatrix; + private Matrix4D lastModelViewMatrix; + private bool sendToShader; + /// Draws a face using the current shader /// The FaceState to draw /// Whether debug touch mode @@ -880,20 +981,35 @@ public void RenderFace(FaceState State, bool IsDebugTouchMode = false) RenderFace(CurrentShader, State.Object, State.Face, IsDebugTouchMode); } + /// Draws a face using the specified shader and matricies + /// The shader to use + /// The ObjectState to draw + /// The Face within the ObjectState + /// The model matrix to use + /// The modelview matrix to use + public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D ModelMatrix, Matrix4D ModelViewMatrix) + { + lastModelMatrix = ModelMatrix; + lastModelViewMatrix = ModelViewMatrix; + sendToShader = true; + RenderFace(Shader, State, Face); + } + /// Draws a face using the specified shader /// The shader to use - /// The FaceState to draw + /// The ObjectState to draw + /// The Face within the ObjectState /// Whether debug touch mode public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, bool IsDebugTouchMode = false) { - Matrix4D modelMatrix = State.ModelMatrix * Camera.TranslationMatrix; - Matrix4D modelViewMatrix = modelMatrix * CurrentViewMatrix; - RenderFace(Shader, State, Face, modelMatrix, modelViewMatrix, IsDebugTouchMode); - } + if (State != lastObjectState && !sendToShader) + { + lastObjectState = State; + lastModelMatrix = State.ModelMatrix * Camera.TranslationMatrix; + lastModelViewMatrix = lastModelMatrix * CurrentViewMatrix; + sendToShader = true; + } - public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D modelMatrix, Matrix4D modelViewMatrix, bool IsDebugTouchMode = false) - { - GL.Disable(EnableCap.Lighting); if (State.Prototype.Mesh.Vertices.Length < 1) { return; @@ -921,9 +1037,13 @@ public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D } // matrix - Shader.SetCurrentModelViewMatrix(modelViewMatrix); - Shader.SetCurrentTextureMatrix(State.TextureTranslation); - + if (sendToShader) + { + Shader.SetCurrentModelViewMatrix(lastModelViewMatrix); + Shader.SetCurrentTextureMatrix(State.TextureTranslation); + sendToShader = false; + } + if (OptionWireFrame || IsDebugTouchMode) { GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); @@ -981,7 +1101,7 @@ public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D float distanceFactor; if (material.GlowAttenuationData != 0) { - distanceFactor = (float)Glow.GetDistanceFactor(modelMatrix, State.Prototype.Mesh.Vertices, ref Face, material.GlowAttenuationData); + distanceFactor = (float)Glow.GetDistanceFactor(lastModelMatrix, State.Prototype.Mesh.Vertices, ref Face, material.GlowAttenuationData); } else { @@ -999,7 +1119,6 @@ public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D // texture if (material.DaytimeTexture != null && currentHost.LoadTexture(material.DaytimeTexture, (OpenGlTextureWrapMode)material.WrapMode)) { - Shader.SetIsTexture(true); if (LastBoundTexture != material.DaytimeTexture.OpenGlTextures[(int)material.WrapMode]) { GL.BindTexture(TextureTarget.Texture2D, @@ -1009,7 +1128,7 @@ public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D } else { - Shader.SetIsTexture(false); + Shader.DisableTexturing(); } // Calculate the brightness of the poly to render float factor; @@ -1049,7 +1168,6 @@ public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D if (material.NighttimeTexture != null && material.NighttimeTexture != material.DaytimeTexture && currentHost.LoadTexture(material.NighttimeTexture, (OpenGlTextureWrapMode)material.WrapMode)) { // texture - Shader.SetIsTexture(true); if (LastBoundTexture != material.NighttimeTexture.OpenGlTextures[(int)material.WrapMode]) { GL.BindTexture(TextureTarget.Texture2D, material.NighttimeTexture.OpenGlTextures[(int)material.WrapMode].Name); @@ -1060,9 +1178,9 @@ public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D GL.Enable(EnableCap.Blend); // alpha test - GL.Enable(EnableCap.AlphaTest); - GL.AlphaFunc(AlphaFunction.Greater, 0.0f); - + Shader.SetAlphaTest(true); + Shader.SetAlphaFunction(AlphaFunction.Greater, 0.0f); + // blend mode float alphaFactor = distanceFactor * blendFactor; @@ -1078,7 +1196,7 @@ public void RenderFace(Shader Shader, ObjectState State, MeshFace Face, Matrix4D // normals if (OptionNormals) { - Shader.SetIsTexture(false); + Shader.DisableTexturing(); Shader.SetBrightness(1.0f); Shader.SetOpacity(1.0f); VertexArrayObject NormalsVAO = (VertexArrayObject)State.Prototype.Mesh.NormalsVAO; diff --git a/source/LibRender2/LibRender2.csproj b/source/LibRender2/LibRender2.csproj index 7d3b75af6..627c5b046 100644 --- a/source/LibRender2/LibRender2.csproj +++ b/source/LibRender2/LibRender2.csproj @@ -70,8 +70,10 @@ + + @@ -94,6 +96,9 @@ + + + diff --git a/source/LibRender2/Lighting/Lighting.cs b/source/LibRender2/Lighting/Lighting.cs index 903268d88..93de8e15a 100644 --- a/source/LibRender2/Lighting/Lighting.cs +++ b/source/LibRender2/Lighting/Lighting.cs @@ -50,9 +50,9 @@ public void Initialize() GL.Light(LightName.Light0, LightParameter.Diffuse, new Color4(OptionDiffuseColor.R, OptionDiffuseColor.G, OptionDiffuseColor.B, 255)); GL.LightModel(LightModelParameter.LightModelAmbient, new[] { (float)LightModel.X, (float)LightModel.Y, (float)LightModel.Z, (float)LightModel.W }); GL.Enable(EnableCap.Light0); + GL.Enable(EnableCap.ColorMaterial); } - GL.Enable(EnableCap.ColorMaterial); - + float x = OptionAmbientColor.R + (float)OptionAmbientColor.G + OptionAmbientColor.B; float y = OptionDiffuseColor.R + (float)OptionDiffuseColor.G + OptionDiffuseColor.B; diff --git a/source/LibRender2/Loading/Loading.cs b/source/LibRender2/Loading/Loading.cs index 22c1cf412..06e9ee0dc 100644 --- a/source/LibRender2/Loading/Loading.cs +++ b/source/LibRender2/Loading/Loading.cs @@ -1,6 +1,5 @@ using System; -using System.Drawing; -using LibRender2.Texts; +using LibRender2.Text; using OpenBveApi.Colors; using OpenBveApi.Graphics; using OpenBveApi.Interface; @@ -30,9 +29,7 @@ public class Loading private bool showLogo; private bool showProgress; private Texture TextureLoadingBkg; - private Texture TextureLogo; private string ProgramVersion = "1.0"; - private readonly string[] LogoFileName = { "logo_256.png", "logo_512.png", "logo_1024.png" }; internal Loading(BaseRenderer renderer) { @@ -65,40 +62,6 @@ public void InitLoading(string Path, string Version, bool ShowLogo = true, bool renderer.TextureManager.RegisterTexture(backgroundFile, out TextureLoadingBkg); } } - - if (TextureLogo == null) - { - // choose logo size according to screen width - string fName; - - if (renderer.Screen.Width > 2048) - { - fName = LogoFileName[2]; - } - else if (renderer.Screen.Width > 1024) - { - fName = LogoFileName[1]; - } - else - { - fName = LogoFileName[0]; - } - - string logoFile = string.Empty; - try - { - logoFile = OpenBveApi.Path.CombineFile(Path, fName); - } - catch - { - //ignored - } - - if (System.IO.File.Exists(logoFile)) - { - renderer.TextureManager.RegisterTexture(logoFile, out TextureLogo); - } - } } /// Sets the loading screen background to a custom image @@ -158,19 +121,11 @@ public void DrawLoadingScreen(OpenGlFont Font, double RouteProgress, double Trai // (the route custom image is loaded in OldParsers/CsvRwRouteParser.cs) if (!customLoadScreen) { - if (showLogo && TextureLogo != null && renderer.currentHost.LoadTexture(TextureLogo, OpenGlTextureWrapMode.ClampClamp)) + if (showLogo && renderer.ProgramLogo != null) { // place the centre of the logo at from the screen top - int logoTop = (int)(renderer.Screen.Height * logoCentreYFactor - TextureLogo.Height / 2.0); - renderer.UnsetBlendFunc(); - renderer.SetAlphaFunc(AlphaFunction.Equal, 1.0f); - GL.DepthMask(true); - renderer.Rectangle.Draw(TextureLogo, new Vector2((renderer.Screen.Width - TextureLogo.Width) / 2.0, logoTop), new Vector2(TextureLogo.Width, TextureLogo.Height), Color128.White); - renderer.SetBlendFunc(); - renderer.SetAlphaFunc(AlphaFunction.Less, 1.0f); - GL.DepthMask(false); - renderer.Rectangle.Draw(TextureLogo, new Vector2((renderer.Screen.Width - TextureLogo.Width) / 2.0, logoTop), new Vector2(TextureLogo.Width, TextureLogo.Height), Color128.White); - renderer.SetAlphaFunc(AlphaFunction.Equal, 1.0f); + int logoTop = (int)(renderer.Screen.Height * logoCentreYFactor - renderer.ProgramLogo.Height / 2.0); + renderer.Rectangle.DrawAlpha(renderer.ProgramLogo, new Vector2((renderer.Screen.Width - renderer.ProgramLogo.Width) / 2.0, logoTop), new Vector2(renderer.ProgramLogo.Width, renderer.ProgramLogo.Height), Color128.White); } } // ReSharper disable once RedundantIfElseBlock @@ -189,7 +144,7 @@ public void DrawLoadingScreen(OpenGlFont Font, double RouteProgress, double Trai // VERSION NUMBER // place the version above the first division int versionTop = logoBottom + blankHeight - fontHeight; - renderer.OpenGlString.Draw(Font, "Version " + ProgramVersion, new Point(halfWidth, versionTop), TextAlignment.TopMiddle, Color128.White); + renderer.OpenGlString.Draw(Font, "Version " + ProgramVersion, new Vector2(halfWidth, versionTop), TextAlignment.TopMiddle, Color128.White); // for the moment, do not show any URL; would go right below the first division // DrawString(Fonts.SmallFont, "https://openbve-project.net", // new Point(halfWidth, versionTop + fontHeight+2), @@ -203,7 +158,7 @@ public void DrawLoadingScreen(OpenGlFont Font, double RouteProgress, double Trai // draw progress message right above the second division string text = Translations.GetInterfaceString(routeProgress < 1.0 ? "loading_loading_route" : trainProgress < 1.0 ? "loading_loading_train" : "message_loading"); - renderer.OpenGlString.Draw(Font, text, new Point(halfWidth, progressTop - fontHeight - 6), TextAlignment.TopMiddle, Color128.White); + renderer.OpenGlString.Draw(Font, text, new Vector2(halfWidth, progressTop - fontHeight - 6), TextAlignment.TopMiddle, Color128.White); // sum of route progress and train progress arrives up to 2.0: // => times 50.0 to convert to % @@ -225,7 +180,7 @@ public void DrawLoadingScreen(OpenGlFont Font, double RouteProgress, double Trai renderer.Rectangle.Draw(null, new Vector2(progrMargin, progressTop), new Vector2(progressWidth * (int)percent / 100.0, fontHeight + 4), ColourProgressBar); // progress percent - renderer.OpenGlString.Draw(Font, percStr, new Point(halfWidth, progressTop), TextAlignment.TopMiddle, Color128.Black); + renderer.OpenGlString.Draw(Font, percStr, new Vector2(halfWidth, progressTop), TextAlignment.TopMiddle, Color128.Black); } } } diff --git a/source/LibRender2/Overlays/Keys.cs b/source/LibRender2/Overlays/Keys.cs index e283b5ab5..1281ae334 100644 --- a/source/LibRender2/Overlays/Keys.cs +++ b/source/LibRender2/Overlays/Keys.cs @@ -1,5 +1,5 @@ using System.Drawing; -using LibRender2.Texts; +using LibRender2.Text; using OpenBveApi.Colors; using OpenBveApi.Graphics; using OpenBveApi.Math; @@ -36,7 +36,7 @@ public void Render(int Left, int Top, int Width, OpenGlFont Font, string[][] Key renderer.Rectangle.Draw(null, new Vector2(px - 1, py - 1), new Vector2(Width + 1, 17), new Color128(0.25f, 0.25f, 0.25f, 0.5f)); renderer.Rectangle.Draw(null, new Vector2(px - 1, py - 1), new Vector2(Width - 1, 15), new Color128(0.75f, 0.75f, 0.75f, 0.5f)); renderer.Rectangle.Draw(null, new Vector2(px, py), new Vector2(Width, 16), new Color128(0.5f, 0.5f, 0.5f, 0.5f)); - renderer.OpenGlString.Draw(Font, text, new Point(px - 1 + Width / 2, py + 7), TextAlignment.CenterMiddle, Color128.White); + renderer.OpenGlString.Draw(Font, text, new Vector2(px - 1 + Width / 2, py + 7), TextAlignment.CenterMiddle, Color128.White); } px += Width + 4; diff --git a/source/LibRender2/Primitives/Cube.cs b/source/LibRender2/Primitives/Cube.cs index d61226bd7..975b58a4e 100644 --- a/source/LibRender2/Primitives/Cube.cs +++ b/source/LibRender2/Primitives/Cube.cs @@ -254,7 +254,6 @@ private void DrawRetained(VertexArrayObject VAO, Vector3 Position, Vector3 Direc // texture if (TextureIndex != null && renderer.currentHost.LoadTexture(TextureIndex, OpenGlTextureWrapMode.ClampClamp)) { - renderer.DefaultShader.SetIsTexture(true); GL.Enable(EnableCap.Texture2D); GL.BindTexture(TextureTarget.Texture2D, TextureIndex.OpenGlTextures[(int)OpenGlTextureWrapMode.ClampClamp].Name); } @@ -266,10 +265,6 @@ private void DrawRetained(VertexArrayObject VAO, Vector3 Position, Vector3 Direc // render polygon VAO.Bind(); VAO.Draw(PrimitiveType.Quads); - renderer.lastVAO = -1; - VAO.UnBind(); - renderer.DefaultShader.Deactivate(); - GL.Disable(EnableCap.Texture2D); } diff --git a/source/LibRender2/Primitives/Picturebox.cs b/source/LibRender2/Primitives/Picturebox.cs new file mode 100644 index 000000000..5a84f6227 --- /dev/null +++ b/source/LibRender2/Primitives/Picturebox.cs @@ -0,0 +1,94 @@ +using OpenBveApi.Colors; +using OpenBveApi.Math; +using OpenBveApi.Textures; +using OpenTK.Graphics.OpenGL; + +namespace LibRender2.Primitives +{ + public class Picturebox + { + /// Holds a reference to the base renderer + private readonly BaseRenderer Renderer; + /// The texture for the picturebox + public Texture Texture; + /// The background color for the picturebox + public Color128 BackgroundColor; + /// The image sizing mode + public ImageSizeMode SizeMode; + /// The stored location for the textbox + public Vector2 Location; + /// The stored size for the textbox + public Vector2 Size; + + + public Picturebox(BaseRenderer renderer) + { + Renderer = renderer; + SizeMode = ImageSizeMode.Zoom; + } + + public void Draw() + { + if (!Renderer.currentHost.LoadTexture(Texture, OpenGlTextureWrapMode.ClampClamp)) + { + return; + } + + GL.DepthMask(true); + Vector2 newSize; + switch (SizeMode) + { + case ImageSizeMode.Normal: + //Draw box containing backing color first + Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor); + //Calculate the new size + newSize = new Vector2(Texture.Width, Texture.Height); + if (newSize.X > Size.X) + { + newSize.X = Size.X; + } + + if (newSize.Y > Size.Y) + { + newSize.Y = Size.Y; + } + //Two-pass draw the texture in appropriate place + Renderer.Rectangle.DrawAlpha(Texture, Location, newSize, Color128.White, new Vector2(newSize / Size)); + break; + case ImageSizeMode.Center: + //Draw box containing backing color first + Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor); + //Calculate the new size + newSize = new Vector2(Texture.Width, Texture.Height); + if (newSize.X > Size.X) + { + newSize.X = Size.X; + } + + if (newSize.Y > Size.Y) + { + newSize.Y = Size.Y; + } + //Two-pass draw the texture in appropriate place + Renderer.Rectangle.DrawAlpha(Texture, Location + new Vector2(newSize - Size) / 2, newSize, Color128.White, new Vector2(newSize / Size)); + break; + case ImageSizeMode.Stretch: + //No neeed to draw a backing color box as texture covers the whole thing + Renderer.Rectangle.Draw(Texture, Location, Size, BackgroundColor); + break; + case ImageSizeMode.Zoom: + //Draw box containing backing color first + Renderer.Rectangle.Draw(null, Location, Size, BackgroundColor); + //Calculate the new size + double ratioW = Size.X / Texture.Width; + double ratioH = Size.Y / Texture.Height; + double newRatio = ratioW < ratioH ? ratioW : ratioH; + newSize = new Vector2(Texture.Width, Texture.Height) * newRatio; + Renderer.Rectangle.DrawAlpha(Texture, new Vector2(Location.X + (Size.X - newSize.X) / 2,Location.Y + (Size.Y - newSize.Y) / 2), newSize, Color128.White); + break; + } + + } + + } +} diff --git a/source/LibRender2/Primitives/Rectangle.cs b/source/LibRender2/Primitives/Rectangle.cs index 08fbc233b..35ecb027d 100644 --- a/source/LibRender2/Primitives/Rectangle.cs +++ b/source/LibRender2/Primitives/Rectangle.cs @@ -1,4 +1,4 @@ -using System.Drawing; +using LibRender2.Shaders; using OpenBveApi.Colors; using OpenBveApi.Math; using OpenBveApi.Textures; @@ -8,32 +8,41 @@ namespace LibRender2.Primitives { public class Rectangle { + /// Holds a reference to the base renderer private readonly BaseRenderer renderer; + /// If using GL3, the shader to draw the rectangle with + private readonly Shader Shader; internal Rectangle(BaseRenderer renderer) { this.renderer = renderer; + try + { + Shader = new Shader(renderer, "rectangle", "rectangle", true); + } + catch + { + renderer.ForceLegacyOpenGL = true; + } } - /// Renders an overlay texture - /// The texture - /// The left co-ordinate - /// The top co-ordinate - /// The right co-ordinate - /// The bottom co-ordinate - public void RenderOverlayTexture(Texture texture, double left, double top, double right, double bottom) - { - Draw(texture, new Vector2(left, top), new Vector2((right - left), (bottom - top))); - } - - /// Renders a solid color rectangular overlay - /// The left co-ordinate - /// The top co-ordinate - /// The right co-ordinate - /// The bottom co-ordinate - public void RenderOverlaySolid(double left, double top, double right, double bottom) + /// Draws a simple 2D rectangle using two-pass alpha blending. + /// The texture, or a null reference. + /// The top-left coordinates in pixels. + /// The size in pixels. + /// The color, or a null reference. + /// The texture coordinates to be applied + public void DrawAlpha(Texture texture, Vector2 point, Vector2 size, Color128? color = null, Vector2? textureCoordinates = null) { - Draw(null, new Vector2(left, top), new Vector2((right - left), (bottom - top))); + renderer.UnsetBlendFunc(); + renderer.SetAlphaFunc(AlphaFunction.Equal, 1.0f); + GL.DepthMask(true); + Draw(texture, point, size, color, textureCoordinates); + renderer.SetBlendFunc(); + renderer.SetAlphaFunc(AlphaFunction.Less, 1.0f); + GL.DepthMask(false); + Draw(texture, point, size, color, textureCoordinates); + renderer.SetAlphaFunc(AlphaFunction.Equal, 1.0f); } /// Draws a simple 2D rectangle. @@ -41,7 +50,27 @@ public void RenderOverlaySolid(double left, double top, double right, double bot /// The top-left coordinates in pixels. /// The size in pixels. /// The color, or a null reference. - public void Draw(Texture texture, Vector2 point, Vector2 size, Color128? color = null) + /// The texture coordinates to be applied + public void Draw(Texture texture, Vector2 point, Vector2 size, Color128? color = null, Vector2? textureCoordinates = null) + { + if (renderer.AvailableNewRenderer && Shader != null) + { + if (textureCoordinates == null) + { + DrawWithShader(texture, point, size, color, Vector2.One); + } + else + { + DrawWithShader(texture, point, size, color, (Vector2)textureCoordinates); + } + } + else + { + DrawImmediate(texture, point, size, color, textureCoordinates); + } + } + + private void DrawImmediate(Texture texture, Vector2 point, Vector2 size, Color128? color, Vector2? textureCoordinates = null) { renderer.LastBoundTexture = null; // TODO: Remove Nullable from color once RenderOverlayTexture and RenderOverlaySolid are fully replaced. @@ -88,14 +117,29 @@ public void Draw(Texture texture, Vector2 point, Vector2 size, Color128? color = } GL.Begin(PrimitiveType.Quads); - GL.TexCoord2(0.0f, 0.0f); - GL.Vertex2(point.X, point.Y); - GL.TexCoord2(1.0f, 0.0f); - GL.Vertex2(point.X + size.X, point.Y); - GL.TexCoord2(1.0f, 1.0f); - GL.Vertex2(point.X + size.X, point.Y + size.Y); - GL.TexCoord2(0.0f, 1.0f); - GL.Vertex2(point.X, point.Y + size.Y); + if (textureCoordinates == null) + { + GL.TexCoord2(0,0); + GL.Vertex2(point.X, point.Y); + GL.TexCoord2(1,0); + GL.Vertex2(point.X + size.X, point.Y); + GL.TexCoord2(1,1); + GL.Vertex2(point.X + size.X, point.Y + size.Y); + GL.TexCoord2(0,1); + GL.Vertex2(point.X, point.Y + size.Y); + } + else + { + Vector2 v = (Vector2) textureCoordinates; + GL.TexCoord2(0,0); + GL.Vertex2(point.X, point.Y); + GL.TexCoord2(v.X, 0); + GL.Vertex2(point.X + size.X, point.Y); + GL.TexCoord2(v.X, v.Y); + GL.Vertex2(point.X + size.X, point.Y + size.Y); + GL.TexCoord2(0, v.Y); + GL.Vertex2(point.X, point.Y + size.Y); + } GL.End(); GL.Disable(EnableCap.Texture2D); } @@ -105,5 +149,33 @@ public void Draw(Texture texture, Vector2 point, Vector2 size, Color128? color = GL.MatrixMode(MatrixMode.Projection); GL.PopMatrix(); } + + private void DrawWithShader(Texture texture, Vector2 point, Vector2 size, Color128? color, Vector2 coordinates) + { + Shader.Activate(); + if (texture != null && renderer.currentHost.LoadTexture(texture, OpenGlTextureWrapMode.ClampClamp)) + { + GL.BindTexture(TextureTarget.Texture2D, texture.OpenGlTextures[(int)OpenGlTextureWrapMode.ClampClamp].Name); + renderer.LastBoundTexture = texture.OpenGlTextures[(int) OpenGlTextureWrapMode.ClampClamp]; + } + else + { + Shader.DisableTexturing(); + } + + Shader.SetCurrentProjectionMatrix(renderer.CurrentProjectionMatrix); + Shader.SetCurrentModelViewMatrix(renderer.CurrentViewMatrix); + Shader.SetColor(color == null ? Color128.White : color.Value); + Shader.SetPoint(point); + Shader.SetSize(size); + Shader.SetCoordinates(coordinates); + /* + * In order to call GL.DrawArrays with procedural data within the shader, + * we first need to bind a dummy VAO + * If this is not done, it will generate an InvalidOperation error code + */ + renderer.dummyVao.Bind(); + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 6); + } } } diff --git a/source/LibRender2/Primitives/Textbox.cs b/source/LibRender2/Primitives/Textbox.cs new file mode 100644 index 000000000..bd5b33059 --- /dev/null +++ b/source/LibRender2/Primitives/Textbox.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using LibRender2.Text; +using OpenBveApi.Colors; +using OpenBveApi.Graphics; +using OpenBveApi.Math; +using OpenBveApi.Textures; + +namespace LibRender2.Primitives +{ + public class Textbox + { + /// Holds a reference to the base renderer + private readonly BaseRenderer renderer; + /// The font the items in this textbox are to be drawn with + private readonly OpenGlFont myFont; + /// The font color + private readonly Color128 myFontColor; + /// The string contents of the textbox + public string Text + { + get + { + return myText; + } + set + { + myText = value; + //reset the scroll value + topLine = 0; + } + } + /// The background texture + public Texture BackgroundTexture; + /// The background color + public Color128 BackgroundColor; + /// Backing property for the textbox text + private string myText; + + /// The border width of the textbox + public readonly int Border; + /// The top line to be renderered + private int topLine; + /// The stored location for the textbox + public Vector2 Location; + /// The stored size for the textbox + public Vector2 Size; + /// Whether the textbox is currently selected by the mouse + public bool CurrentlySelected; + + private List WrappedLines(int width) + { + string[] firstSplit = Text.Split(new[] {"\r\n"}, StringSplitOptions.None); + List wrappedLines = new List(); + string currentLine = string.Empty; + for(int j = 0; j < firstSplit.Length; j++) + { + for (int i = 0; i < firstSplit[j].Length; i++) + { + char currentChar = firstSplit[j][i]; + currentLine += currentChar; + if (myFont.MeasureString(currentLine).X > width) + { + // Exceeded length, back up to last space + int moveback = 0; + int lastChar = i - 1; + while (currentChar != ' ') + { + moveback++; + i--; + if (i == 0) + { + //No spaces found, so just drop back one char + i = lastChar; + moveback = 1; + break; + } + currentChar = firstSplit[j][i]; + } + string lineToAdd = currentLine.Substring(0, currentLine.Length - moveback); + wrappedLines.Add(lineToAdd.TrimStart()); + currentLine = string.Empty; + } + } + wrappedLines.Add(currentLine.TrimStart()); + currentLine = string.Empty; + } + + if (currentLine.Length > 0) + { + wrappedLines.Add(currentLine.TrimStart()); + } + return wrappedLines; + } + + public Textbox(BaseRenderer Renderer, OpenGlFont Font, Color128 FontColor, Color128 backgroundColor) + { + renderer = Renderer; + myFont = Font; + myFontColor = FontColor; + Border = 5; + topLine = 0; + BackgroundTexture = null; + BackgroundColor = backgroundColor; + } + + public void VerticalScroll(int numberOfLines) + { + topLine += numberOfLines; + if (topLine < 0) + { + topLine = 0; + } + } + + public void Draw() + { + renderer.Rectangle.Draw(BackgroundTexture, Location, Size, BackgroundColor); //Draw the backing rectangle first + if (string.IsNullOrEmpty(Text)) + { + return; + } + + List splitString = WrappedLines((int)Size.Y - Border * 2); + if (splitString.Count == 1) + { + //DRAW SINGLE LINE + renderer.OpenGlString.Draw(myFont, Text, new Vector2(Location.X + Border, Location.Y + Border), TextAlignment.TopLeft, myFontColor); + } + else + { + int maxFittingLines = (int)(Size.Y / myFont.MeasureString(Text).Y); + if (topLine + maxFittingLines > splitString.Count) + { + topLine = Math.Max(0, splitString.Count - maxFittingLines); + } + //DRAW SPLIT LINES + int currentLine = topLine; + for (int i = 0; i < Math.Min(maxFittingLines, splitString.Count); i++) + { + renderer.OpenGlString.Draw(myFont, splitString[currentLine], new Vector2(Location.X + Border, Location.Y + Border + myFont.FontSize * i), TextAlignment.TopLeft, myFontColor); + currentLine++; + } + + } + } + + + } +} diff --git a/source/LibRender2/Shaders/Shader.cs b/source/LibRender2/Shaders/Shader.cs index ab3d06dde..468811250 100644 --- a/source/LibRender2/Shaders/Shader.cs +++ b/source/LibRender2/Shaders/Shader.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; @@ -7,8 +6,10 @@ using OpenBveApi.Colors; using OpenBveApi.Math; using OpenBveApi.Objects; +using OpenBveApi.Textures; using OpenTK; using OpenTK.Graphics.OpenGL; +using Vector2 = OpenBveApi.Math.Vector2; using Vector3 = OpenBveApi.Math.Vector3; using Vector4 = OpenBveApi.Math.Vector4; @@ -76,7 +77,7 @@ public Shader(BaseRenderer Renderer, string VertexShaderName, string FragmentSha GL.DeleteShader(vertexShader); GL.DeleteShader(fragmentShader); - + GL.BindFragDataLocation(handle, 0, "fragColor"); GL.LinkProgram(handle); GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out status); @@ -132,10 +133,16 @@ public void Activate() { return; } + + if (renderer.CurrentShader != null) + { + renderer.CurrentShader.isActive = false; + } GL.UseProgram(handle); isActive = true; renderer.lastVAO = -1; renderer.CurrentShader = this; + renderer.RestoreAlphaFunc(); } public VertexLayout GetVertexLayout() @@ -175,11 +182,17 @@ public UniformLayout GetUniformLayout() FogColor = (short)GL.GetUniformLocation(handle, "uFogColor"), FogIsLinear = (short)GL.GetUniformLocation(handle, "uFogIsLinear"), FogDensity = (short)GL.GetUniformLocation(handle, "uFogDensity"), - IsTexture = (short)GL.GetUniformLocation(handle, "uIsTexture"), Texture = (short)GL.GetUniformLocation(handle, "uTexture"), Brightness = (short)GL.GetUniformLocation(handle, "uBrightness"), Opacity = (short)GL.GetUniformLocation(handle, "uOpacity"), - ObjectIndex = (short)GL.GetUniformLocation(handle, "uObjectIndex") + ObjectIndex = (short)GL.GetUniformLocation(handle, "uObjectIndex"), + Point = (short)GL.GetUniformLocation(handle, "uPoint"), + Size = (short)GL.GetUniformLocation(handle, "uSize"), + Color = (short)GL.GetUniformLocation(handle, "uColor"), + Coordinates = (short)GL.GetUniformLocation(handle, "uCoordinates"), + AtlasLocation = (short)GL.GetUniformLocation(handle, "uAtlasLocation"), + AlphaFunction = (short)GL.GetUniformLocation(handle, "uAlphaFunction"), + AlphaComparison = (short)GL.GetUniformLocation(handle, "uAlphaComparison"), }; } @@ -221,7 +234,7 @@ private Matrix4 ConvertToMatrix4(Matrix4D mat) public void SetCurrentProjectionMatrix(Matrix4D ProjectionMatrix) { Matrix4 matrix = ConvertToMatrix4(ProjectionMatrix); - GL.UniformMatrix4(UniformLayout.CurrentProjectionMatrix, false, ref matrix); + GL.ProgramUniformMatrix4(handle, UniformLayout.CurrentProjectionMatrix, false, ref matrix); } /// @@ -254,7 +267,7 @@ public void SetCurrentModelViewMatrix(Matrix4D ModelViewMatrix) // | m12 m22 m32 m42 | // | m13 m23 m33 m43 | // | m14 m24 m34 m44 | - GL.UniformMatrix4(UniformLayout.CurrentModelViewMatrix, false, ref matrix); + GL.ProgramUniformMatrix4(handle, UniformLayout.CurrentModelViewMatrix, false, ref matrix); } /// @@ -264,106 +277,154 @@ public void SetCurrentModelViewMatrix(Matrix4D ModelViewMatrix) public void SetCurrentTextureMatrix(Matrix4D TextureMatrix) { Matrix4 matrix = ConvertToMatrix4(TextureMatrix); - GL.UniformMatrix4(UniformLayout.CurrentTextureMatrix, false, ref matrix); + GL.ProgramUniformMatrix4(handle, UniformLayout.CurrentTextureMatrix, false, ref matrix); } public void SetIsLight(bool IsLight) { - GL.Uniform1(UniformLayout.IsLight, IsLight ? 1 : 0); + GL.ProgramUniform1(handle, UniformLayout.IsLight, IsLight ? 1 : 0); } public void SetLightPosition(Vector3 LightPosition) { - GL.Uniform3(UniformLayout.LightPosition, (float)LightPosition.X, (float)LightPosition.Y, (float)LightPosition.Z); + GL.ProgramUniform3(handle, UniformLayout.LightPosition, (float)LightPosition.X, (float)LightPosition.Y, (float)LightPosition.Z); } public void SetLightAmbient(Color24 LightAmbient) { - GL.Uniform3(UniformLayout.LightAmbient, LightAmbient.R / 255.0f, LightAmbient.G / 255.0f, LightAmbient.B / 255.0f); + GL.ProgramUniform3(handle, UniformLayout.LightAmbient, LightAmbient.R / 255.0f, LightAmbient.G / 255.0f, LightAmbient.B / 255.0f); } public void SetLightDiffuse(Color24 LightDiffuse) { - GL.Uniform3(UniformLayout.LightDiffuse, LightDiffuse.R / 255.0f, LightDiffuse.G / 255.0f, LightDiffuse.B / 255.0f); + GL.ProgramUniform3(handle, UniformLayout.LightDiffuse, LightDiffuse.R / 255.0f, LightDiffuse.G / 255.0f, LightDiffuse.B / 255.0f); } public void SetLightSpecular(Color24 LightSpecular) { - GL.Uniform3(UniformLayout.LightSpecular, LightSpecular.R / 255.0f, LightSpecular.G / 255.0f, LightSpecular.B / 255.0f); + GL.ProgramUniform3(handle, UniformLayout.LightSpecular, LightSpecular.R / 255.0f, LightSpecular.G / 255.0f, LightSpecular.B / 255.0f); } public void SetLightModel(Vector4 LightModel) { - GL.Uniform4(UniformLayout.LightModel, (float)LightModel.X, (float)LightModel.Y, (float)LightModel.Z, (float)LightModel.W); + GL.ProgramUniform4(handle, UniformLayout.LightModel, (float)LightModel.X, (float)LightModel.Y, (float)LightModel.Z, (float)LightModel.W); } public void SetMaterialAmbient(Color32 MaterialAmbient) { - GL.Uniform4(UniformLayout.MaterialAmbient, MaterialAmbient.R / 255.0f, MaterialAmbient.G / 255.0f, MaterialAmbient.B / 255.0f, MaterialAmbient.A / 255.0f); + GL.ProgramUniform4(handle, UniformLayout.MaterialAmbient, MaterialAmbient.R / 255.0f, MaterialAmbient.G / 255.0f, MaterialAmbient.B / 255.0f, MaterialAmbient.A / 255.0f); } public void SetMaterialDiffuse(Color32 MaterialDiffuse) { - GL.Uniform4(UniformLayout.MaterialDiffuse, MaterialDiffuse.R / 255.0f, MaterialDiffuse.G / 255.0f, MaterialDiffuse.B / 255.0f, MaterialDiffuse.A / 255.0f); + GL.ProgramUniform4(handle, UniformLayout.MaterialDiffuse, MaterialDiffuse.R / 255.0f, MaterialDiffuse.G / 255.0f, MaterialDiffuse.B / 255.0f, MaterialDiffuse.A / 255.0f); } public void SetMaterialSpecular(Color32 MaterialSpecular) { - GL.Uniform4(UniformLayout.MaterialSpecular, MaterialSpecular.R / 255.0f, MaterialSpecular.G / 255.0f, MaterialSpecular.B / 255.0f, MaterialSpecular.A / 255.0f); + GL.ProgramUniform4(handle, UniformLayout.MaterialSpecular, MaterialSpecular.R / 255.0f, MaterialSpecular.G / 255.0f, MaterialSpecular.B / 255.0f, MaterialSpecular.A / 255.0f); } public void SetMaterialEmission(Color24 MaterialEmission) { - GL.Uniform3(UniformLayout.MaterialEmission, MaterialEmission.R / 255.0f, MaterialEmission.G / 255.0f, MaterialEmission.B / 255.0f); + GL.ProgramUniform3(handle, UniformLayout.MaterialEmission, MaterialEmission.R / 255.0f, MaterialEmission.G / 255.0f, MaterialEmission.B / 255.0f); } public void SetMaterialShininess(float MaterialShininess) { - GL.Uniform1(UniformLayout.MaterialShininess, MaterialShininess); + GL.ProgramUniform1(handle, UniformLayout.MaterialShininess, MaterialShininess); } public void SetMaterialFlags(MaterialFlags Flags) { - GL.Uniform1(UniformLayout.MaterialFlags, (int)Flags); + GL.ProgramUniform1(handle, UniformLayout.MaterialFlags, (int)Flags); } public void SetIsFog(bool IsFog) { - GL.Uniform1(UniformLayout.IsFog, IsFog ? 1 : 0); + GL.ProgramUniform1(handle, UniformLayout.IsFog, IsFog ? 1 : 0); } public void SetFog(Fog Fog) { - GL.Uniform1(UniformLayout.FogStart, Fog.Start); - GL.Uniform1(UniformLayout.FogEnd, Fog.End); - GL.Uniform3(UniformLayout.FogColor, Fog.Color.R / 255.0f, Fog.Color.G / 255.0f, Fog.Color.B / 255.0f); - GL.Uniform1(UniformLayout.FogIsLinear, Fog.IsLinear ? 1 : 0); - GL.Uniform1(UniformLayout.FogDensity, Fog.Density); + GL.ProgramUniform1(handle, UniformLayout.FogStart, Fog.Start); + GL.ProgramUniform1(handle, UniformLayout.FogEnd, Fog.End); + GL.ProgramUniform3(handle, UniformLayout.FogColor, Fog.Color.R / 255.0f, Fog.Color.G / 255.0f, Fog.Color.B / 255.0f); + GL.ProgramUniform1(handle, UniformLayout.FogIsLinear, Fog.IsLinear ? 1 : 0); + GL.ProgramUniform1(handle, UniformLayout.FogDensity, Fog.Density); } - public void SetIsTexture(bool IsTexture) + public void DisableTexturing() { - GL.Uniform1(UniformLayout.IsTexture, IsTexture ? 1 : 0); + if (renderer.LastBoundTexture != renderer.whitePixel.OpenGlTextures[(int)OpenGlTextureWrapMode.ClampClamp]) + { + /* + * If we do not want to use a texture, set a single white pixel instead + * This eliminates some shader branching, and is marginally faster in some cases + */ + renderer.currentHost.LoadTexture(renderer.whitePixel, OpenGlTextureWrapMode.ClampClamp); + GL.BindTexture(TextureTarget.Texture2D, renderer.whitePixel.OpenGlTextures[(int)OpenGlTextureWrapMode.ClampClamp].Name); + renderer.LastBoundTexture = renderer.whitePixel.OpenGlTextures[(int) OpenGlTextureWrapMode.ClampClamp]; + } } public void SetTexture(int TextureUnit) { - GL.Uniform1(UniformLayout.Texture, TextureUnit); + GL.ProgramUniform1(handle, UniformLayout.Texture, TextureUnit); } public void SetBrightness(float Brightness) { - GL.Uniform1(UniformLayout.Brightness, Brightness); + GL.ProgramUniform1(handle, UniformLayout.Brightness, Brightness); } public void SetOpacity(float Opacity) { - GL.Uniform1(UniformLayout.Opacity, Opacity); + GL.ProgramUniform1(handle, UniformLayout.Opacity, Opacity); } public void SetObjectIndex(int ObjectIndex) { - GL.Uniform1(UniformLayout.ObjectIndex, ObjectIndex); + GL.ProgramUniform1(handle, UniformLayout.ObjectIndex, ObjectIndex); + } + + public void SetPoint(Vector2 point) + { + GL.ProgramUniform2(handle, UniformLayout.Point, (float)point.X, (float)point.Y); + } + + public void SetSize(Vector2 size) + { + GL.ProgramUniform2(handle, UniformLayout.Size, (float) size.X, (float) size.Y); + } + + public void SetColor(Color128 color) + { + GL.ProgramUniform4(handle, UniformLayout.Color, color.R, color.G, color.B, color.A); + } + + public void SetCoordinates(Vector2 coordinates) + { + GL.ProgramUniform2(handle, UniformLayout.Coordinates, (float)coordinates.X, (float)coordinates.Y); + } + + public void SetAtlasLocation(Vector4 atlasLocation) + { + GL.ProgramUniform4(handle, UniformLayout.AtlasLocation, (float)atlasLocation.X, (float)atlasLocation.Y, (float)atlasLocation.Z, (float)atlasLocation.W); + } + + public void SetAlphaFunction(AlphaFunction alphaFunction, float alphaComparison) + { + GL.ProgramUniform1(handle, UniformLayout.AlphaFunction, (int)alphaFunction); + GL.ProgramUniform1(handle, UniformLayout.AlphaComparison, alphaComparison); + } + + public void SetAlphaTest(bool enabled) + { + if (!enabled) + { + GL.ProgramUniform1(handle, UniformLayout.AlphaFunction, (int)AlphaFunction.Never); + } } #endregion diff --git a/source/LibRender2/Text/Fonts.cs b/source/LibRender2/Text/Fonts.cs index 6f2f65795..e709bcd88 100644 --- a/source/LibRender2/Text/Fonts.cs +++ b/source/LibRender2/Text/Fonts.cs @@ -1,6 +1,6 @@  using System.Drawing; -using LibRender2.Texts; +using LibRender2.Text; namespace LibRender2.Text { @@ -24,6 +24,29 @@ public class Fonts public readonly OpenGlFont EvenLargerFont; + /// Gets the next smallest font + /// The font we require the smaller version for + /// The next smallest font + public OpenGlFont NextSmallestFont(OpenGlFont currentFont) + { + switch ((int)currentFont.FontSize) + { + case 9: + case 12: + return VerySmallFont; + case 16: + return SmallFont; + case 21: + return NormalFont; + case 27: + return LargeFont; + case 34: + return VeryLargeFont; + default: + return EvenLargerFont; + } + } + internal Fonts() { VerySmallFont = new OpenGlFont(FontFamily.GenericSansSerif, 9.0f); diff --git a/source/LibRender2/Text/OpenGlFont.cs b/source/LibRender2/Text/OpenGlFont.cs index 509fe8ad8..fb0111d0d 100644 --- a/source/LibRender2/Text/OpenGlFont.cs +++ b/source/LibRender2/Text/OpenGlFont.cs @@ -1,8 +1,9 @@ using System; using System.Drawing; +using OpenBveApi.Math; using OpenBveApi.Textures; -namespace LibRender2.Texts +namespace LibRender2.Text { /// Represents a font. public sealed class OpenGlFont : IDisposable @@ -57,10 +58,10 @@ public int GetCharacterData(string text, int offset, out Texture texture, out Op /// Measures the size of a string as it would be rendered using this font. /// The string to render. /// The size of the string. - public Size MeasureString(string text) + public Vector2 MeasureString(string text) { - int width = 0; - int height = 0; + double width = 0; + double height = 0; if (text != null) { @@ -70,16 +71,16 @@ public Size MeasureString(string text) Texture texture; OpenGlFontChar data; i += GetCharacterData(text, i, out texture, out data) - 1; - width += data.TypographicSize.Width; + width += data.TypographicSize.X; - if (data.TypographicSize.Height > height) + if (data.TypographicSize.Y > height) { - height = data.TypographicSize.Height; + height = data.TypographicSize.Y; } } } - return new Size(width, height); + return new Vector2(width, height); } private void Dispose(bool disposing) diff --git a/source/LibRender2/Text/OpenGlFontChar.cs b/source/LibRender2/Text/OpenGlFontChar.cs index df7073334..102bcff5e 100644 --- a/source/LibRender2/Text/OpenGlFontChar.cs +++ b/source/LibRender2/Text/OpenGlFontChar.cs @@ -1,24 +1,24 @@ -using System.Drawing; +using OpenBveApi.Math; -namespace LibRender2.Texts +namespace LibRender2.Text { /// Represents a single character. public struct OpenGlFontChar { // --- members --- /// The texture coordinates that represent the character in the underlying texture. - public RectangleF TextureCoordinates; + public Vector4 TextureCoordinates; /// The physical size of the character. - public Size PhysicalSize; + public Vector2 PhysicalSize; /// The typographic size of the character. - public Size TypographicSize; + public Vector2 TypographicSize; // --- constructors --- /// Creates a new character. /// The texture coordinates that represent the character in the underlying texture. /// The physical size of the character. /// The typographic size of the character. - public OpenGlFontChar(RectangleF textureCoordinates, Size physicalSize, Size typographicSize) + public OpenGlFontChar(Vector4 textureCoordinates, Vector2 physicalSize, Vector2 typographicSize) { TextureCoordinates = textureCoordinates; PhysicalSize = physicalSize; diff --git a/source/LibRender2/Text/OpenGlFontTable.cs b/source/LibRender2/Text/OpenGlFontTable.cs index 4b6f41d9f..4ec6261d8 100644 --- a/source/LibRender2/Text/OpenGlFontTable.cs +++ b/source/LibRender2/Text/OpenGlFontTable.cs @@ -2,9 +2,10 @@ using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Text; +using OpenBveApi.Math; using OpenBveApi.Textures; -namespace LibRender2.Texts +namespace LibRender2.Text { /// Represents a table of 256 consecutive code points rendered into the same texture. public class OpenGlFontTable : IDisposable @@ -29,8 +30,8 @@ public OpenGlFontTable(Font font, int offset) /* * Measure characters. * */ - Size[] physicalSizes = new Size[256]; - Size[] typographicSizes = new Size[256]; + Vector2[] physicalSizes = new Vector2[256]; + Vector2[] typographicSizes = new Vector2[256]; bitmap = new Bitmap(1, 1, PixelFormat.Format32bppArgb); Graphics graphics = Graphics.FromImage(bitmap); graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; @@ -40,8 +41,8 @@ public OpenGlFontTable(Font font, int offset) string character = char.ConvertFromUtf32(offset + i); SizeF physicalSize = graphics.MeasureString(character, font, int.MaxValue, StringFormat.GenericDefault); SizeF typographicSize = graphics.MeasureString(character, font, int.MaxValue, StringFormat.GenericTypographic); - physicalSizes[i] = new Size((int)Math.Ceiling(physicalSize.Width), (int)Math.Ceiling(physicalSize.Height)); - typographicSizes[i] = new Size((int)Math.Ceiling(typographicSize.Width == 0.0f ? physicalSize.Width : typographicSize.Width), (int)Math.Ceiling(typographicSize.Height == 0.0f ? physicalSize.Height : typographicSize.Height)); + physicalSizes[i] = new Vector2((int)Math.Ceiling(physicalSize.Width), (int)Math.Ceiling(physicalSize.Height)); + typographicSizes[i] = new Vector2((int)Math.Ceiling(typographicSize.Width == 0.0f ? physicalSize.Width : typographicSize.Width), (int)Math.Ceiling(typographicSize.Height == 0.0f ? physicalSize.Height : typographicSize.Height)); } graphics.Dispose(); @@ -51,11 +52,11 @@ public OpenGlFontTable(Font font, int offset) * Find suitable bitmap dimensions. * */ const int border = 1; - int width = border; - int height = border; - int lineWidth = 0; - int lineHeight = 0; - PointF[] coordinates = new PointF[256]; + double width = border; + double height = border; + double lineWidth = 0; + double lineHeight = 0; + Vector2[] coordinates = new Vector2[256]; for (int i = 0; i < 256; i++) { @@ -72,13 +73,13 @@ public OpenGlFontTable(Font font, int offset) lineHeight = 0; } - coordinates[i] = new PointF(lineWidth, height); + coordinates[i] = new Vector2(lineWidth, height); - lineWidth += physicalSizes[i].Width + border; + lineWidth += physicalSizes[i].X + border; - if (physicalSizes[i].Height + border > lineHeight) + if (physicalSizes[i].Y + border > lineHeight) { - lineHeight = physicalSizes[i].Height + border; + lineHeight = physicalSizes[i].Y + border; } } @@ -93,7 +94,7 @@ public OpenGlFontTable(Font font, int offset) /* * Draw character to bitmap. * */ - bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb); + bitmap = new Bitmap((int)width, (int)height, PixelFormat.Format32bppArgb); graphics = Graphics.FromImage(bitmap); graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; graphics.Clear(Color.Black); @@ -101,12 +102,12 @@ public OpenGlFontTable(Font font, int offset) for (int i = 0; i < 256; i++) { - graphics.DrawString(char.ConvertFromUtf32(offset + i), font, Brushes.White, coordinates[i]); - float x0 = (coordinates[i].X - border) / width; - float x1 = (coordinates[i].X + physicalSizes[i].Width + border) / width; - float y0 = (coordinates[i].Y - border) / height; - float y1 = (coordinates[i].Y + physicalSizes[i].Height + border) / height; - Characters[i] = new OpenGlFontChar(new RectangleF(x0, y0, x1 - x0, y1 - y0), new Size(physicalSizes[i].Width + 2 * border, physicalSizes[i].Height + 2 * border), typographicSizes[i]); + graphics.DrawString(char.ConvertFromUtf32(offset + i), font, Brushes.White, new PointF((float)coordinates[i].X, (float)coordinates[i].Y)); + double x0 = (coordinates[i].X - border) / width; + double x1 = (coordinates[i].X + physicalSizes[i].X + border) / width; + double y0 = (coordinates[i].Y - border) / height; + double y1 = (coordinates[i].Y + physicalSizes[i].Y + border) / height; + Characters[i] = new OpenGlFontChar(new Vector4(x0, y0, x1 - x0, y1 - y0), new Vector2(physicalSizes[i].X + 2 * border, physicalSizes[i].Y + 2 * border), typographicSizes[i]); } graphics.Dispose(); diff --git a/source/LibRender2/Text/OpenGlString.cs b/source/LibRender2/Text/OpenGlString.cs index 6b0f94ad0..a2eeb4d0d 100644 --- a/source/LibRender2/Text/OpenGlString.cs +++ b/source/LibRender2/Text/OpenGlString.cs @@ -1,18 +1,32 @@ -using System.Drawing; +using System; +using System.Drawing; +using LibRender2.Shaders; using OpenBveApi.Colors; using OpenBveApi.Graphics; +using OpenBveApi.Math; using OpenBveApi.Textures; using OpenTK.Graphics.OpenGL; -namespace LibRender2.Texts +namespace LibRender2.Text { public class OpenGlString { private readonly BaseRenderer renderer; + private readonly Shader Shader; + internal OpenGlString(BaseRenderer renderer) { this.renderer = renderer; + try + { + this.Shader = new Shader(renderer, "text", "rectangle", true); + } + catch + { + renderer.ForceLegacyOpenGL = true; + } + } /// Renders a string to the screen. @@ -22,7 +36,7 @@ internal OpenGlString(BaseRenderer renderer) /// The alignment. /// The color. /// This function sets the OpenGL blend function to glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA). - public void Draw(OpenGlFont font, string text, Point location, TextAlignment alignment, Color128 color) + public void Draw(OpenGlFont font, string text, Vector2 location, TextAlignment alignment, Color128 color) { if (text == null || font == null) { @@ -33,18 +47,18 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali * Prepare the top-left coordinates for rendering, incorporating the * orientation of the string in relation to the specified location. * */ - int left; + double left; if ((alignment & TextAlignment.Left) == 0) { - int width = 0; + double width = 0; for (int i = 0; i < text.Length; i++) { Texture texture; OpenGlFontChar data; i += font.GetCharacterData(text, i, out texture, out data) - 1; - width += data.TypographicSize.Width; + width += data.TypographicSize.X; } if ((alignment & TextAlignment.Right) != 0) @@ -61,11 +75,11 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali left = location.X; } - int top; + double top; if ((alignment & TextAlignment.Top) == 0) { - int height = 0; + double height = 0; for (int i = 0; i < text.Length; i++) { @@ -73,9 +87,9 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali OpenGlFontChar data; i += font.GetCharacterData(text, i, out texture, out data) - 1; - if (data.TypographicSize.Height > height) + if (data.TypographicSize.Y > height) { - height = data.TypographicSize.Height; + height = data.TypographicSize.Y; } } @@ -93,6 +107,19 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali top = location.Y; } + if (renderer.AvailableNewRenderer && Shader != null) + { + DrawWithShader(text, font, left, top, color); + } + else + { + DrawImmediate(text, font, left, top, color); + } + + } + + private void DrawImmediate(string text, OpenGlFont font, double left, double top, Color128 color) + { /* * Render the string. * */ @@ -125,8 +152,8 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali { GL.BindTexture(TextureTarget.Texture2D, texture.OpenGlTextures[(int)OpenGlTextureWrapMode.ClampClamp].Name); - int x = left - (data.PhysicalSize.Width - data.TypographicSize.Width) / 2; - int y = top - (data.PhysicalSize.Height - data.TypographicSize.Height) / 2; + double x = left - (data.PhysicalSize.X - data.TypographicSize.X) / 2; + double y = top - (data.PhysicalSize.Y - data.TypographicSize.Y) / 2; /* * In the first pass, mask off the background with pure black. @@ -134,14 +161,14 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali GL.BlendFunc(BlendingFactor.Zero, BlendingFactor.OneMinusSrcColor); GL.Begin(PrimitiveType.Quads); GL.Color4(color.A, color.A, color.A, 1.0f); - GL.TexCoord2(data.TextureCoordinates.Left, data.TextureCoordinates.Top); + GL.TexCoord2(data.TextureCoordinates.X, data.TextureCoordinates.Y); GL.Vertex2(x, y); - GL.TexCoord2(data.TextureCoordinates.Right, data.TextureCoordinates.Top); - GL.Vertex2(x + data.PhysicalSize.Width, y); - GL.TexCoord2(data.TextureCoordinates.Right, data.TextureCoordinates.Bottom); - GL.Vertex2(x + data.PhysicalSize.Width, y + data.PhysicalSize.Height); - GL.TexCoord2(data.TextureCoordinates.Left, data.TextureCoordinates.Bottom); - GL.Vertex2(x, y + data.PhysicalSize.Height); + GL.TexCoord2(data.TextureCoordinates.X + data.TextureCoordinates.Z, data.TextureCoordinates.Y); + GL.Vertex2(x + data.PhysicalSize.X, y); + GL.TexCoord2(data.TextureCoordinates.X + data.TextureCoordinates.Z, data.TextureCoordinates.Y + data.TextureCoordinates.W); + GL.Vertex2(x + data.PhysicalSize.X, y + data.PhysicalSize.Y); + GL.TexCoord2(data.TextureCoordinates.X, data.TextureCoordinates.Y + data.TextureCoordinates.W); + GL.Vertex2(x, y + data.PhysicalSize.Y); GL.End(); /* @@ -150,18 +177,18 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One); GL.Begin(PrimitiveType.Quads); GL.Color4(color.R, color.G, color.B, color.A); - GL.TexCoord2(data.TextureCoordinates.Left, data.TextureCoordinates.Top); + GL.TexCoord2(data.TextureCoordinates.X, data.TextureCoordinates.Y); GL.Vertex2(x, y); - GL.TexCoord2(data.TextureCoordinates.Right, data.TextureCoordinates.Top); - GL.Vertex2(x + data.PhysicalSize.Width, y); - GL.TexCoord2(data.TextureCoordinates.Right, data.TextureCoordinates.Bottom); - GL.Vertex2(x + data.PhysicalSize.Width, y + data.PhysicalSize.Height); - GL.TexCoord2(data.TextureCoordinates.Left, data.TextureCoordinates.Bottom); - GL.Vertex2(x, y + data.PhysicalSize.Height); + GL.TexCoord2(data.TextureCoordinates.X + data.TextureCoordinates.Z, data.TextureCoordinates.Y); + GL.Vertex2(x + data.PhysicalSize.X, y); + GL.TexCoord2(data.TextureCoordinates.X + data.TextureCoordinates.Z, data.TextureCoordinates.Y + data.TextureCoordinates.W); + GL.Vertex2(x + data.PhysicalSize.X, y + data.PhysicalSize.Y); + GL.TexCoord2(data.TextureCoordinates.X, data.TextureCoordinates.Y + data.TextureCoordinates.W); + GL.Vertex2(x, y + data.PhysicalSize.Y); GL.End(); } - left += data.TypographicSize.Width; + left += data.TypographicSize.X; } renderer.RestoreBlendFunc(); @@ -173,6 +200,48 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali GL.PopMatrix(); } + private void DrawWithShader(string text, OpenGlFont font, double left, double top, Color128 color) + { + Shader.Activate(); + renderer.CurrentShader = Shader; + Shader.SetCurrentProjectionMatrix(renderer.CurrentProjectionMatrix); + Shader.SetCurrentModelViewMatrix(renderer.CurrentViewMatrix); + + for (int i = 0; i < text.Length; i++) + { + Texture texture; + OpenGlFontChar data; + i += font.GetCharacterData(text, i, out texture, out data) - 1; + if (renderer.currentHost.LoadTexture(texture, OpenGlTextureWrapMode.ClampClamp)) + { + GL.BindTexture(TextureTarget.Texture2D, texture.OpenGlTextures[(int)OpenGlTextureWrapMode.ClampClamp].Name); + Shader.SetAtlasLocation(data.TextureCoordinates); + double x = left - (data.PhysicalSize.X - data.TypographicSize.X) / 2; + double y = top - (data.PhysicalSize.Y - data.TypographicSize.Y) / 2; + + /* + * In the first pass, mask off the background with pure black. + */ + GL.BlendFunc(BlendingFactor.Zero, BlendingFactor.OneMinusSrcColor); + Shader.SetColor(new Color128(color.A, color.A, color.A, 1.0f)); + Shader.SetPoint(new Vector2(x, y)); + Shader.SetSize(data.PhysicalSize); + /* + * In order to call GL.DrawArrays with procedural data within the shader, + * we first need to bind a dummy VAO + * If this is not done, it will generate an InvalidOperation error code + */ + renderer.dummyVao.Bind(); + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 6); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One); + Shader.SetColor(color); + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 6); + } + left += data.TypographicSize.X; + } + renderer.RestoreBlendFunc(); + } + /// Renders a string to the screen. /// The font to use. /// The string to render. @@ -181,11 +250,11 @@ public void Draw(OpenGlFont font, string text, Point location, TextAlignment ali /// The color. /// Whether to draw a shadow. /// This function sets the OpenGL blend function to glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA). - public void Draw(OpenGlFont font, string text, Point location, TextAlignment alignment, Color128 color, bool shadow) + public void Draw(OpenGlFont font, string text, Vector2 location, TextAlignment alignment, Color128 color, bool shadow) { if (shadow) { - Draw(font, text, new Point(location.X - 1, location.Y + 1), alignment, new Color128(0.0f, 0.0f, 0.0f, 0.5f * color.A)); + Draw(font, text, new Vector2(location.X - 1, location.Y + 1), alignment, new Color128(0.0f, 0.0f, 0.0f, 0.5f * color.A)); Draw(font, text, location, alignment, color); } else diff --git a/source/LibRender2/openGL/ShaderLayout.cs b/source/LibRender2/openGL/ShaderLayout.cs index 3c2b840ac..9f3b3bae0 100644 --- a/source/LibRender2/openGL/ShaderLayout.cs +++ b/source/LibRender2/openGL/ShaderLayout.cs @@ -137,12 +137,7 @@ public class UniformLayout /// The handle of "uFogDensity" within the shader /// public short FogDensity = -1; - - /// - /// The handle of "uIsTexture" within the shader - /// - public short IsTexture = -1; - + /// /// The handle of "uTexture" within the shader /// @@ -162,5 +157,40 @@ public class UniformLayout /// The handle of "uObjectIndex" within the shader /// public short ObjectIndex = -1; + + /// + /// The handle "uPoint" within the shader + /// + public short Point = -1; + + /// + /// The handle of "uSize" within the shader + /// + public short Size; + + /// + /// The handle of "uColor" within the shader + /// + public short Color; + + /// + /// The handle of "uCoordinates" within the shader + /// + public short Coordinates; + + /// + /// The handle of "uAtlasLocation" within the shader + /// + public short AtlasLocation; + + /// + /// The handle of "uAlphaFunction" within the shader + /// + public short AlphaFunction; + + /// + /// The handle of "uAlphaComparison" within the shader + /// + public short AlphaComparison; } } diff --git a/source/LibRender2/openGL/VertexArrayObject.cs b/source/LibRender2/openGL/VertexArrayObject.cs index 88649fc15..953683d1b 100644 --- a/source/LibRender2/openGL/VertexArrayObject.cs +++ b/source/LibRender2/openGL/VertexArrayObject.cs @@ -338,7 +338,8 @@ public static void CreateVAO(this StaticBackground background, VertexLayout vert Color = Color128.White }); - indexData.AddRange(new[] { 0, 1, 2, 3 }.Select(x => x + indexOffset).Select(x => (ushort)x)); + indexData.AddRange(new[] { 0, 1, 2 }.Select(x => x + indexOffset).Select(x => (ushort)x)); + indexData.AddRange(new[] { 0, 2, 3 }.Select(x => x + indexOffset).Select(x => (ushort)x)); // top cap vertexData.Add(new LibRenderVertex diff --git a/source/ObjectViewer/Graphics/NewRendererS.cs b/source/ObjectViewer/Graphics/NewRendererS.cs index b6e8cb0cb..e8a62e0a3 100644 --- a/source/ObjectViewer/Graphics/NewRendererS.cs +++ b/source/ObjectViewer/Graphics/NewRendererS.cs @@ -1,4 +1,4 @@ -using System.Drawing; +using System.Drawing; using System.Globalization; using System.Linq; using System.Windows.Forms; @@ -9,6 +9,7 @@ using OpenBve; using OpenBveApi; using OpenBveApi.Colors; +using OpenBveApi.FileSystem; using OpenBveApi.Graphics; using OpenBveApi.Hosts; using OpenBveApi.Interface; @@ -36,9 +37,9 @@ internal class NewRenderer : BaseRenderer private Cube greenAxisVAO; private Cube blueAxisVAO; - public override void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions) + public override void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions, FileSystem system) { - base.Initialize(CurrentHost, CurrentOptions); + base.Initialize(CurrentHost, CurrentOptions, system); if (!ForceLegacyOpenGL) { @@ -255,22 +256,22 @@ private void RenderOverlays() { keys = new[] { new[] { "F7" }, new[] { "F8" }, new[] { "F10" } }; Keys.Render(4, 4, 20, Fonts.SmallFont, keys); - OpenGlString.Draw(Fonts.SmallFont, "Open one or more objects", new Point(32, 4), TextAlignment.TopLeft, TextColor); - OpenGlString.Draw(Fonts.SmallFont, "Display the options window", new Point(32, 24), TextAlignment.TopLeft, TextColor); - OpenGlString.Draw(Fonts.SmallFont, "Display the train settings window", new Point(32, 44), TextAlignment.TopLeft, TextColor); - OpenGlString.Draw(Fonts.SmallFont, $"v{Application.ProductVersion}", new Point(Screen.Width - 8, Screen.Height - 20), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Open one or more objects", new Vector2(32, 4), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Display the options window", new Vector2(32, 24), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Display the train settings window", new Vector2(32, 44), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"v{Application.ProductVersion}", new Vector2(Screen.Width - 8, Screen.Height - 20), TextAlignment.TopLeft, TextColor); if (Interface.LogMessages.Count == 1) { Keys.Render(4, 64, 20, Fonts.SmallFont, new[] { new[] { "F9" } }); if (Interface.LogMessages[0].Type != MessageType.Information) { - OpenGlString.Draw(Fonts.SmallFont, "Display the 1 error message recently generated.", new Point(32, 64), TextAlignment.TopLeft, new Color128(1.0f, 0.5f, 0.5f)); + OpenGlString.Draw(Fonts.SmallFont, "Display the 1 error message recently generated.", new Vector2(32, 64), TextAlignment.TopLeft, new Color128(1.0f, 0.5f, 0.5f)); } else { //If all of our messages are information, then print the message text in grey - OpenGlString.Draw(Fonts.SmallFont, "Display the 1 message recently generated.", new Point(32, 64), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Display the 1 message recently generated.", new Vector2(32, 64), TextAlignment.TopLeft, TextColor); } } else if (Interface.LogMessages.Count > 1) @@ -280,25 +281,25 @@ private void RenderOverlays() if (error) { - OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count.ToString(culture)} error messages recently generated.", new Point(32, 64), TextAlignment.TopLeft, new Color128(1.0f, 0.5f, 0.5f)); + OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count.ToString(culture)} error messages recently generated.", new Vector2(32, 64), TextAlignment.TopLeft, new Color128(1.0f, 0.5f, 0.5f)); } else { - OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count.ToString(culture)} messages recently generated.", new Point(32, 64), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count.ToString(culture)} messages recently generated.", new Vector2(32, 64), TextAlignment.TopLeft, TextColor); } } } else { - OpenGlString.Draw(Fonts.SmallFont, $"Position: {Camera.AbsolutePosition.X.ToString("0.00", culture)}, {Camera.AbsolutePosition.Y.ToString("0.00", culture)}, {Camera.AbsolutePosition.Z.ToString("0.00", culture)}", new Point((int)(0.5 * Screen.Width - 88), 4), TextAlignment.TopLeft, TextColor); - OpenGlString.Draw(Fonts.SmallFont, $"Renderer: {(AvailableNewRenderer ? "New (GL 3.0)" : "Old (GL 1.2)")}", new Point((int)(0.5 * Screen.Width - 88), 24), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"Position: {Camera.AbsolutePosition.X.ToString("0.00", culture)}, {Camera.AbsolutePosition.Y.ToString("0.00", culture)}, {Camera.AbsolutePosition.Z.ToString("0.00", culture)}", new Vector2((int)(0.5 * Screen.Width - 88), 4), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"Renderer: {(AvailableNewRenderer ? "New (GL 3.0)" : "Old (GL 1.2)")}", new Vector2((int)(0.5 * Screen.Width - 88), 24), TextAlignment.TopLeft, TextColor); keys = new[] { new[] { "F5" }, new[] { "F7" }, new[] { "del" }, new[] { "F8" }, new[] { "F10" } }; Keys.Render(4, 4, 24, Fonts.SmallFont, keys); - OpenGlString.Draw(Fonts.SmallFont, "Reload the currently open objects", new Point(32, 4), TextAlignment.TopLeft, TextColor); - OpenGlString.Draw(Fonts.SmallFont, "Open additional objects", new Point(32, 24), TextAlignment.TopLeft, TextColor); - OpenGlString.Draw(Fonts.SmallFont, "Clear currently open objects", new Point(32, 44), TextAlignment.TopLeft, TextColor); - OpenGlString.Draw(Fonts.SmallFont, "Display the options window", new Point(32, 64), TextAlignment.TopLeft, TextColor); - OpenGlString.Draw(Fonts.SmallFont, "Display the train settings window", new Point(32, 84), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Reload the currently open objects", new Vector2(32, 4), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Open additional objects", new Vector2(32, 24), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Clear currently open objects", new Vector2(32, 44), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Display the options window", new Vector2(32, 64), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Display the train settings window", new Vector2(32, 84), TextAlignment.TopLeft, TextColor); keys = new[] { new[] { "F" }, new[] { "N" }, new[] { "L" }, new[] { "G" }, new[] { "B" }, new[] { "I" }, new[] { "R" } }; Keys.Render(Screen.Width - 20, 4, 16, Fonts.SmallFont, keys); @@ -306,14 +307,14 @@ private void RenderOverlays() Keys.Render(Screen.Width - 36, 124, 32, Fonts.SmallFont, keys); keys = new[] { new[] { "R" } }; Keys.Render(Screen.Width - 20, 144, 16, Fonts.SmallFont, keys); - OpenGlString.Draw(Fonts.SmallFont, $"WireFrame: {(OptionWireFrame ? "on" : "off")}", new Point(Screen.Width - 28, 4), TextAlignment.TopRight, TextColor); - OpenGlString.Draw(Fonts.SmallFont, $"Normals: {(OptionNormals ? "on" : "off")}", new Point(Screen.Width - 28, 24), TextAlignment.TopRight, TextColor); - OpenGlString.Draw(Fonts.SmallFont, $"Lighting: {(Program.LightingTarget == 0 ? "night" : "day")}", new Point(Screen.Width - 28, 44), TextAlignment.TopRight, TextColor); - OpenGlString.Draw(Fonts.SmallFont, $"Grid: {(OptionCoordinateSystem ? "on" : "off")}", new Point(Screen.Width - 28, 64), TextAlignment.TopRight, TextColor); - OpenGlString.Draw(Fonts.SmallFont, $"Background: {GetBackgroundColorName()}", new Point(Screen.Width - 28, 84), TextAlignment.TopRight, TextColor); - OpenGlString.Draw(Fonts.SmallFont, "Hide interface:", new Point(Screen.Width - 28, 104), TextAlignment.TopRight, TextColor); - OpenGlString.Draw(Fonts.SmallFont, $"{(RenderStatsOverlay ? "Hide" : "Show")} renderer statistics", new Point(Screen.Width - 44, 124), TextAlignment.TopRight, TextColor); - OpenGlString.Draw(Fonts.SmallFont, "Switch renderer type:", new Point(Screen.Width - 28, 144), TextAlignment.TopRight, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"WireFrame: {(OptionWireFrame ? "on" : "off")}", new Vector2(Screen.Width - 28, 4), TextAlignment.TopRight, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"Normals: {(OptionNormals ? "on" : "off")}", new Vector2(Screen.Width - 28, 24), TextAlignment.TopRight, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"Lighting: {(Program.LightingTarget == 0 ? "night" : "day")}", new Vector2(Screen.Width - 28, 44), TextAlignment.TopRight, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"Grid: {(OptionCoordinateSystem ? "on" : "off")}", new Vector2(Screen.Width - 28, 64), TextAlignment.TopRight, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"Background: {GetBackgroundColorName()}", new Vector2(Screen.Width - 28, 84), TextAlignment.TopRight, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Hide interface:", new Vector2(Screen.Width - 28, 104), TextAlignment.TopRight, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"{(RenderStatsOverlay ? "Hide" : "Show")} renderer statistics", new Vector2(Screen.Width - 44, 124), TextAlignment.TopRight, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Switch renderer type:", new Vector2(Screen.Width - 28, 144), TextAlignment.TopRight, TextColor); keys = new[] { new[] { null, "W", null }, new[] { "A", "S", "D" } }; Keys.Render(4, Screen.Height - 40, 16, Fonts.SmallFont, keys); @@ -330,12 +331,12 @@ private void RenderOverlays() if (Interface.LogMessages[0].Type != MessageType.Information) { - OpenGlString.Draw(Fonts.SmallFont, "Display the 1 error message recently generated.", new Point(32, 112), TextAlignment.TopLeft, new Color128(1.0f, 0.5f, 0.5f)); + OpenGlString.Draw(Fonts.SmallFont, "Display the 1 error message recently generated.", new Vector2(32, 112), TextAlignment.TopLeft, new Color128(1.0f, 0.5f, 0.5f)); } else { //If all of our messages are information, then print the message text in grey - OpenGlString.Draw(Fonts.SmallFont, "Display the 1 message recently generated.", new Point(32, 112), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, "Display the 1 message recently generated.", new Vector2(32, 112), TextAlignment.TopLeft, TextColor); } } else if (Interface.LogMessages.Count > 1) @@ -345,22 +346,22 @@ private void RenderOverlays() if (error) { - OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count.ToString(culture)} error messages recently generated.", new Point(32, 112), TextAlignment.TopLeft, new Color128(1.0f, 0.5f, 0.5f)); + OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count.ToString(culture)} error messages recently generated.", new Vector2(32, 112), TextAlignment.TopLeft, new Color128(1.0f, 0.5f, 0.5f)); } else { - OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count.ToString(culture)} messages recently generated.", new Point(32, 112), TextAlignment.TopLeft, TextColor); + OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count.ToString(culture)} messages recently generated.", new Vector2(32, 112), TextAlignment.TopLeft, TextColor); } } if (RenderStatsOverlay) { Keys.Render(4, Screen.Height - 126, 116, Fonts.SmallFont, new[] { new[] { "Renderer Statistics" } }); - OpenGlString.Draw(Fonts.SmallFont, $"Total static objects: {VisibleObjects.Objects.Count}", new Point(4, Screen.Height - 112), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Total animated objects: {ObjectManager.AnimatedWorldObjectsUsed}", new Point(4, Screen.Height - 100), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Current frame rate: {FrameRate.ToString("0.0", culture)}fps", new Point(4, Screen.Height - 88), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Total opaque faces: {VisibleObjects.OpaqueFaces.Count}", new Point(4, Screen.Height - 76), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Total alpha faces: {VisibleObjects.AlphaFaces.Count}", new Point(4, Screen.Height - 64), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Total static objects: {VisibleObjects.Objects.Count}", new Vector2(4, Screen.Height - 112), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Total animated objects: {ObjectManager.AnimatedWorldObjectsUsed}", new Vector2(4, Screen.Height - 100), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Current frame rate: {FrameRate.ToString("0.0", culture)}fps", new Vector2(4, Screen.Height - 88), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Total opaque faces: {VisibleObjects.OpaqueFaces.Count}", new Vector2(4, Screen.Height - 76), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Total alpha faces: {VisibleObjects.AlphaFaces.Count}", new Vector2(4, Screen.Height - 64), TextAlignment.TopLeft, Color128.White, true); } } } diff --git a/source/ObjectViewer/ObjectViewer.csproj b/source/ObjectViewer/ObjectViewer.csproj index 7a03d2eae..36333264d 100644 --- a/source/ObjectViewer/ObjectViewer.csproj +++ b/source/ObjectViewer/ObjectViewer.csproj @@ -85,9 +85,9 @@ formTrain.cs + - diff --git a/source/ObjectViewer/System/GameWindow.cs b/source/ObjectViewer/System/GameWindow.cs index 5e748464f..da4e00ac0 100644 --- a/source/ObjectViewer/System/GameWindow.cs +++ b/source/ObjectViewer/System/GameWindow.cs @@ -289,7 +289,7 @@ protected override void OnLoad(EventArgs e) MouseWheel += Program.MouseWheelEvent; FileDrop += Program.DragFile; Program.Renderer.Camera.Reset(new Vector3(-5.0, 2.5, -25.0)); - Program.Renderer.Initialize(Program.CurrentHost,Interface.CurrentOptions); + Program.Renderer.Initialize(Program.CurrentHost,Interface.CurrentOptions, Program.FileSystem); Program.Renderer.Lighting.Initialize(); Program.Renderer.UpdateViewport(); Program.Renderer.InitializeVisibility(); diff --git a/source/OpenBVE/Game/Game.cs b/source/OpenBVE/Game/Game.cs index a384c20e8..828ceb283 100644 --- a/source/OpenBVE/Game/Game.cs +++ b/source/OpenBVE/Game/Game.cs @@ -37,7 +37,11 @@ internal static void Reset(bool ResetLogs) { // game Interface.LogMessages.Clear(); Program.CurrentHost.MissingFiles.Clear(); - Program.Renderer.CurrentInterface = InterfaceType.Normal; + if (Program.Renderer.CurrentInterface != InterfaceType.Menu) + { + Program.Renderer.CurrentInterface = InterfaceType.Normal; + } + Program.CurrentRoute.Comment = ""; Program.CurrentRoute.Image = ""; Program.CurrentRoute.Atmosphere = new Atmosphere(); diff --git a/source/OpenBVE/Game/Menu/Menu.MenuCaption.cs b/source/OpenBVE/Game/Menu/Menu.MenuCaption.cs new file mode 100644 index 000000000..ed513eec6 --- /dev/null +++ b/source/OpenBVE/Game/Menu/Menu.MenuCaption.cs @@ -0,0 +1,14 @@ +namespace OpenBve +{ + public sealed partial class Menu + { + /// A caption to be rendered at the top of the menu + private class MenuCaption : MenuEntry + { + internal MenuCaption(string Text) + { + this.Text = Text; + } + } + } +} diff --git a/source/OpenBVE/Game/Menu/Menu.MenuCommand.cs b/source/OpenBVE/Game/Menu/Menu.MenuCommand.cs new file mode 100644 index 000000000..ad855ae53 --- /dev/null +++ b/source/OpenBVE/Game/Menu/Menu.MenuCommand.cs @@ -0,0 +1,32 @@ +using OpenBveApi.Textures; + +namespace OpenBve +{ + public sealed partial class Menu + { + /// A menu entry which performs a command when selected + private class MenuCommand : MenuEntry + { + /// The command tag describing the function of the menu entry + internal readonly MenuTag Tag; + /// The optional data to be passed with the command + internal readonly int Data; + + internal MenuCommand(string Text, MenuTag Tag, int Data) + { + this.Text = Text; + this.Tag = Tag; + this.Data = Data; + this.Icon = null; + } + + internal MenuCommand(string Text, MenuTag Tag, int Data, Texture Icon) + { + this.Text = Text; + this.Tag = Tag; + this.Data = Data; + this.Icon = Icon; + } + } + } +} diff --git a/source/OpenBVE/Game/Menu/Menu.MenuEntry.cs b/source/OpenBVE/Game/Menu/Menu.MenuEntry.cs new file mode 100644 index 000000000..f250e14cf --- /dev/null +++ b/source/OpenBVE/Game/Menu/Menu.MenuEntry.cs @@ -0,0 +1,69 @@ +using OpenBveApi.Textures; + +namespace OpenBve +{ + public sealed partial class Menu + { + /// The base abstract Menu Entry class + private abstract class MenuEntry + { + /// The base text of the menu entry + internal string Text; + /// The display text of the menu entry + internal string DisplayText(double TimeElapsed) + { + if (DisplayLength == 0) + { + return Text; + } + timer += TimeElapsed; + if (timer > 0.5) + { + if (pause) + { + pause = false; + return _displayText; + } + timer = 0; + scroll++; + if (scroll == Text.Length) + { + scroll = 0; + pause = true; + } + _displayText = Text.Substring(scroll); + if (_displayText.Length > _displayLength) + { + _displayText = _displayText.Substring(0, _displayLength); + } + } + return _displayText; + } + /// Backing property for display text + private string _displayText; + /// Backing property for display length + private int _displayLength; + /// The length to display + internal int DisplayLength + { + get + { + return _displayLength; + } + set + { + _displayLength = value; + _displayText = Text.Substring(0, value); + timer = 0; + } + } + /// The icon to draw for this menu entry + internal Texture Icon; + //Properties used for controlling the scrolling text if overlong + private double timer; + private int scroll; + private bool pause; + + } + } +} diff --git a/source/OpenBVE/Game/Menu/Menu.Route.cs b/source/OpenBVE/Game/Menu/Menu.Route.cs new file mode 100644 index 000000000..f00ace4f5 --- /dev/null +++ b/source/OpenBVE/Game/Menu/Menu.Route.cs @@ -0,0 +1,154 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Text; +using LibRender2.Primitives; +using OpenBveApi; +using OpenBveApi.Colors; +using OpenBveApi.Interface; +using OpenBveApi.Textures; +using RouteManager2; +using Path = OpenBveApi.Path; + +namespace OpenBve +{ + public partial class Menu + { + private static BackgroundWorker routeWorkerThread; + private static string SearchDirectory; + private static string RouteFile; + private static Encoding RouteEncoding; + private static RouteState RoutefileState; + private static readonly Picturebox routePictureBox = new Picturebox(Program.Renderer); + private static readonly Textbox routeDescriptionBox = new Textbox(Program.Renderer, Program.Renderer.Fonts.NormalFont, Color128.White, Color128.Black); + + private static void routeWorkerThread_doWork(object sender, DoWorkEventArgs e) + { + if (string.IsNullOrEmpty(RouteFile)) + { + return; + } + RouteEncoding = TextEncoding.GetSystemEncodingFromFile(RouteFile); + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\loading.png"), new TextureParameters(null, null), out routePictureBox.Texture); + routeDescriptionBox.Text = Translations.GetInterfaceString("start_route_processing"); + Game.Reset(false); + bool loaded = false; + for (int i = 0; i < Program.CurrentHost.Plugins.Length; i++) + { + if (Program.CurrentHost.Plugins[i].Route != null && Program.CurrentHost.Plugins[i].Route.CanLoadRoute(RouteFile)) + { + object Route = (object)Program.CurrentRoute; //must cast to allow us to use the ref keyword. + string RailwayFolder = Loading.GetRailwayFolder(RouteFile); + string ObjectFolder = OpenBveApi.Path.CombineDirectory(RailwayFolder, "Object"); + string SoundFolder = OpenBveApi.Path.CombineDirectory(RailwayFolder, "Sound"); + if (Program.CurrentHost.Plugins[i].Route.LoadRoute(RouteFile, RouteEncoding, null, ObjectFolder, SoundFolder, true, ref Route)) + { + Program.CurrentRoute = (CurrentRoute) Route; + } + else + { + if (Program.CurrentHost.Plugins[i].Route.LastException != null) + { + throw Program.CurrentHost.Plugins[i].Route.LastException; //Re-throw last exception generated by the route parser plugin so that the UI thread captures it + } + routeDescriptionBox.Text = "An unknown error was enountered whilst attempting to parse the routefile " + RouteFile; + RoutefileState = RouteState.Error; + } + loaded = true; + break; + } + } + + if (!loaded) + { + throw new Exception("No plugins capable of loading routefile " + RouteFile + " were found."); + } + } + + private static void routeWorkerThread_completed(object sender, RunWorkerCompletedEventArgs e) + { + RoutefileState = RouteState.Processed; + if (e.Error != null || Program.CurrentRoute == null) + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\route_error.png"), new TextureParameters(null, null), out routePictureBox.Texture); + if (e.Error != null) + { + routeDescriptionBox.Text = e.Error.Message; + RoutefileState = RouteState.Error; + } + routeWorkerThread.Dispose(); + return; + } + try + { + // image + if (!string.IsNullOrEmpty(Program.CurrentRoute.Image)) + { + + try + { + if (File.Exists(Program.CurrentRoute.Image)) + { + Program.CurrentHost.RegisterTexture(Program.CurrentRoute.Image, new TextureParameters(null, null), out routePictureBox.Texture); + } + else + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\route_unknown.png"), new TextureParameters(null, null), out routePictureBox.Texture); + } + + } + catch + { + routePictureBox.Texture = null; + } + } + else + { + string[] f = {".png", ".bmp", ".gif", ".tiff", ".tif", ".jpeg", ".jpg"}; + int i; + for (i = 0; i < f.Length; i++) + { + string g = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(RouteFile), + System.IO.Path.GetFileNameWithoutExtension(RouteFile) + f[i]); + if (System.IO.File.Exists(g)) + { + try + { + using (var fs = new FileStream(g, FileMode.Open, FileAccess.Read)) + { + //pictureboxRouteImage.Image = new Bitmap(fs); + } + } + catch + { + //pictureboxRouteImage.Image = null; + } + break; + } + } + if (i == f.Length) + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\route_unknown.png"), new TextureParameters(null, null), out routePictureBox.Texture); + } + } + + // description + string Description = Program.CurrentRoute.Comment.ConvertNewlinesToCrLf(); + if (Description.Length != 0) + { + routeDescriptionBox.Text = Description; + } + else + { + routeDescriptionBox.Text = System.IO.Path.GetFileNameWithoutExtension(RouteFile); + } + } + catch (Exception ex) + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\route_error.png"), new TextureParameters(null, null), out routePictureBox.Texture); + routeDescriptionBox.Text = ex.Message; + RouteFile = null; + } + } + } +} diff --git a/source/OpenBVE/Game/Menu/Menu.RouteState.cs b/source/OpenBVE/Game/Menu/Menu.RouteState.cs new file mode 100644 index 000000000..f52dc92c9 --- /dev/null +++ b/source/OpenBVE/Game/Menu/Menu.RouteState.cs @@ -0,0 +1,19 @@ +namespace OpenBve +{ + public partial class Menu + { + private enum RouteState + { + /// No routefile is currently selected + NoneSelected, + /// The background thread is currently loading the routefile data + Loading, + /// The background thread has processed the route data + Processed, + /// An error was encountered loading the route data + Error + + } + } + +} diff --git a/source/OpenBVE/Game/Menu/Menu.SingleMenu.cs b/source/OpenBVE/Game/Menu/Menu.SingleMenu.cs new file mode 100644 index 000000000..37c35de84 --- /dev/null +++ b/source/OpenBVE/Game/Menu/Menu.SingleMenu.cs @@ -0,0 +1,476 @@ +using System; +using System.ComponentModel; +using System.IO; +using DavyKager; +using OpenBveApi.Graphics; +using OpenBveApi.Hosts; +using OpenBveApi.Interface; +using OpenBveApi.Math; +using OpenBveApi.Textures; +using TrainManager; +using Path = OpenBveApi.Path; + +namespace OpenBve +{ + public sealed partial class Menu + { + /// Describes a single menu of the menu stack. + /// The class is private to Menu, but all its fields are public to allow 'quick-and-dirty' + /// access from Menu itself. + private class SingleMenu + { + /// The text alignment for the menu + public readonly TextAlignment Align; + /// The list of items to be shown + public readonly MenuEntry[] Items = { }; + /// The smaller of the width of the largest item, and the absolute width + public readonly double ItemWidth = 0; + /// The absolute width + public readonly double Width = 0; + /// The absolute height + public readonly double Height = 0; + private readonly double MaxWidth; + + private int lastSelection = int.MaxValue; + private int currentSelection; + + public int Selection + { + get + { + return currentSelection; + } + set + { + lastSelection = currentSelection; + currentSelection = value; + if (currentSelection != lastSelection && Interface.CurrentOptions.ScreenReaderAvailable) + { + if (!Tolk.Output(Items[currentSelection].Text)) + { + // failed to output to screen reader, so don't keep trying + Interface.CurrentOptions.ScreenReaderAvailable = false; + } + } + } + } + public int TopItem; // the top displayed menu item + internal readonly MenuType Type; + + + /******************** + MENU C'TOR + *********************/ + public SingleMenu(MenuType menuType, int data = 0, double maxWidth = 0) + { + MaxWidth = maxWidth; + Type = menuType; + int i, menuItem; + int jump = 0; + Vector2 size; + + Align = TextAlignment.TopMiddle; + Height = Width = 0; + Selection = 0; // defaults to first menu item + switch (menuType) + { + case MenuType.GameStart: // top level menu + if (routeWorkerThread == null) + { + //Create the worker thread for route details processing on first launch of main menu + routeWorkerThread = new BackgroundWorker(); + routeWorkerThread.DoWork += routeWorkerThread_doWork; + routeWorkerThread.RunWorkerCompleted += routeWorkerThread_completed; + //Load texture + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\loading.png"), new TextureParameters(null, null), out routePictureBox.Texture); + } + Items = new MenuEntry[3]; + Items[0] = new MenuCommand("Open Route File", MenuTag.RouteList, 0); + + if (!Interface.CurrentOptions.KioskMode) + { + //Don't allow quitting or customisation of the controls in kiosk mode + Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_customize_controls"), MenuTag.MenuControls, 0); + Items[2] = new MenuCommand(Translations.GetInterfaceString("menu_quit"), MenuTag.MenuQuit, 0); + } + else + { + Array.Resize(ref Items, Items.Length - 2); + } + SearchDirectory = Program.FileSystem.InitialRouteFolder; + Align = TextAlignment.TopLeft; + break; + case MenuType.RouteList: + string[] potentialFiles = { }; + string[] directoryList = { }; + bool drives = false; + if (SearchDirectory != string.Empty) + { + try + { + potentialFiles = Directory.GetFiles(SearchDirectory); + directoryList = Directory.GetDirectories(SearchDirectory); + } + catch + { + // Ignored + } + } + else + { + DriveInfo[] systemDrives = DriveInfo.GetDrives(); + directoryList = new string[systemDrives.Length]; + for (int k = 0; k < systemDrives.Length; k++) + { + directoryList[k] = systemDrives[k].Name; + } + drives = true; + } + + Items = new MenuEntry[potentialFiles.Length + directoryList.Length + 2]; + Items[0] = new MenuCaption(SearchDirectory); + Items[1] = new MenuCommand("...", MenuTag.ParentDirectory, 0); + int totalEntries = 2; + for (int j = 0; j < directoryList.Length; j++) + { + DirectoryInfo directoryInfo = new DirectoryInfo(directoryList[j]); + if (Program.CurrentHost.Platform != HostPlatform.MicrosoftWindows && directoryInfo.Name[0] == '.') + { + continue; + } + Items[totalEntries] = new MenuCommand(directoryInfo.Name, MenuTag.Directory, 0); + if (drives) + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\icon_disk.png"), new TextureParameters(null, null), out Items[totalEntries].Icon); + } + else + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\icon_folder.png"), new TextureParameters(null, null), out Items[totalEntries].Icon); + } + + totalEntries++; + } + + for (int j = 0; j < potentialFiles.Length; j++) + { + string fileName = System.IO.Path.GetFileName(potentialFiles[j]); + if (Program.CurrentHost.Platform != HostPlatform.MicrosoftWindows && fileName[0] == '.') + { + continue; + } + if (fileName.ToLowerInvariant().EndsWith(".csv") || fileName.ToLowerInvariant().EndsWith(".rw")) + { + Items[totalEntries] = new MenuCommand(fileName, MenuTag.RouteFile, 0); + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\icon_route.png"), new TextureParameters(null, null), out Items[totalEntries].Icon); + totalEntries++; + } + } + Array.Resize(ref Items, totalEntries); + Align = TextAlignment.TopLeft; + break; + case MenuType.TrainList: + potentialFiles = new string[] { }; + directoryList = new string[] { }; + drives = false; + if (SearchDirectory != string.Empty) + { + try + { + potentialFiles = Directory.GetFiles(SearchDirectory); + directoryList = Directory.GetDirectories(SearchDirectory); + } + catch + { + // Ignored + } + } + else + { + DriveInfo[] systemDrives = DriveInfo.GetDrives(); + directoryList = new string[systemDrives.Length]; + for (int k = 0; k < systemDrives.Length; k++) + { + directoryList[k] = systemDrives[k].Name; + } + drives = true; + } + + Items = new MenuEntry[potentialFiles.Length + directoryList.Length + 2]; + Items[0] = new MenuCaption(SearchDirectory); + Items[1] = new MenuCommand("...", MenuTag.ParentDirectory, 0); + totalEntries = 2; + for (int j = 0; j < directoryList.Length; j++) + { + bool isTrain = false; + for (int k = 0; k < Program.CurrentHost.Plugins.Length; k++) + { + if (Program.CurrentHost.Plugins[k].Train != null && Program.CurrentHost.Plugins[k].Train.CanLoadTrain(directoryList[j])) + { + isTrain = true; + break; + } + } + DirectoryInfo directoryInfo = new DirectoryInfo(directoryList[j]); + if (Program.CurrentHost.Platform != HostPlatform.MicrosoftWindows && directoryInfo.Name[0] == '.') + { + continue; + } + if (!isTrain) + { + Items[totalEntries] = new MenuCommand(directoryInfo.Name, MenuTag.Directory, 0); + if (drives) + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\icon_disk.png"), new TextureParameters(null, null), out Items[totalEntries].Icon); + } + else + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\icon_folder.png"), new TextureParameters(null, null), out Items[totalEntries].Icon); + } + } + else + { + Items[totalEntries] = new MenuCommand(directoryInfo.Name, MenuTag.TrainDirectory, 0); + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\icon_train.png"), new TextureParameters(null, null), out Items[totalEntries].Icon); + } + totalEntries++; + } + Array.Resize(ref Items, totalEntries); + Align = TextAlignment.TopLeft; + break; + case MenuType.Top: // top level menu + if (Interface.CurrentOptions.ScreenReaderAvailable) + { + if (!Tolk.Output(Translations.GetInterfaceString("menu_title"))) + { + // failed to output to screen reader, so don't keep trying + Interface.CurrentOptions.ScreenReaderAvailable = false; + } + } + for (i = 0; i < Program.CurrentRoute.Stations.Length; i++) + if (Program.CurrentRoute.Stations[i].PlayerStops() & Program.CurrentRoute.Stations[i].Stops.Length > 0) + { + jump = 1; + break; + } + Items = new MenuEntry[4 + jump]; + Items[0] = new MenuCommand(Translations.GetInterfaceString("menu_resume"), MenuTag.BackToSim, 0); + if (jump > 0) + Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_jump"), MenuTag.MenuJumpToStation, 0); + if (!Interface.CurrentOptions.KioskMode) + { + //Don't allow quitting or customisation of the controls in kiosk mode + Items[1 + jump] = new MenuCommand(Translations.GetInterfaceString("menu_exit"), MenuTag.MenuExitToMainMenu, 0); + Items[2 + jump] = new MenuCommand(Translations.GetInterfaceString("menu_customize_controls"), MenuTag.MenuControls, 0); + Items[3 + jump] = new MenuCommand(Translations.GetInterfaceString("menu_quit"), MenuTag.MenuQuit, 0); + } + else + { + Array.Resize(ref Items, Items.Length -3); + } + break; + case MenuType.JumpToStation: // list of stations to jump to + // count the number of available stations + menuItem = 0; + for (i = 0; i < Program.CurrentRoute.Stations.Length; i++) + if (Program.CurrentRoute.Stations[i].PlayerStops() & Program.CurrentRoute.Stations[i].Stops.Length > 0) + menuItem++; + // list available stations, selecting the next station as predefined choice + jump = 0; // no jump found yet + Items = new MenuEntry[menuItem + 1]; + Items[0] = new MenuCommand(Translations.GetInterfaceString("menu_back"), MenuTag.MenuBack, 0); + menuItem = 1; + for (i = 0; i < Program.CurrentRoute.Stations.Length; i++) + if (Program.CurrentRoute.Stations[i].PlayerStops() & Program.CurrentRoute.Stations[i].Stops.Length > 0) + { + Items[menuItem] = new MenuCommand(Program.CurrentRoute.Stations[i].Name, MenuTag.JumpToStation, i); + // if no preferred jump-to-station found yet and this station is + // after the last station the user stopped at, select this item + if (jump == 0 && i > TrainManagerBase.PlayerTrain.LastStation) + { + jump = i; + Selection = menuItem; + } + menuItem++; + } + Align = TextAlignment.TopLeft; + break; + + case MenuType.ExitToMainMenu: + Items = new MenuEntry[3]; + Items[0] = new MenuCaption(Translations.GetInterfaceString("menu_exit_question")); + Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_exit_no"), MenuTag.MenuBack, 0); + Items[2] = new MenuCommand(Translations.GetInterfaceString("menu_exit_yes"), MenuTag.ExitToMainMenu, 0); + Selection = 1; + break; + + case MenuType.Quit: // ask for quit confirmation + Items = new MenuEntry[3]; + Items[0] = new MenuCaption(Translations.GetInterfaceString("menu_quit_question")); + Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_quit_no"), MenuTag.MenuBack, 0); + Items[2] = new MenuCommand(Translations.GetInterfaceString("menu_quit_yes"), MenuTag.Quit, 0); + Selection = 1; + break; + + case MenuType.Controls: + //Refresh the joystick list + Program.Joysticks.RefreshJoysticks(); + Items = new MenuEntry[Interface.CurrentControls.Length + 1]; + Items[0] = new MenuCommand(Translations.GetInterfaceString("menu_back"), MenuTag.MenuBack, 0); + for (i = 0; i < Interface.CurrentControls.Length; i++) + Items[i + 1] = new MenuCommand(Interface.CurrentControls[i].Command.ToString(), MenuTag.Control, i); + Align = TextAlignment.TopLeft; + break; + + case MenuType.Control: + //Refresh the joystick list + Program.Joysticks.RefreshJoysticks(); + Selection = SelectionNone; + Items = new MenuEntry[4]; + // get code name and description + Control loadedControl = Interface.CurrentControls[data]; + for (int h = 0; h < Translations.CommandInfos.Length; h++) + { + if (Translations.CommandInfos[h].Command == loadedControl.Command) + { + Items[0] = new MenuCommand(loadedControl.Command.ToString() + " - " + + Translations.CommandInfos[h].Description, MenuTag.None, 0); + break; + } + } + // get assignment + String str = ""; + switch (loadedControl.Method) + { + case ControlMethod.Keyboard: + string keyName = loadedControl.Key.ToString(); + for (int k = 0; k < Translations.TranslatedKeys.Length; k++) + { + if (Translations.TranslatedKeys[k].Key == loadedControl.Key) + { + keyName = Translations.TranslatedKeys[k].Description; + break; + } + } + if (loadedControl.Modifier != KeyboardModifier.None) + { + str = Translations.GetInterfaceString("menu_keyboard") + " [" + loadedControl.Modifier + "-" + keyName + "]"; + } + else + { + str = Translations.GetInterfaceString("menu_keyboard") + " [" + keyName + "]"; + } + break; + case ControlMethod.Joystick: + str = Translations.GetInterfaceString("menu_joystick") + " " + loadedControl.Device + " [" + loadedControl.Component + " " + loadedControl.Element + "]"; + switch (loadedControl.Component) + { + case JoystickComponent.FullAxis: + case JoystickComponent.Axis: + str += " " + (loadedControl.Direction == 1 ? Translations.GetInterfaceString("menu_joystickdirection_positive") : Translations.GetInterfaceString("menu_joystickdirection_negative")); + break; + // case Interface.JoystickComponent.Button: // NOTHING TO DO FOR THIS CASE! + // str = str; + // break; + case JoystickComponent.Hat: + str += " " + (OpenTK.Input.HatPosition)loadedControl.Direction; + break; + case JoystickComponent.Invalid: + str = Translations.GetInterfaceString("menu_joystick_notavailable"); + break; + } + break; + case ControlMethod.RailDriver: + str = "RailDriver [" + loadedControl.Component + " " + loadedControl.Element + "]"; + switch (loadedControl.Component) + { + case JoystickComponent.FullAxis: + case JoystickComponent.Axis: + str += " " + (loadedControl.Direction == 1 ? Translations.GetInterfaceString("menu_joystickdirection_positive") : Translations.GetInterfaceString("menu_joystickdirection_negative")); + break; + case JoystickComponent.Invalid: + str = Translations.GetInterfaceString("menu_joystick_notavailable"); + break; + } + break; + case ControlMethod.Invalid: + str = Translations.GetInterfaceString("menu_joystick_notavailable"); + break; + } + Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_assignment_current") + " " + str, MenuTag.None, 0); + Items[2] = new MenuCommand(" ", MenuTag.None, 0); + Items[3] = new MenuCommand(Translations.GetInterfaceString("menu_assign"), MenuTag.None, 0); + break; + case MenuType.TrainDefault: + Interface.CurrentOptions.TrainFolder = Loading.GetDefaultTrainFolder(RouteFile); + bool canLoad = false; + for (int j = 0; j < Program.CurrentHost.Plugins.Length; j++) + { + if (Program.CurrentHost.Plugins[j].Train != null && Program.CurrentHost.Plugins[j].Train.CanLoadTrain(Interface.CurrentOptions.TrainFolder)) + { + canLoad = true; + break; + } + } + + if (canLoad) + { + Items = new MenuEntry[3]; + Items[0] = new MenuCaption(Translations.GetInterfaceString("start_train_default")); + Items[1] = new MenuCommand(Translations.GetInterfaceString("start_train_default_yes"), MenuTag.Yes, 0); + Items[2] = new MenuCommand(Translations.GetInterfaceString("start_train_default_no"), MenuTag.No, 0); + Selection = 1; + } + else + { + SearchDirectory = Program.FileSystem.InitialTrainFolder; + //Default train not found or not valid + Instance.PushMenu(MenuType.TrainList); + } + break; + } + // compute menu extent + for (i = 0; i < Items.Length; i++) + { + if (Items[i] == null) + { + continue; + } + size = Game.Menu.MenuFont.MeasureString(Items[i].Text); + if (Items[i].Icon != null) + { + size.X += size.Y * 1.25; + } + if (size.X > Width) + { + Width = size.X; + } + + if (MaxWidth != 0 && size.X > MaxWidth) + { + for (int j = Items[i].Text.Length - 1; j > 0; j--) + { + string trimmedText = Items[i].Text.Substring(0, j); + size = Game.Menu.MenuFont.MeasureString(trimmedText); + double mwi = MaxWidth; + if (Items[i].Icon != null) + { + mwi -= size.Y * 1.25; + } + if (size.X < mwi) + { + Items[i].DisplayLength = trimmedText.Length; + break; + } + } + Width = MaxWidth; + } + if (!(Items[i] is MenuCaption && menuType!= MenuType.RouteList && menuType != MenuType.GameStart) && size.X > ItemWidth) + ItemWidth = size.X; + } + Height = Items.Length * Game.Menu.LineHeight; + TopItem = 0; + } + + } + } +} diff --git a/source/OpenBVE/Game/Menu.cs b/source/OpenBVE/Game/Menu/Menu.cs similarity index 52% rename from source/OpenBVE/Game/Menu.cs rename to source/OpenBVE/Game/Menu/Menu.cs index 8bbc19565..6fe8ba270 100644 --- a/source/OpenBVE/Game/Menu.cs +++ b/source/OpenBVE/Game/Menu/Menu.cs @@ -1,15 +1,21 @@ -using OpenBveApi.Colors; +using OpenBveApi.Colors; using OpenBveApi.Graphics; using OpenBveApi.Interface; using System; using System.Drawing; -using DavyKager; +using System.IO; +using System.Text; +using System.Windows.Forms; +using LibRender2.Primitives; using LibRender2.Screens; -using LibRender2.Texts; +using LibRender2.Text; using OpenBve.Input; using OpenBveApi; using OpenBveApi.Input; +using OpenBveApi.Textures; using OpenTK; +using TrainManager; +using Path = OpenBveApi.Path; using Vector2 = OpenBveApi.Math.Vector2; namespace OpenBve @@ -22,65 +28,22 @@ Implemented as a singleton. Keeps a stack of menus, allowing navigating forward and back */ /// Implements the in-game menu system; manages addition and removal of individual menus. - public sealed class Menu + public sealed partial class Menu { - /// The list of possible tags for a menu entry- These define the functionality of a given menu entry - public enum MenuTag - { - /// Has no functionality/ is blank - None, - /// Is a caption for another menu item - Caption, - /// Moves up a menu level - MenuBack, - /// Enters the submenu containing the list of stations to which the player train may be jumped - MenuJumpToStation, - /// Enters the submenu for exiting to the main menu - MenuExitToMainMenu, - /// Enters the submenu for customising controls - MenuControls, - /// Enters the submenu for quitting the program - MenuQuit, - /// Returns to the simulation - BackToSim, - /// Jumps to the selected station - JumpToStation, - /// Exits to the main menu - ExitToMainMenu, - /// Quits the program - Quit, - /// Customises the selected control - Control - }; - - /// The list of possible sub-menu types - public enum MenuType - { - /// Not a sub menu - None, - /// Returns to the menu level above - Top, - /// The station jump menu - JumpToStation, - /// Returns to the main menu - ExitToMainMenu, - /// Provides a list of controls and allows customisation whilst in-game - Controls, - /// Customises the specified control - Control, - /// Quits the game - Quit - }; - // components of the semi-transparent screen overlay private readonly Color128 overlayColor = new Color128(0.0f, 0.0f, 0.0f, 0.2f); private readonly Color128 backgroundColor = new Color128(0.0f, 0.0f, 0.0f, 1.0f); private readonly Color128 highlightColor = new Color128(1.0f, 0.69f, 0.0f, 1.0f); + private readonly Color128 folderHighlightColor = new Color128(0.0f, 0.69f, 1.0f, 1.0f); + private readonly Color128 routeHighlightColor = new Color128(0.0f, 1.0f, 0.69f, 1.0f); // text colours private static readonly Color128 ColourCaption = new Color128(0.750f, 0.750f, 0.875f, 1.0f); private static readonly Color128 ColourDimmed = new Color128(1.000f, 1.000f, 1.000f, 0.5f); private static readonly Color128 ColourHighlight = Color128.Black; private static readonly Color128 ColourNormal = Color128.White; + private static readonly Picturebox LogoPictureBox = new Picturebox(Program.Renderer); + + // some sizes and constants // TODO: make borders Menu fields dependent on font size @@ -91,277 +54,9 @@ public enum MenuType private const float LineSpacing = 1.75f; // the ratio between the font size and line distance private const int SelectionNone = -1; - /******************** - BASE MENU ENTRY CLASS - *********************/ - private abstract class MenuEntry - { - internal string Text; - } - - /******************** - DERIVED MENU ITEM CLASSES - *********************/ - private class MenuCaption : MenuEntry - { - internal MenuCaption(string Text) - { - this.Text = Text; - } - } - private class MenuCommand : MenuEntry - { - internal readonly MenuTag Tag; - internal readonly int Data; - internal MenuCommand(string Text, MenuTag Tag, int Data) - { - this.Text = Text; - this.Tag = Tag; - this.Data = Data; - } - } - - /******************** - SINGLE-MENU ENTRY CLASS - ********************* - Describes a single menu of the menu stack. - The class is private to Menu, but all its fields are public to allow 'quick-and-dirty' - access from Menu itself. */ - private class SingleMenu - { - /******************** - MENU FIELDS - *********************/ - public readonly TextAlignment Align; - public readonly MenuEntry[] Items = { }; - public readonly int ItemWidth = 0; - public readonly int Width = 0; - public readonly int Height = 0; - - private int lastSelection = int.MaxValue; - private int currentSelection; - - public int Selection - { - get - { - return currentSelection; - } - set - { - lastSelection = currentSelection; - currentSelection = value; - if (currentSelection != lastSelection && Interface.CurrentOptions.ScreenReaderAvailable) - { - if (!Tolk.Output(Items[currentSelection].Text)) - { - // failed to output to screen reader, so don't keep trying - Interface.CurrentOptions.ScreenReaderAvailable = false; - } - } - } - } - public int TopItem; // the top displayed menu item - - - /******************** - MENU C'TOR - *********************/ - public SingleMenu(MenuType menuType, int data = 0) - { - int i, menuItem; - int jump = 0; - Size size; - - Align = TextAlignment.TopMiddle; - Height = Width = 0; - Selection = 0; // defaults to first menu item - switch (menuType) - { - case MenuType.Top: // top level menu - if (Interface.CurrentOptions.ScreenReaderAvailable) - { - if (!Tolk.Output(Translations.GetInterfaceString("menu_title"))) - { - // failed to output to screen reader, so don't keep trying - Interface.CurrentOptions.ScreenReaderAvailable = false; - } - } - for (i = 0; i < Program.CurrentRoute.Stations.Length; i++) - if (Program.CurrentRoute.Stations[i].PlayerStops() & Program.CurrentRoute.Stations[i].Stops.Length > 0) - { - jump = 1; - break; - } - Items = new MenuEntry[4 + jump]; - Items[0] = new MenuCommand(Translations.GetInterfaceString("menu_resume"), MenuTag.BackToSim, 0); - if (jump > 0) - Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_jump"), MenuTag.MenuJumpToStation, 0); - if (!Interface.CurrentOptions.KioskMode) - { - //Don't allow quitting or customisation of the controls in kiosk mode - Items[1 + jump] = new MenuCommand(Translations.GetInterfaceString("menu_exit"), MenuTag.MenuExitToMainMenu, 0); - Items[2 + jump] = new MenuCommand(Translations.GetInterfaceString("menu_customize_controls"), MenuTag.MenuControls, 0); - Items[3 + jump] = new MenuCommand(Translations.GetInterfaceString("menu_quit"), MenuTag.MenuQuit, 0); - } - else - { - Array.Resize(ref Items, Items.Length -3); - } - break; - - case MenuType.JumpToStation: // list of stations to jump to - // count the number of available stations - menuItem = 0; - for (i = 0; i < Program.CurrentRoute.Stations.Length; i++) - if (Program.CurrentRoute.Stations[i].PlayerStops() & Program.CurrentRoute.Stations[i].Stops.Length > 0) - menuItem++; - // list available stations, selecting the next station as predefined choice - jump = 0; // no jump found yet - Items = new MenuEntry[menuItem + 1]; - Items[0] = new MenuCommand(Translations.GetInterfaceString("menu_back"), MenuTag.MenuBack, 0); - menuItem = 1; - for (i = 0; i < Program.CurrentRoute.Stations.Length; i++) - if (Program.CurrentRoute.Stations[i].PlayerStops() & Program.CurrentRoute.Stations[i].Stops.Length > 0) - { - Items[menuItem] = new MenuCommand(Program.CurrentRoute.Stations[i].Name, MenuTag.JumpToStation, i); - // if no preferred jump-to-station found yet and this station is - // after the last station the user stopped at, select this item - if (jump == 0 && i > TrainManager.PlayerTrain.LastStation) - { - jump = i; - Selection = menuItem; - } - menuItem++; - } - Align = TextAlignment.TopLeft; - break; - - case MenuType.ExitToMainMenu: - Items = new MenuEntry[3]; - Items[0] = new MenuCaption(Translations.GetInterfaceString("menu_exit_question")); - Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_exit_no"), MenuTag.MenuBack, 0); - Items[2] = new MenuCommand(Translations.GetInterfaceString("menu_exit_yes"), MenuTag.ExitToMainMenu, 0); - Selection = 1; - break; - - case MenuType.Quit: // ask for quit confirmation - Items = new MenuEntry[3]; - Items[0] = new MenuCaption(Translations.GetInterfaceString("menu_quit_question")); - Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_quit_no"), MenuTag.MenuBack, 0); - Items[2] = new MenuCommand(Translations.GetInterfaceString("menu_quit_yes"), MenuTag.Quit, 0); - Selection = 1; - break; - - case MenuType.Controls: - //Refresh the joystick list - Program.Joysticks.RefreshJoysticks(); - Items = new MenuEntry[Interface.CurrentControls.Length + 1]; - Items[0] = new MenuCommand(Translations.GetInterfaceString("menu_back"), MenuTag.MenuBack, 0); - for (i = 0; i < Interface.CurrentControls.Length; i++) - Items[i + 1] = new MenuCommand(Interface.CurrentControls[i].Command.ToString(), MenuTag.Control, i); - Align = TextAlignment.TopLeft; - break; - - case MenuType.Control: - //Refresh the joystick list - Program.Joysticks.RefreshJoysticks(); - Selection = SelectionNone; - Items = new MenuEntry[4]; - // get code name and description - Control loadedControl = Interface.CurrentControls[data]; - for (int h = 0; h < Translations.CommandInfos.Length; h++) - { - if (Translations.CommandInfos[h].Command == loadedControl.Command) - { - Items[0] = new MenuCommand(loadedControl.Command.ToString() + " - " + - Translations.CommandInfos[h].Description, MenuTag.None, 0); - break; - } - } - // get assignment - String str = ""; - switch (loadedControl.Method) - { - case ControlMethod.Keyboard: - string keyName = loadedControl.Key.ToString(); - for (int k = 0; k < Translations.TranslatedKeys.Length; k++) - { - if (Translations.TranslatedKeys[k].Key == loadedControl.Key) - { - keyName = Translations.TranslatedKeys[k].Description; - break; - } - } - if (loadedControl.Modifier != KeyboardModifier.None) - { - str = Translations.GetInterfaceString("menu_keyboard") + " [" + loadedControl.Modifier + "-" + keyName + "]"; - } - else - { - str = Translations.GetInterfaceString("menu_keyboard") + " [" + keyName + "]"; - } - break; - case ControlMethod.Joystick: - str = Translations.GetInterfaceString("menu_joystick") + " " + loadedControl.Device + " [" + loadedControl.Component + " " + loadedControl.Element + "]"; - switch (loadedControl.Component) - { - case JoystickComponent.FullAxis: - case JoystickComponent.Axis: - str += " " + (loadedControl.Direction == 1 ? Translations.GetInterfaceString("menu_joystickdirection_positive") : Translations.GetInterfaceString("menu_joystickdirection_negative")); - break; - // case Interface.JoystickComponent.Button: // NOTHING TO DO FOR THIS CASE! - // str = str; - // break; - case JoystickComponent.Hat: - str += " " + (OpenTK.Input.HatPosition)loadedControl.Direction; - break; - case JoystickComponent.Invalid: - str = Translations.GetInterfaceString("menu_joystick_notavailable"); - break; - } - break; - case ControlMethod.RailDriver: - str = "RailDriver [" + loadedControl.Component + " " + loadedControl.Element + "]"; - switch (loadedControl.Component) - { - case JoystickComponent.FullAxis: - case JoystickComponent.Axis: - str += " " + (loadedControl.Direction == 1 ? Translations.GetInterfaceString("menu_joystickdirection_positive") : Translations.GetInterfaceString("menu_joystickdirection_negative")); - break; - case JoystickComponent.Invalid: - str = Translations.GetInterfaceString("menu_joystick_notavailable"); - break; - } - break; - case ControlMethod.Invalid: - str = Translations.GetInterfaceString("menu_joystick_notavailable"); - break; - } - Items[1] = new MenuCommand(Translations.GetInterfaceString("menu_assignment_current") + " " + str, MenuTag.None, 0); - Items[2] = new MenuCommand(" ", MenuTag.None, 0); - Items[3] = new MenuCommand(Translations.GetInterfaceString("menu_assign"), MenuTag.None, 0); - break; - } - - // compute menu extent - for (i = 0; i < Items.Length; i++) - { - if (Items[i] == null) - { - continue; - } - size = Game.Menu.MenuFont.MeasureString(Items[i].Text); - if (size.Width > Width) - Width = size.Width; - if (!(Items[i] is MenuCaption) && size.Width > ItemWidth) - ItemWidth = size.Width; - } - Height = Items.Length * Game.Menu.LineHeight; - TopItem = 0; - } - - } // end of private class SingleMenu + private double lastTimeElapsed; + + // end of private class SingleMenu /******************** MENU SYSTEM FIELDS @@ -376,8 +71,8 @@ MENU SYSTEM FIELDS private SingleMenu[] Menus = { }; private OpenGlFont menuFont = null; // area occupied by the items of the current menu in screen coordinates - private int menuXmin, menuXmax, menuYmin, menuYmax; - private int topItemY; // the top edge of top item + private double menuXmin, menuXmax, menuYmin, menuYmax; + private double topItemY; // the top edge of top item private int visibleItems; // the number of visible items // properties (to allow read-only access to some fields) internal int LineHeight @@ -446,6 +141,23 @@ private void Init() break; } } + int quarterWidth = (int) (Program.Renderer.Screen.Width / 4.0); + int descriptionLoc = Program.Renderer.Screen.Width - quarterWidth - quarterWidth / 2; + int descriptionWidth = quarterWidth + quarterWidth / 2; + int descriptionHeight = descriptionWidth; + if (descriptionHeight + quarterWidth > Program.Renderer.Screen.Height - 50) + { + descriptionHeight = Program.Renderer.Screen.Height - quarterWidth - 50; + } + routeDescriptionBox.Location = new Vector2(descriptionLoc, quarterWidth); + routeDescriptionBox.Size = new Vector2(descriptionWidth, descriptionHeight); + int imageLoc = Program.Renderer.Screen.Width - quarterWidth - quarterWidth / 4; + routePictureBox.Location = new Vector2(imageLoc, 0); + routePictureBox.Size = new Vector2(quarterWidth, quarterWidth); + routePictureBox.BackgroundColor = Color128.White; + LogoPictureBox.Location = new Vector2(Program.Renderer.Screen.Width / 2.0, Program.Renderer.Screen.Height / 8.0); + LogoPictureBox.Size = new Vector2(Program.Renderer.Screen.Width / 2.0, Program.Renderer.Screen.Width / 2.0); + LogoPictureBox.Texture = Program.Renderer.ProgramLogo; isInitialized = true; } @@ -457,6 +169,7 @@ private void Reset() CurrMenu = -1; Menus = new SingleMenu[] { }; isCustomisingControl = false; + routeDescriptionBox.CurrentlySelected = false; } // @@ -466,7 +179,8 @@ private void Reset() /// Pushes a menu into the menu stack /// The type of menu to push /// The index of the menu in the menu stack (If pushing an existing higher level menu) - public void PushMenu(MenuType type, int data = 0) + /// Whether we are replacing the selected menu item + public void PushMenu(MenuType type, int data = 0, bool replace = false) { if (Program.Renderer.CurrentInterface != InterfaceType.Menu) { @@ -475,10 +189,23 @@ public void PushMenu(MenuType type, int data = 0) } if (!isInitialized) Init(); - CurrMenu++; + if (!replace) + { + CurrMenu++; + } + if (Menus.Length <= CurrMenu) Array.Resize(ref Menus, CurrMenu + 1); - Menus[CurrMenu] = new Menu.SingleMenu(type, data); + int MaxWidth = 0; + if (type == MenuType.RouteList || type == MenuType.TrainList) + { + MaxWidth = Program.Renderer.Screen.Width / 2; + } + Menus[CurrMenu] = new SingleMenu(type, data, MaxWidth); + if (replace) + { + Menus[CurrMenu].Selection = 1; + } PositionMenu(); Program.Renderer.CurrentInterface = InterfaceType.Menu; } @@ -491,7 +218,7 @@ public void PopMenu() { if (CurrMenu > 0) // if more than one menu remaining... { - CurrMenu--; // ...back to previous smenu + CurrMenu--; // ...back to previous menu PositionMenu(); } else @@ -561,6 +288,21 @@ internal void ProcessMouseScroll(int Scroll) { // Load the current menu SingleMenu menu = Menus[CurrMenu]; + if (menu.Type == MenuType.RouteList || menu.Type == MenuType.TrainList) + { + if (routeDescriptionBox.CurrentlySelected) + { + if (Math.Abs(Scroll) == Scroll) + { + routeDescriptionBox.VerticalScroll(-1); + } + else + { + routeDescriptionBox.VerticalScroll(1); + } + return; + } + } if (Math.Abs(Scroll) == Scroll) { //Negative @@ -568,7 +310,6 @@ internal void ProcessMouseScroll(int Scroll) { menu.TopItem--; } - } else { @@ -604,12 +345,29 @@ internal bool ProcessMouseMove(int x, int y) menu.Selection = menu.TopItem - 1; return true; } + if (menu.Type == MenuType.RouteList || menu.Type == MenuType.TrainList) + { + if (x > routeDescriptionBox.Location.X && x < routeDescriptionBox.Location.X + routeDescriptionBox.Size.X && y > routeDescriptionBox.Location.Y && y < routeDescriptionBox.Location.Y + routeDescriptionBox.Size.Y) + { + routeDescriptionBox.CurrentlySelected = true; + } + else + { + routeDescriptionBox.CurrentlySelected = false; + } + //HACK: Use this to trigger our menu start button! + if (x > Program.Renderer.Screen.Width - 200 && x < Program.Renderer.Screen.Width - 10 && y > Program.Renderer.Screen.Height - 40 && y < Program.Renderer.Screen.Height - 10) + { + menu.Selection = int.MaxValue; + return true; + } + } if (x < menuXmin || x > menuXmax || y < menuYmin || y > menuYmax) { return false; } - int item = (y - topItemY) / lineHeight + menu.TopItem; + int item = (int) ((y - topItemY) / lineHeight + menu.TopItem); // if the mouse is above a command item, select it if (item >= 0 && item < menu.Items.Length && menu.Items[item] is MenuCommand) { @@ -620,7 +378,7 @@ internal bool ProcessMouseMove(int x, int y) return true; } } - + return false; } @@ -661,16 +419,43 @@ internal void ProcessCommand(Translations.Command cmd, double timeElapsed) { return; } + SingleMenu menu = Menus[CurrMenu]; // MenuBack is managed independently from single menu data if (cmd == Translations.Command.MenuBack) { - PopMenu(); + if (menu.Type == MenuType.GameStart) + { + Instance.PushMenu(MenuType.Quit); + } + else + { + PopMenu(); + } return; } - - SingleMenu menu = Menus[CurrMenu]; + if (menu.Selection == SelectionNone) // if menu has no selection, do nothing return; + if (menu.Selection == int.MaxValue) + { + if (RoutefileState == RouteState.Error) + return; + if (menu.Type == MenuType.TrainDefault || menu.Type == MenuType.TrainList) + { + Reset(); + //Launch the game! + Loading.Complete = false; + Loading.LoadAsynchronously(RouteFile, Encoding.UTF8, Interface.CurrentOptions.TrainFolder, Encoding.UTF8); + OpenBVEGame g = Program.currentGameWindow as OpenBVEGame; + // ReSharper disable once PossibleNullReferenceException + g.LoadingScreenLoop(); + Program.Renderer.CurrentInterface = InterfaceType.Normal; + return; + } + Instance.PushMenu(MenuType.TrainDefault); + return; + + } switch (cmd) { case Translations.Command.MenuUp: // UP @@ -716,11 +501,78 @@ internal void ProcessCommand(Translations.Command cmd, double timeElapsed) Reset(); Program.Renderer.CurrentInterface = InterfaceType.Normal; break; + // route menu commands + case MenuTag.RouteList: // TO ROUTE LIST MENU + Menu.instance.PushMenu(MenuType.RouteList); + routeDescriptionBox.Text = Translations.GetInterfaceString("errors_route_please_select"); + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\please_select.png"), new TextureParameters(null, null), out routePictureBox.Texture); + break; + case MenuTag.Directory: // SHOWS THE LIST OF FILES IN THE SELECTED DIR + SearchDirectory = SearchDirectory == string.Empty ? menu.Items[menu.Selection].Text : Path.CombineDirectory(SearchDirectory, menu.Items[menu.Selection].Text); + Menu.instance.PushMenu(Instance.Menus[CurrMenu].Type, 0, true); + break; + case MenuTag.ParentDirectory: // SHOWS THE LIST OF FILES IN THE PARENT DIR + if (string.IsNullOrEmpty(SearchDirectory)) + { + return; + } - // simulation commands + string oldSearchDirectory = SearchDirectory; + try + { + DirectoryInfo newDirectory = Directory.GetParent(SearchDirectory); + SearchDirectory = newDirectory == null ? string.Empty : Directory.GetParent(SearchDirectory)?.ToString(); + } + catch + { + SearchDirectory = oldSearchDirectory; + return; + } + Menu.instance.PushMenu(Instance.Menus[CurrMenu].Type, 0, true); + break; + case MenuTag.RouteFile: + RoutefileState = RouteState.Loading; + RouteFile = Path.CombineFile(SearchDirectory, menu.Items[menu.Selection].Text); + if (!routeWorkerThread.IsBusy) + { + routeWorkerThread.RunWorkerAsync(); + } + + break; + case MenuTag.TrainDirectory: + for (int i = 0; i < Program.CurrentHost.Plugins.Length; i++) + { + string trainDir = Path.CombineDirectory(SearchDirectory, menu.Items[menu.Selection].Text); + if (Program.CurrentHost.Plugins[i].Train != null && Program.CurrentHost.Plugins[i].Train.CanLoadTrain(trainDir)) + { + if (Interface.CurrentOptions.TrainFolder == trainDir) + { + //enter folder + SearchDirectory = SearchDirectory == string.Empty ? menu.Items[menu.Selection].Text : Path.CombineDirectory(SearchDirectory, menu.Items[menu.Selection].Text); + Menu.instance.PushMenu(Instance.Menus[CurrMenu].Type, 0, true); + } + else + { + //Show details + Interface.CurrentOptions.TrainFolder = trainDir; + routeDescriptionBox.Text = Program.CurrentHost.Plugins[i].Train.GetDescription(trainDir); + Image trainImage = Program.CurrentHost.Plugins[i].Train.GetImage(trainDir); + if (trainImage != null) + { + Program.CurrentHost.RegisterTexture(new Bitmap(trainImage), new TextureParameters(null, null), out routePictureBox.Texture); + } + else + { + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\train_unknown.png"), new TextureParameters(null, null), out routePictureBox.Texture); + } + } + } + } + break; + // simulation commands case MenuTag.JumpToStation: // JUMP TO STATION Reset(); - TrainManager.PlayerTrain.Jump(menuItem.Data); + TrainManagerBase.PlayerTrain.Jump(menuItem.Data); Program.TrainManager.JumpTFO(); break; case MenuTag.ExitToMainMenu: // BACK TO MAIN MENU @@ -738,6 +590,28 @@ internal void ProcessCommand(Translations.Command cmd, double timeElapsed) Reset(); MainLoop.Quit = MainLoop.QuitMode.QuitProgram; break; + case MenuTag.Yes: + if (menu.Type == MenuType.TrainDefault) + { + Reset(); + //Launch the game! + Loading.Complete = false; + Loading.LoadAsynchronously(RouteFile, Encoding.UTF8, Interface.CurrentOptions.TrainFolder, Encoding.UTF8); + OpenBVEGame g = Program.currentGameWindow as OpenBVEGame; + // ReSharper disable once PossibleNullReferenceException + g.LoadingScreenLoop(); + Program.Renderer.CurrentInterface = InterfaceType.Normal; + } + break; + case MenuTag.No: + if (menu.Type == MenuType.TrainDefault) + { + SearchDirectory = Program.FileSystem.InitialTrainFolder; + Instance.PushMenu(MenuType.TrainList); + routeDescriptionBox.Text = Translations.GetInterfaceString("start_train_choose"); + Program.CurrentHost.RegisterTexture(Path.CombineFile(Program.FileSystem.DataFolder, "Menu\\please_select.png"), new TextureParameters(null, null), out routePictureBox.Texture); + } + break; } } break; @@ -757,9 +631,10 @@ internal void ProcessCommand(Translations.Command cmd, double timeElapsed) // DRAW MENU // /// Draws the current menu as a screen overlay - internal void Draw() + internal void Draw(double RealTimeElapsed) { - + double TimeElapsed = RealTimeElapsed - lastTimeElapsed; + lastTimeElapsed = RealTimeElapsed; int i; if (CurrMenu < 0 || CurrMenu >= Menus.Length) @@ -769,15 +644,28 @@ internal void Draw() // overlay background Program.Renderer.Rectangle.Draw(null, Vector2.Null, new Vector2(Program.Renderer.Screen.Width, Program.Renderer.Screen.Height), overlayColor); - // HORIZONTAL PLACEMENT: centre the menu in the main window - int itemLeft = (Program.Renderer.Screen.Width - menu.ItemWidth) / 2; // item left edge - // if menu alignment is left, left-align items, otherwise centre them in the screen - int itemX = (menu.Align & TextAlignment.Left) != 0 ? itemLeft : Program.Renderer.Screen.Width / 2; - + + double itemLeft, itemX; + if (menu.Type == MenuType.GameStart || menu.Type == MenuType.RouteList || menu.Type == MenuType.TrainList) + { + itemLeft = 0; + itemX = 16; + Program.Renderer.Rectangle.Draw(null, new Vector2(0, menuYmin - MenuBorderY), new Vector2(menuXmax - menuXmin + 2.0f * MenuBorderX, menuYmax - menuYmin + 2.0f * MenuBorderY), backgroundColor); + } + else + { + itemLeft = (Program.Renderer.Screen.Width - menu.ItemWidth) / 2; // item left edge + // if menu alignment is left, left-align items, otherwise centre them in the screen + itemX = (menu.Align & TextAlignment.Left) != 0 ? itemLeft : Program.Renderer.Screen.Width / 2.0; + Program.Renderer.Rectangle.Draw(null, new Vector2(menuXmin - MenuBorderX, menuYmin - MenuBorderY), new Vector2(menuXmax - menuXmin + 2.0f * MenuBorderX, menuYmax - menuYmin + 2.0f * MenuBorderY), backgroundColor); + } + + // draw the menu background + + int menuBottomItem = menu.TopItem + visibleItems - 1; - // draw the menu background - Program.Renderer.Rectangle.Draw(null, new Vector2(menuXmin - MenuBorderX, menuYmin - MenuBorderY), new Vector2(menuXmax - menuXmin + 2.0f * MenuBorderX, menuYmax - menuYmin + 2.0f * MenuBorderY), backgroundColor); + // if not starting from the top of the menu, draw a dimmed ellipsis item if (menu.Selection == menu.TopItem - 1 && !isCustomisingControl) @@ -785,33 +673,72 @@ internal void Draw() Program.Renderer.Rectangle.Draw(null, new Vector2(itemLeft - MenuItemBorderX, menuYmin /*-MenuItemBorderY*/), new Vector2(menu.ItemWidth + MenuItemBorderX, em + MenuItemBorderY * 2), highlightColor); } if (menu.TopItem > 0) - Program.Renderer.OpenGlString.Draw(MenuFont, "...", new Point(itemX, menuYmin), + Program.Renderer.OpenGlString.Draw(MenuFont, "...", new Vector2(itemX, menuYmin), menu.Align, ColourDimmed, false); // draw the items - int itemY = topItemY; + double itemY = topItemY; for (i = menu.TopItem; i <= menuBottomItem && i < menu.Items.Length; i++) { if (menu.Items[i] == null) { continue; } + + double itemHeight = MenuFont.MeasureString(menu.Items[i].Text).Y; + double iconX = itemX; + if (menu.Items[i].Icon != null) + { + itemX += itemHeight * 1.25; + } if (i == menu.Selection) { // draw a solid highlight rectangle under the text // HACK! the highlight rectangle has to be shifted a little down to match // the text body. OpenGL 'feature'? - Program.Renderer.Rectangle.Draw(null, new Vector2(itemLeft - MenuItemBorderX, itemY /*-MenuItemBorderY*/), new Vector2(menu.ItemWidth + 2.0f * MenuItemBorderX, em + MenuItemBorderY * 2), highlightColor); + MenuCommand command = menu.Items[i] as MenuCommand; + Color128 color = highlightColor; + if(command != null) + { + switch (command.Tag) + { + case MenuTag.Directory: + case MenuTag.ParentDirectory: + color = folderHighlightColor; + break; + case MenuTag.RouteFile: + color = routeHighlightColor; + break; + default: + color = highlightColor; + break; + } + } + + if (itemLeft == 0) + { + Program.Renderer.Rectangle.Draw(null, new Vector2(MenuItemBorderX, itemY /*-MenuItemBorderY*/), new Vector2(menu.Width + 2.0f * MenuItemBorderX, em + MenuItemBorderY * 2), color); + } + else + { + Program.Renderer.Rectangle.Draw(null, new Vector2(itemLeft - MenuItemBorderX, itemY /*-MenuItemBorderY*/), new Vector2(menu.ItemWidth + 2.0f * MenuItemBorderX, em + MenuItemBorderY * 2), color); + } + // draw the text - Program.Renderer.OpenGlString.Draw(MenuFont, menu.Items[i].Text, new Point(itemX, itemY), + Program.Renderer.OpenGlString.Draw(MenuFont, menu.Items[i].DisplayText(TimeElapsed), new Vector2(itemX, itemY), menu.Align, ColourHighlight, false); } else if (menu.Items[i] is MenuCaption) - Program.Renderer.OpenGlString.Draw(MenuFont, menu.Items[i].Text, new Point(itemX, itemY), + Program.Renderer.OpenGlString.Draw(MenuFont, menu.Items[i].DisplayText(TimeElapsed), new Vector2(itemX, itemY), menu.Align, ColourCaption, false); else - Program.Renderer.OpenGlString.Draw(MenuFont, menu.Items[i].Text, new Point(itemX, itemY), + Program.Renderer.OpenGlString.Draw(MenuFont, menu.Items[i].DisplayText(TimeElapsed), new Vector2(itemX, itemY), menu.Align, ColourNormal, false); itemY += lineHeight; + if (menu.Items[i].Icon != null) + { + Program.Renderer.Rectangle.DrawAlpha(menu.Items[i].Icon, new Vector2(iconX, itemY - itemHeight * 1.5), new Vector2(itemHeight, itemHeight), Color128.White); + itemX = iconX; + } } @@ -821,8 +748,80 @@ internal void Draw() } // if not at the end of the menu, draw a dimmed ellipsis item at the bottom if (i < menu.Items.Length - 1) - Program.Renderer.OpenGlString.Draw(MenuFont, "...", new Point(itemX, itemY), + Program.Renderer.OpenGlString.Draw(MenuFont, "...", new Vector2(itemX, itemY), menu.Align, ColourDimmed, false); + switch (menu.Type) + { + case MenuType.GameStart: + LogoPictureBox.Draw(); + string currentVersion = @"v" + Application.ProductVersion + Program.VersionSuffix; + if (IntPtr.Size != 4) + { + currentVersion += @" 64-bit"; + } + + OpenGlFont versionFont = Program.Renderer.Fonts.NextSmallestFont(MenuFont); + Program.Renderer.OpenGlString.Draw(versionFont, currentVersion, new Vector2(Program.Renderer.Screen.Width - Program.Renderer.Screen.Width / 4, Program.Renderer.Screen.Height - versionFont.FontSize * 2), TextAlignment.TopLeft, Color128.Black); + break; + case MenuType.RouteList: + case MenuType.TrainList: + { + switch (RoutefileState) + { + case RouteState.NoneSelected: + routePictureBox.Draw(); + routeDescriptionBox.Draw(); + Program.Renderer.Rectangle.Draw(null, new Vector2(Program.Renderer.Screen.Width - 200, Program.Renderer.Screen.Height - 40), new Vector2(190, 30), Color128.Black); + Program.Renderer.OpenGlString.Draw(MenuFont, Translations.GetInterfaceString("start_train_choose"), new Vector2(Program.Renderer.Screen.Width - 180, Program.Renderer.Screen.Height - 35), TextAlignment.TopLeft, Color128.Grey); + break; + case RouteState.Loading: + routePictureBox.Draw(); + routeDescriptionBox.Draw(); + Program.Renderer.Rectangle.Draw(null, new Vector2(Program.Renderer.Screen.Width - 200, Program.Renderer.Screen.Height - 40), new Vector2(190, 30), Color128.Black); + Program.Renderer.OpenGlString.Draw(MenuFont, Translations.GetInterfaceString("start_train_choose"), new Vector2(Program.Renderer.Screen.Width - 180, Program.Renderer.Screen.Height - 35), TextAlignment.TopLeft, Color128.Grey); + break; + case RouteState.Processed: + routePictureBox.Draw(); + routeDescriptionBox.Draw(); + //Game start button + if (menu.Selection == int.MaxValue) //HACK: Special value to make this work with minimum extra code + { + Program.Renderer.Rectangle.Draw(null, new Vector2(Program.Renderer.Screen.Width - 200, Program.Renderer.Screen.Height - 40), new Vector2(190, 30), Color128.Black); + Program.Renderer.Rectangle.Draw(null, new Vector2(Program.Renderer.Screen.Width - 197, Program.Renderer.Screen.Height - 37), new Vector2(184, 24), highlightColor); + if (menu.Type == MenuType.RouteList) + { + Program.Renderer.OpenGlString.Draw(MenuFont, Translations.GetInterfaceString("start_train_choose"), new Vector2(Program.Renderer.Screen.Width - 180, Program.Renderer.Screen.Height - 35), TextAlignment.TopLeft, Color128.Black); + } + else + { + Program.Renderer.OpenGlString.Draw(MenuFont, Translations.GetInterfaceString("start_start_start"), new Vector2(Program.Renderer.Screen.Width - 180, Program.Renderer.Screen.Height - 35), TextAlignment.TopLeft, Color128.Black); + } + } + else + { + Program.Renderer.Rectangle.Draw(null, new Vector2(Program.Renderer.Screen.Width - 200, Program.Renderer.Screen.Height - 40), new Vector2(190, 30), Color128.Black); + if (menu.Type == MenuType.RouteList) + { + Program.Renderer.OpenGlString.Draw(MenuFont, Translations.GetInterfaceString("start_train_choose"), new Vector2(Program.Renderer.Screen.Width - 180, Program.Renderer.Screen.Height - 35), TextAlignment.TopLeft, Color128.White); + } + else + { + Program.Renderer.OpenGlString.Draw(MenuFont, Translations.GetInterfaceString("start_start_start"), new Vector2(Program.Renderer.Screen.Width - 180, Program.Renderer.Screen.Height - 35), TextAlignment.TopLeft, Color128.White); + } + } + break; + case RouteState.Error: + routePictureBox.Draw(); + routeDescriptionBox.Draw(); + Program.Renderer.Rectangle.Draw(null, new Vector2(Program.Renderer.Screen.Width - 200, Program.Renderer.Screen.Height - 40), new Vector2(190, 30), Color128.Black); + Program.Renderer.OpenGlString.Draw(MenuFont, Translations.GetInterfaceString("start_train_choose"), new Vector2(Program.Renderer.Screen.Width - 180, Program.Renderer.Screen.Height - 35), TextAlignment.TopLeft, Color128.Grey); + break; + } + + break; + } + } + } // @@ -838,8 +837,26 @@ private void PositionMenu() return; SingleMenu menu = Menus[CurrMenu]; - // HORIZONTAL PLACEMENT: centre the menu in the main window - menuXmin = (Program.Renderer.Screen.Width - menu.Width) / 2; // menu left edge (border excluded) + for (int i = 0; i < menu.Items.Length; i++) + { + /* + * HACK: This is a property method, and is also used to + * reset the timer and display string back to the starting values + */ + menu.Items[i].DisplayLength = menu.Items[i].DisplayLength; + } + if (menu.Type == MenuType.GameStart || menu.Type == MenuType.RouteList || menu.Type == MenuType.TrainList) + { + // Left aligned, used for route browser + menuXmin = 0; + } + + else + { + // HORIZONTAL PLACEMENT: centre the menu in the main window + menuXmin = (Program.Renderer.Screen.Width - menu.Width) / 2; // menu left edge (border excluded) + } + menuXmax = menuXmin + menu.Width; // menu right edge (border excluded) // VERTICAL PLACEMENT: centre the menu in the main window menuYmin = (Program.Renderer.Screen.Height - menu.Height) / 2; // menu top edge (border excluded) @@ -860,7 +877,7 @@ private void PositionMenu() menu.TopItem = menu.Selection - (menu.Selection % visibleItems); visibleItems = menu.Items.Length - menu.TopItem < visibleItems ? // in the last chunk, menu.Items.Length - menu.TopItem : visibleItems; // display remaining items only - menuYmin = (Program.Renderer.Screen.Height - numOfLines * lineHeight) / 2; + menuYmin = (Program.Renderer.Screen.Height - numOfLines * lineHeight) / 2.0; menuYmax = menuYmin + numOfLines * lineHeight; // first menu item is drawn on second line (first line is empty // on first screen and contains an ellipsis on following screens diff --git a/source/OpenBVE/Game/Menu/MenuTag.cs b/source/OpenBVE/Game/Menu/MenuTag.cs new file mode 100644 index 000000000..20f330c5a --- /dev/null +++ b/source/OpenBVE/Game/Menu/MenuTag.cs @@ -0,0 +1,47 @@ +namespace OpenBve +{ + /// The list of possible tags for a menu entry- These define the functionality of a given menu entry + public enum MenuTag + { + /// Is unselectable + Unselectable, + /// Has no functionality/ is blank + None, + /// Is a caption for another menu item + Caption, + /// Moves up a menu level + MenuBack, + /// Enters the submenu containing the list of stations to which the player train may be jumped + MenuJumpToStation, + /// Enters the submenu for exiting to the main menu + MenuExitToMainMenu, + /// Enters the submenu for customising controls + MenuControls, + /// Enters the submenu for quitting the program + MenuQuit, + /// Returns to the simulation + BackToSim, + /// Jumps to the selected station + JumpToStation, + /// Exits to the main menu + ExitToMainMenu, + /// Quits the program + Quit, + /// Customises the selected control + Control, + /// Displays a list of routefiles + RouteList, + /// Selects a routefile to load + RouteFile, + /// A directory + Directory, + /// Enters the parent directory + ParentDirectory, + /// Selects Yes for a menu choice + Yes, + /// Selects No for a menu choice + No, + /// A train directory + TrainDirectory + } +} diff --git a/source/OpenBVE/Game/Menu/MenuType.cs b/source/OpenBVE/Game/Menu/MenuType.cs new file mode 100644 index 000000000..64cd90b7a --- /dev/null +++ b/source/OpenBVE/Game/Menu/MenuType.cs @@ -0,0 +1,29 @@ +namespace OpenBve +{ + /// The list of possible sub-menu types + public enum MenuType + { + /// Not a sub menu + None, + /// Returns to the menu level above + Top, + /// The station jump menu + JumpToStation, + /// Returns to the main menu + ExitToMainMenu, + /// Provides a list of controls and allows customisation whilst in-game + Controls, + /// Customises the specified control + Control, + /// Quits the game + Quit, + /// The game start menu + GameStart, + /// Displays a list of routefiles + RouteList, + /// Asks whether the user wishes to use the default train + TrainDefault, + /// Displays a list of train folders + TrainList, + } +} diff --git a/source/OpenBVE/Game/MessageManager.TextualMessages.cs b/source/OpenBVE/Game/MessageManager.TextualMessages.cs index 955267572..7e27c3a12 100644 --- a/source/OpenBVE/Game/MessageManager.TextualMessages.cs +++ b/source/OpenBVE/Game/MessageManager.TextualMessages.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.Eventing.Reader; using DavyKager; -using LibRender2.Texts; +using LibRender2.Text; using OpenBveApi; using OpenBveApi.Trains; using RouteManager2; diff --git a/source/OpenBVE/Game/Score/Score.cs b/source/OpenBVE/Game/Score/Score.cs index daea2e449..83781c4a8 100644 --- a/source/OpenBVE/Game/Score/Score.cs +++ b/source/OpenBVE/Game/Score/Score.cs @@ -38,6 +38,10 @@ internal class Score /// The time elapsed since this function was last called internal void Update(double TimeElapsed) { + if (TrainManager.PlayerTrain == null) + { + return; + } // doors { bool leftopen = false; diff --git a/source/OpenBVE/Graphics/HUD/HUD.Element.cs b/source/OpenBVE/Graphics/HUD/HUD.Element.cs index 0ee7e0468..6efaddfa7 100644 --- a/source/OpenBVE/Graphics/HUD/HUD.Element.cs +++ b/source/OpenBVE/Graphics/HUD/HUD.Element.cs @@ -1,4 +1,4 @@ -using LibRender2.Texts; +using LibRender2.Text; using OpenBveApi.Colors; using OpenBveApi.Math; diff --git a/source/OpenBVE/Graphics/NewRenderer.cs b/source/OpenBVE/Graphics/NewRenderer.cs index 5dd619ea2..2309128dd 100644 --- a/source/OpenBVE/Graphics/NewRenderer.cs +++ b/source/OpenBVE/Graphics/NewRenderer.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using LibRender2; using LibRender2.MotionBlurs; using LibRender2.Objects; @@ -9,6 +8,7 @@ using OpenBve.Graphics.Renderers; using OpenBveApi; using OpenBveApi.Colors; +using OpenBveApi.FileSystem; using OpenBveApi.Graphics; using OpenBveApi.Hosts; using OpenBveApi.Interface; @@ -52,9 +52,9 @@ internal enum DistanceToNextStationDisplayMode private Overlays overlays; internal Touch Touch; - public override void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions) + public override void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions, FileSystem FileSystem) { - base.Initialize(CurrentHost, CurrentOptions); + base.Initialize(CurrentHost, CurrentOptions, FileSystem); try { @@ -93,6 +93,7 @@ internal int CreateStaticObject(UnifiedObject Prototype, Vector3 Position, Trans public override void UpdateViewport(int Width, int Height) { + _programLogo = null; Screen.Width = Width; Screen.Height = Height; GL.Viewport(0, 0, Screen.Width, Screen.Height); @@ -198,7 +199,11 @@ internal void RenderScene(double TimeElapsed, double RealTimeElapsed) Fog.Color = Program.CurrentRoute.CurrentFog.Color; Fog.Density = Program.CurrentRoute.CurrentFog.Density; Fog.IsLinear = Program.CurrentRoute.CurrentFog.IsLinear; - Fog.SetForImmediateMode(); + if (!AvailableNewRenderer) + { + Fog.SetForImmediateMode(); + } + } else { @@ -441,14 +446,6 @@ internal void RenderScene(double TimeElapsed, double RealTimeElapsed) face.Draw(); } } - if (AvailableNewRenderer) - { - /* - * Must remember to de-activate at the end of the render sequence if in GL3 mode. - * The overlays currently use immediate mode and do not work correctly with the shader active - */ - DefaultShader.Deactivate(); - } // render touch OptionLighting = false; Touch.RenderScene(); diff --git a/source/OpenBVE/Graphics/Renderers/Overlays.ATS.cs b/source/OpenBVE/Graphics/Renderers/Overlays.ATS.cs index f28110cf7..fd2c13f59 100644 --- a/source/OpenBVE/Graphics/Renderers/Overlays.ATS.cs +++ b/source/OpenBVE/Graphics/Renderers/Overlays.ATS.cs @@ -195,7 +195,7 @@ private void RenderATSLamps(HUD.Element Element, double TimeElapsed) double q = Math.Round(Element.TextAlignment.Y < 0 ? y : Element.TextAlignment.Y > 0 ? y + lcrh - v : y + 0.5 * (lcrh - v)); p += Element.TextPosition.X; q += Element.TextPosition.Y; - renderer.OpenGlString.Draw(Element.Font, t, new System.Drawing.Point((int)p, (int)q), TextAlignment.TopLeft, tc, Element.TextShadow); + renderer.OpenGlString.Draw(Element.Font, t, new Vector2(p, q), TextAlignment.TopLeft, tc, Element.TextShadow); } // left overlay if (Left.OverlayTexture != null) diff --git a/source/OpenBVE/Graphics/Renderers/Overlays.Debug.cs b/source/OpenBVE/Graphics/Renderers/Overlays.Debug.cs index c66058b13..0af119e0e 100644 --- a/source/OpenBVE/Graphics/Renderers/Overlays.Debug.cs +++ b/source/OpenBVE/Graphics/Renderers/Overlays.Debug.cs @@ -45,7 +45,7 @@ private void RenderDebugOverlays() t += " - " + (TrainManager.PlayerTrain.Handles.LocoBrake.Actual != 0 ? "L" + TrainManager.PlayerTrain.Handles.LocoBrake.Actual.ToString(Culture) : "N"); } } - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, t, new Point(2, renderer.Screen.Height - 46), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, t, new Vector2(2, renderer.Screen.Height - 46), TextAlignment.TopLeft, Color128.White, true); } // safety handles { @@ -76,7 +76,7 @@ private void RenderDebugOverlays() t += " - " + (TrainManager.PlayerTrain.Handles.LocoBrake.Actual != 0 ? "L" + TrainManager.PlayerTrain.Handles.LocoBrake.Actual.ToString(Culture) : "N"); } } - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, t, new Point(2, renderer.Screen.Height - 32), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, t, new Vector2(2, renderer.Screen.Height - 32), TextAlignment.TopLeft, Color128.White, true); } // driver handles { @@ -106,7 +106,7 @@ private void RenderDebugOverlays() t += " - " + (TrainManager.PlayerTrain.Handles.LocoBrake.Actual != 0 ? "L" + TrainManager.PlayerTrain.Handles.LocoBrake.Actual.ToString(Culture) : "N"); } } - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, t, new Point(2, renderer.Screen.Height - 18), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, t, new Vector2(2, renderer.Screen.Height - 18), TextAlignment.TopLeft, Color128.White, true); } // debug information int texturesLoaded = renderer.TextureManager.GetNumberOfLoadedTextures(); @@ -204,13 +204,13 @@ private void RenderDebugOverlays() if (Lines[i][0] == '=') { string text = Lines[i].Substring(1); - Size size = renderer.Fonts.SmallFont.MeasureString(text); - renderer.Rectangle.Draw(null, new Vector2((float)x, (float)y), new Vector2(size.Width + 6.0f, size.Height + 2.0f), new Color128(0.35f, 0.65f, 0.90f, 0.8f)); - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, text, new Point((int)x + 3, (int)y), TextAlignment.TopLeft, Color128.White); + Vector2 size = renderer.Fonts.SmallFont.MeasureString(text); + renderer.Rectangle.Draw(null, new Vector2(x, y), new Vector2(size.X + 6.0f, size.Y + 2.0f), new Color128(0.35f, 0.65f, 0.90f, 0.8f)); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, text, new Vector2(x + 3, y), TextAlignment.TopLeft, Color128.White); } else { - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, Lines[i], new Point((int)x, (int)y), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, Lines[i], new Vector2(x, y), TextAlignment.TopLeft, Color128.White, true); } y += 14.0; } @@ -257,13 +257,13 @@ private void RenderATSDebugOverlay() if (Lines[i][0] == '=') { string text = Lines[i].Substring(1); - Size size = renderer.Fonts.SmallFont.MeasureString(text); - renderer.Rectangle.Draw(null, new Vector2(x, y), new Vector2(size.Width + 6.0f, size.Height + 2.0f), new Color128(0.35f, 0.65f, 0.9f, 0.8f)); - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, text, new Point((int)x + 3, (int)y), TextAlignment.TopLeft, Color128.White); + Vector2 size = renderer.Fonts.SmallFont.MeasureString(text); + renderer.Rectangle.Draw(null, new Vector2(x, y), new Vector2(size.X + 6.0f, size.Y + 2.0f), new Color128(0.35f, 0.65f, 0.9f, 0.8f)); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, text, new Vector2(x + 3, y), TextAlignment.TopLeft, Color128.White); } else { - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, Lines[i], new Point((int)x, (int)y), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, Lines[i], new Vector2(x, y), TextAlignment.TopLeft, Color128.White, true); } y += 14.0; if (y > renderer.Screen.Height - 20.0) @@ -292,7 +292,7 @@ private void RenderBrakeSystemDebug() { if (!heading[0]) { - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Brake pipe", new Point((int)x, (int)(oy - 16)), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Brake pipe", new Vector2(x, oy - 16), TextAlignment.TopLeft, Color128.White, true); heading[0] = true; } renderer.Rectangle.Draw(null, new Vector2(x, y), new Vector2(w, h), Color128.Black); @@ -306,7 +306,7 @@ private void RenderBrakeSystemDebug() { if (!heading[1]) { - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Auxillary reservoir", new Point((int)x, (int)(oy - 16)), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Auxillary reservoir", new Vector2(x, oy - 16), TextAlignment.TopLeft, Color128.White, true); heading[1] = true; } renderer.Rectangle.Draw(null, new Vector2(x, y), new Vector2(w, h), Color128.Black); @@ -319,7 +319,7 @@ private void RenderBrakeSystemDebug() { if (!heading[2]) { - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Brake cylinder", new Point((int)x, (int)(oy - 16)), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Brake cylinder", new Vector2(x, oy - 16), TextAlignment.TopLeft, Color128.White, true); heading[2] = true; } @@ -334,7 +334,7 @@ private void RenderBrakeSystemDebug() { if (!heading[3]) { - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Main reservoir", new Point((int)x, (int)(oy - 16)), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Main reservoir", new Vector2(x, oy - 16), TextAlignment.TopLeft, Color128.White, true); heading[3] = true; } renderer.Rectangle.Draw(null, new Vector2(x, y), new Vector2(w, h), Color128.Black); @@ -348,7 +348,7 @@ private void RenderBrakeSystemDebug() { if (!heading[4]) { - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Equalizing reservoir", new Point((int)x, (int)(oy - 16)), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Equalizing reservoir", new Vector2(x, oy - 16), TextAlignment.TopLeft, Color128.White, true); heading[4] = true; } renderer.Rectangle.Draw(null, new Vector2(x, y), new Vector2(w, h), Color128.Black); @@ -362,7 +362,7 @@ private void RenderBrakeSystemDebug() { if (!heading[5]) { - renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Straight air pipe", new Point((int)x, (int)(oy - 16)), TextAlignment.TopLeft, Color128.White, true); + renderer.OpenGlString.Draw(renderer.Fonts.SmallFont, "Straight air pipe", new Vector2(x, oy - 16), TextAlignment.TopLeft, Color128.White, true); heading[5] = true; } renderer.Rectangle.Draw(null, new Vector2(x, y), new Vector2((float)w, (float)h), Color128.Black); diff --git a/source/OpenBVE/Graphics/Renderers/Overlays.GameMessages.cs b/source/OpenBVE/Graphics/Renderers/Overlays.GameMessages.cs index c40806de0..9a7928b5d 100644 --- a/source/OpenBVE/Graphics/Renderers/Overlays.GameMessages.cs +++ b/source/OpenBVE/Graphics/Renderers/Overlays.GameMessages.cs @@ -23,9 +23,9 @@ private void RenderGameMessages(HUD.Element Element, double TimeElapsed) for (int j = 0; j < n; j++) { //Update font size for the renderer - Size size = Element.Font.MeasureString((string)MessageManager.TextualMessages[j].MessageToDisplay); - MessageManager.TextualMessages[j].Width = size.Width; - MessageManager.TextualMessages[j].Height = size.Height; + Vector2 size = Element.Font.MeasureString((string)MessageManager.TextualMessages[j].MessageToDisplay); + MessageManager.TextualMessages[j].Width = size.X; + MessageManager.TextualMessages[j].Height = size.Y; //Run through the list of current messages double a = MessageManager.TextualMessages[j].Width - j * (double)Element.Value1; //If our width is wider than the old, use this as the NEW viewing plane width @@ -218,7 +218,7 @@ private void RenderGameMessages(HUD.Element Element, double TimeElapsed) : py + 0.5 * (lcrh - v)); p += Element.TextPosition.X; q += Element.TextPosition.Y; - renderer.OpenGlString.Draw(Element.Font, t, new Point((int)p, (int)q), + renderer.OpenGlString.Draw(Element.Font, t, new Vector2(p, q), TextAlignment.TopLeft, new Color128(tc.R, tc.G, tc.B, tc.A * alpha), Element.TextShadow); } // left overlay diff --git a/source/OpenBVE/Graphics/Renderers/Overlays.HUD.cs b/source/OpenBVE/Graphics/Renderers/Overlays.HUD.cs index a9fc941a6..34d374c08 100644 --- a/source/OpenBVE/Graphics/Renderers/Overlays.HUD.cs +++ b/source/OpenBVE/Graphics/Renderers/Overlays.HUD.cs @@ -896,15 +896,13 @@ private void RenderHUDElement(HUD.Element Element, double TimeElapsed) } } { // text - System.Drawing.Size size = Element.Font.MeasureString(t); - float u = size.Width; - float v = size.Height; - double p = Math.Round(Element.TextAlignment.X < 0 ? x : Element.TextAlignment.X == 0 ? x + 0.5 * (w - u) : x + w - u); - double q = Math.Round(Element.TextAlignment.Y < 0 ? y : Element.TextAlignment.Y == 0 ? y + 0.5 * (h - v) : y + h - v); + Vector2 size = Element.Font.MeasureString(t); + double p = Math.Round(Element.TextAlignment.X < 0 ? x : Element.TextAlignment.X == 0 ? x + 0.5 * (w - size.X) : x + w - size.X); + double q = Math.Round(Element.TextAlignment.Y < 0 ? y : Element.TextAlignment.Y == 0 ? y + 0.5 * (h - size.Y) : y + h - size.Y); p += Element.TextPosition.X; q += Element.TextPosition.Y; Color128 c = Element.TextColor.CreateTextColor(sc, alpha); - Program.Renderer.OpenGlString.Draw(Element.Font, t, new System.Drawing.Point((int)p, (int)q), TextAlignment.TopLeft, c, Element.TextShadow); + Program.Renderer.OpenGlString.Draw(Element.Font, t, new Vector2(p, q), TextAlignment.TopLeft, c, Element.TextShadow); } // overlay if (Element.CenterMiddle.OverlayTexture != null) diff --git a/source/OpenBVE/Graphics/Renderers/Overlays.Lamp.cs b/source/OpenBVE/Graphics/Renderers/Overlays.Lamp.cs index b3b452196..7891abeaf 100644 --- a/source/OpenBVE/Graphics/Renderers/Overlays.Lamp.cs +++ b/source/OpenBVE/Graphics/Renderers/Overlays.Lamp.cs @@ -1,6 +1,7 @@ using System; -using LibRender2.Texts; +using LibRender2.Text; using OpenBveApi.Interface; +using OpenBveApi.Math; using TrainManager.SafetySystems; using TrainManager.Trains; @@ -25,8 +26,8 @@ private struct Lamp { internal readonly LampType Type; internal readonly string Text; - internal readonly float Width; - internal readonly float Height; + internal readonly double Width; + internal readonly double Height; internal Lamp(LampType Type) { this.Type = Type; @@ -90,16 +91,16 @@ internal Lamp(LampType Type) break; } } - System.Drawing.Size size = font.MeasureString(this.Text); - this.Width = size.Width; - this.Height = size.Height; + Vector2 size = font.MeasureString(this.Text); + this.Width = size.X; + this.Height = size.Y; } } private class LampCollection { internal readonly Lamp[] Lamps; - internal readonly float Width; + internal readonly double Width; /// Initialises the ATS lamps for the specified train using one of the default safety systems internal LampCollection(TrainBase Train) diff --git a/source/OpenBVE/Graphics/Renderers/Overlays.ScoreMessages.cs b/source/OpenBVE/Graphics/Renderers/Overlays.ScoreMessages.cs index a29b28de6..a1cd819b9 100644 --- a/source/OpenBVE/Graphics/Renderers/Overlays.ScoreMessages.cs +++ b/source/OpenBVE/Graphics/Renderers/Overlays.ScoreMessages.cs @@ -16,15 +16,15 @@ private void RenderScoreMessages(HUD.Element Element, double TimeElapsed) { // score messages int n = Game.ScoreMessages.Length; - float totalwidth = 16.0f; - float[] widths = new float[n]; - float[] heights = new float[n]; + double totalwidth = 16.0f; + double[] widths = new double[n]; + double[] heights = new double[n]; for (int j = 0; j < n; j++) { - Size size = Element.Font.MeasureString(Game.ScoreMessages[j].Text); - widths[j] = size.Width; - heights[j] = size.Height; - float a = widths[j] - j * Element.Value1; + Vector2 size = Element.Font.MeasureString(Game.ScoreMessages[j].Text); + widths[j] = size.X; + heights[j] = size.Y; + double a = widths[j] - j * Element.Value1; if (a > totalwidth) { totalwidth = a; @@ -187,7 +187,7 @@ private void RenderScoreMessages(HUD.Element Element, double TimeElapsed) double q = Math.Round(Element.TextAlignment.Y < 0 ? py : Element.TextAlignment.Y > 0 ? py + lcrh - v : py + 0.5 * (lcrh - v)); p += Element.TextPosition.X; q += Element.TextPosition.Y; - renderer.OpenGlString.Draw(Element.Font, t, new Point((int)p, (int)q), TextAlignment.TopLeft, new Color128(tc.R, tc.G, tc.B, tc.A * alpha), Element.TextShadow); + renderer.OpenGlString.Draw(Element.Font, t, new Vector2(p, q), TextAlignment.TopLeft, new Color128(tc.R, tc.G, tc.B, tc.A * alpha), Element.TextShadow); } // left overlay if (Left.OverlayTexture != null) diff --git a/source/OpenBVE/Graphics/Renderers/Overlays.cs b/source/OpenBVE/Graphics/Renderers/Overlays.cs index 595b07fcd..5152cea8c 100644 --- a/source/OpenBVE/Graphics/Renderers/Overlays.cs +++ b/source/OpenBVE/Graphics/Renderers/Overlays.cs @@ -153,8 +153,8 @@ internal void Render(double TimeElapsed) { //If paused, fade out the screen & write PAUSE renderer.Rectangle.Draw(null, Vector2.Null, new Vector2(renderer.Screen.Width, renderer.Screen.Height), new Color128(0.0f, 0.0f, 0.0f, 0.5f)); - renderer.OpenGlString.Draw(renderer.Fonts.VeryLargeFont, Translations.GetInterfaceString("menu_pause_title"), new Point(renderer.Screen.Width / 2, renderer.Screen.Height / 2), TextAlignment.CenterMiddle, Color128.White, true); - if (Interface.CurrentOptions.ScreenReaderAvailable && !PauseAnnounced) + renderer.OpenGlString.Draw(renderer.Fonts.VeryLargeFont, Translations.GetInterfaceString("menu_pause_title"), new Vector2(renderer.Screen.Width / 2.0, renderer.Screen.Height / 2.0), TextAlignment.CenterMiddle, Color128.White, true); + if (!PauseAnnounced) { if (!Tolk.Output(Translations.GetInterfaceString("menu_pause_title"))) { @@ -165,7 +165,7 @@ internal void Render(double TimeElapsed) break; } case InterfaceType.Menu: - Game.Menu.Draw(); + Game.Menu.Draw(TimeElapsed); PauseAnnounced = false; break; default: diff --git a/source/OpenBVE/Graphics/Screen.cs b/source/OpenBVE/Graphics/Screen.cs index 1b5e1d3c5..e9613f612 100644 --- a/source/OpenBVE/Graphics/Screen.cs +++ b/source/OpenBVE/Graphics/Screen.cs @@ -3,6 +3,7 @@ using System.Windows.Forms; using LibRender2; using LibRender2.Viewports; +using OpenBveApi.Hosts; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; @@ -37,12 +38,29 @@ internal static void Initialize() try { DisplayDevice.Default.ChangeResolution(currentResolution); - Program.currentGameWindow = new OpenBVEGame(currentResolution.Width, currentResolution.Height, currentGraphicsMode, - GameWindowFlags.Default) + if (Interface.CurrentOptions.IsUseNewRenderer && (Program.CurrentHost.Platform == HostPlatform.AppleOSX && IntPtr.Size != 4 || Interface.CurrentOptions.ForceForwardsCompatibleContext)) { - Visible = true, - WindowState = WindowState.Fullscreen, - }; + /* + * OS-X is a fickle beast + * In order to get a functioning GL3 context, we appear to need to be running as 64-bit & explicitly specify the forwards compatible flag + */ + Program.currentGameWindow = new OpenBVEGame(currentResolution.Width, currentResolution.Height, currentGraphicsMode, + GameWindowFlags.Default, GraphicsContextFlags.ForwardCompatible) + { + Visible = true, + WindowState = WindowState.Fullscreen, + }; + } + else + { + Program.currentGameWindow = new OpenBVEGame(currentResolution.Width, currentResolution.Height, currentGraphicsMode, + GameWindowFlags.Default) + { + Visible = true, + WindowState = WindowState.Fullscreen, + }; + } + resolutionFound = true; break; } @@ -71,11 +89,27 @@ internal static void Initialize() { try { - Program.currentGameWindow = new OpenBVEGame(Interface.CurrentOptions.WindowWidth, - Interface.CurrentOptions.WindowHeight, currentGraphicsMode, GameWindowFlags.Default) + if (Interface.CurrentOptions.IsUseNewRenderer && (Program.CurrentHost.Platform == HostPlatform.AppleOSX && IntPtr.Size != 4 || Interface.CurrentOptions.ForceForwardsCompatibleContext)) + { + /* + * OS-X is a fickle beast + * In order to get a functioning GL3 context, we appear to need to be running as 64-bit & explicitly specify the forwards compatible flag + */ + Program.currentGameWindow = new OpenBVEGame(Interface.CurrentOptions.WindowWidth, + Interface.CurrentOptions.WindowHeight, currentGraphicsMode, GameWindowFlags.Default, GraphicsContextFlags.ForwardCompatible) + { + Visible = true + }; + } + else { - Visible = true - }; + Program.currentGameWindow = new OpenBVEGame(Interface.CurrentOptions.WindowWidth, + Interface.CurrentOptions.WindowHeight, currentGraphicsMode, GameWindowFlags.Default) + { + Visible = true + }; + } + } catch { @@ -165,7 +199,7 @@ internal static void ToggleFullscreen() System.Threading.Thread.Sleep(20); if (Program.currentGameWindow.WindowState != WindowState.Fullscreen) { - MessageBox.Show(Translations.GetInterfaceString("errors_fullscreen_switch1") + System.Environment.NewLine + + MessageBox.Show(Translations.GetInterfaceString("errors_fullscreen_switch1") + Environment.NewLine + Translations.GetInterfaceString("errors_fullscreen_switch2"), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Hand); Program.Renderer.Screen.Fullscreen = false; } diff --git a/source/OpenBVE/OpenBve.csproj b/source/OpenBVE/OpenBve.csproj index c7a408d10..472dbed07 100644 --- a/source/OpenBVE/OpenBve.csproj +++ b/source/OpenBVE/OpenBve.csproj @@ -136,6 +136,14 @@ AI.cs + + + + + + + + MessageManager.cs @@ -144,7 +152,7 @@ - + diff --git a/source/OpenBVE/System/Functions/CrashHandler.cs b/source/OpenBVE/System/Functions/CrashHandler.cs index 98b5491dd..cdcd83989 100644 --- a/source/OpenBVE/System/Functions/CrashHandler.cs +++ b/source/OpenBVE/System/Functions/CrashHandler.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; using System.Windows.Forms; +using OpenBveApi.Hosts; namespace OpenBve { @@ -14,6 +15,13 @@ class CrashHandler /// Catches all unhandled exceptions within the current appdomain internal static void CurrentDomain_UnhandledException(Object sender, UnhandledExceptionEventArgs e) { + if (Program.CurrentHost.Platform == HostPlatform.AppleOSX && IntPtr.Size !=4) + { + Console.WriteLine("UNHANDLED EXCEPTION:"); + Console.WriteLine("--------------------"); + Console.WriteLine(e.ExceptionObject); + Environment.Exit(0); + } try { Exception ex = (Exception)e.ExceptionObject; @@ -51,6 +59,13 @@ internal static void CurrentDomain_UnhandledException(Object sender, UnhandledEx /// Catches all unhandled exceptions within the current UI thread internal static void UIThreadException(object sender, ThreadExceptionEventArgs t) { + if (Program.CurrentHost.Platform == HostPlatform.AppleOSX && IntPtr.Size !=4) + { + Console.WriteLine("UNHANDLED EXCEPTION:"); + Console.WriteLine("--------------------"); + Console.WriteLine(t.Exception); + Environment.Exit(0); + } try { MessageBox.Show("Unhandled Windows Forms Exception"); diff --git a/source/OpenBVE/System/GameWindow.cs b/source/OpenBVE/System/GameWindow.cs index ee1769a2a..662b507bb 100644 --- a/source/OpenBVE/System/GameWindow.cs +++ b/source/OpenBVE/System/GameWindow.cs @@ -50,6 +50,30 @@ class OpenBVEGame: GameWindow //We need to explicitly specify the default constructor public OpenBVEGame(int width, int height, GraphicsMode currentGraphicsMode, GameWindowFlags @default): base(width, height, currentGraphicsMode, Translations.GetInterfaceString("program_title"), @default) { + Program.FileSystem.AppendToLogFile("Creating game window with standard context."); + if (Program.CurrentHost.Platform == HostPlatform.AppleOSX && IntPtr.Size != 4) + { + return; + } + try + { + var assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + Icon ico = new Icon(OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(assemblyFolder, "Data"), "icon.ico")); + this.Icon = ico; + } + catch + { + //it's only an icon + } + } + + public OpenBVEGame(int width, int height, GraphicsMode currentGraphicsMode, GameWindowFlags @default, GraphicsContextFlags flags): base(width, height, currentGraphicsMode, Translations.GetInterfaceString("program_title"), @default, DisplayDevice.Default, 3,3, flags) + { + Program.FileSystem.AppendToLogFile("Creating game window with forwards-compatible context."); + if (Program.CurrentHost.Platform == HostPlatform.AppleOSX && IntPtr.Size != 4) + { + return; + } try { var assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); @@ -85,7 +109,6 @@ protected override void OnRenderFrame(FrameEventArgs e) { Thread.Sleep(10); } - //Renderer.UpdateLighting(); Program.Renderer.RenderScene(TimeElapsed, RealTimeElapsed); Program.currentGameWindow.SwapBuffers(); if (MainLoop.Quit != MainLoop.QuitMode.ContinueGame) @@ -360,14 +383,25 @@ protected override void OnLoad(EventArgs e) //Initialise the loader thread queues jobs = new Queue(10); locks = new Queue(10); - Program.Renderer.Initialize(Program.CurrentHost, Interface.CurrentOptions); + Program.Renderer.Initialize(Program.CurrentHost, Interface.CurrentOptions, Program.FileSystem); Program.Renderer.DetermineMaxAFLevel(); HUD.LoadHUD(); Program.Renderer.Loading.InitLoading(Program.FileSystem.GetDataFolder("In-game"), typeof(NewRenderer).Assembly.GetName().Version.ToString()); Program.Renderer.UpdateViewport(ViewportChangeMode.NoChange); Program.Renderer.MotionBlur.Initialize(Interface.CurrentOptions.MotionBlur); - Loading.LoadAsynchronously(MainLoop.currentResult.RouteFile, MainLoop.currentResult.RouteEncoding, MainLoop.currentResult.TrainFolder, MainLoop.currentResult.TrainEncoding); - LoadingScreenLoop(); + if (string.IsNullOrEmpty(MainLoop.currentResult.RouteFile)) + { + Game.Menu.PushMenu(MenuType.GameStart); + Loading.Complete = true; + Program.Renderer.CameraTrackFollower = new TrackFollower(Program.CurrentHost); + loadComplete = true; + } + else + { + Loading.LoadAsynchronously(MainLoop.currentResult.RouteFile, MainLoop.currentResult.RouteEncoding, MainLoop.currentResult.TrainFolder, MainLoop.currentResult.TrainEncoding); + LoadingScreenLoop(); + } + //Add event handler hooks for keyboard and mouse buttons //Do this after the renderer has init and the loop has started to prevent timing issues KeyDown += MainLoop.keyDownEvent; @@ -1004,7 +1038,7 @@ private void SetupSimulation() } } - private void LoadingScreenLoop() + public void LoadingScreenLoop() { Program.Renderer.PushMatrix(MatrixMode.Projection); Matrix4D.CreateOrthographicOffCenter(0.0f, Program.Renderer.Screen.Width, Program.Renderer.Screen.Height, 0.0f, -1.0f, 1.0f, out Program.Renderer.CurrentProjectionMatrix); diff --git a/source/OpenBVE/System/Input/Keyboard.cs b/source/OpenBVE/System/Input/Keyboard.cs index 8c1d1423e..86d2b5b4e 100644 --- a/source/OpenBVE/System/Input/Keyboard.cs +++ b/source/OpenBVE/System/Input/Keyboard.cs @@ -19,7 +19,7 @@ internal static void keyDownEvent(object sender, KeyboardKeyEventArgs e) if (Loading.Complete == true && e.Key == OpenTK.Input.Key.F4 && e.Alt == true) { // Catch standard ALT + F4 quit and push confirmation prompt - Game.Menu.PushMenu(Menu.MenuType.Quit); + Game.Menu.PushMenu(MenuType.Quit); return; } BlockKeyRepeat = true; diff --git a/source/OpenBVE/System/Input/ProcessControls.cs b/source/OpenBVE/System/Input/ProcessControls.cs index 4fc271a3d..fc73856aa 100644 --- a/source/OpenBVE/System/Input/ProcessControls.cs +++ b/source/OpenBVE/System/Input/ProcessControls.cs @@ -171,10 +171,10 @@ internal static void ProcessControls(double TimeElapsed) Program.Renderer.CurrentInterface = InterfaceType.Normal; break; case Translations.Command.MenuActivate: - Game.Menu.PushMenu(Menu.MenuType.Top); + Game.Menu.PushMenu(MenuType.Top); break; case Translations.Command.MiscQuit: - Game.Menu.PushMenu(Menu.MenuType.Quit); + Game.Menu.PushMenu(MenuType.Quit); break; case Translations.Command.MiscFullscreen: Screen.ToggleFullscreen(); @@ -693,7 +693,7 @@ internal static void ProcessControls(double TimeElapsed) { case Translations.Command.MiscQuit: // quit - Game.Menu.PushMenu(Menu.MenuType.Quit); + Game.Menu.PushMenu(MenuType.Quit); break; case Translations.Command.CameraInterior: // camera: interior @@ -1733,7 +1733,7 @@ internal static void ProcessControls(double TimeElapsed) break; case Translations.Command.MenuActivate: // menu - Game.Menu.PushMenu(Menu.MenuType.Top); + Game.Menu.PushMenu(MenuType.Top); break; case Translations.Command.MiscPause: // pause diff --git a/source/OpenBVE/System/Loading.cs b/source/OpenBVE/System/Loading.cs index 67847a071..fad329b31 100644 --- a/source/OpenBVE/System/Loading.cs +++ b/source/OpenBVE/System/Loading.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; @@ -10,6 +11,7 @@ using RouteManager2; using TrainManager.Car; using TrainManager.Trains; +using Path = OpenBveApi.Path; namespace OpenBve { internal static class Loading @@ -142,6 +144,108 @@ internal static string GetRailwayFolder(string RouteFile) { return Application.StartupPath; } + /// Gets the default train folder for a given route file + /// The absolute on-disk path of the train folder + internal static string GetDefaultTrainFolder(string RouteFile) + { + if (string.IsNullOrEmpty(Interface.CurrentOptions.TrainName)) { + return string.Empty; + } + + string Folder; + try { + Folder = System.IO.Path.GetDirectoryName(RouteFile); + if (Interface.CurrentOptions.TrainName[0] == '$') { + Folder = Path.CombineDirectory(Folder, Interface.CurrentOptions.TrainName); + if (Directory.Exists(Folder)) { + string File = Path.CombineFile(Folder, "train.dat"); + if (System.IO.File.Exists(File)) { + + return Folder; + } + } + } + } catch { + Folder = null; + } + bool recursionTest = false; + string lastFolder = null; + try + { + while (true) + { + string TrainFolder = Path.CombineDirectory(Folder, "Train"); + var OldFolder = Folder; + if (Directory.Exists(TrainFolder)) + { + try + { + Folder = Path.CombineDirectory(TrainFolder, Interface.CurrentOptions.TrainName); + } + catch (Exception ex) + { + if (ex is ArgumentException) + { + break; // Invalid character in path causes infinite recursion + } + + Folder = null; + } + + if (Folder != null) + { + char c = System.IO.Path.DirectorySeparatorChar; + if (Directory.Exists(Folder)) + { + + string File = Path.CombineFile(Folder, "train.dat"); + if (System.IO.File.Exists(File)) + { + // train found + return Folder; + } + + if (lastFolder == Folder || recursionTest) + { + break; + } + + lastFolder = Folder; + } + else if (Folder.ToLowerInvariant().Contains(c + "railway" + c)) + { + //If we have a misplaced Train folder in either our Railway\Route + //or Railway folders, this can cause the train search to fail + //Detect the presence of a railway folder and carry on traversing upwards if this is the case + recursionTest = true; + Folder = OldFolder; + } + else + { + break; + } + } + } + + if (Folder == null) continue; + DirectoryInfo Info = Directory.GetParent(Folder); + if (Info != null) + { + Folder = Info.FullName; + } + else + { + break; + } + } + } + catch + { + //Something broke, but we don't care as it just shows an error below + } + return string.Empty; + } + // load threaded private static void LoadThreaded() { try { diff --git a/source/OpenBVE/System/Options.cs b/source/OpenBVE/System/Options.cs index 180739b8c..f025bd893 100644 --- a/source/OpenBVE/System/Options.cs +++ b/source/OpenBVE/System/Options.cs @@ -179,6 +179,7 @@ internal Options() this.CursorHideDelay = 10; this.Accessibility = false; this.ScreenReaderAvailable = false; + this.ForceForwardsCompatibleContext = false; } } /// The current game options @@ -357,6 +358,9 @@ internal static void LoadOptions() case "isusenewrenderer": Interface.CurrentOptions.IsUseNewRenderer = string.Compare(Value, "false", StringComparison.OrdinalIgnoreCase) != 0; break; + case "forwardscompatiblecontext": + Interface.CurrentOptions.ForceForwardsCompatibleContext = string.Compare(Value, "false", StringComparison.OrdinalIgnoreCase) != 0; + break; } break; case "quality": switch (Key) @@ -805,6 +809,7 @@ internal static void SaveOptions() Builder.AppendLine("loadInAdvance = " + (CurrentOptions.LoadInAdvance ? "true" : "false")); Builder.AppendLine("unloadtextures = " + (CurrentOptions.UnloadUnusedTextures ? "true" : "false")); Builder.AppendLine("isUseNewRenderer = " + (CurrentOptions.IsUseNewRenderer ? "true" : "false")); + Builder.AppendLine("forwardsCompatibleContext = " + (CurrentOptions.ForceForwardsCompatibleContext ? "true" : "false")); Builder.AppendLine(); Builder.AppendLine("[quality]"); { diff --git a/source/OpenBVE/System/Program.cs b/source/OpenBVE/System/Program.cs index e5124f519..826d76ed4 100644 --- a/source/OpenBVE/System/Program.cs +++ b/source/OpenBVE/System/Program.cs @@ -62,6 +62,25 @@ internal static partial class Program { /// The command-line arguments. [STAThread] private static void Main(string[] args) { + // --- load options and controls --- + try + { + FileSystem = FileSystem.FromCommandLineArgs(args, CurrentHost); + FileSystem.CreateFileSystem(); + Interface.LoadOptions(); + } + catch + { + // ignored + } + //Switch between SDL2 and native backends; use native backend by default + var options = new ToolkitOptions(); + if (Interface.CurrentOptions.PreferNativeBackend) + { + options.Backend = PlatformBackend.PreferNative; + } + Toolkit.Init(options); + // Add handler for UI thread exceptions Application.ThreadException += (CrashHandler.UIThreadException); @@ -110,24 +129,9 @@ private static void Main(string[] args) { } - // --- load options and controls --- - try - { - Interface.LoadOptions(); - } - catch - { - // ignored - } + TrainManager = new TrainManager(CurrentHost, Renderer, Interface.CurrentOptions, FileSystem); - //Switch between SDL2 and native backends; use native backend by default - var options = new ToolkitOptions(); - if (Interface.CurrentOptions.PreferNativeBackend) - { - options.Backend = PlatformBackend.PreferNative; - } - Toolkit.Init(options); // --- load language --- string folder = Program.FileSystem.GetDataFolder("Languages"); Translations.LoadLanguageFiles(folder); @@ -234,17 +238,36 @@ private static void Main(string[] args) { } Game.Reset(false); } + // --- show the main menu if necessary --- if (result.RouteFile == null | result.TrainFolder == null) { Joysticks.RefreshJoysticks(); - - // end HACK // - result = formMain.ShowMainDialog(result); + + if (CurrentHost.Platform == HostPlatform.AppleOSX && IntPtr.Size != 4) + { + //WinForms are not supported on 64-bit Apple, so show the experimental GL menu + result.ExperimentalGLMenu = true; + } + else + { + if (!result.ExperimentalGLMenu) + { + result = formMain.ShowMainDialog(result); + } + } } else { result.Start = true; //Apply translations Translations.SetInGameLanguage(Translations.CurrentLanguageCode); } + + if (result.ExperimentalGLMenu) + { + result.Start = true; + result.RouteFile = null; + result.TrainFolder = null; + } + // --- start the actual program --- if (result.Start) { if (Initialize()) { diff --git a/source/OpenBVE/System/Program/CommandLine.cs b/source/OpenBVE/System/Program/CommandLine.cs index 0e13e50e4..c05bbbf99 100644 --- a/source/OpenBVE/System/Program/CommandLine.cs +++ b/source/OpenBVE/System/Program/CommandLine.cs @@ -57,6 +57,12 @@ internal static void ParseArguments(string[] Arguments, ref formMain.MainDialogR case "/height": NumberFormats.TryParseIntVb6(value, out Result.Height); break; + case "/glmenu": + if (value.ToLowerInvariant() == "true" || value.ToLowerInvariant() == "1") + { + Result.ExperimentalGLMenu = true; + } + break; } } } diff --git a/source/OpenBVE/UserInterface/formMain.Start.cs b/source/OpenBVE/UserInterface/formMain.Start.cs index dac1e9c53..419450a24 100644 --- a/source/OpenBVE/UserInterface/formMain.Start.cs +++ b/source/OpenBVE/UserInterface/formMain.Start.cs @@ -1222,11 +1222,42 @@ private void ShowTrain(bool UserSelectedEncoding) throw new Exception("Unable to load the required plugins- Please reinstall OpenBVE"); } bool canLoad = false; + Image trainImage = null; for (int i = 0; i < Program.CurrentHost.Plugins.Length; i++) { if (Program.CurrentHost.Plugins[i].Train != null && Program.CurrentHost.Plugins[i].Train.CanLoadTrain(Result.TrainFolder)) { canLoad = true; + trainImage = Program.CurrentHost.Plugins[i].Train.GetImage(Result.TrainFolder); + if (!UserSelectedEncoding) { + Result.TrainEncoding = TextEncoding.GetSystemEncodingFromFile(Result.TrainFolder, "train.txt"); + comboboxTrainEncoding.Tag = new object(); + comboboxTrainEncoding.SelectedIndex = 0; + comboboxTrainEncoding.Items[0] = $"{Result.TrainEncoding.EncodingName} - {Result.TrainEncoding.CodePage}"; + + comboboxTrainEncoding.Tag = null; + for (int k = 0; k < Interface.CurrentOptions.TrainEncodings.Length; k++) { + if (Interface.CurrentOptions.TrainEncodings[k].Value == Result.TrainFolder) { + int j; + for (j = 1; j < EncodingCodepages.Length; j++) { + if (EncodingCodepages[j] == Interface.CurrentOptions.TrainEncodings[k].Codepage) { + comboboxTrainEncoding.SelectedIndex = j; + Result.TrainEncoding = Encoding.GetEncoding(EncodingCodepages[j]); + break; + } + } + if (j == EncodingCodepages.Length) { + comboboxTrainEncoding.SelectedIndex = 0; + Result.TrainEncoding = Encoding.UTF8; + } + break; + } + } + panelTrainEncoding.Enabled = true; + comboboxTrainEncoding.Tag = null; + } + textboxTrainDescription.Text = Program.CurrentHost.Plugins[i].Train.GetDescription(Result.TrainFolder, Result.TrainEncoding); + break; } } @@ -1237,61 +1268,18 @@ private void ShowTrain(bool UserSelectedEncoding) //No plugin capable of loading train found return; } - if (!UserSelectedEncoding) { - Result.TrainEncoding = TextEncoding.GetSystemEncodingFromFile(Result.TrainFolder, "train.txt"); - comboboxTrainEncoding.Tag = new object(); - comboboxTrainEncoding.SelectedIndex = 0; - comboboxTrainEncoding.Items[0] = $"{Result.TrainEncoding.EncodingName} - {Result.TrainEncoding.CodePage}"; - comboboxTrainEncoding.Tag = null; - int i; - for (i = 0; i < Interface.CurrentOptions.TrainEncodings.Length; i++) { - if (Interface.CurrentOptions.TrainEncodings[i].Value == Result.TrainFolder) { - int j; - for (j = 1; j < EncodingCodepages.Length; j++) { - if (EncodingCodepages[j] == Interface.CurrentOptions.TrainEncodings[i].Codepage) { - comboboxTrainEncoding.SelectedIndex = j; - Result.TrainEncoding = Encoding.GetEncoding(EncodingCodepages[j]); - break; - } - } - if (j == EncodingCodepages.Length) { - comboboxTrainEncoding.SelectedIndex = 0; - Result.TrainEncoding = Encoding.UTF8; - } - break; - } - } - panelTrainEncoding.Enabled = true; - comboboxTrainEncoding.Tag = null; - } + if (trainImage != null) { - // train image - string File = Path.CombineFile(Result.TrainFolder, "train.png"); - if (!System.IO.File.Exists(File)) { - File = Path.CombineFile(Result.TrainFolder, "train.bmp"); - } - - TryLoadImage(pictureboxTrainImage, System.IO.File.Exists(File) ? File : "train_unknown.png"); + pictureboxTrainImage.Image = trainImage; + pictureboxTrainImage.Enabled = true; } + else { - // train description - string File = Path.CombineFile(Result.TrainFolder, "train.txt"); - if (System.IO.File.Exists(File)) { - try { - string trainText = System.IO.File.ReadAllText(File, Result.TrainEncoding); - trainText = trainText.ConvertNewlinesToCrLf(); - textboxTrainDescription.Text = trainText; - textboxTrainEncodingPreview.Text = trainText; - } catch { - textboxTrainDescription.Text = System.IO.Path.GetFileName(Result.TrainFolder); - textboxTrainEncodingPreview.Text = ""; - } - } else { - textboxTrainDescription.Text = System.IO.Path.GetFileName(Result.TrainFolder); - textboxTrainEncodingPreview.Text = ""; - } + TryLoadImage(pictureboxTrainImage, "train_unknown.png"); + pictureboxTrainImage.Enabled = false; } + groupboxTrainDetails.Visible = true; labelTrainEncoding.Enabled = true; labelTrainEncodingPreview.Enabled = true; @@ -1301,108 +1289,12 @@ private void ShowTrain(bool UserSelectedEncoding) // show default train private void ShowDefaultTrain() { - - if (string.IsNullOrEmpty(Result.RouteFile)) { - return; - } - if (string.IsNullOrEmpty(Interface.CurrentOptions.TrainName)) { - return; - } - - string Folder; - try { - Folder = System.IO.Path.GetDirectoryName(Result.RouteFile); - if (Interface.CurrentOptions.TrainName[0] == '$') { - Folder = Path.CombineDirectory(Folder, Interface.CurrentOptions.TrainName); - if (Directory.Exists(Folder)) { - string File = Path.CombineFile(Folder, "train.dat"); - if (System.IO.File.Exists(File)) { - - Result.TrainFolder = Folder; - ShowTrain(false); - return; - } - } - } - } catch { - Folder = null; - } - bool recursionTest = false; - string lastFolder = null; - try + string trainFolder = Loading.GetDefaultTrainFolder(Result.RouteFile); + if (!string.IsNullOrEmpty(trainFolder)) { - while (true) - { - string TrainFolder = Path.CombineDirectory(Folder, "Train"); - var OldFolder = Folder; - if (Directory.Exists(TrainFolder)) - { - try - { - Folder = Path.CombineDirectory(TrainFolder, Interface.CurrentOptions.TrainName); - } - catch (Exception ex) - { - if (ex is ArgumentException) - { - break; // Invalid character in path causes infinite recursion - } - - Folder = null; - } - - if (Folder != null) - { - char c = System.IO.Path.DirectorySeparatorChar; - if (Directory.Exists(Folder)) - { - - string File = Path.CombineFile(Folder, "train.dat"); - if (System.IO.File.Exists(File)) - { - // train found - Result.TrainFolder = Folder; - ShowTrain(false); - return; - } - - if (lastFolder == Folder || recursionTest) - { - break; - } - - lastFolder = Folder; - } - else if (Folder.ToLowerInvariant().Contains(c + "railway" + c)) - { - //If we have a misplaced Train folder in either our Railway\Route - //or Railway folders, this can cause the train search to fail - //Detect the presence of a railway folder and carry on traversing upwards if this is the case - recursionTest = true; - Folder = OldFolder; - } - else - { - break; - } - } - } - - if (Folder == null) continue; - DirectoryInfo Info = Directory.GetParent(Folder); - if (Info != null) - { - Folder = Info.FullName; - } - else - { - break; - } - } - } - catch - { - //Something broke, but we don't care as it just shows an error below + Result.TrainFolder = trainFolder; + ShowTrain(false); + return; } // train not found Result.TrainFolder = null; diff --git a/source/OpenBVE/UserInterface/formMain.cs b/source/OpenBVE/UserInterface/formMain.cs index d3b258576..a45409fdc 100644 --- a/source/OpenBVE/UserInterface/formMain.cs +++ b/source/OpenBVE/UserInterface/formMain.cs @@ -56,6 +56,8 @@ internal struct MainDialogResult internal bool FullScreen; internal int Width; internal int Height; + /// Whether to show the experimental GL menu + internal bool ExperimentalGLMenu; } internal static MainDialogResult ShowMainDialog(MainDialogResult initial) { diff --git a/source/OpenBveApi/Objects/ObjectTypes/StaticObject.cs b/source/OpenBveApi/Objects/ObjectTypes/StaticObject.cs index 73b9191c7..0d1e81080 100644 --- a/source/OpenBveApi/Objects/ObjectTypes/StaticObject.cs +++ b/source/OpenBveApi/Objects/ObjectTypes/StaticObject.cs @@ -1,5 +1,6 @@ using System; using OpenBveApi.Colors; +using OpenBveApi.Hosts; using OpenBveApi.Math; using OpenBveApi.World; @@ -12,9 +13,9 @@ public class StaticObject : UnifiedObject /// The mesh of the object public Mesh Mesh; /// The starting track position, for static objects only. - public float StartingDistance; + public float StartingTrackDistance; /// The ending track position, for static objects only. - public float EndingDistance; + public float EndingTrackDistance; /// Whether the object is dynamic, i.e. not static. public bool Dynamic; /// Stores the author for this object. @@ -22,10 +23,10 @@ public class StaticObject : UnifiedObject /// Stores the copyright information for this object. public string Copyright; - private readonly Hosts.HostInterface currentHost; + private readonly HostInterface currentHost; /// Creates a new empty object - public StaticObject(Hosts.HostInterface Host) + public StaticObject(HostInterface Host) { currentHost = Host; Mesh = new Mesh @@ -44,8 +45,8 @@ public StaticObject(Hosts.HostInterface Host) { StaticObject Result = new StaticObject(currentHost) { - StartingDistance = StartingDistance, - EndingDistance = EndingDistance, + StartingTrackDistance = StartingTrackDistance, + EndingTrackDistance = EndingTrackDistance, Dynamic = Dynamic, Mesh = {Vertices = new VertexTemplate[Mesh.Vertices.Length]} }; @@ -92,8 +93,8 @@ public override UnifiedObject Clone() { StaticObject Result = new StaticObject(currentHost) { - StartingDistance = StartingDistance, - EndingDistance = EndingDistance, + StartingTrackDistance = StartingTrackDistance, + EndingTrackDistance = EndingTrackDistance, Dynamic = Dynamic, Mesh = {Vertices = new VertexTemplate[Mesh.Vertices.Length]} }; @@ -290,7 +291,7 @@ public void ApplyScale(double x, double y, double z) double u = nx2 * rx2 + ny2 * ry2 + nz2 * rz2; if (u != 0.0) { - u = (float) System.Math.Sqrt((double) ((nx2 + ny2 + nz2) / u)); + u = (float) System.Math.Sqrt((nx2 + ny2 + nz2) / u); Mesh.Faces[j].Vertices[k].Normal.X *= rx * u; Mesh.Faces[j].Vertices[k].Normal.Y *= ry * u; Mesh.Faces[j].Vertices[k].Normal.Z *= rz * u; @@ -470,15 +471,21 @@ public override void OptimizeObject(bool PreserveVerticies, int Threshold, bool int v = Mesh.Vertices.Length; int m = Mesh.Materials.Length; int f = Mesh.Faces.Length; - if (f >= Threshold) + if (f >= Threshold && currentHost.Platform != HostPlatform.AppleOSX) { + /* + * HACK: + * A forwards compatible GL3 context (required on OS-X) only supports tris + * No access to the renderer type here, so let's cheat and assume that OS-X + * requires an optimized object (therefore decomposed into tris) in all circumstances + */ return; } // eliminate invalid faces and reduce incomplete faces for (int i = 0; i < f; i++) { - FaceFlags type = (FaceFlags)Mesh.Faces[i].Flags & FaceFlags.FaceTypeMask; + FaceFlags type = Mesh.Faces[i].Flags & FaceFlags.FaceTypeMask; bool keep; switch (type) { @@ -695,7 +702,7 @@ public override void OptimizeObject(bool PreserveVerticies, int Threshold, bool // Trangularize all polygons and quads into triangles for (int i = 0; i < f; ++i) { - FaceFlags type = (FaceFlags)Mesh.Faces[i].Flags & FaceFlags.FaceTypeMask; + FaceFlags type = Mesh.Faces[i].Flags & FaceFlags.FaceTypeMask; // Only transform quads and polygons if (type == FaceFlags.Quads || type == FaceFlags.Polygon) { @@ -733,18 +740,15 @@ public override void OptimizeObject(bool PreserveVerticies, int Threshold, bool } // Mark as triangle - unchecked - { - Mesh.Faces[i].Flags &= ~FaceFlags.FaceTypeMask; - Mesh.Faces[i].Flags |= FaceFlags.Triangles; - } + Mesh.Faces[i].Flags &= ~FaceFlags.FaceTypeMask; + Mesh.Faces[i].Flags |= FaceFlags.Triangles; } } // decomposit TRIANGLES and QUADS for (int i = 0; i < f; i++) { - FaceFlags type = (FaceFlags)Mesh.Faces[i].Flags & FaceFlags.FaceTypeMask; + FaceFlags type = Mesh.Faces[i].Flags & FaceFlags.FaceTypeMask; int face_count = 0; FaceFlags face_bit = 0; if (type == FaceFlags.Triangles) @@ -778,11 +782,8 @@ public override void OptimizeObject(bool PreserveVerticies, int Threshold, bool Mesh.Faces[f + j].Material = Mesh.Faces[i].Material; Mesh.Faces[f + j].Flags = Mesh.Faces[i].Flags; - unchecked - { - Mesh.Faces[i].Flags &= ~FaceFlags.FaceTypeMask; - Mesh.Faces[i].Flags |= face_bit; - } + Mesh.Faces[i].Flags &= ~FaceFlags.FaceTypeMask; + Mesh.Faces[i].Flags |= face_bit; } Array.Resize(ref Mesh.Faces[i].Vertices, face_count); diff --git a/source/OpenBveApi/OpenBveApi.csproj b/source/OpenBveApi/OpenBveApi.csproj index d4d6871b4..4ec20bda4 100644 --- a/source/OpenBveApi/OpenBveApi.csproj +++ b/source/OpenBveApi/OpenBveApi.csproj @@ -203,6 +203,7 @@ + diff --git a/source/OpenBveApi/System/BaseOptions.cs b/source/OpenBveApi/System/BaseOptions.cs index e0538bbc5..4f7485551 100644 --- a/source/OpenBveApi/System/BaseOptions.cs +++ b/source/OpenBveApi/System/BaseOptions.cs @@ -63,7 +63,8 @@ public abstract class BaseOptions public string TrainName = ""; /// The current compatibility signal set public string CurrentCompatibilitySignalSet; - + /// Allows a forwards compatible context to be forced + public bool ForceForwardsCompatibleContext; /* * Note: Object optimisation takes time whilst loading, but may increase the render performance of an * object by checking for duplicate vertices etc. diff --git a/source/OpenBveApi/Textures/Textures.ImageSizeMode.cs b/source/OpenBveApi/Textures/Textures.ImageSizeMode.cs new file mode 100644 index 000000000..8cee65dd2 --- /dev/null +++ b/source/OpenBveApi/Textures/Textures.ImageSizeMode.cs @@ -0,0 +1,15 @@ +namespace OpenBveApi.Textures +{ + /// Differing methods of fitting an image to a surface size + public enum ImageSizeMode + { + /// The texture is placed at the top left corner, and is clipped if larger than the surface + Normal = 0, + /// The texture is centered on the surface + Center = 1, + /// The texture is stretched or shrunk to fit the surface + Stretch = 2, + /// The texture is stretched or shrunk to fit the surface, maintaining it's aspect ratio + Zoom = 3 + } +} diff --git a/source/OpenBveApi/Train/TrainInterface.cs b/source/OpenBveApi/Train/TrainInterface.cs index d0251ca7f..f21a674b5 100644 --- a/source/OpenBveApi/Train/TrainInterface.cs +++ b/source/OpenBveApi/Train/TrainInterface.cs @@ -1,4 +1,6 @@ using System; +using System.Drawing; +using System.Text; using OpenBveApi.Interface; namespace OpenBveApi.Trains @@ -28,6 +30,17 @@ public virtual void Unload() { } /// Whether loading the route was successful. public abstract bool LoadTrain(System.Text.Encoding Encoding, string trainPath, ref AbstractTrain train, ref Control[] currentControls); + /// Gets the description for the selected train + /// The path to the selected train + /// The user selected text encoding + /// The description + public abstract string GetDescription(string trainPath, Encoding userSelectedEncoding = null); + + /// Gets the image for the selected train + /// The path to the selected train + /// The image + public abstract Image GetImage(string trainPath); + /// Holds whether loading is currently in progress public bool IsLoading; diff --git a/source/Plugins/Train.OpenBve/Panel/Panel2CfgParser.cs b/source/Plugins/Train.OpenBve/Panel/Panel2CfgParser.cs index 04973210e..f8d9a7d41 100644 --- a/source/Plugins/Train.OpenBve/Panel/Panel2CfgParser.cs +++ b/source/Plugins/Train.OpenBve/Panel/Panel2CfgParser.cs @@ -1713,7 +1713,7 @@ internal int CreateElement(ref ElementsGroup Group, double Left, double Top, dou Vertex t3 = new Vertex(v[3], new Vector2(1.0f, 1.0f)); StaticObject Object = new StaticObject(Plugin.currentHost); Object.Mesh.Vertices = new VertexTemplate[] { t0, t1, t2, t3 }; - Object.Mesh.Faces = new[] { new MeshFace(new[] { 0, 1, 2, 3 }) }; + Object.Mesh.Faces = new[] { new MeshFace(new[] { 0, 1, 2, 0, 2, 3 }, FaceFlags.Triangles) }; //Must create as a single face like this to avoid Z-sort issues with overlapping bits Object.Mesh.Materials = new MeshMaterial[1]; Object.Mesh.Materials[0].Flags = new MaterialFlags(); if (DaytimeTexture != null) diff --git a/source/Plugins/Train.OpenBve/Panel/PanelCfgParser.cs b/source/Plugins/Train.OpenBve/Panel/PanelCfgParser.cs index 78958cb04..1ad4bcfe4 100644 --- a/source/Plugins/Train.OpenBve/Panel/PanelCfgParser.cs +++ b/source/Plugins/Train.OpenBve/Panel/PanelCfgParser.cs @@ -1562,7 +1562,7 @@ private int CreateElement(CarBase Car, double Left, double Top, double Width, do Vertex t2 = new Vertex(v[2], new Vector2(1.0f, 0.0f)); Vertex t3 = new Vertex(v[3], new Vector2(1.0f, 1.0f)); Object.Mesh.Vertices = new VertexTemplate[] {t0, t1, t2, t3}; - Object.Mesh.Faces = new[] {new MeshFace(new[] {0, 1, 2, 3})}; + Object.Mesh.Faces = new[] { new MeshFace(new[] { 0, 1, 2, 0, 2, 3 }, FaceFlags.Triangles) }; //Must create as a single face like this to avoid Z-sort issues with overlapping bits Object.Mesh.Materials = new MeshMaterial[1]; Object.Mesh.Materials[0].Flags = new MaterialFlags(); if (Texture != null) diff --git a/source/Plugins/Train.OpenBve/Plugin.cs b/source/Plugins/Train.OpenBve/Plugin.cs index 62878bcd8..259be5469 100644 --- a/source/Plugins/Train.OpenBve/Plugin.cs +++ b/source/Plugins/Train.OpenBve/Plugin.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; using System.Text; @@ -105,7 +106,7 @@ public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions public override bool CanLoadTrain(string path) { - if (path == null) + if (string.IsNullOrEmpty(path)) { return false; } @@ -182,7 +183,6 @@ public override bool CanLoadTrain(string path) */ } } - return false; } @@ -345,6 +345,55 @@ public override bool LoadTrain(Encoding Encoding, string trainPath, ref Abstract return true; } + public override string GetDescription(string trainPath, Encoding encoding = null) + { + try + { + string descriptionFile = Path.CombineFile(trainPath, "train.txt"); + if (File.Exists(descriptionFile)) + { + if (encoding == null) + { + return File.ReadAllText(descriptionFile); + } + return File.ReadAllText(descriptionFile, encoding); + + } + } + catch(Exception ex) + { + currentHost.ReportProblem(ProblemType.UnexpectedException, "Unable to get the description for train " + trainPath + " due to the exeception: " + ex.Message); + } + return string.Empty; + } + + public override Image GetImage(string trainPath) + { + try + { + string imageFile = Path.CombineFile(trainPath, "train.png"); + if (File.Exists(imageFile)) + { + return Image.FromFile(imageFile); + } + imageFile = Path.CombineFile(trainPath, "train.gif"); + if (File.Exists(imageFile)) + { + return Image.FromFile(imageFile); + } + imageFile = Path.CombineFile(trainPath, "train.bmp"); + if (File.Exists(imageFile)) + { + return Image.FromFile(imageFile); + } + } + catch (Exception ex) + { + currentHost.ReportProblem(ProblemType.UnexpectedException, "Unable to get the image for train " + trainPath + " due to the exeception: " + ex.Message); + } + return null; + } + /// Attempts to load and parse the current train's panel configuration file. /// The train diff --git a/source/Plugins/Train.OpenBve/Train/XML/TrainXmlParser.cs b/source/Plugins/Train.OpenBve/Train/XML/TrainXmlParser.cs index ce5a00342..b688a8e96 100644 --- a/source/Plugins/Train.OpenBve/Train/XML/TrainXmlParser.cs +++ b/source/Plugins/Train.OpenBve/Train/XML/TrainXmlParser.cs @@ -165,10 +165,10 @@ internal void Parse(string fileName, TrainBase Train, ref UnifiedObject[] CarObj Train.Handles.Power.NotchDescriptions = c.InnerText.Split(';'); for (int j = 0; j < Train.Handles.Power.NotchDescriptions.Length; j++) { - Size s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Power.NotchDescriptions[j]); - if (s.Width > Train.Handles.Power.MaxWidth) + Vector2 s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Power.NotchDescriptions[j]); + if (s.X > Train.Handles.Power.MaxWidth) { - Train.Handles.Power.MaxWidth = s.Width; + Train.Handles.Power.MaxWidth = s.X; } } break; @@ -176,10 +176,10 @@ internal void Parse(string fileName, TrainBase Train, ref UnifiedObject[] CarObj Train.Handles.Brake.NotchDescriptions = c.InnerText.Split(';'); for (int j = 0; j < Train.Handles.Brake.NotchDescriptions.Length; j++) { - Size s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Brake.NotchDescriptions[j]); - if (s.Width > Train.Handles.Brake.MaxWidth) + Vector2 s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Brake.NotchDescriptions[j]); + if (s.X > Train.Handles.Brake.MaxWidth) { - Train.Handles.Brake.MaxWidth = s.Width; + Train.Handles.Brake.MaxWidth = s.X; } } break; @@ -191,10 +191,10 @@ internal void Parse(string fileName, TrainBase Train, ref UnifiedObject[] CarObj Train.Handles.LocoBrake.NotchDescriptions = c.InnerText.Split(';'); for (int j = 0; j < Train.Handles.LocoBrake.NotchDescriptions.Length; j++) { - Size s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.LocoBrake.NotchDescriptions[j]); - if (s.Width > Train.Handles.LocoBrake.MaxWidth) + Vector2 s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.LocoBrake.NotchDescriptions[j]); + if (s.X > Train.Handles.LocoBrake.MaxWidth) { - Train.Handles.LocoBrake.MaxWidth = s.Width; + Train.Handles.LocoBrake.MaxWidth = s.X; } } break; @@ -202,10 +202,10 @@ internal void Parse(string fileName, TrainBase Train, ref UnifiedObject[] CarObj Train.Handles.Reverser.NotchDescriptions = c.InnerText.Split(';'); for (int j = 0; j < Train.Handles.Reverser.NotchDescriptions.Length; j++) { - Size s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Reverser.NotchDescriptions[j]); - if (s.Width > Train.Handles.Reverser.MaxWidth) + Vector2 s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Reverser.NotchDescriptions[j]); + if (s.X > Train.Handles.Reverser.MaxWidth) { - Train.Handles.Reverser.MaxWidth = s.Width; + Train.Handles.Reverser.MaxWidth = s.X; } } break; diff --git a/source/RouteManager2/CurrentRoute.cs b/source/RouteManager2/CurrentRoute.cs index d916b6e0b..44178cc03 100644 --- a/source/RouteManager2/CurrentRoute.cs +++ b/source/RouteManager2/CurrentRoute.cs @@ -454,7 +454,10 @@ public void UpdateBackground(double TimeElapsed, bool GamePaused) renderer.Fog.Color = CurrentFog.Color; renderer.Fog.Density = CurrentFog.Density; renderer.Fog.IsLinear = CurrentFog.IsLinear; - renderer.Fog.SetForImmediateMode(); + if (!renderer.AvailableNewRenderer) + { + renderer.Fog.SetForImmediateMode(); + } } else { diff --git a/source/RouteManager2/MessageManager/MessageTypes/GeneralMessage.cs b/source/RouteManager2/MessageManager/MessageTypes/GeneralMessage.cs index 5a3ca1019..ba2f969c7 100644 --- a/source/RouteManager2/MessageManager/MessageTypes/GeneralMessage.cs +++ b/source/RouteManager2/MessageManager/MessageTypes/GeneralMessage.cs @@ -1,6 +1,6 @@ using System; using System.Drawing; -using LibRender2.Texts; +using LibRender2.Text; using OpenBveApi.Colors; namespace RouteManager2.MessageManager.MessageTypes diff --git a/source/RouteManager2/MessageManager/MessageTypes/MarkerText.cs b/source/RouteManager2/MessageManager/MessageTypes/MarkerText.cs index c0a8513be..021332d9b 100644 --- a/source/RouteManager2/MessageManager/MessageTypes/MarkerText.cs +++ b/source/RouteManager2/MessageManager/MessageTypes/MarkerText.cs @@ -1,4 +1,4 @@ -using LibRender2.Texts; +using LibRender2.Text; using OpenBveApi.Colors; namespace RouteManager2.MessageManager.MessageTypes diff --git a/source/RouteViewer/NewRendererR.cs b/source/RouteViewer/NewRendererR.cs index ff36c2aaf..703c6e25b 100644 --- a/source/RouteViewer/NewRendererR.cs +++ b/source/RouteViewer/NewRendererR.cs @@ -1,5 +1,4 @@ using System; -using System.Drawing; using System.Globalization; using System.Linq; using System.Text; @@ -8,6 +7,7 @@ using LibRender2.Objects; using OpenBveApi; using OpenBveApi.Colors; +using OpenBveApi.FileSystem; using OpenBveApi.Graphics; using OpenBveApi.Hosts; using OpenBveApi.Interface; @@ -46,9 +46,9 @@ internal class NewRenderer : BaseRenderer private Texture PointSoundTexture; private Texture RunSoundTexture; - public override void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions) + public override void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions, FileSystem FileSystem) { - base.Initialize(CurrentHost, CurrentOptions); + base.Initialize(CurrentHost, CurrentOptions, FileSystem); string Folder = Path.CombineDirectory(Program.FileSystem.GetDataFolder(), "RouteViewer"); TextureManager.RegisterTexture(Path.CombineFile(Folder, "background.png"), out BackgroundChangeTexture); @@ -151,7 +151,10 @@ internal void RenderScene(double TimeElapsed) Fog.Color = Program.CurrentRoute.CurrentFog.Color; Fog.Density = Program.CurrentRoute.CurrentFog.Density; Fog.IsLinear = Program.CurrentRoute.CurrentFog.IsLinear; - Fog.SetForImmediateMode(); + if (!AvailableNewRenderer) + { + Fog.SetForImmediateMode(); + } } else { @@ -469,29 +472,29 @@ private void RenderOverlays() { keys = new[] { new[] { "F7" }, new[] { "F8" } }; Keys.Render(4, 4, 20, Fonts.SmallFont, keys); - OpenGlString.Draw(Fonts.SmallFont, "Open route", new Point(32, 4), TextAlignment.TopLeft, Color128.White); - OpenGlString.Draw(Fonts.SmallFont, "Display the options window", new Point(32, 24), TextAlignment.TopLeft, Color128.White); - OpenGlString.Draw(Fonts.SmallFont, $"v{Application.ProductVersion}", new Point(Screen.Width - 8, Screen.Height - 20), TextAlignment.TopLeft, Color128.White); + OpenGlString.Draw(Fonts.SmallFont, "Open route", new Vector2(32, 4), TextAlignment.TopLeft, Color128.White); + OpenGlString.Draw(Fonts.SmallFont, "Display the options window", new Vector2(32, 24), TextAlignment.TopLeft, Color128.White); + OpenGlString.Draw(Fonts.SmallFont, $"v{Application.ProductVersion}", new Vector2(Screen.Width - 8, Screen.Height - 20), TextAlignment.TopLeft, Color128.White); } else if (OptionInterface) { // keys keys = new[] { new[] { "F5" }, new[] { "F7" }, new[] { "F8" } }; Keys.Render(4, 4, 24, Fonts.SmallFont, keys); - OpenGlString.Draw(Fonts.SmallFont, "Reload route", new Point(32, 4), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, "Open route", new Point(32, 24), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, "Display the options window", new Point(32, 44), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Reload route", new Vector2(32, 4), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Open route", new Vector2(32, 24), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Display the options window", new Vector2(32, 44), TextAlignment.TopLeft, Color128.White, true); keys = new[] { new[] { "F" }, new[] { "N" }, new[] { "E" }, new[] { "C" }, new[] { "M" }, new[] { "I" } }; Keys.Render(Screen.Width - 20, 4, 16, Fonts.SmallFont, keys); - OpenGlString.Draw(Fonts.SmallFont, "WireFrame:", new Point(Screen.Width - 32, 4), TextAlignment.TopRight, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, "Normals:", new Point(Screen.Width - 32, 24), TextAlignment.TopRight, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, "Events:", new Point(Screen.Width - 32, 44), TextAlignment.TopRight, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, "CPU:", new Point(Screen.Width - 32, 64), TextAlignment.TopRight, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, "Mute:", new Point(Screen.Width - 32, 84), TextAlignment.TopRight, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, "Hide interface:", new Point(Screen.Width - 32, 104), TextAlignment.TopRight, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"{(RenderStatsOverlay ? "Hide" : "Show")} renderer statistics", new Point(Screen.Width - 32, 124), TextAlignment.TopRight, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Switch renderer type:", new Point(Screen.Width - 32, 144), TextAlignment.TopRight, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "WireFrame:", new Vector2(Screen.Width - 32, 4), TextAlignment.TopRight, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Normals:", new Vector2(Screen.Width - 32, 24), TextAlignment.TopRight, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Events:", new Vector2(Screen.Width - 32, 44), TextAlignment.TopRight, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "CPU:", new Vector2(Screen.Width - 32, 64), TextAlignment.TopRight, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Mute:", new Vector2(Screen.Width - 32, 84), TextAlignment.TopRight, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Hide interface:", new Vector2(Screen.Width - 32, 104), TextAlignment.TopRight, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"{(RenderStatsOverlay ? "Hide" : "Show")} renderer statistics", new Vector2(Screen.Width - 32, 124), TextAlignment.TopRight, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Switch renderer type:", new Vector2(Screen.Width - 32, 144), TextAlignment.TopRight, Color128.White, true); keys = new[] { new[] { "F10" } }; Keys.Render(Screen.Width - 32, 124, 30, Fonts.SmallFont, keys); @@ -513,7 +516,7 @@ private void RenderOverlays() if (Program.JumpToPositionEnabled) { - OpenGlString.Draw(Fonts.SmallFont, "Jump to track position:", new Point(4, 80), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Jump to track position:", new Vector2(4, 80), TextAlignment.TopLeft, Color128.White, true); double distance; @@ -521,11 +524,11 @@ private void RenderOverlays() { if (distance < Program.MinimumJumpToPositionValue - 100) { - OpenGlString.Draw(Fonts.SmallFont, (Environment.TickCount % 1000 <= 500 ? $"{Program.JumpToPositionValue}_" : Program.JumpToPositionValue), new Point(4, 100), TextAlignment.TopLeft, Color128.Red, true); + OpenGlString.Draw(Fonts.SmallFont, (Environment.TickCount % 1000 <= 500 ? $"{Program.JumpToPositionValue}_" : Program.JumpToPositionValue), new Vector2(4, 100), TextAlignment.TopLeft, Color128.Red, true); } else { - OpenGlString.Draw(Fonts.SmallFont, (Environment.TickCount % 1000 <= 500 ? $"{Program.JumpToPositionValue}_" : Program.JumpToPositionValue), new Point(4, 100), TextAlignment.TopLeft, distance > Program.CurrentRoute.Tracks[0].Elements[Program.CurrentRoute.Tracks[0].Elements.Length - 1].StartingTrackPosition + 100 ? Color128.Red : Color128.Yellow, true); + OpenGlString.Draw(Fonts.SmallFont, (Environment.TickCount % 1000 <= 500 ? $"{Program.JumpToPositionValue}_" : Program.JumpToPositionValue), new Vector2(4, 100), TextAlignment.TopLeft, distance > Program.CurrentRoute.Tracks[0].Elements[Program.CurrentRoute.Tracks[0].Elements.Length - 1].StartingTrackPosition + 100 ? Color128.Red : Color128.Yellow, true); } } @@ -533,9 +536,9 @@ private void RenderOverlays() // info double x = 0.5 * Screen.Width - 256.0; - OpenGlString.Draw(Fonts.SmallFont, $"Position: {GetLengthString(Camera.Alignment.TrackPosition)} (X={GetLengthString(Camera.Alignment.Position.X)}, Y={GetLengthString(Camera.Alignment.Position.Y)}), Orientation: (Yaw={(Camera.Alignment.Yaw * 57.2957795130824).ToString("0.00", culture)}°, Pitch={(Camera.Alignment.Pitch * 57.2957795130824).ToString("0.00", culture)}°, Roll={(Camera.Alignment.Roll * 57.2957795130824).ToString("0.00", culture)}°)", new Point((int)x, 4), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Radius: {GetLengthString(CameraTrackFollower.CurveRadius)}, Cant: {(1000.0 * CameraTrackFollower.CurveCant).ToString("0", culture)} mm, Adhesion={(100.0 * CameraTrackFollower.AdhesionMultiplier).ToString("0", culture)}" + " , Rain intensity= " + CameraTrackFollower.RainIntensity +"%", new Point((int)x, 20), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Renderer: {(AvailableNewRenderer ? "New (GL 3.0)" : "Old (GL 1.2)")}", new Point((int)x, 40), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Position: {GetLengthString(Camera.Alignment.TrackPosition)} (X={GetLengthString(Camera.Alignment.Position.X)}, Y={GetLengthString(Camera.Alignment.Position.Y)}), Orientation: (Yaw={(Camera.Alignment.Yaw * 57.2957795130824).ToString("0.00", culture)}°, Pitch={(Camera.Alignment.Pitch * 57.2957795130824).ToString("0.00", culture)}°, Roll={(Camera.Alignment.Roll * 57.2957795130824).ToString("0.00", culture)}°)", new Vector2((int)x, 4), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Radius: {GetLengthString(CameraTrackFollower.CurveRadius)}, Cant: {(1000.0 * CameraTrackFollower.CurveCant).ToString("0", culture)} mm, Adhesion={(100.0 * CameraTrackFollower.AdhesionMultiplier).ToString("0", culture)}" + " , Rain intensity= " + CameraTrackFollower.RainIntensity +"%", new Vector2((int)x, 20), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Renderer: {(AvailableNewRenderer ? "New (GL 3.0)" : "Old (GL 1.2)")}", new Vector2((int)x, 40), TextAlignment.TopLeft, Color128.White, true); int stationIndex = Program.Renderer.CameraTrackFollower.StationIndex; @@ -599,7 +602,7 @@ private void RenderOverlays() t.Append(", Ratio=").Append((100.0 * Program.CurrentRoute.Stations[stationIndex].PassengerRatio).ToString("0", culture)).Append("%"); - OpenGlString.Draw(Fonts.SmallFont, t.ToString(), new Point((int)x, 60), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, t.ToString(), new Vector2((int)x, 60), TextAlignment.TopLeft, Color128.White, true); } if (Interface.LogMessages.Count == 1) @@ -609,12 +612,12 @@ private void RenderOverlays() if (Interface.LogMessages[0].Type != MessageType.Information) { - OpenGlString.Draw(Fonts.SmallFont, "Display the 1 error message recently generated.", new Point(32, 72), TextAlignment.TopLeft, Color128.Red, true); + OpenGlString.Draw(Fonts.SmallFont, "Display the 1 error message recently generated.", new Vector2(32, 72), TextAlignment.TopLeft, Color128.Red, true); } else { //If all of our messages are information, then print the message text in grey - OpenGlString.Draw(Fonts.SmallFont, "Display the 1 message recently generated.", new Point(32, 72), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, "Display the 1 message recently generated.", new Vector2(32, 72), TextAlignment.TopLeft, Color128.White, true); } } else if (Interface.LogMessages.Count > 1) @@ -624,22 +627,22 @@ private void RenderOverlays() if (error) { - OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count} error messages recently generated.", new Point(32, 72), TextAlignment.TopLeft, Color128.Red, true); + OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count} error messages recently generated.", new Vector2(32, 72), TextAlignment.TopLeft, Color128.Red, true); } else { - OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count} messages recently generated.", new Point(32, 72), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Display the {Interface.LogMessages.Count} messages recently generated.", new Vector2(32, 72), TextAlignment.TopLeft, Color128.White, true); } } if (RenderStatsOverlay) { Keys.Render(4, Screen.Height - 126, 116, Fonts.SmallFont, new[] { new[] { "Renderer Statistics" } }); - OpenGlString.Draw(Fonts.SmallFont, $"Total static objects: {VisibleObjects.Objects.Count}", new Point(4, Screen.Height - 112), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Total animated objects: {ObjectManager.AnimatedWorldObjectsUsed}", new Point(4, Screen.Height - 100), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Current frame rate: {FrameRate.ToString("0.0", culture)}fps", new Point(4, Screen.Height - 88), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Total opaque faces: {VisibleObjects.OpaqueFaces.Count}", new Point(4, Screen.Height - 76), TextAlignment.TopLeft, Color128.White, true); - OpenGlString.Draw(Fonts.SmallFont, $"Total alpha faces: {VisibleObjects.AlphaFaces.Count}", new Point(4, Screen.Height - 64), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Total static objects: {VisibleObjects.Objects.Count}", new Vector2(4, Screen.Height - 112), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Total animated objects: {ObjectManager.AnimatedWorldObjectsUsed}", new Vector2(4, Screen.Height - 100), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Current frame rate: {FrameRate.ToString("0.0", culture)}fps", new Vector2(4, Screen.Height - 88), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Total opaque faces: {VisibleObjects.OpaqueFaces.Count}", new Vector2(4, Screen.Height - 76), TextAlignment.TopLeft, Color128.White, true); + OpenGlString.Draw(Fonts.SmallFont, $"Total alpha faces: {VisibleObjects.AlphaFaces.Count}", new Vector2(4, Screen.Height - 64), TextAlignment.TopLeft, Color128.White, true); } } } diff --git a/source/RouteViewer/System/Gamewindow.cs b/source/RouteViewer/System/Gamewindow.cs index 9935336c9..a62612a9d 100644 --- a/source/RouteViewer/System/Gamewindow.cs +++ b/source/RouteViewer/System/Gamewindow.cs @@ -116,7 +116,7 @@ protected override void OnLoad(EventArgs e) Program.Renderer.Camera.BackwardViewingDistance = 0.0; Program.Renderer.Camera.ExtraViewingDistance = 50.0; - Program.Renderer.Initialize(Program.CurrentHost, Interface.CurrentOptions); + Program.Renderer.Initialize(Program.CurrentHost, Interface.CurrentOptions, Program.FileSystem); Program.Renderer.Lighting.Initialize(); Program.Sounds.Initialize(Program.CurrentHost, SoundRange.Low); Program.Renderer.UpdateViewport(); diff --git a/source/TrainEditor2/Graphics/NewRenderer.cs b/source/TrainEditor2/Graphics/NewRenderer.cs index fc02e34e7..455355c40 100644 --- a/source/TrainEditor2/Graphics/NewRenderer.cs +++ b/source/TrainEditor2/Graphics/NewRenderer.cs @@ -1,5 +1,6 @@ using LibRender2; using OpenBveApi; +using OpenBveApi.FileSystem; using OpenBveApi.Hosts; using OpenTK.Graphics.OpenGL; @@ -7,9 +8,9 @@ namespace TrainEditor2.Graphics { internal class NewRenderer : BaseRenderer { - public override void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions) + public override void Initialize(HostInterface CurrentHost, BaseOptions CurrentOptions, FileSystem FileSystem) { - base.Initialize(CurrentHost, CurrentOptions); + base.Initialize(CurrentHost, CurrentOptions, FileSystem); GL.Disable(EnableCap.CullFace); } diff --git a/source/TrainEditor2/Models/Trains/Motor.Functions.cs b/source/TrainEditor2/Models/Trains/Motor.Functions.cs index 17f016a75..4816da5a7 100644 --- a/source/TrainEditor2/Models/Trains/Motor.Functions.cs +++ b/source/TrainEditor2/Models/Trains/Motor.Functions.cs @@ -1105,7 +1105,7 @@ internal void DrawGlControl() for (double v = 0.0; v < MaxVelocity; v += 10.0) { - Program.Renderer.OpenGlString.Draw(Fonts.VerySmallFont, v.ToString("0", culture), new Point((int)VelocityToX(v) + 1, 1), TextAlignment.TopLeft, new Color128(Color24.Grey)); + Program.Renderer.OpenGlString.Draw(Fonts.VerySmallFont, v.ToString("0", culture), new Vector2(VelocityToX(v) + 1, 1), TextAlignment.TopLeft, new Color128(Color24.Grey)); } GL.Disable(EnableCap.Texture2D); @@ -1141,7 +1141,7 @@ internal void DrawGlControl() for (double p = 0.0; p < MaxPitch; p += 100.0) { - Program.Renderer.OpenGlString.Draw(Fonts.VerySmallFont, p.ToString("0", culture), new Point(1, (int)PitchToY(p) + 1), TextAlignment.TopLeft, new Color128(Color24.Grey)); + Program.Renderer.OpenGlString.Draw(Fonts.VerySmallFont, p.ToString("0", culture), new Vector2(1, PitchToY(p) + 1), TextAlignment.TopLeft, new Color128(Color24.Grey)); } GL.Disable(EnableCap.Texture2D); @@ -1173,7 +1173,7 @@ internal void DrawGlControl() for (double v = 0.0; v < MaxVolume; v += 128.0) { - Program.Renderer.OpenGlString.Draw(Fonts.VerySmallFont, v.ToString("0", culture), new Point(1, (int)VolumeToY(v) + 1), TextAlignment.TopLeft, new Color128(Color24.Grey)); + Program.Renderer.OpenGlString.Draw(Fonts.VerySmallFont, v.ToString("0", culture), new Vector2(1, VolumeToY(v) + 1), TextAlignment.TopLeft, new Color128(Color24.Grey)); } GL.Disable(EnableCap.Texture2D); diff --git a/source/TrainEditor2/Systems/Fonts.cs b/source/TrainEditor2/Systems/Fonts.cs index bb59344cb..904569ea8 100644 --- a/source/TrainEditor2/Systems/Fonts.cs +++ b/source/TrainEditor2/Systems/Fonts.cs @@ -1,5 +1,5 @@ using System.Drawing; -using LibRender2.Texts; +using LibRender2.Text; namespace TrainEditor2.Systems { diff --git a/source/TrainEditor2/Views/FormEditor.cs b/source/TrainEditor2/Views/FormEditor.cs index 7012ecdfa..e58e2a828 100644 --- a/source/TrainEditor2/Views/FormEditor.cs +++ b/source/TrainEditor2/Views/FormEditor.cs @@ -729,7 +729,7 @@ private void ToolStripComboBoxIndex_DrawItem(object sender, DrawItemEventArgs e) private void GlControlMotor_Load(object sender, EventArgs e) { glControlMotor.MakeCurrent(); - Program.Renderer.Initialize(Program.CurrentHost, Interface.CurrentOptions); + Program.Renderer.Initialize(Program.CurrentHost, Interface.CurrentOptions, Program.FileSystem); } private void GlControlMotor_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) diff --git a/source/TrainManager/Handles/AbstractHandle.cs b/source/TrainManager/Handles/AbstractHandle.cs index 35c4d7731..0e57e604d 100644 --- a/source/TrainManager/Handles/AbstractHandle.cs +++ b/source/TrainManager/Handles/AbstractHandle.cs @@ -49,7 +49,7 @@ public abstract class AbstractHandle public string[] NotchDescriptions; /// The max width used in px for the description string - public int MaxWidth = 48; + public double MaxWidth = 48; internal readonly TrainBase baseTrain; diff --git a/source/TrainManager/Handles/Handles.Reverser.cs b/source/TrainManager/Handles/Handles.Reverser.cs index 50a99c41e..3d19e021a 100644 --- a/source/TrainManager/Handles/Handles.Reverser.cs +++ b/source/TrainManager/Handles/Handles.Reverser.cs @@ -17,7 +17,7 @@ public class ReverserHandle /// Contains the notch descriptions to be displayed on the in-game UI public string[] NotchDescriptions; /// The max width used in px for the reverser HUD string - public int MaxWidth = 48; + public double MaxWidth = 48; private readonly TrainBase baseTrain;