Showing posts with label 3D. Show all posts
Showing posts with label 3D. Show all posts

Wednesday, March 20, 2019

MonoGame: Static Camera Tutorial and code


Some of you may have noticed my series on openGL with openTK in C# that I wrote some years ago. Since then my life has been quite full of stuff happening so I never got around to look at lightning or other stuff to make sure that I would end up with a working engine.

Lately I found MonoGame and started to experiment with it, turns out that it has much of the stuff that I want to use so I ended up writing some stuff with it.

Here I thought that I would share a basic static camera class and its usage as it took some time for me to understand how to get it working together with the BasicEffect class provided by MonoGame.

Some code. Let's start to look at the interface that we would want to use with out cameras.

public interface ICamera
{
    Matrix ViewMatrix { get; }
    Matrix ProjectionMatrix { get; }
    void Update(GameTime gameTime);
}

We want our cameras to expose their View and Projection matrices as they will be used to integrate the camera with MonoGame BasicEffect.
Also, as we will be using this interface with all of our cameras we may want to update it in each frame, hence the Update(GameTime gametTime) method. GameTime is provided by MonoGame and contains two timespans, time since game start and time since last frame.


Next we look at the actual StaticCamera, a camera that once created will remain static in the world. I.e. you place it at a position and point it towards something interesting and it will look at that position until you remove the camera. Useful for some scenarios and a good stepping point for creating more advanced camera classes.

We want our camera to have a Position in in the game world and a Direction.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
public class StaticCamera : ICamera
{
    public Vector3 Position { get; }
    public float FieldOfView { get; }
    public float NearPlane { get; }
    public float FarPlane { get; }
    public Vector3 Direction { get; }
    public Matrix ViewMatrix { get; }
    public Matrix ProjectionMatrix { get; }

    public StaticCamera(GraphicsDevice graphicsDevice, float fieldOfViewDegrees, float nearPlane, float farPlane)
        : this(graphicsDevice, fieldOfViewDegrees, nearPlane, farPlane, Vector3.Zero, -Vector3.UnitZ)
    { }

    public StaticCamera(GraphicsDevice graphicsDevice, float fieldOfViewDegrees, float nearPlane, float farPlane, Vector3 position, Vector3 target)
    {
        FieldOfView = fieldOfViewDegrees * 0.0174532925f;
        Position = position;
        Direction = Vector3.Normalize(target - position);
        ViewMatrix = Matrix.CreateLookAt(Position, Direction, Vector3.Up);
        ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(
            FieldOfView,
            graphicsDevice.Viewport.AspectRatio,
            nearPlane,
            farPlane);
    }
    public void Update(GameTime gameTime)
    { }
}

We have two constructors, the first one takes a GraphicsDevice reference that we need to figure out what resolution and especially what aspect ration our current view port has. (i.e. screen resolution/window size)
Also, we want to specify the lense angle in degrees. I.e. 60 degree lense.
The near and far clipping planes, i.e. we will only want to render objects that are between those two distances from the camera.
The first constructor creates a camera located at coordinates (0, 0, 0) looking inward (0, -1, 0)

The second constructor takes also the position and target to look at.
We calculate the radians of the camera lense by multiplying the field of view degrees with 0.01745...
After that we figure out the direction that the camera points at by subtracting the camera location from the target and normalizing the result.

To get the ViewMatrix we use the Matrix.CreateLookAt function that takes Position, Direction and an up vector
To get the ProjectionMatrix we use the Matrix.CreatePerspectiveFieldOfView function that takes the field of view, aspect ration and near/far planes.
This is pretty much all for a simple static camera using the MonoGame framework.

Next step is to use the camera.

private ICamera _camera;
protected override void LoadContent()
{
 _camera = new StaticCamera(GraphicsDevice, 60, 1, 200, new Vector3(1, -10, 0), Vector3.Zero);
}

I put the above code in my Game1.cs file. As the default Game1 class inherits from Game, it will have GraphicsDevice provided and we just need to send it into the constructor together with field of view angle, near and far planes, position and target coordinates to look at.

All of my game objects inherit from the following abstract GameObject class
public abstract class AGameObject
{
 public Vector3 Coordinates;
 public Vector3 Rotation;
 public Vector3 Velocity;
 public float Scale = 1f;
}
So each game object has a position in the game world that is stored in Coordinates, it also has an rotation, velocity and scale.


Whenever we want to render a game object in a scene, we wrap it in a Renderable object that also has a model and a Draw method:
public class Renderable
{
 public VertexPositionNormalTexture[] Model;
 public BasicEffect BasicEffect;
 public AGameObject GameObject;

 public Renderable(AGameObject gameObject, GraphicsDevice graphicsDevice)
 {
  GameObject = gameObject;
  BasicEffect = new BasicEffect(graphicsDevice)
  {
   AmbientLightColor = Vector3.One,
   LightingEnabled = true,
   DiffuseColor = Vector3.One,
  };
 }

 public void Draw(GraphicsDevice graphicsDevice, ICamera camera)
 {
  var t2 = Matrix.CreateTranslation(GameObject.Coordinates.X, GameObject.Coordinates.Y, GameObject.Coordinates.Z);
  var r1 = Matrix.CreateRotationX(GameObject.Rotation.X);
  var r2 = Matrix.CreateRotationY(GameObject.Rotation.Y);
  var r3 = Matrix.CreateRotationZ(GameObject.Rotation.Z);
  var s = Matrix.CreateScale(GameObject.Scale);

  BasicEffect.World = r1 * r2 * r3 * s * t2;

  BasicEffect.View = camera.ViewMatrix;
  BasicEffect.Projection = camera.ProjectionMatrix;
  
  BasicEffect.EnableDefaultLighting();

  foreach (var pass in BasicEffect.CurrentTechnique.Passes)
  {
   pass.Apply();

   graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, Model, 0, Model.Length / 3);
  }

 }
}

Here we can see that the render method takes a graphicsDevice and camera. Here we take the ViewMatrix from the camera and put it in the BasicEffect.View, and the same goes for the ProjectionMatrix that is put in BasicEffect.Projection.
The BasicEffect.World receives the rotated, scaled and translated matrix generated from the GameObject.

Now we need to look up our Draw method in the main game file (Game1.cs) and place calls to render our Renderables there
private List<Renderable> _scene = new List<Renderable>();
private readonly RasterizerState _rasterizerState = new RasterizerState
{
    CullMode = CullMode.CullClockwiseFace
};

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.RasterizerState = _rasterizerState;
    GraphicsDevice.Clear(Color.CornflowerBlue);
    foreach(var x in _scene)
    {
        x.Draw(GraphicsDevice, _camera);
    }
    base.Draw(gameTime);
}
First we tell the graphics device to save some time by culling triangles that are backwards facing, we then clear the scene to a background color.
For simplicity we store all our Renderables in a list called _scene and iterate through it and call Draw on each element.


I hope this helps someone out there to get unstuck when starting to use MonoGame.

All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment or share on social media, not required but much appreciated! :)

Wednesday, January 17, 2018

Wings3D basic spacecraft modelling



After trying to use Blender each time I've wanted to model anything in 3D and pretty much closing the application after 5 minutes as it just makes me stressed I was quite happy when I heard about Wings3D. A free, no bullshit, simple to use, 3D modeller that lets you model 3D models.

I decided to try it out with modelling a simple (can't really stress how simple here...) spacecraft with it.

I played around with the tool for maybe 10 minutes before starting to record, i.e. I figured out how to do this and then recorded it.

All commands are on the context menu that is accessed by right-clicking anywhere.
There are 4 modes of selection: Vertex, Edge, Plane or Object.
In this example I will sculpt half of a spaceship from a cube and then duplicate and flip it so that it becomes a whole ship.

Hope this helps someone out there!

Monday, February 13, 2017

Basic bullet movement patterns in Asteroid Invaders


In this part we will go through some basic movement patters for the bullets in Asteroid Invaders, the game that we began with in OpenGL 4 with OpenTK in C# Part 10: Asteroid Invaders.

For other posts in this series:
OpenGL 4 with OpenTK in C# Part 1: Initialize the GameWindow
OpenGL 4 with OpenTK in C# Part 2: Compiling shaders and linking them
OpenGL 4 with OpenTK in C# Part 3: Passing data to shaders
OpenGL 4 with OpenTK in C# Part 4: Refactoring and adding error handling
OpenGL 4 with OpenTK in C# Part 5: Buffers and Triangle
OpenGL 4 with OpenTK in C# Part 6: Rotations and Movement of objects
OpenGL 4 with OpenTK in C# Part 7: Vectors and Matrices
OpenGL 4 with OpenTK in C# Part 8: Drawing multiple objects
OpenGL 4 with OpenTK in C# Part 9: Texturing
OpenGL 4 with OpenTK in C# Part 10: Asteroid Invaders
Basic bullet movement patterns in Asteroid Invaders
OpenGL 4 with OpenTK in C# Part 11: Mipmap
OpenGL 4 with OpenTK in C# Part 12: Basic Moveable Camera
OpenGL 4 with OpenTK in C# Part 13: IcoSphere
OpenGL 4 with OpenTK in C# Part 14: Basic Text
OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse

Wave pattern

Here we add a displacement on the X axis based on either Sine or Cosine function (every other bullet) to get a wave like pattern for the bullet.
public override void Update(double time, double delta)
{
 _life += delta;
 if (_life > 5)
  ToBeRemoved = true;
 if (_bulletNumber % 2 == 0)
  _direction.X = (float)Math.Sin(_position.Y * 33f);
 else
  _direction.X = (float)Math.Cos(_position.Y * 33f);
 _direction.Normalize();
 _rotation = _direction * _velocity;
 base.Update(time, delta);
}
This is quite basic, and the amount of waves is set with the 33f, in this case quite rapid. The wave form is based of the location of the bullet in on the Y axis. As it moves upwards this value changes and it is sent to the Sin or Cos functions.
Lastly our new direction is normalized, note that this takes away some velocity as the object now moves on a curved road and the velocity is distributed on more than 1 axis. We could set the Y to 1 to achieve the original upward velocity. Up to the coder :)

Seeker/Homing

A little more advanced. Here we try to get the bullet to seek towards the asteroid that it has locked on to.
First we need to lock the bullet to an Asteroid.
public void SetTarget(Asteroid target)
{
    _target = target;
    target.LockBullet(this);
}

This in turn calls LockBullet on the Asteroid and we change the model of the asteroid to the bullet model during the time of the lock.
public void LockBullet(Bullet bullet)
{
    _lockedBullet = bullet;
    _model = bullet.Model;
}
In the Asteroid update method we add the following code to reset the model if the bullet misses
public override void Update(double time, double delta)
{
    _rotation.X = (float)Math.Sin((time + GameObjectNumber) * 0.3);
    _rotation.Y = (float)Math.Cos((time + GameObjectNumber) * 0.5);
    _rotation.Z = (float)Math.Cos((time + GameObjectNumber) * 0.2);
    var d = new Vector4(_rotation.X, _rotation.Y, 0, 0);
    d.Normalize();
    _direction = d;
    if (_lockedBullet != null && _lockedBullet.ToBeRemoved)
    {
        _lockedBullet = null;
        _model = _original;
    }
    base.Update(time, delta);
}

In our bullet update code we add the following to nudge the direction towards our target over time.
if (_target != null && !_target.ToBeRemoved)
{
    _direction = _direction + ((float)delta * (_target.Position - Position));
}
This gets normalized as well.
Lastly, when the bullet object is created, we assign
var bullet = _gameObjectFactory.CreateBullet(_player.Position, _bulletType);
var asteroids = _gameObjects.Where(x => x.GetType() == typeof (Asteroid)).ToList();
bullet.SetTarget((Asteroid) asteroids[bullet.GameObjectNumber%asteroids.Count]);
_gameObjects.Add(bullet);

Here we lock the asteroid directly after creation of the bullet. We just pick one asteroid from the list based on the bullets GameObjectNumber.
This is quite naive homing algorithm, it does not take into account any obstacles on its path and hits them many times instead.

For demo


For the complete source code for the tutorial at the end of this part, go to: https://github.com/eowind/dreamstatecoding

So there, thank you for reading. Hope this helps someone out there : )