martedì 31 gennaio 2012

Tutorial Direct X C#

Avevo iniziato un po' per gioco, un po' per passare del buon tempo con del buon codice, e così mi sono imbattutto nell'idea di voler approfondire e così è stato.

Ho trovato qualcosa di interessante su web, che di contro ho provato leggere per imparare a utilizzare la tecnologia e in breve mi sono reso conto di quanto stavo realizzando.

Quindi pubblico in toto un visualizzatore 3d di superfici.

Lo scopo è quello di leggere un immagine a colori, creare il corrispettivo in bianco e nero, e basandomi sulle scale di grigio ottenere le altezze, ammetto che un codice simile su web si trova già bello fatto ma non tutti spiegano approfondendo l'argomento.

Per prima cosa ci servono due progetti all'interno di un unica soluzione:

La prima l'ho chiamata xa3dViewer.ToolsLibrary

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;

namespace xa3dViewer.ToolsLibrary
{
public class ImageHelpers
{
#region Private Fields
private int _width;
private int _height;
private string _fileName;
private string _fileRampName;
#endregion

#region Public Properties
public int Width
{
get { return _width; }
}

public int Height
{
get { return _height; }
}

public string FileName
{
get { return _fileName; }
}

public string FileRampName
{
get { return _fileRampName; }
}

#endregion

#region builders
/// <summary>
/// costruttore vuoto
/// </summary>
public ImageHelpers()
{

}
/// <summary>
/// Costruttore parametrico con nome file
/// Da utilizzare quando la classe deve fornire informazioni
/// su tutto il file
/// </summary>
/// <param name="bmpFileName">percorso del file</param>
public ImageHelpers(string bmpFileName)
{
_fileName = bmpFileName;
Bitmap gr = new Bitmap(Bitmap.FromFile(_fileName));

_width = gr.Width;
_height = gr.Height;

gr = null;
}

#endregion

#region MakeGraysacleRamp
public void MakeGrayScaleRamp()
{
MakeGrayScaleRamp(_fileName);
}

/// <summary>
/// Crea la mappa di grigi dell'immagine
/// Crea la mappa delle altezze in base alla profondita del grigio.
/// </summary>
/// <param name="bmpFileName">Nome del file da Convertire</param>
public void MakeGrayScaleRamp(string bmpFileName)
{

Bitmap gr;
if (!File.Exists(bmpFileName + "_rmp.jpg"))
{
gr = MakeGrayscale(bmpFileName);
}
else
{
gr = new Bitmap(Bitmap.FromFile(bmpFileName + "_rmp.jpg"));
}

TextWriter tw = new StreamWriter(bmpFileName + "_rmp.txt");

for (int i = 0; i < gr.Height; i++)
{
for (int j = 0; j < gr.Width; j++)
{
Color originalColor = gr.GetPixel(j, i);

tw.WriteLine(i.ToString() + " " + j.ToString() + " " + originalColor.R.ToString());
}
}
tw.Close();

_fileRampName = bmpFileName + "_rmp.txt";

gr.Save(bmpFileName + "_rmp.jpg");
}
#endregion

#region MakeGraysacle

/// <summary>
/// Crea la mappa di grigi dell'immagine
/// </summary>
/// <param name="fileName">Nome file da analizzare</param>
/// <returns>immagine con mappa di grigi</returns>
public Bitmap MakeGrayscale(string fileName)
{
Bitmap original = new Bitmap(Bitmap.FromFile(fileName));

return MakeGrayscale(original);
}

/// <summary>
/// Crea la mappa di grigi dell'immagine
/// </summary>
/// <param name="original">Immagine Originale</param>
/// <returns>immagine con mappa di grigi</returns>
public Bitmap MakeGrayscale(Bitmap original)
{
Bitmap newBitmap = new Bitmap(original.Width, original.Height);

for (int i = 0; i < original.Width; i++)
{
for (int j = 0; j < original.Height; j++)
{

Color originalColor = original.GetPixel(i, j);

int grayScale = (int)((originalColor.R * .3) + (originalColor.G * .59)
+ (originalColor.B * .11));

Color newColor = Color.FromArgb(grayScale, grayScale, grayScale);

newBitmap.SetPixel(i, j, newColor);
}
}

return newBitmap;
}
#endregion
}
}


Questa classe Rappresenta un immagine e quanto serve per realizzare la mappa di grigi e il file delle altezze. Sembra semplice, ed il codice è abbastanza leggibile. Di concetto altro non fa che prelevare pixel per pixel l'immagine e convertirla su scale di grigio.
L'altezza è salvata proprio sulla scala di grigio ottenuta ( 0-256 ) un to do potrebbe essere estendere l'altezza con un fattore di crescita proporzionale alla dimensione dell'immagine.

public class dxVertex
{
#region private field
private float _x;
private float _y;
private float _z;
#endregion

#region public properties
public float x
{
get { return _x; }
set { _x = value; }
}

public float y
{
get { return _y; }
set { _y = value; }
}

public float z
{
get { return _z; }
set { _z = value; }
}
#endregion
}


Questa classe serve esclusivamente come mappa dei vertici

public class dxTriangle
{
#region private field
private long _vert0;
private long _vert1;
private long _vert2;
#endregion

#region public properties
public long vert0
{
get { return _vert0; }
set { _vert0 = value; }
}

public long vert1
{
get { return _vert1; }
set { _vert1 = value; }
}

public long vert2
{
get { return _vert2; }
set { _vert2 = value; }
}
#endregion
}


Mentre questa come mappa dei triangoli.

Questo perchè per DIRECT X intende come superficie minima rappresentabile un triangolo, e va da se che geometricamente parlando il triangolo è la superficie più semplice "primitiva" da rappresentare.

Dato che sono un personaggio comodo comodo mi sono creato un controllo in modo da avere un oggetto molto molto portatile.

Tuttavia ammetto che alcune parti di quanto qui menzionato provengon dal web.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectInput;
using xa3dViewer.ToolsLibrary;

namespace xa3dViewer.ToolsLibrary.DX3D
{
public partial class DXSceneRender : UserControl
{
private float angleZ = 0f;
private float angleX = 0f;

private ImageHelpers _ih;

public int[] gridMatrix;
public float[,] heightData;

public IndexBuffer iB = null;
public VertexBuffer vB = null;

public ImageHelpers ih
{
get { return _ih; }
set
{
_ih = value;
if (value != null)
{
CreateDevice();
}
}

}
public Texture texures = null;

public Microsoft.DirectX.DirectInput.Device keybD = null;
public Microsoft.DirectX.Direct3D.Device device = null;



public DXSceneRender()
{
InitializeComponent();

}


private void CreateDevice()
{

createGrid();
LoadHeightData();

this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);


/* device */
PresentParameters presentParams = new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.EnableAutoDepthStencil = true;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;


device = new Microsoft.DirectX.Direct3D.Device(0,
Microsoft.DirectX.Direct3D.DeviceType.Hardware,
this,
CreateFlags.SoftwareVertexProcessing,
presentParams);

device.RenderState.FillMode = FillMode.Solid;
device.RenderState.CullMode = Cull.None;
device.DeviceReset += new EventHandler(this.OnDeviceReset);
this.OnDeviceReset(device, null);

/* vertex buffer */
vB = new VertexBuffer(typeof(CustomVertex.PositionTextured),
ih.Width * ih.Height,
device,
Usage.Dynamic | Usage.WriteOnly,
CustomVertex.PositionTextured.Format,
Pool.Default);

vB.Created += new EventHandler(this.OnVertexBufferCreate);
OnVertexBufferCreate(vB, null);


/* index buffer */
iB = new IndexBuffer(typeof(int),
(ih.Width - 1) * (ih.Height - 1) * 6,
device,
Usage.WriteOnly,
Pool.Default);

iB.Created += new EventHandler(this.OnIndexBufferCreate);
OnIndexBufferCreate(iB, null);

CameraPositioning();
}



private void CameraPositioning()
{
device.Transform.Projection = Matrix.PerspectiveFovLH(
(float)Math.PI / 4, this.Width / this.Height,
1f,
350f
);

device.Transform.View = Matrix.LookAtLH(new Vector3(0, -70, -35),
new Vector3(0, -5, 0),
new Vector3(0, 1, 0));
device.RenderState.Lighting = false;
device.RenderState.CullMode = Cull.None;
}


private void createGrid()
{
if (ih != null)
{
gridMatrix = new int[(ih.Width - 1) * (ih.Height - 1) * 6];
for (int x = 0; x < ih.Width - 1; x++)
{
for (int y = 0; y < ih.Height - 1; y++)
{
gridMatrix[(x + y * (ih.Width - 1)) * 6] = (x + 1) + (y + 1) * ih.Width;
gridMatrix[(x + y * (ih.Width - 1)) * 6 + 1] = (x + 1) + y * ih.Width;
gridMatrix[(x + y * (ih.Width - 1)) * 6 + 2] = x + y * ih.Width;

gridMatrix[(x + y * (ih.Width - 1)) * 6 + 3] = (x + 1) + (y + 1) * ih.Width;
gridMatrix[(x + y * (ih.Width - 1)) * 6 + 4] = x + y * ih.Width;
gridMatrix[(x + y * (ih.Width - 1)) * 6 + 5] = x + (y + 1) * ih.Width;
}
}
}
}

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
if (device != null)
{
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.LightBlue, 1.0f, 0);

CameraPositioning();

device.BeginScene();
device.SetTexture(0, texures);
device.VertexFormat = CustomVertex.PositionTextured.Format;
device.SetStreamSource(0, vB, 0);
device.Indices = iB;
device.Transform.World = Matrix.Translation(
-ih.Width / 2, -ih.Height / 2, 1) * Matrix.RotationZ(angleZ) * Matrix.RotationX(angleX);

device.DrawIndexedPrimitives(
PrimitiveType.TriangleList, 0, 0, ih.Width * ih.Height, 0, gridMatrix.Length / 3);
device.EndScene();
device.Present();

this.Invalidate();

KeyTester();
}

}


private void LoadHeightData()
{

if (ih != null)
{
string line;
string[] point;
heightData = new float[ih.Width, ih.Height];
float z;

System.IO.StreamReader file = new System.IO.StreamReader(ih.FileRampName);
for (int y = ih.Height - 1; y >= 0; y--)
{
for (int x = 0; x < ih.Width; x++)
{
line = file.ReadLine();
point = line.Split(' ');
z = System.Convert.ToSingle(point[2]) / 250;
heightData[x, y] = z;
}
}
file.Close();
}
}

#region Handlers

private void OnDeviceReset(object sender, EventArgs e)
{

if (device.DeviceCaps.TextureFilterCaps.SupportsMinifyAnisotropic)
{
device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
}
else if (device.DeviceCaps.TextureFilterCaps.SupportsMinifyLinear)
{
device.SamplerState[0].MinFilter = TextureFilter.Linear;
}


if (device.DeviceCaps.TextureFilterCaps.SupportsMagnifyAnisotropic)
{
device.SamplerState[0].MagFilter = TextureFilter.Anisotropic;
}
else if (device.DeviceCaps.TextureFilterCaps.SupportsMagnifyLinear)
{
device.SamplerState[0].MagFilter = TextureFilter.Linear;
}

}


private void OnIndexBufferCreate(object sender, EventArgs e)
{
IndexBuffer buffer = (IndexBuffer)sender;
iB.SetData(gridMatrix, 0, LockFlags.None);
}


private void OnVertexBufferCreate(object sender, EventArgs e)
{
float u, v;

if (ih != null)
{
texures = new Texture(device, new Bitmap(ih.FileName), Usage.Dynamic, Pool.Default);

VertexBuffer buffer = (VertexBuffer)sender;
CustomVertex.PositionTextured[] verts = new CustomVertex.PositionTextured[ih.Width * ih.Height];


for (int x = ih.Width - 1; x >= 0; x--)
{
for (int y = ih.Height - 1; y >= 0; y--)
{
u = System.Convert.ToSingle(x) / System.Convert.ToSingle(ih.Width - 1);
v = System.Convert.ToSingle((ih.Height - 1) - y) / System.Convert.ToSingle(ih.Height - 1);

verts[x + y * ih.Width] = new CustomVertex.PositionTextured(x, y, -(heightData[x, y]) * 5, u, v);
}
}

buffer.SetData(verts, 0, LockFlags.None);
}
}

public void OnKeyPress(object sender, KeyPressEventArgs e)
{
KeyTester();
}

private void KeyTester()
{
KeyboardState keys = keybD.GetCurrentKeyboardState();
if (keys[Key.LeftArrow])
{
angleZ += 0.06f;
}
if (keys[Key.RightArrow])
{
angleZ -= 0.06f;
}
if (keys[Key.UpArrow])
{
angleX += 0.03f;
}
if (keys[Key.DownArrow])
{
angleX -= 0.03f;
}
if (keys[Key.W])
{
device.RenderState.FillMode = FillMode.WireFrame;
}
if (keys[Key.S])
{
device.RenderState.FillMode = FillMode.Solid;
}
if (keys[Key.P])
{
device.RenderState.FillMode = FillMode.Point;
device.RenderState.PointSize = 2.0f; // double the point size
}
if (keys[Key.L])
{
device.RenderState.Lighting = true;
}
}

#endregion

}
}


La CreateDevice() serve a creare i parametri di presentazione necessari al device, a inizializzare la griglia, e a caricare il file delle altezze.
Diciamo che si tratta del cuore del controllo stesso, anche se ho scelto forse esagernado di inizializzare tutta la struttura partendo dall'assegnazione dell'immagine. ( scelta discutibile ).

L'evento di OnPaint() esegue fisicamente il rendering della scena, partendo dal device.

Funziona ?
si con :
framework 3.5
x86
E questa versione di DX
Windows\Microsoft.NET\DirectX for Managed Code\1.0.2902.0

Buon divertimento.

Un consiglio dato che si tratta di un codice d'esempio non utilizzate immagini enermi perchè non è troppo performante.
Lo scopo è quello di comprendere come funziona Direct X senza necessariamente sfruttare al meglio le prestazioni del pc...

venerdì 27 gennaio 2012

How To: Lambda Union

Giusto perchè è una cosa che capita sporadicamente e giusto perchè non sono più giovane come un tempo, mi pubblico questo semplice ma pur utile brano di codice.

public class Entity
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
}

public class EntityListA : List<Entity>
{

}

public class EntityListB : List<Entity>
{

}

class Program
{
static void Main(string[] args)
{
EntityListA aL = new EntityListA();
EntityListB bL = new EntityListB();

var es1= aL.Where(a => a.id > 1).Union (bL.Where(b => b.id > 3));

var es2_1 = aL.Where(a => a.id > 1);
var es2_2 = bL.Where(b => b.id > 3).Union(es2_1);

Console.ReadLine();
}
}


L'esempio come al soliti riporta sempre una condizione minima minima, ed in questo caso funziona come "ricordati .. si fa così "...

lunedì 23 gennaio 2012

MVC .. Contrlli e dati e presentazione.

Ho scritto quest'esempio perchè a fronte di un applicazione scritta da un amico, mi è stato chiesto un consiglio su come deve essere gestito il codice in caso di controlli.

La mia idea è premetto si tratta della mia idea, e che il form debba essere stupido, e quasi del tutto capace d'intendere e volere.

A tal proposito deve essere il controllo ad avere un filo di cervello in più. Tutto naturalmente dipende da che cosa dobbiamo realizzare e come lo dobbiamo fare.

Chiaramente questo codice è un esempio, anche se funziona.. e potrebbe non essere sempre contestualizzato.



Di contro questo è quanto è emerso ossia:

File Data Provider ( si occupa di leggere e scrivere un file ) :
namespace FileDataProvider
{
public class FileDP : TestSimoneMVS.Commons.IDataProvider
{
public List<string> Open(string objectToOpen)
{
return File.ReadAllLines(objectToOpen).ToList();
}

public void Save(string objectToSave, List<string> obj)
{
File.WriteAllLines(objectToSave, obj.ToArray());
}
}
}


Commons Espone un interfaccia semplice semplice

namespace TestSimoneMVS.Commons
{
public interface IDataProvider
{
List<string> Open(string objectToOpen);
void Save(string objectToSave, List<string> obj);
}
}


Il controllo fa quanto è necessario per gestire il caricamento ..

namespace TextEditor
{
public partial class ucTextEditor : UserControl
{
private string _fileName = "";

private TestSimoneMVS.Commons.IDataProvider idf =
new FileDataProvider.FileDP();

public string CurrentFile
{
get { return txtEditor.Text; }
set { txtEditor.Text = value; }
}

public string FileName
{
get
{
return _fileName;
}
set
{
_fileName = value;
txtEditor.Text = String.Join(
string.Empty,
idf.Open(_fileName).ToArray()
);
}
}

public ucTextEditor()
{
InitializeComponent();
}

}
}


E nell'applicazione principale ...

private void mnuOpen_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
DialogResult dr = ofd.ShowDialog();
if (dr == DialogResult.OK)
{
ucTextEditor1.FileName = ofd.FileName;
}
ofd = null;
}


Il minimo indispensabile...

Tuttavia c'e' da fare qualche considerazione...

Per come è stata strutturata questa soluzione, ogni elemento risulta indipendente,
e di contro tutto può funzionare anche se se interviene su ogni singolo elemento.

Che sia efficace ? Per questo esempio forse no.. ma contestualizzato su soluzioni più grandi si ha il beneficio che un architettura può dare.