I am working in OMR project and we are using C#. When we come to scan the answer sheets, the images are skewed. How can we deskew them?
VB.Net Code for this is available here, however since you asked for C# here is a C# translation of their Deskew class (note: Binarize (strictly not necessary, but works much better) and Rotate are exercises left to the user).
public class Deskew
{
// Representation of a line in the image.
private class HougLine
{
// Count of points in the line.
public int Count;
// Index in Matrix.
public int Index;
// The line is represented as all x,y that solve y*cos(alpha)-x*sin(alpha)=d
public double Alpha;
}
// The Bitmap
Bitmap _internalBmp;
// The range of angles to search for lines
const double ALPHA_START = -20;
const double ALPHA_STEP = 0.2;
const int STEPS = 40 * 5;
const double STEP = 1;
// Precalculation of sin and cos.
double[] _sinA;
double[] _cosA;
// Range of d
double _min;
int _count;
// Count of points that fit in a line.
int[] _hMatrix;
public Bitmap DeskewImage(Bitmap image, int type, int binarizeThreshold)
{
Size oldSize = image.Size;
_internalBmp = BitmapFunctions.Resize(image, new Size(1000, 1000), true, image.PixelFormat);
Binarize(_internalBmp, binarizeThreshold);
return Rotate(image, GetSkewAngle());
}
// Calculate the skew angle of the image cBmp.
private double GetSkewAngle()
{
// Hough Transformation
Calc();
// Top 20 of the detected lines in the image.
HougLine[] hl = GetTop(20);
// Average angle of the lines
double sum = 0;
int count = 0;
for (int i = 0; i <= 19; i++)
{
sum += hl[i].Alpha;
count += 1;
}
return sum / count;
}
// Calculate the Count lines in the image with most points.
private HougLine[] GetTop(int count)
{
HougLine[] hl = new HougLine[count];
for (int i = 0; i <= count - 1; i++)
{
hl[i] = new HougLine();
}
for (int i = 0; i <= _hMatrix.Length - 1; i++)
{
if (_hMatrix[i] > hl[count - 1].Count)
{
hl[count - 1].Count = _hMatrix[i];
hl[count - 1].Index = i;
int j = count - 1;
while (j > 0 && hl[j].Count > hl[j - 1].Count)
{
HougLine tmp = hl[j];
hl[j] = hl[j - 1];
hl[j - 1] = tmp;
j -= 1;
}
}
}
for (int i = 0; i <= count - 1; i++)
{
int dIndex = hl[i].Index / STEPS;
int alphaIndex = hl[i].Index - dIndex * STEPS;
hl[i].Alpha = GetAlpha(alphaIndex);
//hl[i].D = dIndex + _min;
}
return hl;
}
// Hough Transforamtion:
private void Calc()
{
int hMin = _internalBmp.Height / 4;
int hMax = _internalBmp.Height * 3 / 4;
Init();
for (int y = hMin; y <= hMax; y++)
{
for (int x = 1; x <= _internalBmp.Width - 2; x++)
{
// Only lower edges are considered.
if (IsBlack(x, y))
{
if (!IsBlack(x, y + 1))
{
Calc(x, y);
}
}
}
}
}
// Calculate all lines through the point (x,y).
private void Calc(int x, int y)
{
int alpha;
for (alpha = 0; alpha <= STEPS - 1; alpha++)
{
double d = y * _cosA[alpha] - x * _sinA[alpha];
int calculatedIndex = (int)CalcDIndex(d);
int index = calculatedIndex * STEPS + alpha;
try
{
_hMatrix[index] += 1;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
}
private double CalcDIndex(double d)
{
return Convert.ToInt32(d - _min);
}
private bool IsBlack(int x, int y)
{
Color c = _internalBmp.GetPixel(x, y);
double luminance = (c.R * 0.299) + (c.G * 0.587) + (c.B * 0.114);
return luminance < 140;
}
private void Init()
{
// Precalculation of sin and cos.
_cosA = new double[STEPS];
_sinA = new double[STEPS];
for (int i = 0; i < STEPS; i++)
{
double angle = GetAlpha(i) * Math.PI / 180.0;
_sinA[i] = Math.Sin(angle);
_cosA[i] = Math.Cos(angle);
}
// Range of d:
_min = -_internalBmp.Width;
_count = (int)(2 * (_internalBmp.Width + _internalBmp.Height) / STEP);
_hMatrix = new int[_count * STEPS];
}
private static double GetAlpha(int index)
{
return ALPHA_START + index * ALPHA_STEP;
}
}
Scanned document are always skewed for an average [-10;+10] degrees angle.
It's easy to deskew them using the Hough transform, like Lou Franco said. This transform detects lines on your image for several angles. You just have to select the corresponding one to your document horizontal lines, then rotate it.
try to isolate the pixel corresponding to your document horizontal lines (for instance, black pixels that have a white pixel at their bottom).
Run Hough transform. Do not forget to use 'unsafe' mode in C# to fasten the process of your whole image by using a pointor.
Rotate your document in the opposite angle found.
Works like a charm on binary documents (easily extendable to grey level ones)
Disclaimer: I work at Atalasoft, DotImage Document Imaging can do this with a couple of lines of code.
Deskew is a term of art that describes what you are trying to do. As Ben Voigt said, it's technically rotation, not skew -- however, you will find algorithms under automatic deskew if you search.
The normal way to do this is to do a hough transform to look for the prevalent lines in the image. With normal documents, many of them will be orthogonal to the sides of the paper.
Are you sure it's "skew" rather than "rotation" (rotation preserves angles, skew doesn't).
Use some sort of registration mark (in at least two places) which you can recognize even when rotated.
Find the coordinates of these marks and calculate the rotation angle.
Apply a rotation transformation matrix to the image.
Related
I have a gameobject that occupies the whole screen just for testing purposes. I'm drawing a line btw. What I'm trying to achieve is if the mouse position hits a gameobject it will store the vector2 coordinates in a list. But raycast is not storing all the coordinates. Below is my code
private void Update()
{
if (Input.GetMouseButton(0))
{
Vector2 mousePos = Input.mousePosition;
Vector2 Pos = _camera.ScreenToWorldPoint(mousePos);
if(!mousePositions.Contains(Pos))
mousePositions.Add(Pos);
if (Physics.Raycast(Camera.main.ScreenPointToRay(mousePos), out RaycastHit hit))
{
Vector2 textureCoord = hit.textureCoord;
int pixelX = (int)(textureCoord.x * _templateDirtMask.width);
int pixelY = (int)(textureCoord.y * _templateDirtMask.height);
Vector2Int paintPixelPosition = new Vector2Int(pixelX, pixelY);
if (!linePositions.Contains(paintPixelPosition))
linePositions.Add(paintPixelPosition);
foreach (Vector2Int pos in linePositions)
{
int pixelXOffset = pos.x - (_brush.width / 2);
int pixelYOffset = pos.y - (_brush.height / 2);
for (int x = 0; x < _brush.width; x++)
{
for (int y = 0; y < _brush.height; y++)
{
_templateDirtMask.SetPixel(
pixelXOffset + x,
pixelYOffset + y,
Color.black
);
}
}
}
_templateDirtMask.Apply();
}
}
}
Everytime I checked the element count mousePositions are always greater than linePositions. I don't know what's causing this
the element count mousePositions are always greater than linePosition
well it is quite simple: In
int pixelX = (int)(textureCoord.x * _templateDirtMask.width);
int pixelY = (int)(textureCoord.y * _templateDirtMask.height);
you are casting to int and cut off any decimals after the comma (basically like doing Mathf.FloorToInt).
So you can totally have multiple mouse positions which result in float pixel positions like e.g.
1.2, 1.2
1.4, 1.7
1.02, 1.93
...
all these will map to
Vector2Int paintPixelPosition = new Vector2Int(1, 1);
Besides, you might want to look at some better line drawing algorithms like e.g. this simple one
And then note that calling SetPixel repeatedly is quite expensive. You want to do a single SetPixels call like e.g.
var pixels = _templateDirtMask.GetPixels();
foreach (Vector2Int pos in linePositions)
{
int pixelXOffset = pos.x - (_brush.width / 2);
int pixelYOffset = pos.y - (_brush.height / 2);
for (int x = 0; x < _brush.width; x++)
{
for (int y = 0; y < _brush.height; y++)
{
pixels[(pixelXOffset + x) + (pixelYOffset + y) * _templateDirtMask.width] = Color.black;
}
}
}
_templateDirtMask.SetPixels(pixels);
_templateDirtMask.Apply();
It happens because there is really could be a case, when several elements from mousePositions are associated with one elment from linePositions.
Rough example: your texture resolution is only 1x1px. In this case you linePositons will contain only one element. And this element will be associated with all elements from mosePositions.
So, relation of the number of elements in these lists depends on relation of your texture and screen resolutions.
So, I was trying to come up with my own simple implementation of Perlin/Fractal noise(I'm just adding the noise to itself resampled multiple times).
When it comes to the resampling part of the algorithm, to achieve lower frequency noise I'm using bilinear interpolation on a smaller part of the array to effectively "stretch out" that smaller part (i.e: look at a 16x16 part of a 256x256 sheet of noise, but treat it as if it was a 256x256 array of pixels)
The site I use (well, one of them) for reference is http: //lodev.org/cgtutor/randomnoise.html
From the site the first two pictures is the result I'm looking for but instead i get something like this (I used a 64x64 array):
Original
Resampled using x8 Zoom
All of the code I'm using is in Unity (I know there is a built-in Perlin-generator for it, but I wanted my own):
using UnityEngine;
using System.Collections;
public class NoiseGenerator : MonoBehaviour {
//Dimensions of the array
public int width;
public int height;
//RNG Stuff for reproducability
public bool useTimeAsSeed;
public string seed;
//The array the nopise gets stored in
int[,] noise;
//These are used in the editor to control if we zoon in or not, original image gets resized to 1/ratio
public bool zoom;
public int ratio=1;
//Function used to fill up the array with white noise (random numbers with a uniform distribution between 0-255)
void InitNoise() {
noise = new int[width, height];
if (useTimeAsSeed) {
seed = Time.time.ToString();
}
System.Random r = new System.Random(seed.GetHashCode());
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
noise[x, y] = r.Next(0,256);
}
}
}
//Function to smooth the noise when it's being resampled
/*
AB
CD
|when resampled
v
A--R---B
| X |
| |
| |
C--J---D
Uses bilinear Interpolation to get the wighted average of A B C and D, which are all neighbouring pixels in the original picture
Returns an int value between 0-255
*/
int SmoothNoise(float x, float y) {
float fractX = x - (int)x; //Fractional part of the X-coord, used as the weight for Lerping in the x-direction
float fractY = y - (int)y; //Fractional part of the Y-coord, used as the weight for Lerping in the y-direction
//Calculating the neigbouring pixels
//For edge values the respective "missing pixel" is filled in by the other end of the noise sheet
int x1 = ((int)x + width) % width;
int x2 = (x1 + width - 1) % width;
int y1 = ((int)y + height) % height;
int y2 = (y1 + height - 1) % height;
//Calculating R and J by Lerping the respective pixels
float R = Mathf.Lerp(noise[x1, y1], noise[x2, y1], fractX);
float J = Mathf.Lerp(noise[x2, y1], noise[x2, y2], fractX);
//Finally Lerp R and J to get the value for X
return (int)(Mathf.Lerp(R,J,fractY));
}
void Start() {
InitNoise();
}
//Easy way to display the array as a temporary solution until actual terrain display is implemented
void OnDrawGizmos() {
if (noise != null) {
for (int x = 0; x < width; x++){
for (int y = 0; y < height; y++){
float component = (float)noise[x, y] / 255;
if (zoom) {
//Using the result of SmoothNoise normalised to be between 0-1 we construct a color component to be used when displaying
component = (float)SmoothNoise((float)x/ratio, (float)y/ratio) / 255;
}
Gizmos.color = new Color(component,component,component);
Vector3 pos = new Vector3(-width/2 + x + .5f, 0, -height/2 + y + .5f);
Gizmos.DrawCube(pos, Vector3.one);
}
}
}
}
}
Edit: Rewrote my question after trying a few things and made it more specific.
Hi, so I'm creating a mobile RTS game with procedurally generated maps. I've worked out how to create a terrain with a basic perlin noise on it, and tried to integrate https://gamedev.stackexchange.com/questions/54276/a-simple-method-to-create-island-map-mask method to creating an island procedurally. This is the result so far:
The image below from http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/ shows the kind of terrain I'm after. The tutorial there is great but would be too intensive, thus the post.
I want the Random Shaped island with Perlin noise generated land mass, surrounded by water.
edit: Basic Perlin terrain gen working now =)
Here is my code. A script attached to a null with a button to activate Begin():
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class Gen_Perlin : MonoBehaviour {
public float Tiling = 0.5f;
private bool active = false;
public int mapHeight = 10;
public void Begin()
{
if (active == false) {
TerrainData terrainData = new TerrainData ();
const int size = 513;
terrainData.heightmapResolution = size;
terrainData.size = new Vector3 (2000, mapHeight, 2000);
terrainData.heightmapResolution = 513;
terrainData.baseMapResolution = 1024;
terrainData.SetDetailResolution (1024, 1024);
Terrain.CreateTerrainGameObject (terrainData);
GameObject obj = GameObject.Find ("Terrain");
obj.transform.parent = this.transform;
if (obj.GetComponent<Terrain> ()) {
GenerateHeights (obj.GetComponent<Terrain> (), Tiling);
}
} else {
GameObject obj = GameObject.Find ("Terrain");
if (obj.GetComponent<Terrain> ()) {
GenerateHeights (obj.GetComponent<Terrain> (), Tiling);
}
}
}
public void GenerateHeights(Terrain terrain, float tileSize)
{
Debug.Log ("Start_Height_Gen");
float[,] heights = new float[terrain.terrainData.heightmapWidth, terrain.terrainData.heightmapHeight];
for (int i = 0; i < terrain.terrainData.heightmapWidth; i++)
{
for (int k = 0; k < terrain.terrainData.heightmapHeight; k++)
{
heights[i, k] = 0.25f + Mathf.PerlinNoise(((float)i / (float)terrain.terrainData.heightmapWidth) * tileSize, ((float)k / (float)terrain.terrainData.heightmapHeight) * tileSize);
heights[i, k] *= makeMask( terrain.terrainData.heightmapWidth, terrain.terrainData.heightmapHeight, i, k, heights[i, k] );
}
}
terrain.terrainData.SetHeights(0, 0, heights);
}
public static float makeMask( int width, int height, int posX, int posY, float oldValue ) {
int minVal = ( ( ( height + width ) / 2 ) / 100 * 2 );
int maxVal = ( ( ( height + width ) / 2 ) / 100 * 10 );
if( getDistanceToEdge( posX, posY, width, height ) <= minVal ) {
return 0;
} else if( getDistanceToEdge( posX, posY, width, height ) >= maxVal ) {
return oldValue;
} else {
float factor = getFactor( getDistanceToEdge( posX, posY, width, height ), minVal, maxVal );
return oldValue * factor;
}
}
private static float getFactor( int val, int min, int max ) {
int full = max - min;
int part = val - min;
float factor = (float)part / (float)full;
return factor;
}
public static int getDistanceToEdge( int x, int y, int width, int height ) {
int[] distances = new int[]{ y, x, ( width - x ), ( height - y ) };
int min = distances[ 0 ];
foreach( var val in distances ) {
if( val < min ) {
min = val;
}
}
return min;
}
}
Yeah. The article in question is using a waaay complex method.
The best way of doing this is to take a function that represents the shape of your basic island, with height values between 0 and 1. For the type of island in the picture, you'd basically want something which smoothly rises from the edges, and smoothly dips back to zero where you want lakes.
Now you either add that surface to your basic fractal surface (if you want to preserve spikiness at low elevations) or you multiply it (if you want lower elevations to be smooth). Then you define a height, below which is water.
Here is my very quick go at doing this, rendered with Terragen:
I used a function that rises in a ring from the edge of the map to halfway to the middle, then drops again, to match a similar shape to the one from the article. In practice, you might only use this to get the shape of the island, and then carve the bit of terrain that matches the contour, and bury everything else.
I used my own fractal landscape generator as described here: https://fractal-landscapes.co.uk for the basic fractal.
Here is the C# code that modifies the landscape:
public void MakeRingIsland()
{
this.Normalize(32768);
var ld2 = (double) linearDimension / 2;
var ld4 = 4 / (double) linearDimension;
for (var y = 0u; y < linearDimension; y++)
{
var yMul = y * linearDimension;
for (var x = 0u; x < linearDimension; x++)
{
var yCoord = (y - ld2) * ld4;
var xCoord = (x - ld2) * ld4;
var dist = Math.Sqrt(xCoord * xCoord + yCoord * yCoord);
var htMul = dist > 2 ? 0 :
(dist < 1 ?
dist + dist - dist * dist :
1 - (dist - 1) * (dist - 1));
var height = samples[x + yMul];
samples[x + yMul] = (int) (height + htMul * 32768);
}
}
}
the image you are showing comes from article describing how to generate it
I have a problem regarding frame rate drop while trying to make real time 3D terrain changes. I use C#, XNA 4.0 and VS 2010. This is my old school project and time has come to finish it.
I already did terrain generation from image file, with all effects and stuff and it is running smoothly no matter what resolution image file is. Problem is with my terrain editor. I want it to be able to manually alter the terrain. I did that part too, but it works only if terrain size is equal or less than 128x128 pixels. If the terrain size is greater I start to get frame rate drops around 150x150 pixels, and it is completely unmanageable if terrain size is greater than 512x512 pixels.
I already tried several approaches:
tried to use threads, but then I get weird error saying something like "Draw method can be called in one thread at a time" or something similar, and that I can't resolve.
next I tried to use DynamicVertexBuffer and DynamicIndexBuffer. That helped a lot and now my code is working with acceptable frame rate for terrain size of up to 256x256 pixels.
Have a look at my code:
public void ChangeTerrain(float[,] heightData)
{
int x, y;
int v = 1;
if (currentMouseState.LeftButton == ButtonState.Pressed && currentMouseState.X < 512)
{
x = (int)currentMouseState.X / 2;
y = (int)currentMouseState.Y / 2;
if (x < 5)
x = 5;
if (x >= 251)
x = 251;
if (y < 5)
y = 5;
if (y >= 251)
y = 251;
for (int i = x - 4; i < x + 4; i++)
{
for (int j = y - 4; j < y + 4; j++)
{
if (i == x - 4 || i == x + 3 || j == y - 4 || j == y + 3)
v = 3;
else
v = 5;
if (heightData[i, j] < 210)
{
heightData[i, j] += v;
}
}
}
}
if (currentMouseState.RightButton == ButtonState.Pressed && currentMouseState.X < 512)
{
x = (int)currentMouseState.X / 2;
y = (int)currentMouseState.Y / 2;
if (x < 5)
x = 5;
if (x >= 251)
x = 251;
if (y < 5)
y = 5;
if (y >= 251)
y = 251;
for (int i = x - 4; i < x + 4; i++)
{
for (int j = y - 4; j < y + 4; j++)
{
if (heightData[i, j] > 0)
{
heightData[i, j] -= 1;
}
}
}
}
if (keyState.IsKeyDown(Keys.R))
{
for (int i = 0; i < 256; i++)
for (int j = 0; j < 256; j++)
heightData[i, j] = 0f;
}
SetUpTerrainVertices();
CalculateNormals();
terrainVertexBuffer.SetData(vertices, 0, vertices.Length);
}
I work with resolution of 1024x512 pixels, so I scale mouse position by 1/2 to get terrain position. I use left and right mouse button to alter terrain, i.e. to alter heightData from which 3D terrain is generated.
Last 3 lines create Vertices from new heightData, calculate Normals so shades could be applied and last line is just throwing Vertices data to Vertex Buffer.
Prior to that, I set up dynamic Vertex and Index buffer in LoadContent method and call initial Vertices and Indices setup. This method (ChangeTerrain) is called from Update method.
I did some debugging and found out that maximum size of vertices in most extreme case would be around 260000 +- few thousands. Is it possible that .SetData is so much time consuming it is causing frame rate drops? Or is it something else? How can I fix that and make my editor functioning normally for any terrain size?
Also, i red that I need to use this code with DynamicVertexBuffer, but I can't make it work in XNA 4.0.
terrainVertexBuffer.ContentLost += new EventHandler(TerrainVertexBufferContentLost);
public void TerrainVertexBufferContentLost()
{
terrainVertexBuffer(vertices, 0, vertices.Length, SetDataOptions.NoOverwrite);
}
Thanks for your help!
EDIT:
This is my SetUpTerrainVertices code:
private void SetUpTerrainVertices()
{
vertices = new VertexPositionNormalColored[terrainWidth * terrainLength];
for (int x = 0; x < terrainWidth; x++)
{
for (int y = 0; y < terrainLength; y++)
{
vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y);
vertices[x + y * terrainWidth].Color = Color.Gray;
}
}
}
And my CalculateNormals
private void CalculateNormals()
{
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal = new Vector3(0, 0, 0);
for (int i = 0; i < indices.Length / 3; i++)
{
int index1 = indices[i * 3];
int index2 = indices[i * 3 + 1];
int index3 = indices[i * 3 + 2];
Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
Vector3 normal = Vector3.Cross(side1, side2);
vertices[index1].Normal += normal;
vertices[index2].Normal += normal;
vertices[index3].Normal += normal;
}
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal.Normalize();
}
I set up vertex and index buffers in XNA LoadContent Method using lines:
terrainVertexBuffer = new DynamicVertexBuffer(device, VertexPositionNormalColored.VertexDeclaration, vertices.Length,
BufferUsage.None);
terrainIndexBuffer = new DynamicIndexBuffer(device, typeof(int), indices.Length, BufferUsage.None);
I call ChangeTerrain method from Update and this is how i Draw:
private void DrawTerrain(Matrix currentViewMatrix)
{
device.DepthStencilState = DepthStencilState.Default;
device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);
effect.CurrentTechnique = effect.Techniques["Colored"];
Matrix worldMatrix = Matrix.Identity;
effect.Parameters["xWorld"].SetValue(worldMatrix);
effect.Parameters["xView"].SetValue(currentViewMatrix);
effect.Parameters["xProjection"].SetValue(projectionMatrix);
effect.Parameters["xEnableLighting"].SetValue(true);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
device.Indices = terrainIndexBuffer;
device.SetVertexBuffer(terrainVertexBuffer);
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);
}
}
EDIT2:
Ok, I decided to go for your second suggestion and got into some problems. I modified my methods like this:
public void ChangeTerrain(Texture2D heightmap)
{
Color[] mapColors = new Color[256 * 256];
Color[] originalColors = new Color[256 * 256];
for (int i = 0; i < 256 * 256; i++)
originalColors[i] = new Color(0, 0, 0);
heightMap2.GetData(mapColors);
device.Textures[0] = null;
device.Textures[1] = null;
int x, y;
int v = 1;
if (currentMouseState.LeftButton == ButtonState.Pressed && currentMouseState.X < 512)
{
x = (int)currentMouseState.X / 2;
y = (int)currentMouseState.Y / 2;
if (x < 4)
x = 4;
if (x >= 251)
x = 251;
if (y < 4)
y = 4;
if (y >= 251)
y = 251;
for (int i = x-4; i < x+4; i++)
{
for (int j = y-4; j < y+4; j++)
{
if (i == x - 4 || i == x + 3 || j == y - 4 || j == y + 3)
v = 3;
else
v = 5;
if (mapColors[i + j * 256].R < 210)
{
mapColors[i + j * 256].R += (byte)(v);
mapColors[i + j * 256].G += (byte)(v);
mapColors[i + j * 256].B += (byte)(v);
}
heightMap2.SetData(mapColors);
}
}
}
if (currentMouseState.RightButton == ButtonState.Pressed && currentMouseState.X < 512)
{
x = (int)currentMouseState.X / 2;
y = (int)currentMouseState.Y / 2;
if (x < 4)
x = 4;
if (x >= 251)
x = 251;
if (y < 4)
y = 4;
if (y >= 251)
y = 251;
for (int i = x - 4; i < x + 4; i++)
{
for (int j = y - 4; j < y + 4; j++)
{
if (mapColors[i + j * 256].R > 0)
{
mapColors[i + j * 256].R -= 1;
mapColors[i + j * 256].G -= 1;
mapColors[i + j * 256].B -= 1;
}
heightMap2.SetData(mapColors);
}
}
}
if (keyState.IsKeyDown(Keys.R))
heightMap2.SetData(originalColors);
}
Generating flat surface - only once in LoadContent() method:
vertices are assigned only once
private void SetUpTerrainVertices()
{
for (int x = 0; x < terrainWidth; x++)
{
for (int y = 0; y < terrainLength; y++)
{
vertices[x + y * terrainWidth].Position = new Vector3(x, 0, -y);
vertices[x + y * terrainLength].Color = Color.Gray;
}
}
}
Draw method is same as previous, but with one extra line:
effect.Parameters["xTexture0"].SetValue(heightMap2);
also, I made new technique called Editor and it looks like this:
//------- Technique: Editor --------
struct EditorVertexToPixel
{
float4 Position : POSITION;
float4 Color : COLOR0;
float LightingFactor: TEXCOORD0;
float2 TextureColor : TEXCOORD1;
};
struct EditorPixelToFrame
{
float4 Color : COLOR0;
};
EditorVertexToPixel EditorVS( float4 inPos : POSITION, float4 inColor: COLOR, float3 inNormal: NORMAL, float2 inTextureColor: TEXCOORD1)
{
EditorVertexToPixel Output = (EditorVertexToPixel)0;
float4x4 preViewProjection = mul (xView, xProjection);
float4x4 preWorldViewProjection = mul (xWorld, preViewProjection);
float4 Height;
float4 position2 = inPos;
position2.y += Height;
Output.Color = inColor;
Output.Position = mul(position2, preWorldViewProjection);
Output.TextureColor = inTextureColor;
float3 Normal = normalize(mul(normalize(inNormal), xWorld));
Output.LightingFactor = 1;
if (xEnableLighting)
Output.LightingFactor = saturate(dot(Normal, -xLightDirection));
return Output;
}
EditorPixelToFrame EditorPS(EditorVertexToPixel PSIn)
{
EditorPixelToFrame Output = (EditorPixelToFrame)0;
//float4 height2 = tex2D(HeightSAmpler, PSIn.TextureColor);
float4 colorNEW = float4(0.1f, 0.1f, 0.6f, 1);
Output.Color = PSIn.Color * colorNEW;
Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient;
return Output;
}
technique Editor
{
pass Pass0
{
VertexShader = compile vs_3_0 EditorVS();
PixelShader = compile ps_3_0 EditorPS();
}
}
this code doesn't work because float4 Height is not set. What I wanted to do is to sample texture colors into float4 Height (using Sample), but I can not use sampler in VertexShader. I get error message "X4532 cannot map expression to vertex shader instruction set".
Then, I red that you can use SampleLevel in VertexShader to sample color data and thought I found solution, but I get strange error that is only documented in one Russian blog, but I can't speak or read Russian. Error is: "X4814 unexpected Alias on texture declaration"
Is there a way to sample colors in PixelShader and then pass them to VertexShader?
This could work cos I managed to set float4 Height to various values and it altered vertices height. Problem is, I don't know how to read texture color in VertexShader, or how to pass red texture color data from PixelShader to VertexShader.
EDIT3:
I think I found solution. Was searching the net and found out about tex2Dlod function to use as VertexShader texture sampler. But there are different syntax displayed and I can't make them work.
Can anyone point out on good HLSL literature to learn a bit about HLSL coding. This task seems pretty easy, but somehow, I can't make it to work.
Ok, so I can't offer "real" performance advice - because I haven't measured your code. And measuring is probably the most important part of performance optimisation - you need to be able to answer the questions: "am I slower than my performance target?" and "why am I slower than my target?"
That being said - here are the things that stand out to me as a seasoned developer:
This method (ChangeTerrain) is called from Update method
You should probably consider splitting that method up so that, rather than recreating your data every frame, it only does work when the terrain is actually changed.
vertices = new VertexPositionNormalColored[terrainWidth * terrainLength];
Allocating a new vertices buffer each frame is huge memory allocation (6MB at 512x512). This is going to put a big strain on the garbage collector - and I suspect this is the primary cause of your performance issues.
Given that you're about to set all the data in that array anyway, simply delete that line and the old data in the array will be overwritten.
Better yet, you could leave the data that doesn't change as-is, and only modify the vertices that are actually changed. In much the same way you are doing for heightData.
As part of this, it would be a very good idea to modify CalculateNormals so that, rather than having to rely on the index buffer and going through every triangle, it could calculate the indices of surrounding vertices (that form triangles) for any specific vertex - something you can do because vertices is ordered. Again, kind of like what you're doing for heightData.
terrainVertexBuffer.SetData(vertices, 0, vertices.Length);
This is sending the full 6MB buffer to the GPU. There are versions of SetData that only send a subset of the full buffer to the GPU. You should probably try and use these.
Just remember that each SetData call comes with some overhead, so don't get too granular. It's probably best to have just one call per vertex buffer, even if that means some unmodified parts of the buffer must be sent.
This is probably the only place where "chunking" your terrain would have an significant impact, as it would allow you to specify a tighter region for each SetData call - allowing you to send less unmodified data. (I'll leave figuring out why this is the case as an exercise.)
(You're already using DynamicVertexBuffer, which is good, because this means the GPU will automatically handle the pipeline issues of having its buffer changed on-the-fly.)
Finally, if performance is still an issue, you could consider a different approach entirely.
One example might be to offload the calculation of the geometry to the GPU. You'd convert your heightData to a texture, and use a vertex shader (with a flat grid of vertices as input) to sample that texture and output the appropriate positions and normals.
One big advantage of this approach is that heightData can be a lot smaller (0.25MB at 512x512) than your vertex buffer - that's much less data that the CPU needs to process and send to the GPU.
I was searching for a stretch like warp algorithm to apply in my Windows 8 store application.
I found this android (java) code below.
Most things I can port to C# but some android sdk specific things are blurry for me to port to .NET code.
For example the call too : canvas.drawBitmapMesh , is there some .NET counterpart for this?
The Matrix classes in .NET are also a bit different I think but I can figure that out I think.
Any tips on helping converting/porting the code below to .NET are very welcome.
public class BitmapMesh extends GraphicsActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static final int WIDTH = 20;
private static final int HEIGHT = 20;
private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
private final Bitmap mBitmap;
private final float[] mVerts = new float[COUNT*2];
private final float[] mOrig = new float[COUNT*2];
private final Matrix mMatrix = new Matrix();
private final Matrix mInverse = new Matrix();
private static void setXY(float[] array, int index, float x, float y) {
array[index*2 + 0] = x;
array[index*2 + 1] = y;
}
public SampleView(Context context) {
super(context);
setFocusable(true);
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.beach);
float w = mBitmap.getWidth();
float h = mBitmap.getHeight();
// construct our mesh
int index = 0;
for (int y = 0; y <= HEIGHT; y++) {
float fy = h * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++) {
float fx = w * x / WIDTH;
setXY(mVerts, index, fx, fy);
setXY(mOrig, index, fx, fy);
index += 1;
}
}
mMatrix.setTranslate(10, 10);
mMatrix.invert(mInverse);
}
#Override protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFCCCCCC);
canvas.concat(mMatrix);
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, mVerts, 0,
null, 0, null);
}
private void warp(float cx, float cy) {
final float K = 10000;
float[] src = mOrig;
float[] dst = mVerts;
for (int i = 0; i < COUNT*2; i += 2) {
float x = src[i+0];
float y = src[i+1];
float dx = cx - x;
float dy = cy - y;
float dd = dx*dx + dy*dy;
float d = FloatMath.sqrt(dd);
float pull = K / (dd + 0.000001f);
pull /= (d + 0.000001f);
// android.util.Log.d("skia", "index " + i + " dist=" + d + " pull=" + pull);
if (pull >= 1) {
dst[i+0] = cx;
dst[i+1] = cy;
} else {
dst[i+0] = x + dx * pull;
dst[i+1] = y + dy * pull;
}
}
}
private int mLastWarpX = -9999; // don't match a touch coordinate
private int mLastWarpY;
#Override public boolean onTouchEvent(MotionEvent event) {
float[] pt = { event.getX(), event.getY() };
mInverse.mapPoints(pt);
int x = (int)pt[0];
int y = (int)pt[1];
if (mLastWarpX != x || mLastWarpY != y) {
mLastWarpX = x;
mLastWarpY = y;
warp(pt[0], pt[1]);
invalidate();
}
return true;
}
}
}
There is no direct equivalent in Windows Store apps to be able to draw a texture mapped mesh (and represent image warping and distortion that way). You will have to use Direct3D to draw the texture mapped mesh onto your page.
If you're reluctant to mix C++ and C# in your app, you have two choices.
SharpDX - these are .NET bindings to DirectX.
MonoGame - this is an XNA-compliant, cross-platform graphics interface that uses DirectX on WinRT.
Hope this helps!