I got one in C# after all... If you know a place where I can post binaries,
I would gladly post the "debug" directory of the project.
You will need the Dx SDK and the references Microsoft.DirectX (1.0.290.0)
and Direct3D (1.0.2902.0).
I tried to keep the stuff to a minimum. X axis is coral, Z axis is blue.
Culling is set to none but the triangles are ready with their normal for the
Right Hand (but you will need 'two passes' if you use some culling, in order
to display the 'back' of the 'sheet').
Vanderghast, Access MVP
----------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX; // <------ add
using Microsoft.DirectX.Direct3D; // <------ add
namespace D3Surface
{
public partial class Form1 : Form
{
private Device device = null; // Direct3D 'device'
private int nframe = 0; // counting the number of frames
private Boolean re_init = true;
private IndexBuffer ib = null; // will hold the index
// allowing to built the triangles
// the 'index buffer'
private short[] indices = null; // working var to define ib
private VertexBuffer vb = null; // will hold the vertexes
// of the triangles, the 'vertex buffer'
private CustomVertex.PositionNormalColored[] vert = null;
// working var to define vb
public Form1()
{
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.Opaque, true);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
InitializeGraphics(); // not a D3X definition, but ours.
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer,
System.Drawing.Color.FromArgb(255, 221, 204, 102).ToArgb(),
1.0f, 0);
#region device rendering
device.BeginScene();
{
SetupCamera(); // not a D3X definition, but ours.
PlayScenario(); // not a D3X definition, but ours.
}
device.EndScene();
device.Present();
#endregion
this.Invalidate(); // dirty-loop driving the rendering
}
/// <summary> some curve y=fct(x, z)
/// defined over the normalized interval (-1, -1), (+1, +1)
/// </summary>
/// <param name="x"></param>
/// <param name="z"></param>
/// <returns></returns>
private float MyCurve(float x, float z)
{
return 0.15f*x + 0.5f*(float)Math.Sin(Math.PI * 3* x) +
..2f*(float)Math.Sin(Math.PI * 2.0f * z);
}
/// <summary> Set up a moving camera
/// </summary>
private void SetupCamera()
{
// an an engineer, I use Right Hand rules
device.Transform.Projection = Matrix.PerspectiveFovRH(
(float)Math.PI / 4, this.Width / (float)this.Height,
1.0f, 10.0f);
// move the camera between each rendered frame
// in a circle of radius 3, at a constant height Y=-1.
float X = 3.0f * (float)Math.Cos(nframe / 100.0f);
float Z = 3.0f * (float)Math.Sin(nframe / 100.0f);
device.Transform.View = Matrix.LookAtRH(
new Vector3(X, -1.0f, Z), // camera position
new Vector3(0, 0, 0), // where we look at
new Vector3(0, 1.0f, 0)); // head up direction
++nframe; // update our frame count.
}
/// <summary> Play the scenario, always the same,
/// one object, a predefined simple curve z=f(x, y)
/// over the (normalized) square domain (-1, -1) to (+1, +1).
/// </summary>
private void PlayScenario()
{
DrawXZpositive(System.Drawing.Color.Coral,
System.Drawing.Color.DarkBlue);
device.VertexFormat = CustomVertex.PositionNormalColored.Format;
#region ligths
device.RenderState.Lighting = true;
device.RenderState.Ambient = System.Drawing.Color.FromArgb(255,
127, 127, 127);
device.RenderState.AmbientMaterialSource = ColorSource.Color1;
// since we have no material, use the color of the
vertex
device.RenderState.DiffuseMaterialSource = ColorSource.Color1;
device.Lights[0].Type = LightType.Directional;
device.Lights[0].DiffuseColor = new
ColorValue(-1.0f, -1.0f, -1.0f);
device.Lights[0].Direction = new Vector3(-1.0f, -2.0f, 0.5f);
device.Lights[0].Enabled = true;
#endregion
device.RenderState.CullMode = Cull.None;
device.SetStreamSource(0, vb, 0);
device.Indices = ib;
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
441, 0, 800);
// The magical constants in this call come from this:
// we define a grid of 20 * 20 rectangles (from (-1,-1) to
(+1,+1))
// which makes 21 points by 21 points = 441 points, or
vertices.
// Each rectangle, or quad, is rendered with two triangles,
// 20 * 20 * 2 = 800 triangles.
// The method allows us to 'skip' some node, we won't, we start
// from the beginning of our data for each drawing.
}
/// <summary> Set the device, if required, and
/// pre-compute the data, before starting any rendering.
/// </summary>
private void InitializeGraphics()
{
if (null == device)
{
PresentParameters pp = new PresentParameters();
pp.Windowed = true;
pp.SwapEffect = SwapEffect.Discard;
pp.AutoDepthStencilFormat = DepthFormat.D16;
pp.EnableAutoDepthStencil = true;
device = new Device(0, // first adapter
DeviceType.Hardware,
this, // this window
CreateFlags.SoftwareVertexProcessing,
pp);
}
if (re_init)
{
re_init = false;
// technically, we should use a mesh, but to avoid
// to have to define texture, we just use a
// vertex buffer and an index buffer.
#region compute the (x, y, z) values
// The vertex buffer contains the ... vertexes... of
// each triangle to be rendered. A vertex is defined
// by a (x, y, z) value, a color, and a NORMAL to
// the surface of the ... triangle. But since a given vertex
// may be part of many triangles, ... more about it later
// we make a grid (x, z) of 21 by 21 points.
float[] x = new float[21];
float[] z = new float[21];
for (int i = 0; i < 21; i++)
{
x
= (float)(-1 + i / 10m);
z = x;
}
vert = new CustomVertex.PositionNormalColored[441];
// a grid of 21 x values,by 21 z values = 441 points
Boolean raiseConcern = false;
int k = 0;
for (int i = 0; i < 21; i++)
{
for (int j = 0; j < 21; j++)
{
float y = MyCurve(x, z[j]);
if (Math.Abs(y) >= 3) raiseConcern = true;
vert[k++] = new CustomVertex.PositionNormalColored(
new Vector3(x, y, z[j]),
new Vector3(0, 1, 0), // will be overwritten
Color.DarkOliveGreen.ToArgb());
}
}
if (raiseConcern)
System.Windows.Forms.MessageBox.Show("The curve may
appear out limit, or nothing could be viewable...");
// hint: have I forgot to normalize my curve definition?
// Remember the camera runs on a circle of
radius 3,
// centered on (0, -1, 0), always
looking
// at the origin.
#endregion
#region fill the index buffer
// Each of the quad, in the grid we just have defined,
// can be split into 2 triangles.
// The index buffer is a collection of multiple set of
// 3 integers: each set of 3 integers define the indexes
// in our vertexes array
// to be used in order to define one triangle.
indices = new short[2400]; // 20*20 quads * 2 = 800
triangles
// 800 triangle * 3 vertex per triangle = 2400
k = 0;
for (short strip = 0; strip < 20; strip++)
{
short baseStrip = (short)(strip * 21);
short topStrip = (short)(baseStrip + 21);
for (short j = 0; j < 20; j++)
{
// 2 triangles per quads. The first one:
indices[k++] = (short)(baseStrip+j);
indices[k++] = (short)(topStrip + j);
indices[k++] = (short)(baseStrip + j + 1);
// the second one:
indices[k++] = (short)(baseStrip + j + 1);
indices[k++] = (short)(topStrip + j);
indices[k++] = (short)(topStrip + j + 1);
}
}
ib = new IndexBuffer(typeof(short),
indices.Length,
device,
Usage.WriteOnly,
Pool.Default);
ib.Created += new EventHandler(this.OnIndexBufferCreated);
OnIndexBufferCreated(ib, null);
#endregion
Vector3[] wn = new Vector3[800]; // the weighted normal of
800 triangles
#region compute the normal to each triangle
// Now that we can 'draw' each triangle, time to compute
// the normal of each triangle, the perpendicular to the
// triangle, in space.
// Since there is two possible directions,
// we choose one, I use the righ hand, RH, rule.
// The indexex were defined with that in mind, so
// all triangles are RH 'ready'.
// Math tell us that the normal of a triangle can be
// computed using a Vector3 cross product.
// url: http://mathworld.wolfram.com/CrossProduct.html
// the following expression compute a vector in the normal
// direction, but with an intensity equal to twice
// the area of the triangle
k = 0;
for (int i = 0; i < 2400; i += 3)
{
Vector3 a = vert[indices].Position;
Vector3 b = vert[indices[i + 1]].Position;
Vector3 c = vert[indices[i + 2]].Position;
wn[k++] = Vector3.Cross(b - a, c - a);
}
#endregion
#region move the normal to the vertexes
// That normal should be given to each vertex defining
// a triangle. But to add 'smootness' all triangles
// sharing the same (x, y, z) will also have to share
// the same vertex. So, for all the possible normal
// to be assigned to a vertex, we will simply average
// them, in proportion of the area of the triangle
// that produced them.
// we will first add the weighted normal from each triangle
// the vertex belong to
Vector3[] cumul = new Vector3[441]; // 441 vertexes
k = 0;
for (int i = 0; i < 2400; i += 3)
{
cumul[indices] += wn[k];
cumul[indices[i + 1]] += wn[k];
cumul[indices[i + 2]] += wn[k];
k++;
}
// and next, we will normalize each
// normal (make its module, intensity, =1)
for (int i = 0; i < 441; i++)
{
if (cumul.Length() != 0)
{
Vector3 nn = Vector3.Normalize(cumul);
vert.Nx = nn.X;
vert.Ny = nn.Y;
vert.Nz = nn.Z;
}
else
{
System.Diagnostics.Trace.WriteLine(String.Format("Vertex
{0} has no normal", i));
}
}
#endregion
#region fill the vertex buffer
vb = new
VertexBuffer(typeof(CustomVertex.PositionNormalColored),
441, // 441 vertexes
device,
Usage.Dynamic | Usage.WriteOnly,
CustomVertex.PositionNormalColored.Format,
Pool.Default);
vb.Created += new EventHandler(this.OnVertexBufferCreate);
OnVertexBufferCreate(vb, null);
#endregion
}
}
private void DrawXZpositive(System.Drawing.Color colorX,
System.Drawing.Color colorZ)
{
device.RenderState.Lighting = false;
CustomVertex.PositionColored[] TwoPoints = new
CustomVertex.PositionColored[2];
device.VertexFormat = CustomVertex.PositionColored.Format;
device.Transform.World = Matrix.Identity;
TwoPoints[0] = new CustomVertex.PositionColored(new Vector3(0,
0, 0), colorX.ToArgb());
TwoPoints[1] = new CustomVertex.PositionColored(new Vector3(2,
0, 0), colorX.ToArgb());
device.DrawUserPrimitives(PrimitiveType.LineList, 1, TwoPoints);
TwoPoints[0] = new CustomVertex.PositionColored(new Vector3(0,
0, 0), colorZ.ToArgb());
TwoPoints[1] = new CustomVertex.PositionColored(new Vector3(0,
0, 2), colorZ.ToArgb());
device.DrawUserPrimitives(PrimitiveType.LineList, 1, TwoPoints);
device.VertexFormat = CustomVertex.PositionNormalColored.Format;
device.SetStreamSource(0, vb, 0);
device.RenderState.Lighting = true;
}
/// <summary> To handle resizing of our vertex buffer and other
cases...
/// </summary>
/// <param name="sender">the vertex buffer</param>
/// <param name="e">null</param>
private void OnVertexBufferCreate(object sender, EventArgs e)
{
VertexBuffer buffer = (VertexBuffer)sender;
buffer.SetData(vert, 0, LockFlags.None);
}
/// <summary> To handle cases where the index buffer is
re-created...
/// </summary>
/// <param name="sender">the index buffer</param>
/// <param name="e">null</param>
private void OnIndexBufferCreated(object sender, EventArgs e)
{
IndexBuffer buffer = (IndexBuffer)sender;
buffer.SetData(indices, 0, LockFlags.None);
}
}
}