tiistaina 7. huhtikuuta 2009

Blogi vaihtuu

Tämä blogi vaihtuu uuteen. Uusi blogi löytyy osoitteesta

http://www.verajankorva.com/openblog/

Tämän blogin artikkelit löytyvät myös uudelta blogilta.

maanantaina 6. huhtikuuta 2009

Lisää custom dataa .NET ListViewItem olioon.

Usein tulee vastaan tilanne, että ListViewItem tai TreeViewItem pitäisi sisältää dataa, joka ei näy itse komponentissa esim. jokin tunnisteguid.

Oman tiedon kirjoittaminen ListViewItemiin ei ole lainkaan vaikeaa. Näin se käy.

Luodaan luokka, joka perii ListViewItem luokan.

public class MyListViewItem : ListViewItem
{
private Guid m_guid;

public MyListViewItem()
{
}

public Guid Guid
{
set
{
m_guid = value;
}

get
{
return m_guid;
}
}
}

Ja nyt voit käyttää luokkaasi ihan normaalisti.

MyListViewItem mlvi = MyListViewItem();
mlvi.Text = "TEST TEXT";
mlvi.Guid = Guid.NewGuid();

this.listView1.Items.Add( mlvi );

Ja kun nyt haluat päästä käsiksi omaan dataasi.

MyListViewItem mlvi = (MyListViewItem)this.listView1.Items[0];
Console.Out.WriteLine( mlvi.Guid.ToString() );

Siinä se. Helppoa, nopeaa ja käytännöllistä.

maanantaina 30. maaliskuuta 2009

Scanline rendaaja Flashille

Pitkään on pitänyt tätä kokeilla ja loputakin löysin aikaa tämän tekemiseen.

Katso esimerkki

Eli scanline rendaaja Flash 10:lle. Tärkeintä tässä on se, että enginessä on oikea zbuffer. Eli joka pikselillä on z arvo jota vasten voidaan tarkistaa mikä pikseli on minkäkin pikselin etu- vai takapuolella.
Tavallisesti Flash 3d-enginet eivät tee näin, koska polygonien rasterointi on flashilla hidasta, puhumattakaa pikselin piirtämisestä. Kuitenkin nykyisin alkaa vaikuttaa siltä, että pikselin piirto alkaa olla sen verran nopeaa, että jonkinlaiseen käyttöön sitä voisi jo käyttää. Edelleen olisi nopeampaa tehdä, kuten Papervision, Sandy3d tai Away3d eli käyttää Flashin omaa polygonin piirtoapia ja sorttaa polyt esim zsortilla. Ongelma kuitenkin on, että polyt voivat joissain tilanteissa piirtyä väärässä järjestyksessä, koska z arvot lasketaan vain polyn keskipisteen mukaan, jolloin tulos ei ole aina oikein. Lisäksi zsort ei voi piirtää leikkaavia polygoneja okein. Pikselipohjainen zbuffer on melkoisesti hitaampi, mutta polyt piirtyy aina oikein myös silloin jos polyt leikaavat toisiaan.

Lähdekoodit

perjantaina 28. marraskuuta 2008

XNA

Microsoft XNA on ilmainen "peliohjelmointikirjasto", jonka tarkoitus on olla helppokäyttöinen Direct X rajapinnan päälle rakennettu Framework. XNA ei kuitenkaan eroa kovinkaan paljoa Direct X:stä. Se on kuitenkin selkeämpi ja siitä on poistettu epäolennaisia osia. XNA ohjelmia voidaan ohjelmoida .NET kielillä ja tässä käytetämme C# kieltä, mutta voisit käyttää muitakin .NET kieliä oman valintasi mukaan.

XNA käytännössä korvaa Managed Direct X:n.

XNA on melko pieni paketti, jonka voi ladata täältä http://creators.xna.com/en-US/downloads

Tarvitset kuitenkin myös DirectX SDK:n ja Visual Studion tai Visual C# 2008 Express Edition.

Kolmio

Värillinen kolmio on melkein "Hello world" ohjelma, kun aloitetaan opiskelemaan 3d-grafiikkaohjelmointia. Tällä kertaa aionkin esitellä miten kolmio luodaan XNA:lla.

Käynnistä Visual C# ja luo uusi XNA projekti. XNA projekti on hämäävästi nimetty Windows Game ja XBox360 Game.



Tämä luo sinulle projektin, jossa on valmiiksi tehtynä pari perusjuttua peliä tehdessä, kuten ikkuna ja pääsilmukka. Nämä mm. ovat asioita, jotka teet aina likimain samalla tavalla. On siis varsin mukavaa, että XNA projekti tekee nämä valmiiksi.

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion

namespace BlogExample01
{

public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
ContentManager content;


public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}

protected override void Initialize()
{
base.Initialize();
}

protected override void LoadGraphicsContent(
bool loadAllContent)
{
if (loadAllContent)
{
}
}

protected override void UnloadGraphicsContent(
bool unloadAllContent)
{
if (unloadAllContent)
{
content.Unload();
}
}

protected override void Update(GameTime gameTime)
{
base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

base.Draw(gameTime);
}
}
}


Jos käännät koodin saat eteesi ikkunan sinisellä pohjalla.

Haluamme kuitenkin sen kolmion ruudulle, joten meidän pitää kertoa millaisista pisteistä kolmio koostuu. Näitä pisteitä sanotaan vertekseiksi. XNA:ssa verteksit voivat sisältää paljonkin tietoa. Se mitä tietoa verteksillä on kerrotaan verteksiformaatissa. Näitä formaatteja voi tehdä itse tai käyttää XNA:n valmiita formaatteja, kuten tässä.
Käytämme verteksiformaattia VertexPositionColor, johon voidaan siis kirjoittaa paikka- ja väridata. Luodaan siis luokalle uusi member muuttuja (kaamea suomennos "jäsenmuuttuja") private VertexPositionColor[] m_vertices.

...
{

public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
ContentManager content;

private VertexPositionColor[] m_vertices;

public Game1()
{
...

Seuraavaksi luodaan taulukkoon ne verteksit.

...
protected override void LoadGraphicsContent(
bool loadAllContent)
{
if (loadAllContent)
{
m_vertices = new VertexPositionColor[3];

m_vertices[0].Position = new Vector3(
-0.5f, -0.5f, 0f);
m_vertices[0].Color = Color.Red;
m_vertices[1].Position = new Vector3(
0, 0.5f, 0f);
m_vertices[1].Color = Color.Green;
m_vertices[2].Position = new Vector3(
0.5f, -0.5f, 0f);
m_vertices[2].Color = Color.Yellow;

}
}
...

Nyt meillä on verteksit, joilla voidaan piirtää kolmio, mutta näytönohjaimelle on kerrottava millaista vertexdataa on tulossa ja XNA:ssa se tehdään luomalla VertexDeclaration olio. VertexDeclaration tarvitsee myös pääsyn näytönohjaimeen, joka onnistuu XNA:n GraphicsDevice luokan kautta. Meidän pitää siis luoda oliot VertexDeclaration ja GraphicsDevice.

...
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager m_graphics;
ContentManager m_content;
GraphicsDevice m_device;

private VertexPositionColor[] m_vertices;
private VertexDeclaration m_vd;

public Game1()
{
...

...
protected override void LoadGraphicsContent(
bool loadAllContent)
{
if (loadAllContent)
{
m_device = m_graphics.GraphicsDevice;

m_vertices = new VertexPositionColor[3];

m_vertices[0].Position = new Vector3(
-0.5f, -0.5f, 0f);
m_vertices[0].Color = Color.Red;
m_vertices[1].Position = new Vector3(
0, 0.5f, 0f);
m_vertices[1].Color = Color.Green;
m_vertices[2].Position = new Vector3(
0.5f, -0.5f, 0f);
m_vertices[2].Color = Color.Yellow;

m_vd = new VertexDeclaration(m_device, VertexPositionColor.VertexElements);
...

Emme ole vieläkään valmiit piirtämään mitään :(, mutta olemme jo puolivälissä :).
XNA piirtää kaiken kaman ruudulle shaderien kautta. Eli meidän kirjoitettava Vertex- ja PixelShader voidaksemme piirtää jotain. Niiden kirjoittaminen on kuitenkin oma asiansa ja voimme tässä vaiheessa aivan hyvin tyytyä käyttämään jotain valmista shaderia ja murehtia niiden tekemisestä sitten joskus myöhemmin. Halutessasi voit lukea shadereistä lisää Direct X SDK:n dokuista tai vaikka Riemersin tutoriaaleista, joista meidän käyttämämme shader on otettu linkki. Tässä vaiheessa on kuitenkin tarpeen tietää miten shadereitä käytetään.
Shadereitä hallitaan Effect luokan kautta. Eli luomme Effect olion, johon ladataan HLSL tiedosto. Itse lataaminen suoritetaan Content luokan avulla, jonka XNA projekti on jo valmiiksi alustanut ohjelman käytettäväksi.
Shader tiedosto on normaali tekstitiedosto, jonka pääte tavallisesti on .fx. Tässä ohjeessa käytän simpleshader.fx shaderia. Se on äärimmäisen yksinkertainen shaderi, mutta se riittää meidän tarpeisiimme.
Lisää shader tiedosto projektiisi ja lataa shader Content luokan avulla käyttäen Visual C#:pin shaderille antamaa Asset Name nimeä. Se on yleensä tiedoston nimi ilman päätettä.

...
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager m_graphics;
ContentManager m_content;
GraphicsDevice m_device;

private Effect m_effect;
private VertexPositionColor[] m_vertices;
private VertexDeclaration m_vd;
...

...
protected override void LoadGraphicsContent(
bool loadAllContent)
{
if (loadAllContent)
{
m_device = m_graphics.GraphicsDevice;
m_effect = m_content.Load("simpleshader");

m_vertices = new VertexPositionColor[3];
...


Nyt meillä on kolmion verteksit, verteksiformaatti ja shaderi, jolla piirtää. Mitä vielä tarvitaan? Matriisi tai oikeastaan matriisit. Matriisi on taulukko numeroita, joiden selittäminen ei ole tässä juuri nyt tarpeen. Oikeastaan ne ovat melkoisen yksinkertaisia ja selkiintyvät kuin itsestään. Voimme kuitenkin käyttää niitä aivan hyvin vaikka emme niiden toimintaa ymmärtäisikään. Tarvitsemme kaksi matriisia. Näkymämatriisin ja projektiomatriisin. Yleensä tarvitaan matriiseja enemmänkin, mutta juuri nyt nuo kaksi riittävät. Näkymämatriisi on matriisi, joka kertoo XNA:lle missä ja missä asennossa katsojan silmä, eli kamera, on. Projektiomatriisi kertoo millainen kamera. Eli minkälainen FOV, lähi -ja kaukoleikkaus ja millainen aspect ratio näkymässä on.
Tarvitsemme kaksi uutta memberiä luokkaamme.

...
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager m_graphics;
ContentManager m_content;
GraphicsDevice m_device;

private Effect m_effect;
private VertexPositionColor[] m_vertices;
private VertexDeclaration m_vd;

private Matrix m_matView;
private Matrix m_matProj;


public Game1()
...

Ja asetamme näille asetukset, jotka sopivat ko. tilanteeseen.

...
protected override void LoadGraphicsContent(
bool loadAllContent)
{
if (loadAllContent)
{
m_device = m_graphics.GraphicsDevice;
m_effect = m_content.Load("simpleshader");

m_vertices = new VertexPositionColor[3];

m_vertices[0].Position = new Vector3(
-0.5f, -0.5f, 0f);
m_vertices[0].Color = Color.Red;
m_vertices[1].Position = new Vector3(
0, 0.5f, 0f);
m_vertices[1].Color = Color.Green;
m_vertices[2].Position = new Vector3(
0.5f, -0.5f, 0f);
m_vertices[2].Color = Color.Yellow;

m_vd = new VertexDeclaration(
m_device,
VertexPositionColor.VertexElements);

m_matView = Matrix.CreateLookAt(new Vector3(
0.0f, 0.0f, 2.0f),
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(0.0f, 1.0f, 0.0f));
m_matProj = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4,
(float)m_device.Viewport.Width /
(float)m_device.Viewport.Height,
1.0f, 100.0f);

}
}
...

Ja lopultakin olemme valmiit piirtämään!

...
protected override void Draw(GameTime gameTime)
{
m_graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

m_device.RenderState.CullMode = CullMode.None;
m_device.RenderState.FillMode = FillMode.Solid;

...
Asetetaan piilopintojen poisto pois päältä, jotta kolmio näkyy vaikka se olisi "väärin päin" ja asetetaan kolmion piirto fill moodiin. Eli kolmiot täytetään eikä piirretä niistä vain reunoja tai kärkipisteitä.

...
m_effect.CurrentTechnique = m_effect.Techniques["Simplest"];
m_effect.Parameters["xViewProjection"].SetValue(
m_matView * m_matProj);
m_effect.Begin();

...

Otetaan shaderista käyttöön Simplest tekniikka. Eli tämä on shaderiin kirjoitettu efekti, joka voi olla melkin mitä tahansa. Tällä kertaa se on hyvin yksinkertainen värien interpolointi verteksien välillä.
Shader myös sisältää muuttujan, joka on xViewProjection matriisi. Sinun tulee antaa tämä matriisi jotta shader osaa projisoida verteksit oikein.

...
foreach (EffectPass pass in
m_effect.CurrentTechnique.Passes)
{
pass.Begin();

...

Koodi käy kaikki shaderin "passit" läpi. Tässä shaderissa passeja on vain yksi, mutta niitä voi olla enemmänkin.

...
m_device.VertexDeclaration = m_vd;
m_device.DrawUserPrimitives(
PrimitiveType.TriangleList,
m_vertices, 0, 1);

pass.End();
}
m_effect.End();

base.Draw(gameTime);
}
...

Kerrotaan näyttökortille, millaista verteksidataa olemme lähettämässä. Lähetetään polygoni kortille ja lopuksi piirretään näyttökortin muistissa oleva data. Ja tulos näyttää tältä.



Imuroi lähdekoodi

sunnuntaina 16. marraskuuta 2008

C# ja JavaScript

Usein tulee vastaan tilanteita jolloin selainohjelman tekeminen esim. yrityksen sisäiseen käyttöön tuntuu järkevältä. Selaimet ovat mm. erittäin hyviä käsittelemään isoja määriä kuvia. Ne mukavasti lataavat kuvat streamaamalla ja pysyvät interaktiivisina isojenkin kuva määrien kanssa.

Nettiselain on kuitenkin tehty turvalliseksi ja niistä ei ole pääsyä asiakaskoneeseen. Paikallisessa käytössä se on kuitenkin usein toivottua ja tarpaallista.
Internet Explorer kylläkin tarjoaa ActiveX:n kautta pääsyn Windowsiin, mutta siihen on myös toinenkin keino.

C# tai ylipäätään kaikki .NET kielet tarjoavat WebBrowser komponentin, jolla voidaan omaan .NET ohjelmaan upottaa webbiselain ja kirjottaa sen päälle omaa koodia. Kuitenkin on lievästi hankalaa saada HTML sivulta tietoa .NET ohjelmaasi. Usein varmasti haluaisit saada tiedon esim. siitä onko käyttäjä painanut nappia ja mitä asetuksia hän on sivulle laittanut.

Eli voidaksemme käyttää .NET:iä laajentamaan selaimen toimintaa pitää meidän kirjoittaa oma .NET ohjelma. Ohjelman pitää sisältää WebBrowser komponentti, joka löytyy System.Windows.Forms.WebBrowser
http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.aspx

C# koodia

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace WebpageTest
{
public partial class MainForm : Form
{
WebBrowser m_wb = new WebBrowser();

public MainForm()
{
InitializeComponent();

m_wb.DocumentCompleted +=
new WebBrowserDocumentCompletedEventHandler(
this.wb_OnDocumentComplete );
m_wb.ObjectForScripting = new JavaScript();
m_wb.Dock = DockStyle.Fill;
this.Controls.Add( m_wb );

try
{
m_wb.Url = new Uri( Application.StartupPath +
"/example.html");
}
catch ( Exception e )
{
MessageBox.Show ( e.Message );
}
}

public void wb_OnDocumentComplete(object sender,
System.EventArgs e)
{
m_wb.Document.InvokeScript ("testDotNet", null );
}
}
}


Ohjelma on yksinkertainen, mutta käydään silti joitain kohtia läpi.
m_wb.DocumentCompleted += 
new WebBrowserDocumentCompletedEventHandler(
this.wb_OnDocumentComplete );

Kun HTML tiedosto on ladattu siitä lähetetään DocumentCompleted tapahtuma. Tämä rivi kertoo ohjelmalle, että haluamme tapahtuman käsiteltävän funktiossa wb_OnDocumentComplete(). Tämä on tärkeää, koska emme halua yrittää kutsua JavaScript funktioita ennen, kuin sivu on ladattu.

m_wb.ObjectForScripting = new JavaScript();

Rivi kertoo WebBrowser komponentille, että mihin olioon internet sivulla oleva scripti voi päästä käsiksi.

m_wb.Document.InvokeScript ("testDotNet", null );

DocumentComplete tapahtuman käsittelevässä funktiossa oleva rivi. Komentaa WebBrowseria ajamaan scriptiä. Tässä tapaauksessa ajamme testDotNet() funktion ilman funktioparametreja.

.NET ohjelma tarvitsee vielä JavaScript olion. Eli olion, joka näkyy nettisivulla olevalle JavaScriptille. Olion nimi voi olla mitä vain, mutta itse käytän nimeä JavaScript.

C# koodia


using System;
using System.Windows.Forms;

namespace WebpageTest
{
[System.Runtime.InteropServices.ComVisible(true)]
public class JavaScript
{
public JavaScript()
{
}

public void testJS( int iId )
{
MessageBox.Show( iId.ToString() );
}
}
}


Luokka on simppeli, jossa on vain yksi methodi testJS( int id ). Tätä methodia voidaan kutsua nettisivulta. Huomaa, että luokan pitää olla näkyvissä COM:lle.
[System.Runtime.InteropServices.ComVisible(true)]


Lopuksi vielä pieni nettisivu, joka osaa käyttää .NET koodia. Huomaa, että sivu on ajettava .NET ohjelmasi kautta, jotta oma koodisi toimii.

HTML koodia


<html>
<head>
<title>.NET example</title>
<style type="text/css">
p.option
{
cursor:pointer;
}
</style>
<script type="text/javascript">
function testDotNet()
{
alert ("Dot net made this function call.");
}

function OnClick(id)
{
window.external.testJS( id )
}
</script>
</head>
<body>
<p class="option" onClick="OnClick(1);">Option 1</p>
<p class="option" onClick="OnClick(2);">Option 2</p>
<p class="option" onClick="OnClick(3);">Option 3</p>
</body>
</html>