OpenGL 3D viewport renderer

Forum for technical discussions regarding development. If you have a general suggestion, problem or comment, please use one of the other forums.

Moderator: OpenTTD Developers

Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

OpenGL 3D viewport renderer

Post by Assert » 02 Dec 2019 19:42

General idea:

OTTD is a 2.5D game, having an adequate orthogonal XYZ space (Z is scaled by some factor, but it’s ok). OTTD draws everything with sprites. Each sprite represents some object, or a part of the object. So, we can say, OTTD scene consists of an objects in XYZ space represented by the sprites. And what if we replace the sprites, by a full scale 3D objects, and render everything with OpenGL 3D engine? We’ve got a 3D version of the OTTD that would be nice.

Sprite is an image coming from the GRF file. Each sprite can be identified by the GRFID of the GRF file, and it’s id inside this file. So, we can build a map, from the drawn sprite, to some 3D geometry. Now we just need to replace a 2.5D viewport rendering procedure with our 3D rendering procedure to render 3D objects instead of 2D sprites. Actual things are slightly more complex, but everything fits this idea. We are leaving all of the game logic, GUI and a hi level tile drawings untouched, intercepting only the output in terms of sprites mapped to the 3D resources. This is a general constraint that probably fits to the project goal. Having this in mind, I've started my experiments.

The intermediate result:



This is a test graphic set extracted from the default resources, to check that the new 3D engine is working properly. It’s able to render a huge amount of different objects with different textures, computing pixel lighting and casting 3D shadows, but still supporting palette recoloring and the global color palette animation. I’ve made some overrides, like a foundations drawing, because it was not possible to use original sprite mapping for the full 3D scene. Fixed trees Z positions on slopes, made vehicles positions change smoother, extracting fractional part of the speed, and correcting it’s trajectories on slopes and corners. But everything still from the bottom, on a renderer level. Original game logics was not changed, and still driving everything.

As you can see, nearly everything is working. The only missing thing, is an effects, like a broken down smoke. I am still thinking about it’s implementation (particle system or an animated sprites)… but, all of the other things, can be added with new 3D graphical resources, and we get a complete working and a nice looking game. Of course some things still needs refinement, but they are all already working correctly.

Compatibility and performance:

This is a case where the OpenGL 2D texture arrays and instancing is absolutely required to have a good performance. With it, it’s possible to make a really fast renderer. That’s why the 3D engine was designed to use OpenGL 3.3+ spec compatible hardware (and running best on 4.3). I think this should be ok, OpenGL 3.3 == 10 years old graphic cards. I am using all of benefits from the modern interface. With my computer, drawing everything each frame in 3D with AA and shadows, is even faster in some cases, than the default 2.5D renderer, with it’s dirty blocks and updates on demand. It’s possible to make a compatibility patches for the lower OpenGL versions (with a lot of coding), but the performance of this hardware is too low in any way.

Internal architecture:

OpenGL subsystem, consists of the two things. The Blitter, with a standard interface, and a new 3D viewport renderer.

Blitter is implemented with OpenGL driver (windows driver was split to the base part, and a GDI + OpenGL implementations). Driver itself, does nothing, except the initialization of an OpenGL context, and can be ported very easy for the other platforms. Blitter is implemented as a 2D renderer engine of cached drawing commands. It have a recolor palettes cache, sprites cache as a huge 2D array texture atlas, and a pixel buffer for the damn SetPixel function (still used in the small map). It is a replacement for the software blitters, and designed to reproduce exactly the same 2D output, but with hardware acceleration for drawing, filtering, scrolling, blitting, filling, palette animation, etc. with services required for the 3D engine. It works 5-10 times faster than the standard SSE blitter on my hardware.

Its completed and can be used standalone, without a 3D viewport renderer. But it’s not supporting screenshot functions (lazy to add it). I’ve made some very tiny changes to the blitter interface, to make it much more hardware friendly. Recolor pointer replaced with a palette id, to allow hardware caching and operations (text recolor data stored now in the PaletteID itself). Checker rects drawing was moved from gfx, directly inside the blitter. Added a Flush/Finish functions, to separate mouse and network chat drawing from everything else, removing bad need to save screen blocks under a cursor and a chat window.

3D viewport renderer, works as a call interceptor. It declares the global _draw3d flag, and some important drawing functions, are redirected to the 3d drawing subroutines. Not a lot, just few. Switching off this flag, simply reverts us to the default 2.5D renderer on fly.

First of all, the 3D viewports overrides a dirty block system, with dirty rect system, to allow the GUI still be redrawn infrequently, with a full update of each pixel in each frame at the viewports. It uses stencil masks, built from the screen scape dirty rects binary tree (created with GUI invalidation calls and overlapped regions information), to select the screen areas for the viewport redraw, and manages top GUI redraw calls. This logic is concentrated in the gfx3d source file.

Second thing, is a tiny modification of the OTTD sprite cache, to store GRFID with other information about the sprites. This allow to identify each sprite uniquely across all of the existing sprites in the base set, and an external NewGRF files.

Actual 3D viewport renderer, has a config loader, for linking sprites to resources, a resource manager, to load and cache that resources, and a landscape data structure, to store its state and drawing caches. It intercepts viewport invalidation calls, marking tiles in the internal structure as dirty, instead of marking dirty the screen blocks. In the next frame, it calls a normal tile drawing functions, like the 2D viewport doing this, for each dirty tile, intercepting draw requests from this functions, and resolving sprite links and resources, making actual drawing lists for each tile. It caches nearly everything at this step, leaving for a frame update only an instansing buffer batching for the visible area, and a few OpenGL draw calls.

Resources:

The system has two types of resources. Images and models.

Image, is a PNG image, storing RGB color data, and a palette index in the alpha channel. Index 0 used for opacity (0 = completely transparent). Index 255, used as a selector for RGB color stored in the image. Other indexes, are used for fetching recolor palettes or a global animation palettes directly, if a recolor palette is not specified for the drawing. So, it’s possible to use a pixel color animation and a recolor mechanics for the 3D rendering, exactly like in 2.5D. Alpha blending is not supported, but it’s actually not required, with a transparent flag, recolor mapping and multisampling + ALPHA_TO_COVERAGE function. Images are stored in a 2D array atlas with blocks of 32x32 pixels, so it’s strongly recommended to use a power of two image dimensions, larger or equal to 32.

Model, is a human readable wavefront .obj file in its simplest implementation. Vertex positions, texture coords, normals, faces. That’s all. A lot of existing tools to work with.

Images and models are linked to the sprites by the config files. I am used existing OTTD ini file infrastructure. Config manager scans NewGRF directory for this ini files recursively, and trying to load all of this, at each startup. Files are consists of object definitions, and a sprites linkage. I’ve made a spec for this files (see below), but briefly: Object is a model + image + transform. Sprite linkage is a GRFID + SpriteID\InternalID + optional PaletteID + optional image for the landscape tile with texture coords + optional links to the objects.

Each landscape tile consists of the two triangles, with only one image on each. Yes, this is a limitation, OTTD can draw the landscape overlays for tracks, etc, but this can be implemented by the 3D objects, as a textured planes in a worst case. Generally, only one image can be applied to each tile, but for the shore halftiles, it is separated internally, assigning different images for both triangles, with respect to drawn sprites. Each tile has an additional layer for the selection sprites, that handled separately without lighting and shadows. Selection sprites are linked exactly the same as the other things. Moreover, they can be replaced with 3D objects (like for the terraforming dot). Each tile have a list of the drawn objects, created at the tile validation step. Vehicle sprites, are linked in the same way, as a tile objects.

One of the things that we should change while going from 2.5D to 3D, is a Z scale value. Proper orthogonal projection of the original view, result a tile height of 3.26598620 units, with 16 units of the tile side. Getting future, we can define a default vehicle size (width and height) as 2.82842712 units (8 px). And everything was ok, except catenary, added to the original game. So, instead of 3.26598620 units for the height scale in 3D, to get 1:1 projection with the original 2.5D render, 4.0 units scale was chosen. I want to stay as close as possible to the original look, so 4.0 is not too high, enough for catenary, and easy to use in the model editors. It's exactly half of the original Z scale, that is 8 units height, and makes things simpler.

An example of the config to link the bridge slopes drawing:

Code: Select all

[BRIDGE_SLOPE_SE]
model = base/bridge_slope.obj
image = base/bridge.png
trans = 8 8 0
rotate = 0 0 1 180
trans = -8 -8 0

[SPR_BRIDGE_SLOPE_SE]
grfid = BASE
sprite = 2440:2:8, 4:2:4329, 4368, 4328
object = BRIDGE_SLOPE_SE
BRIDGE_SLOPE_SE is a declaration of an object for SE bridge slope. It’s made of the model for bridge slope, rotated about its center (shifted for this to 8 units) by 180 degrees in Z axis, and bridge.png attached as a texture.

SPR_BRIDGE_SLOPE_SE is a sprite linkage for all bridges SE slopes at once (for the test). grfid is an ID of the GRF file, from where the sprite can came on. sprite, is a list of the sprite ids for this entry. Actual sprite ids are used, because this is a base set (for the new grf sets, file internal sprite ids should be used with ident keyword, if it is not a replacement for the base set). So the definition is: link this entry to 2 sprites, from sprite 2440 with step 8, etc. and draw object BRIDGE_SLOPE_SE in 3D for this tile, if the OTTD wants to draw any of this sprites.

Another example of linking the landscape tile image:

Code: Select all

[SPR_ROAD_SLOPE_X]
grfid = 0x42415345
sprite = 1343:2:2
land_image = base/SPR_ROAD_X.png
land_coord = [ 0.5 0.2421875 ] [ 0.0 0.4921875 ] [ 0.5 0.7421875 ] [ 1.0 0.4921875 ]
SPR_ROAD_SLOPE_X just only linkage itself. GRFID is the BASE again, but now in hex. Two sprites from 1343 with step 2 (replacing both inclinations). land_image image to use. land_coord tile corners coordinates in the image coordinates. This is bad-bad diagonal mapping that distorts image, and its used only for tests, to map the original sprite image directly.

Where to get GRFID, FileID, SpriteID and PaletteID for the config files? Tile info tool was modified (actually just current cursor image checked), to show you this info. Selecting any tile with tile info tool, will show you a full drawing stack for this tile in a format GRFID:FileID SpriteID:PaletteID with an optional T postfix, for the transparent sprites. It also shows drawing info for the visible vehicles.

Config file spec:
[+] Spoiler
All config files must be placed inside a NewGRF directory. All of the configs and resources can be placed in the TAR container, readable by OTTD. All of the paths in the config files, are relative to the NewGRF directory (if it starts with ‘\’ or ‘/’), or the config file itself.

Sprite linkage definition is a config group with a ‘grfid’ entry. It’s format:

grfid - Is a four character ident or a hex value of the GRF file identifier. Base set identifiers are: ‘BASE’, ‘LOGO’, 'ARCT', 'TROP', 'TOYL', and 0x01544FFF for the extended set.

sprite - Is a list of the sprite identifiers to link to, in a format of id[[:count]:step]. Use sprite linking only for the base set, or for the replacement sets.

ident - Same as ‘sprite’, but uses sprite number in a GRF file. Use this for a dynamically loaded sprites (like a NewGRF addons).

rebase - An integer offset for all ‘sprite’ and ‘ident’ entries, allows to rebase things fast with respect to the GRF file changes.

palette - Is a palette id to link as integer or hex. Omitting this parameter, you are linking to all palette ids, and it should be resolved by the images.

land_image - landscape image for the tile sprites, as a path to the image file.

land_coord - landscape tile texture coords in the image as four corners [ xN yN ] [ xW yW ] [ xS yS ] [ xE yE ]

land_rotate - rotation of the texture coords about tile center in degrees.

veh_length - Is a vehicle base length (a distance from the center to the wheels axis).

object - zero or more object entries, with object groupe name in the same ini file.
Object definition, is a config group with some name referred in the sprite object entries. It’s format:

model - object geometry, as a path to the model file.

Image - object texture, as a path to the image file.

trans - adds translation to the object transform matrix, as x y z.

scale - adds scaling to the object transform matrix, as x y z.

rotate - adds rotation around axis to the object transform matrix, as x y z angle.

Sources:
src.zip
OTTD 3D Sources
(8.89 MiB) Downloaded 11 times
Windows binaries and a test set:
bin.zip
OTTD 3D windows Binaries
(3.02 MiB) Downloaded 10 times
Should be launched with -v win32ogl

User avatar
jfs
Director
Director
Posts: 609
Joined: 08 Jan 2003 23:09
Location: Denmark

Re: OpenGL 3D viewport renderer

Post by jfs » 02 Dec 2019 21:28

While I don't doubt the technical parts of this are good, there are two major conceptual issues I see:

First, there is a library of more than 15 years of NewGRF add-ons containing sprites that are not compatible with this mode. I see two possible outcomes, one is that nobody might want to use this 3D mode because it doesn't support their NewGRF. The other is that some kind of "re-wrapping" of sprites onto 3D models (or just as billboard sprites) that will look bad and possibly anger artists having their work poorly presented.

Second, and related, is that I think a large number of players stick with OpenTTD specifically because of the art style.

So yeah, sorry for being only negative, but I just can't see this becoming a popular feature.


Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

Re: OpenGL 3D viewport renderer

Post by Assert » 02 Dec 2019 22:31

jfs wrote:
02 Dec 2019 21:28
nobody might want to use this 3D mode because it doesn't support their NewGRF.
This is a user choise. It's not blocking anything. At some point, somebody can make a 3D set for this NewGRF, and everything will work...
jfs wrote:
02 Dec 2019 21:28
Second, and related, is that I think a large number of players stick with OpenTTD specifically because of the art style.
It's possible to reproduce any artstyle, and i am personally prefer to be as close as possible to the original dataset. Just in 3D. When somebody making a good sprites for OTTD, he is making a 3D model, then renders it with 3Ds MAX to the sprite... we just skipping this step.
jfs wrote:
02 Dec 2019 21:28
So yeah, sorry for being only negative
Anyway, there was some requests for this "feature". And i am implemented it in a most harmless form. Minimum dependincies, and easy to maintain. Then community should choose of what to do with this.
jfs wrote:
02 Dec 2019 21:28
but I just can't see this becoming a popular feature.
Time will tell us. I've already get everything i want from this. It was a very nice and funny workout. If somebody will start to make a content for this, then it will be a motivation to continue.

Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

Re: OpenGL 3D viewport renderer

Post by Assert » 02 Dec 2019 22:38

andythenorth wrote:
02 Dec 2019 21:51
It does look very good in the demo though :)
Thanks, may be you know someone, who can help to make a 3D content for this? :) I know, A LOT of NewGRF developers have a 3D models of it's sprites...

User avatar
kamnet
Moderator
Moderator
Posts: 6894
Joined: 28 Sep 2009 17:15
Location: Eastern KY
Contact:

Re: OpenGL 3D viewport renderer

Post by kamnet » 02 Dec 2019 23:55

jfs wrote:
02 Dec 2019 21:28
While I don't doubt the technical parts of this are good, there are two major conceptual issues I see:

First, there is a library of more than 15 years of NewGRF add-ons containing sprites that are not compatible with this mode. I see two possible outcomes, one is that nobody might want to use this 3D mode because it doesn't support their NewGRF. The other is that some kind of "re-wrapping" of sprites onto 3D models (or just as billboard sprites) that will look bad and possibly anger artists having their work poorly presented.

Second, and related, is that I think a large number of players stick with OpenTTD specifically because of the art style.

So yeah, sorry for being only negative, but I just can't see this becoming a popular feature.
It may not be a majorly popular feature, but I believe there would be a significant number of users who would jump onto this faster than they did zBase, who appreciate the 3D aspects and ultra-clean, simplified renderings over detailed 32bpp 2D or low-res 8bpp 2D graphics.

And because it's a 3D engine, it may actually attract more new NewGRF developers on board to create graphics for them.

perverted monkey
Engineer
Engineer
Posts: 42
Joined: 02 Mar 2009 02:07

Re: OpenGL 3D viewport renderer

Post by perverted monkey » 03 Dec 2019 02:49

OTTD 2.0.0 ?

I like what I see. It was time.

If this project continues many curent and future players of OTTD will :bow: in gratitude.

Thumbs up! :D

User avatar
planetmaker
OpenTTD Developer
OpenTTD Developer
Posts: 9334
Joined: 07 Nov 2007 22:44
Location: Sol d

Re: OpenGL 3D viewport renderer

Post by planetmaker » 03 Dec 2019 11:09

Hm, I like what I see!

And I'd totally love to see this brought further.

Arguably, NewGRFs will hardly be interchangeably be usable with the 2.5D version and a 3D version. That works for ground tiles and anything flat. It even may somewhat work for stuff where four views are provided like rail depots and stations (but not airports). But it will be extremely difficult for industries which are extended objects without rotation views provided.

However: There do exist blender files with models to start with for everything needed by vanilla OpenTTD in the zBase repository. Thus some models to start with do exist (look for the .blend files in all the sub-dirs). Do you happen to have any github repo instead of tar balls attached here?
Attachments
Bildschirmfoto von »2019-12-03 11-15-59«.png
part of farm, without texture
(327.44 KiB) Not downloaded yet
Bildschirmfoto von »2019-12-03 11-15-09«.png
part of gold mine, without texture
(449.08 KiB) Not downloaded yet

User avatar
PNDA_
Engineer
Engineer
Posts: 54
Joined: 18 Jul 2018 17:26
Location: Germany

Re: OpenGL 3D viewport renderer

Post by PNDA_ » 03 Dec 2019 13:41

Hey,

I really like this idea and I am usually a big fan of 3D and realistic games. Though I just don't think it fits this game, just as I don't really like zoomed sprites either.

Other thing is, I can't get it to work on my side. I used the executable from the bin.zip you attached and it lets me activate the 3d viewport and then further on still renders like it would normally. Also, would be nice if you would attach the \lang folder aswell, as we require the lang files created when building. In the main menu I also get that weird blue sea background bug and I forgot what caused that, sorry. There's also an issue with the game finding music and sound sets.
Good luck developing this further!
Attachments
unknown.png
(1.24 MiB) Not downloaded yet
Image

milek7
Engineer
Engineer
Posts: 8
Joined: 18 Jul 2016 08:04

Re: OpenGL 3D viewport renderer

Post by milek7 » 03 Dec 2019 16:33

Hi.

I wanted to write video driver for SDL2, but I have little trouble with understand frame flow. How does it work when _draw3d is disabled? Is it drawn directly on front framebuffer, or is there legacy intermediary surface? And there is no SwapBuffers at all in the code you provided, I can't see how it could work.
EDIT: OK, it was there, just commented out.

Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

Re: OpenGL 3D viewport renderer

Post by Assert » 03 Dec 2019 22:56

kamnet wrote:
02 Dec 2019 23:55
It may not be a majorly popular feature, but I believe there would be a significant number of users who would jump onto this faster than they did zBase, who appreciate the 3D aspects and ultra-clean, simplified renderings over detailed 32bpp 2D or low-res 8bpp 2D graphics.

And because it's a 3D engine, it may actually attract more new NewGRF developers on board to create graphics for them.
Yes, i hope this will be intrested to the community. And it's simplier to make a one 3D model, than prperly render A LOT of sprites. But the future is only possible, if this will be a part of the main branch i think...

Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

Re: OpenGL 3D viewport renderer

Post by Assert » 03 Dec 2019 22:58

planetmaker wrote:
03 Dec 2019 11:09
However: There do exist blender files with models to start with for everything needed by vanilla OpenTTD in the zBase repository. Thus some models to start with do exist (look for the .blend files in all the sub-dirs). Do you happen to have any github repo instead of tar balls attached here?
Thanks i've checked zBase files.

Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

Re: OpenGL 3D viewport renderer

Post by Assert » 03 Dec 2019 22:59

PNDA_ wrote:
03 Dec 2019 13:41
Hey,

I really like this idea and I am usually a big fan of 3D and realistic games. Though I just don't think it fits this game, just as I don't really like zoomed sprites either.

Other thing is, I can't get it to work on my side. I used the executable from the bin.zip you attached and it lets me activate the 3d viewport and then further on still renders like it would normally. Also, would be nice if you would attach the \lang folder aswell, as we require the lang files created when building. In the main menu I also get that weird blue sea background bug and I forgot what caused that, sorry. There's also an issue with the game finding music and sound sets.
Good luck developing this further!
You should start it with -v win32ogl command line, to enable OGL driver.

Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

Re: OpenGL 3D viewport renderer

Post by Assert » 03 Dec 2019 23:02

milek7 wrote:
03 Dec 2019 16:33
Hi.

I wanted to write video driver for SDL2, but I have little trouble with understand frame flow. How does it work when _draw3d is disabled? Is it drawn directly on front framebuffer, or is there legacy intermediary surface? And there is no SwapBuffers at all in the code you provided, I can't see how it could work.
EDIT: OK, it was there, just commented out.
Double buffer eats performance by the some unknown reason, so i am used a front buffer directly. But it's ok, because there is an internal framebuffer where everything is drawn, then flipped to the screen. There is a macro DEF_BUFFER, to select a target buffer for rendering, you can change it to GL_BACK and enable doublebuffering with adding SwapBuffers.

User avatar
orudge
Administrator
Administrator
Posts: 24042
Joined: 26 Jan 2001 20:18
Skype: orudge
Location: Banchory, UK
Contact:

Re: OpenGL 3D viewport renderer

Post by orudge » 03 Dec 2019 23:15

Assert wrote:
02 Dec 2019 19:42
Sources:
src.zip

Windows binaries and a test set:
bin.zip
This looks interesting. However, what git revision should this be built/run against? If you could provide a link to a GitHub branch that would be most useful. :)

milek7
Engineer
Engineer
Posts: 8
Joined: 18 Jul 2016 08:04

Re: OpenGL 3D viewport renderer

Post by milek7 » 03 Dec 2019 23:29

I tried to make it work on SDL2: https://github.com/Milek7/OpenTTD/tree/3d (for now it dirties whole frame always, because SDL doesn't provide intermediary buffer with InvalidateRect like windows)
It almost works, however it looks like that: https://i.imgur.com/4yPs6wE.png
Hovewer same thing happens when running your posted binaries on Wine, so I think this is shader problem. Sampling textures with half-pixel offset, or some filtering issue?

Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

Re: OpenGL 3D viewport renderer

Post by Assert » 03 Dec 2019 23:56

milek7 wrote:
03 Dec 2019 23:29
I tried to make it work on SDL2: https://github.com/Milek7/OpenTTD/tree/3d (for now it dirties whole frame always, because SDL doesn't provide intermediary buffer with InvalidateRect like windows)
It almost works, however it looks like that: https://i.imgur.com/4yPs6wE.png
Hovewer same thing happens when running your posted binaries on Wine, so I think this is shader problem. Sampling textures with half-pixel offset, or some filtering issue?
No, this is just because the land generator do not invalidate all of the tiles, just save and load. I will fix this soon.

Assert
Engineer
Engineer
Posts: 10
Joined: 24 Nov 2019 22:19

Re: OpenGL 3D viewport renderer

Post by Assert » 04 Dec 2019 00:00

orudge wrote:
03 Dec 2019 23:15
This looks interesting. However, what git revision should this be built/run against? If you could provide a link to a GitHub branch that would be most useful. :)
It's branched from 20191126-master-m4233e11aa1, i will make a git repo as soon as understand that this thing have a future. Anyway, i am packed the whole src directory, so you can build it with any latest branch of the master.

User avatar
odisseus
Engineer
Engineer
Posts: 58
Joined: 01 Nov 2017 21:19

Re: OpenGL 3D viewport renderer

Post by odisseus » 04 Dec 2019 00:14

Well done! This is the logical next step after 32bpp graphics.

(That said, I personally find 32bpp ugly and bloating. But apparently many people like it.)

User avatar
andythenorth
Tycoon
Tycoon
Posts: 5046
Joined: 31 Mar 2007 14:23
Location: Lost in Music

Re: OpenGL 3D viewport renderer

Post by andythenorth » 04 Dec 2019 07:07

Variant of this approach:
* fix the camera
* draw existing sprites as textures on planes with OpenGL, hardware accelerated
* see whether it's faster or slower than current 2D blitters??

Post Reply

Return to “OpenTTD Development”

Who is online

Users browsing this forum: No registered users and 1 guest