Hi,
Using the code from this site
http://blogs.msdn.com/rogerboesch/archive/2007/01/20/tipp-xna-in-eigene-applikationen-einbinden-winforms.aspx Thanks to Roger Boesch! I have been able to create a sample application that can be a starting point for an XNA Scene Editor. I read the tutorials for the Hazy Mind Scene Editor and realised that they were tied to the Hazy Mind 3D Engine, which is written in Managed DirectX. I really wanted to stick with XNA, so I hacked together the following code. In the spirit of this community, I offer my work in the hope somebody else will find it helpful.
The following may go without saying on this site, but just in case... DISCLAIMER: As the code is mostly the work of Roger Boesch and Michael Schuld, full credit should go to them. In regard to any editing that I have done to bring the code together, I disclaim any responsibility for any damage or loss that you suffer from the use of this code. USE AT YOUR OWN RISK.using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using HMEngine.HMOctrees;
using HMEngine.HMShaders;
using HMEngine.HMCameras;
using HMEngine.HMScene;
namespace HMEngine
{
// This class mostly copied from http://blogs.msdn.com/rogerboesch/archive/2007/01/20/tipp-xna-in-eigene-applikationen-einbinden-winforms.aspx
// Partially refactored in line with the naming convention of the HMEngine http://www.thehazymind.com/
class HMGraphicsDeviceService : IGraphicsDeviceService, IGraphicsDeviceManager
{
private GraphicsDevice m_graphicsDevice;
private Control m_renderControl;
public HMGraphicsDeviceService(Control control)
{
this.m_renderControl = control;
}
#region IGraphicsDeviceService Members
public event EventHandler DeviceCreated;
public event EventHandler DeviceDisposing;
public event EventHandler DeviceReset;
public event EventHandler DeviceResetting;
public GraphicsDevice GraphicsDevice
{
get { return this.m_graphicsDevice; }
}
#endregion
#region IGraphicsDeviceManager Members
public bool BeginDraw()
{
return false;
}
public void CreateDevice()
{
PresentationParameters pp = new PresentationParameters();
pp.IsFullScreen = false;
pp.BackBufferCount = 1;
pp.BackBufferHeight = this.m_renderControl.Height;
pp.BackBufferWidth = this.m_renderControl.Width;
this.m_graphicsDevice = new GraphicsDevice(GraphicsAdapter.Adapters[0], DeviceType.Hardware,
this.m_renderControl.Handle, CreateOptions.SoftwareVertexProcessing, pp);
}
public void EndDraw()
{
}
#endregion
}
public class HMGameView : HMPanel, IServiceProvider
{
private HMGraphicsDeviceService m_graphicsDeviceService;
// Roger Boesch omitted this line from his sample code
private ContentManager m_contentManager;
// Reference to our scene graph for setting up our scene
private HMSceneGraph myScene;
public HMSceneGraph Scene { get { return myScene; } }
public void InitializeGraphics()
{
// Credit to Roger Boesch
this.m_graphicsDeviceService = new HMGraphicsDeviceService(this);
this.m_graphicsDeviceService.CreateDevice();
this.m_contentManager = new ContentManager(this);
// Credit to Michael Schuld
// Load Shader Graphics
HMShaderManager.LoadGraphicsContent(m_graphicsDeviceService.GraphicsDevice, m_contentManager);
// Load Scene Graphics (models)
myScene.SceneRoot.LoadGraphicsContent(m_graphicsDeviceService.GraphicsDevice, m_contentManager);
// Load octree graphics
HMOctreeManager.LoadGraphicsContent(m_graphicsDeviceService.GraphicsDevice);
// Create the default camera
HMCamera defaultCamera = new HMCamera(m_graphicsDeviceService.GraphicsDevice.Viewport);
HMCameraManager.AddCamera(defaultCamera, "default");
HMCameraManager.SetActiveCamera("default");
// Credit to Roger Boesch
// Initialise the timer
Timer timer = new Timer();
timer.Tick += new EventHandler(TimerHandler);
timer.Interval = 100;
timer.Start();
}
// Added so that the scene graph could be initialised prior to setting up the models
public void Initialize()
{
// Initialise the scene graph
myScene = new HMSceneGraph();
// Initialise Shader - will give us a BasicEffect to use for lines in the Octree
HMShaderManager.Initialize();
}
void TimerHandler(object sender, EventArgs e)
{
// Draw all object currently in the screen graph / octree
// When the same scene is drawn by the runtime engine, there may be other effects applied additionally. e.g. PostProcessing
m_graphicsDeviceService.GraphicsDevice.Clear(Color.CornflowerBlue);
HMOctreeManager.OctRoot.Draw(m_graphicsDeviceService.GraphicsDevice);
// Present() is required because our object is derived from Control (I think)
// instead of Microsoft.Xna.Framework.Game
// Comment out the next line and the gameview will be undrawn (contain background garbage)
m_graphicsDeviceService.GraphicsDevice.Present();
// Roger Boesch's code simply said RenderLoop();
}
#region IServiceProvider Members
public new object GetService(Type serviceType)
{
if (serviceType == typeof(Microsoft.Xna.Framework.Graphics.IGraphicsDeviceService))
{
return this.m_graphicsDeviceService;
}
else
{
return base.GetService(serviceType);
}
}
#endregion
}
// Roger Boesch's code omitted this class, fortunately Michael Schuld's code from his Hazy Mind Scene Editor was suitable to use
public class HMPanel : Panel
{
public HMPanel()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
}
}
}
The key here is that you are basically replacing/removing your HMGame class for this application and reusing the other classes that have been developed.
So, HMGame is for game type applications (windowed or fullscreen) and HMGameView is for windows forms applications that also require a 3d rendered surface as part of the user interface, for example, a scene editor.
I had a hard time getting the timing right between the Initialize() call and the InitializeGraphics() call. My runtime was raising a lot NullReferenceExceptions because it was trying to draw an object or use an effect before it had been loaded. I believe I have resolved this in the code I am about to show.
Using HMGameView in an ApplicationThe following notes are basically the steps followed by Michael Schuld in his Hazy Mind Scene Editor. Please read his tutoriall also in case I have glossed over or missed a step.Add a new Windows Game to your HMEngine solution.
Rename Program.cs to HMSceneEditor.cs and Form1.cs to HMSceneEditorForm.cs. Refactor your project.
Open the visual editor for HMSceneEditorForm and drop an instance of HMGameView onto the surface.
View code on HMSceneEditorForm and add the following code after the call to InitializeComponent();
// After InitializeComponent()
// Initialize the game view
hmGameView1.Initialize();
Open up HMSceneEditor.cs and look for your Main() function.
Make your function something like this:
// The Hazy Mind XNA Engine tutorial omit's this attribute but I do not know why
[STAThread]
public static void Main()
{
// Create your form
HMSceneEditorForm editor = new HMSceneEditorForm();
// Put some objects in the scene
SetupScene(editor.GameView.Scene);
// Load the graphics setup in the scene
editor.GameView.InitializeGraphics();
// Start the message loop and show the form
Application.Run(editor);
}
If you have been following the XNA Engine tutorials on this site, you probably have your own SetupScene function already. Notice that I am passing in my HMSceneManager member from the GameView class. This is different from the code in the HMDemo.cs, I don't think it really matters, whatever suits your application.
My SetupScene loads 100 teapots and randomly distributes them throughout the Octree. This is just for testing at this stage, as the purpose of the scene editor is to actually have some control over object placement. However, in the long run you might want to load some objects in setup scene that are helpful to the user, e.g. a grid showing the axes of the coordinate system.
Once you have setup your scene, you must call InitializeGraphics() from the HMGameView class to actually load the objects into the content pipeline for later rendering by the timer. (The Hazy Mind Scene Editor returns a boolean from the InitializeGraphics call to indicate success prior to drawing the form. I will leave that as an exercise for the reader).
This is my SetupScene that I use for testing:
private static void SetupScene(HMSceneGraph scene)
{
HMShader shader = new HMShader(@"Content/Shaders/BasicShader");
HMShaderManager.AddShader(shader, "BS");
// Enable drawing octrees (demo/debugging use only)
HMOctreeManager.DrawOctnodes = true;
HMModel[] models = new HMModel[100];
int i = 0;
for (i = 0; i < models.Length; i++)
{
models[i] = new HMModel(@"Content/Models/teapot");
models[i].SetShader("BS");
}
float offset = 100.0f / 2.0f;
Random r = new Random();
float scale = 1.0f;
for (i = 0; i < models.Length; i++)
{
// Move the model to a position within the bounds of the octree
models[i].Position = new Vector3(r.Next(Convert.ToInt32(-offset), Convert.ToInt32(offset)),
r.Next(Convert.ToInt32(-offset), Convert.ToInt32(offset)),
r.Next(Convert.ToInt32(-offset), Convert.ToInt32(offset)));
// Make the object up to 4 times bigger
scale = r.Next(1, 5);
models[i].Scaling = new Vector3(scale, scale, scale);
scene.AddObject(models[i]);
HMOctreeManager.OctRoot.AddObject(models[i]);
}
}
Note that an object must be added both to HMSceneGraph and to the OctRoot. This code could possibly be restructured, so that calling OctRoot.AddObject automatically added the object to the scene also. I am just learning this engine, so there could be undesired effects of doing that. If you forget to call both, you will get a NullReferenceException when you call HMOctreeManager.OctRoot.Draw(m_graphicsDeviceService.GraphicsDevice);
I think that is about it. Read Michael's tutorials on the Hazy Mind Scene Editor
http://www.thehazymind.com/SceneEditor.htm for some instructions on handling user input e.g. mouse movement.
As this is my first post, I must say what a huge help this community has been for me with learning XNA development. There are many websites out there that are cut and paste articles from other sites, generally poor information. Also, many books I read do not reach the quality of information that I have read here. Keep up the good work!