I am trying to create a dynamic view of 3D points using WPF, I am able to draw the first set of points but then it crashes bcuz the Mesh3DGeometry is frozen .. is there anyway to unfreeze the Mesh3DGeomerty and Clear the Grid so the next sets can be drawn ?
var rand = new Random();
while (true)
{
points.Clear();
for (int i = 0; i < 100; i++)
{
points.Add(new Point3D(rand.NextDouble(), rand.NextDouble(), rand.NextDouble()));
}
for (int i = 0; i < points.Count; i++)
{
AddCubeToMesh(pointCloudMesh, points[i], 0.01);
}
pointCloudMesh.Freeze();
}
Also XAML is here
<Grid Grid.Row="1" Grid.Column="1" Margin="0 20 0 -20" Background="LightGray" Name="MyCanvas" >
<Viewport3D>
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camMain" Position="0 0 5" LookDirection="0 0 -5"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<Model3DGroup.Children>
<AmbientLight Color="#AAAAAA" />
</Model3DGroup.Children>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D x:Name="pointCloudMesh" />
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial Brush="Red"/>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Grid>
Code for AddCubeToMesh
private void AddCubeToMesh(MeshGeometry3D mesh, Point3D center, double size)
{
if (mesh != null)
{
int offset = mesh.Positions.Count;
mesh.Positions.Add(new Point3D(center.X - size, center.Y + size, center.Z - size));
mesh.Positions.Add(new Point3D(center.X + size, center.Y + size, center.Z - size));
mesh.Positions.Add(new Point3D(center.X + size, center.Y + size, center.Z + size));
mesh.Positions.Add(new Point3D(center.X - size, center.Y + size, center.Z + size));
mesh.Positions.Add(new Point3D(center.X - size, center.Y - size, center.Z - size));
mesh.Positions.Add(new Point3D(center.X + size, center.Y - size, center.Z - size));
mesh.Positions.Add(new Point3D(center.X + size, center.Y - size, center.Z + size));
mesh.Positions.Add(new Point3D(center.X - size, center.Y - size, center.Z + size));
mesh.TriangleIndices.Add(offset + 3);
mesh.TriangleIndices.Add(offset + 2);
mesh.TriangleIndices.Add(offset + 6);
mesh.TriangleIndices.Add(offset + 3);
mesh.TriangleIndices.Add(offset + 6);
mesh.TriangleIndices.Add(offset + 7);
mesh.TriangleIndices.Add(offset + 2);
mesh.TriangleIndices.Add(offset + 1);
mesh.TriangleIndices.Add(offset + 5);
mesh.TriangleIndices.Add(offset + 2);
mesh.TriangleIndices.Add(offset + 5);
mesh.TriangleIndices.Add(offset + 6);
mesh.TriangleIndices.Add(offset + 1);
mesh.TriangleIndices.Add(offset + 0);
mesh.TriangleIndices.Add(offset + 4);
mesh.TriangleIndices.Add(offset + 1);
mesh.TriangleIndices.Add(offset + 4);
mesh.TriangleIndices.Add(offset + 5);
mesh.TriangleIndices.Add(offset + 0);
mesh.TriangleIndices.Add(offset + 3);
mesh.TriangleIndices.Add(offset + 7);
mesh.TriangleIndices.Add(offset + 0);
mesh.TriangleIndices.Add(offset + 7);
mesh.TriangleIndices.Add(offset + 4);
mesh.TriangleIndices.Add(offset + 7);
mesh.TriangleIndices.Add(offset + 6);
mesh.TriangleIndices.Add(offset + 5);
mesh.TriangleIndices.Add(offset + 7);
mesh.TriangleIndices.Add(offset + 5);
mesh.TriangleIndices.Add(offset + 4);
mesh.TriangleIndices.Add(offset + 2);
mesh.TriangleIndices.Add(offset + 3);
mesh.TriangleIndices.Add(offset + 0);
mesh.TriangleIndices.Add(offset + 2);
mesh.TriangleIndices.Add(offset + 0);
mesh.TriangleIndices.Add(offset + 1);
}
}
I solution that kinda worked for me is to add all of this into a separated UserControl and call it every time a new data arrive
First: An endless loop that generates UI parts without giving time to the UI Thread is will not work. Generate the data on a seperate thread and pass the data to the UI thread to create the model and display it.
Then, unfreezing a freezable is not possible:
"Unfreezing" a Freezable Once frozen, a Freezable can never be
modified or unfrozen; however, you can create an unfrozen clone using
the Clone or CloneCurrentValue method.
Source
So if you think it is really needed to Freeze/Unfreeze you can do it by making a clone. I don't think it is needed here.
the solution I came up with is to separate the Point Cloud view to another User Control and every time I wanna display a new set of data i destroy the old User Control and rebuild it .. it's an aggressive approach but worked fine
XAML for the new User Control
<UserControl x:Class="LPDTool.View3D"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LPDTool"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="500"
KeyDown="Window_KeyDown">
<GroupBox x:Name="GBox" Margin="0 20 0 -20" Foreground="#AAAAAA" FontSize="18" FontWeight="Medium" Background="#DDDBDA" BorderBrush="#7B7704" BorderThickness="2" FontFamily="Abadi MT">
<Viewport3D>
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camMain"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<Model3DGroup.Children>
<AmbientLight Color="#AAAAAA" />
</Model3DGroup.Children>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D x:Name="pointCloudMesh" />
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial Brush="Red"/>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</GroupBox>
Initialization Constructor
public View3D()
{
InitializeComponent();
PositionCamera();
for (int i = 0; i < LPDTest.points.Count; i++)
{
AddCubeToMesh(pointCloudMesh, points[i], 0.02);
}
}
XAML for the inclusion part
<Grid x:Name="My3dview">
</Grid>
Here is the generating code part (with dumpy data)
var rand = new Random();
while (true)
{
My3dview.Children.Clear();
points.Clear();
for (int i = 0; i < 100; i++)
{
points.Add(new Point3D(rand.NextDouble(), rand.NextDouble(), rand.NextDouble()));
}
My3dview.Children.Add(new View3D());
}
Related
I'm working on a custom WPF control that should visualize thousands of graphical primitives in a scrollable area. The core part of the control's template is the following:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ItemVisualizer}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<local:ItemAreaElement Grid.Row="0" Grid.Column="0" x:Name="PART_ItemArea" />
<ScrollBar Grid.Row="0" Grid.Column="1" x:Name="PART_ScrollBarVert" Orientation="Vertical" Maximum="100" />
<ScrollBar Grid.Row="1" Grid.Column="0" x:Name="PART_ScrollBarHorz" Orientation="Horizontal" Maximum="100" />
<Rectangle Grid.Row="1" Grid.Column="1" x:Name="PART_SizeGrip" Focusable="False" Fill="#F0F0F0" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
For better performance, all drawing operations are performed in the OnRender method of the ItemAreaElement. To have crisp drawing, I also use the following setting in the initialization code:
this.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
However, I have some strange issues with my drawing. To demonstrate them, I simplified the definition of my ItemAreaElement to the following:
class ItemAreaElement : FrameworkElement
{
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
const int ITEM_WIDTH = 60;
const int ITEM_HEIGHT = 20;
Pen penRed = new Pen(Brushes.Red, 1);
Pen penGreen = new Pen(Brushes.Green, 1);
int y = 0;
for (int iRow = 0; iRow < 3; iRow++)
{
int x = 0;
for (int iCol = 0; iCol < 2; iCol++)
{
Point cornerTopLeft = new Point(x, y);
dc.DrawLine(penRed, cornerTopLeft, new Point(x + ITEM_WIDTH - 1, y));
dc.DrawLine(penRed, cornerTopLeft, new Point(x, y + ITEM_HEIGHT - 1));
Point cornerBottomRight = new Point(x + ITEM_WIDTH - 1, y + ITEM_HEIGHT - 1);
dc.DrawLine(penGreen, new Point(x + ITEM_WIDTH - 1, y), cornerBottomRight);
dc.DrawLine(penGreen, new Point(x, y + ITEM_HEIGHT - 1), cornerBottomRight);
x += ITEM_WIDTH;
}
y += ITEM_HEIGHT;
}
}
}
When I launch this code on my main dev laptop equipped with an Ultra-HD screen with 282ppi (the system scale factor is 300%), I get this picture:
Or, after zooming in paint.net with gridlines:
As you see, the left, and top edges of my ItemAreaElement are partially covered by the border of the control. Must it be so? Is there a setting I can use to avoid this?
The second problem are lines that do not include the start point (see the top-left corner of my "cells"). Is this the expected behavior? IF so, how to force WPF to draw the start pixel?
And the third problem is the place or device-independent point in which the green lines should meet (the bottom-right corner of my cells). As you can see, this point is jagged. I expected to see just a green square in that place. Can I implement this with the help of the DrawingContext.DrawLine method? Or do I need to use a more complex geometry with special settings for multi-point lines, etc.?
By the way, when I launch this code on a test pc with a "classic" 96 ppi monitor and the scale factor of the OS set to 100%, the situation is a little bit better in the bottom-right corner:
But I even do not see the horizontal red lines in the top row or vertical red lines in the first column. I expected to see them there but not to be covered by the control's border.
I've managed to solve all my problems by setting the corresponding guidelines. Below you'll find the improved version of the OnRender() method presented above:
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
const int ITEM_WIDTH = 60;
const int ITEM_HEIGHT = 20;
const double sizeOfPen = 1;
double halfSizeOfPen = sizeOfPen / 2.0;
Pen penRed = new Pen
{
Brush = Brushes.Red,
Thickness = sizeOfPen,
StartLineCap = PenLineCap.Square,
EndLineCap = PenLineCap.Square
};
Pen penGreen = new Pen
{
Brush = Brushes.Green,
Thickness = sizeOfPen,
StartLineCap = PenLineCap.Square,
EndLineCap = PenLineCap.Square
};
int y = 0;
for (int iRow = 0; iRow < 3; iRow++)
{
int x = 0;
for (int iCol = 0; iCol < 2; iCol++)
{
GuidelineSet guidelines = new GuidelineSet();
guidelines.GuidelinesX.Add(x);
guidelines.GuidelinesX.Add(x + ITEM_WIDTH);
guidelines.GuidelinesY.Add(y);
guidelines.GuidelinesY.Add(y + ITEM_HEIGHT);
dc.PushGuidelineSet(guidelines);
Point cornerTopLeft = new Point(x + halfSizeOfPen, y + halfSizeOfPen);
dc.DrawLine(penRed, cornerTopLeft, new Point(x + ITEM_WIDTH - halfSizeOfPen, y + halfSizeOfPen));
dc.DrawLine(penRed, cornerTopLeft, new Point(x + halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen));
Point cornerBottomRight = new Point(x + ITEM_WIDTH - halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen);
dc.DrawLine(penGreen, new Point(x + ITEM_WIDTH - halfSizeOfPen, y + halfSizeOfPen), cornerBottomRight);
dc.DrawLine(penGreen, new Point(x + halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen), cornerBottomRight);
dc.Pop();
x += ITEM_WIDTH;
}
y += ITEM_HEIGHT;
}
}
I'm assuming this is impossible, but I've seen plenty of crazy preprocessor magic before so I thought I'd ask. Can the preprocessor insert code after every statement between two points?
For example, I have the following code:
// ...
dy2 = dy2 + (rand()%3) - 1; swapTask();
if (dy2 < D2Upper) dy2 = D2Upper; swapTask();
if (dy2 > D2Lower) dy2 = D2Lower; swapTask();
dy2 = (dy2+9) >= dy1 ? dy1-9 : dy2; swapTask();
if (direction2 > 0)
{
memcpy(&pk[dy2+0][direction2+7], "_", 1); swapTask();
memcpy(&pk[dy2+1][direction2+6], "/o\\", 3); swapTask();
memcpy(&pk[dy2+2][direction2+4], "</ _<", 5); swapTask();
memcpy(&pk[dy2+3][direction2+3], "</ /", 4); swapTask();
memcpy(&pk[dy2+4][direction2+2], "</ ==x", 6); swapTask();
memcpy(&pk[dy2+5][direction2+3], "/ \\", 4); swapTask();
memcpy(&pk[dy2+6][direction2+2], "//)__)", 6); swapTask();
memcpy(&pk[dy2+7][direction2+0], "<<< \\_ \\_", 9); swapTask();
if (++direction2 > D2Right)
direction2 = -direction2; swapTask();
}
// ...
I need to call swapTask(); after each statement. This becomes bothersome over time (especially if I want it to look nice and aligned).
Originally I thought of programmatically adding it to each line, but that obviously breaks block-less if statements. Then I thought of adding it after each semicolon, but that obviously breaks for statements. Is there any preprocessor, compiler, or other method of magic that can call this after each line? Thanks.
Learning OpenGL here. Trying to write a bitmap font rendering system.
I'm using Hiero to generate the font file (Angel Code font format) and atlas (.fnt and .png files).
I first parse the font file reading in the font and character data. That part is easy. (I verified that the parsed results are indeed all correct)
// (typedefs) u32: unsigned int, r32: float, string: char*
struct font_character
{
u32 Id;
r32 X, Y;
r32 Width, Height;
r32 XOffset, YOffset;
r32 XAdvance;
};
struct font_set
{
string Name;
u32 Atlas;
r32 Size;
u32 Stretch;
u32 Smooth;
u32 AntiAliasing;
u32 Padding[4];
u32 Spacing[2];
u32 LineHeight;
u32 BaseLine;
r32 Width, Height;
u32 CharacterCount;
font_character Characters[128];
};
// Parsing related codes...
font_set FontLoad(string FilePath)
{
font_set Result = {};
string Content = ReadFile(FilePath);
if (Content)
{
List(string) FontSettings;
ListAlloc(FontSettings, 1024);
bool DoneParsing = false;
while(!DoneParsing)
{
token Token = GetToken(Content);
switch(Token.Type)
{
case TokenType_EOF:
DoneParsing = true;
break;
case TokenType_Unknown:
Assert(!"Unknown token in font file");
break;
case TokenType_Number:
case TokenType_String:
case TokenType_Identifier:
ListPush(FontSettings, Token.Content);
break;
}
}
for (int i = 0, Count = ListCount(FontSettings); i < Count; i += 2)
{
string SettingKey = FontSettings[i];
string SettingValue = FontSettings[i + 1];
if (StringEqual(SettingKey, "face"))
Result.Name = SettingValue;
else if (StringEqual(SettingKey, "size"))
Result.Size = atoi(SettingValue);
else if (StringEqual(SettingKey, "stretchH"))
Result.Stretch = atoi(SettingValue);
else if (StringEqual(SettingKey, "smooth"))
Result.Smooth = atoi(SettingValue);
else if (StringEqual(SettingKey, "aa"))
Result.AntiAliasing = atoi(SettingValue);
else if (StringEqual(SettingKey, "lineHeight"))
Result.LineHeight = atoi(SettingValue);
else if (StringEqual(SettingKey, "base"))
Result.BaseLine = atoi(SettingValue);
else if (StringEqual(SettingKey, "scaleW"))
Result.Width = atoi(SettingValue);
else if (StringEqual(SettingKey, "scaleH"))
Result.Height = atoi(SettingValue);
else if (StringEqual(SettingKey, "spacing"))
{
// Ascii(48) = Decimal(0)
Result.Spacing[0] = SettingValue[0] - 48;
Result.Spacing[1] = SettingValue[2] - 48;
}
else if (StringEqual(SettingKey, "padding"))
{
Result.Padding[0] = SettingValue[0] - 48;
Result.Padding[1] = SettingValue[2] - 48;
Result.Padding[2] = SettingValue[4] - 48;
Result.Padding[3] = SettingValue[6] - 48;
}
else if (StringEqual(SettingKey, "char"))
{
font_character Character;
// Although they're 10 pairs of data, we're gonna skip the last two cause we don't care about them
For(u32, PairIndex, 8)
{
string CharKey = FontSettings[(i + 1) + PairIndex * 2];
string CharValue = FontSettings[(i + 2) + PairIndex * 2];
if (StringEqual(CharKey, "id"))
Character.Id = atoi(CharValue);
else if (StringEqual(CharKey, "x"))
Character.X = atoi(CharValue);
else if (StringEqual(CharKey, "y"))
Character.Y = atoi(CharValue);
else if (StringEqual(CharKey, "width"))
Character.Width = atoi(CharValue);
else if (StringEqual(CharKey, "height"))
Character.Height = atoi(CharValue);
else if (StringEqual(CharKey, "xoffset"))
Character.XOffset = atoi(CharValue);
else if (StringEqual(CharKey, "yoffset"))
Character.YOffset = atoi(CharValue);
else if (StringEqual(CharKey, "xadvance"))
Character.XAdvance = atoi(CharValue);
}
Result.Characters[Result.CharacterCount++] = Character;
i += 19;
}
else i--;
}
}
// Load texture
char TexturePath[256];
sprintf(TexturePath, "%s.png", FilePath);
Result.Atlas = TextureLoad(TexturePath); // loads texture from file via stbi_load, does glGenTexture, configures texture parameters etc.
return (Result);
}
Then we get to rendering, which is where I'm struggling a bit. My understanding is that I need to use the character data I got from the font data to build quads that I could render to screen.
First, here's my shaders. Vertex shader:
#version 330 core
layout (location = 0) in vec2 VertPos;
layout (location = 1) in vec2 VertUV;
out vec2 FragUV;
uniform mat4 Projection;
void main()
{
gl_Position = Projection * vec4(VertPos, 0, 1);
FragUV = VertUV;
}
And fragment shader:
#version 330 core
out vec4 FinalColor;
in vec2 FragUV;
uniform sampler2D FontAtlas;
uniform vec3 Color;
void main()
{
FinalColor = vec4(Color, texture(FontAtlas, FragUV).a);
}
Here's how I load the font and render:
struct font_renderer
{
string Text;
v3 Color;
r32 CurrentX;
u32 VAO, VBO;
u32 Initialized;
u32 Shader;
};
// In an initialization function
font_set Font = FontLoad("res/fonts/Courier New.fnt");
u32 FontShader = ShaderLoadFromFile("Font.vert", "Font.frag");
// In an update/render function
font_renderer FontRenderer = {};
FontRenderer.Text = "A";
FontRenderer.Color = V3(1, 0, 0);
FontRenderer.CurrentX = 20;
FontRenderer.Shader = FontShader;
FontRender(&FontRenderer, &Font);
Rendering function (just trying to get something on the screen)
void FontRender(font_renderer *Renderer, font_set *Font)
{
u32 NumChars = StringLength(Renderer->Text);
u32 Size = NumChars * 12;
if (!Renderer->Initialized)
{
glGenBuffers(1, &Renderer->VBO);
glBindBuffer(GL_ARRAY_BUFFER, Renderer->VBO);
glBufferData(GL_ARRAY_BUFFER, Size * 2, 0, GL_STATIC_DRAW);
glGenVertexArrays(1, &Renderer->VAO);
glBindVertexArray(Renderer->VAO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
Renderer->Initialized = 1;
}
/*r32 *VertPos = Calloc(Size, r32);
r32 *VertUV = Calloc(Size, r32);*/
// Temporary code, just trying to render a single character on screen
r32 VertPos[12]; // we need a quad <=> 2 triangles <=> 6 vertices <=> 12 floats
r32 VertUV[12]; // same for UVs
For(u32, i, NumChars) // for loop macro
{
font_character Character = Font->Characters[Renderer->Text[i]]; // assuming that 'Characters' are ordered correctly
r32 X = Character.X;
r32 Y = Character.Y;
r32 XOffset = Character.XOffset;
r32 YOffset = Character.YOffset;
r32 XAdvance = Character.XAdvance;
r32 Width = Character.Width;
r32 Height = Character.Height;
// Triangle 1 (clock-wise winding order)
{
// Top Left
VertPos[i] = Renderer->CurrentX + XOffset;
VertPos[i + 1] = YOffset;
// Bottom Left
VertPos[i + 2] = Renderer->CurrentX + XOffset;
VertPos[i + 3] = YOffset + Height;
// Bottom Right
VertPos[i + 4] = Renderer->CurrentX + XOffset + Width;
VertPos[i + 5] = YOffset + Height;
}
// Triangle 2
{
// Bottom Right
VertPos[i + 6] = VertPos[i + 4];
VertPos[i + 7] = VertPos[i + 5];
// Top Right
VertPos[i + 8] = Renderer->CurrentX + XOffset + Width;
VertPos[i + 9] = YOffset;
// Top Left
VertPos[i + 10] = VertPos[i];
VertPos[i + 11] = VertPos[i + 1];
}
// UV 1
{
// Top left
VertUV[i] = X / Font->Width;
VertUV[i + 1] = Y / Font->Height;
// Bottom left
VertUV[i + 2] = X / Font->Width;
VertUV[i + 3] = (Y + Height) / Font->Height;
// Bottom right
VertUV[i + 4] = (X + Width) / Font->Width;
VertUV[i + 5] = (Y + Height) / Font->Height;
}
// UV 2
{
// Bottom right
VertUV[i + 6] = VertUV[i + 4];
VertUV[i + 7] = VertUV[i + 5];
// Top right
VertUV[i + 8] = (X + Width) / Font->Width;
VertUV[i + 9] = Y / Font->Height;
// Top left
VertUV[i + 10] = VertUV[i];
VertUV[i + 11] = VertUV[i + 1];
}
}
glBindBuffer(GL_ARRAY_BUFFER, Renderer->VBO);
u32 Offset = 0;
glBufferSubData(GL_ARRAY_BUFFER, Offset, Size, VertPos);
Offset += Size;
glBufferSubData(GL_ARRAY_BUFFER, Offset, Size, VertUV);
m4 FontProjection = Orthographic(0, 800, 600, 0, -1, +1);
glDisable(GL_DEPTH_TEST);
ShaderUse(Renderer->Shader);
glBindVertexArray(Renderer->VAO);
TextureBind(Font->Atlas);
ShaderSetV3(Renderer->Shader, "Color", Renderer->Color);
ShaderSetM4(Renderer->Shader, "Projection", &FontProjection);
glDrawArrays(GL_TRIANGLES, 0, NumChars * 6);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
As you can see I'm really just trying to get a single character on screen. I'm not even taking into consideration the font size etc just keeping it simple. I choose the vertices going from TopLeft->BottomLeft->BottomRight->TopRight->TopLeft which, correct me if I'm wrong but is a clock-wise winding order. I have two slots in my vertex shader: Position and UV. I'm specifying the buffer data via the call to glBufferSubData
Running the program I don't get any rendering output. Just blank screen. Pretty sure I'm missing something obvious or doing something stupid, I can't see it. What am I doing wrong?
Note that the texture, font data and shader are loaded all correctly.
Appreciate any help.
Your code seems to have a number of problems (even ignoring the horrible C-in-C++ coding style you employ). I haven't found all of them, so I can't say I found the one that you ran into.
But I did find these:
u32 Size = NumChars * 12;
You use Size * 2 as the size for your buffer. So each character in your string takes up 24 bytes; 12 for your positions and 12 for your texture coordinates.
But your positions and texcoords are floats. 4 bytes per float * 2 floats per position * 6 positions per character = 48 bytes per character.
So your size calculation is very off. You can verify this by checking sizeof(VertPos) and comparing that to your Size variable. You'll find that they're very different.
glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0);
glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0);
This says that your position and texture coordinate both have the same offset. That would mean that your position and texcoord in the shader will get the same value. I'm pretty sure that's not what you want.
Your texture coordinate array comes from a different part of the buffer. So you need to apply an appropriate offset. Assuming you have correctly computed Size, that would look like this:
glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, reinterpret_cast<void*>(Size));
I am working on a countdown project that involves an arc. I've been struggling with the math for a few hours now and hope someone can help me. I have a 150px circle on which I want to draw arc that runs over starting at the top. The circle (ellipse really) is at 160,4
<Ellipse Height="150" HorizontalAlignment="Left" Margin="160,4,0,0"
Name="minutesClock" Stroke="Gainsboro" StrokeThickness="20"
VerticalAlignment="Top" Width="150" />
I have a method that is supposed to calculate the arc from the center top of the circle to whatever number of seconds is left
private void drawArc(int timevalue, int maxvalue)
{
Ellipse element = (Ellipse)LayoutRoot.FindName("minutesClock");
double top = element.Margin.Top;
double left = element.Margin.Left;
double width = element.Width;
double height = element.Height;
double strokeWidth = element.StrokeThickness;
double startX = left + (width / 2);
double startY = top + (strokeWidth / 2);
double radius = (width / 2) - (strokeWidth / 2);
double percent = (double)timevalue / (double)maxvalue;
Point center = new Point();
center.X = top + (height / 2);
center.Y = left + (width / 2);
Point newEnd = calculatePoint(radius, percent, center);
Path arc = (Path)LayoutRoot.FindName(what + "Arc");
PathFigure path = new PathFigure();
path.StartPoint = new Point(startX, startY);
ArcSegment arcSegment = new ArcSegment();
arcSegment.IsLargeArc = false;
Size arcSize = new Size(radius,radius);
arcSegment.Size = arcSize;
arcSegment.SweepDirection = SweepDirection.Clockwise;
arcSegment.Point = newEnd;
arcSegment.RotationAngle = 45;
path.Segments.Add(arcSegment);
PathGeometry pg = new PathGeometry();
pg.Figures = new PathFigureCollection();
pg.Figures.Add(path);
arc.Data = pg;
}
The arc starts at the right place but doesn't end up in the right place (the end point is all over the place). My calculatePoint code has to be where the error is. I figure it has something to do with
private Point calculatePoint(double radius, double percent, Point center)
{
double degrees = 90 - (360 * percent);
double radians = (Math.PI * degrees) / 180;
Point endPoint = new Point();
endPoint.X = center.X + (radius * Math.Sin(radians));
endPoint.Y = center.Y + (radius * Math.Cos(radians));
return endPoint;
}
Where am I going wrong?
You need to subtract the sinus (to go from the center "upwards" on the UI canvas):
endPoint.X = center.X - (radius * Math.Sin(radians));
Origin 0,0 is Top Left corner, not bottom left.
[Edit]
Oh and you are confusding x and y: think x is horizontal coordinated and y is vertical, so this is wrong:
center.X = top + (height / 2);
center.Y = left + (width / 2);
and this is wrong:
endPoint.X = center.X + (radius * Math.Sin(radians));
endPoint.Y = center.Y + (radius * Math.Cos(radians));
Corrected:
center.Y = top + (height / 2);
center.X = left + (width / 2);
and (with the subtraction fix I mentioned)
endPoint.Y = center.Y - (radius * Math.Sin(radians));
endPoint.X = center.X + (radius * Math.Cos(radians));
I have this texture representing internal area of a pipe:
I have created a simple cylinder model (GeometryModel3D) by dissecting the above texture to a grid of points and than wrapping this to form a cylinder, then mapping the above texture to it (each square on a grid having two triangles):
The left and right edges of the texture meet at the above black seam line, which is unwanted.
I could not use single vertex point for smooth continuation there, because that point corresponds to both 0 and 360 degrees which is single angle, but two distinct points on edges of the 2D texture.
So I have used pairs of vertices on the same location but each corresponding to different point in the source texture.
Is there anything I can do to hide the seam line?
Model creation code:
private void SceneViewerWindowOnLoaded(object sender, RoutedEventArgs e)
{
var imageSource = new BitmapImage(new Uri("pack://application:,,,/Resources/out1.jpg"));
var meshGeometry3D = new MeshGeometry3D();
double circumference = imageSource.PixelWidth;
double radius = (circumference / (2.0 * Math.PI));
double angleSegment = (2.0 * Math.PI / SegmentCountTrans);
double sizeSegmentLong = ((double)imageSource.PixelHeight / SegmentCountLong);
double sizeSegmentTrans = ((double)imageSource.PixelWidth / SegmentCountTrans);
for (int indexSegmentLong = 0; indexSegmentLong < SegmentCountLong; indexSegmentLong++)
{
double depth = (indexSegmentLong * sizeSegmentLong);
for (int indexSegmentTrans = 0; indexSegmentTrans < SegmentCountTrans; indexSegmentTrans++)
{
meshGeometry3D.Positions.Add(GetVertexPosition(indexSegmentTrans, depth, radius, angleSegment));
meshGeometry3D.TextureCoordinates.Add(new Point(indexSegmentTrans * sizeSegmentTrans, depth));
}
// add one extra vertex representing 360 degrees for complete wrap-around
meshGeometry3D.Positions.Add(GetVertexPosition(SegmentCountTrans, depth, radius, angleSegment));
meshGeometry3D.TextureCoordinates.Add(new Point(SegmentCountTrans * sizeSegmentTrans, depth));
}
meshGeometry3D.TriangleIndices.Clear();
for (int indexSegmentLong = 0; indexSegmentLong < (SegmentCountLong - 1); indexSegmentLong++)
{
for (int indexSegmentTrans = 0; indexSegmentTrans < SegmentCountTrans; indexSegmentTrans++)
{
int indexCurrent = (indexSegmentLong * (SegmentCountTrans + 1) + indexSegmentTrans);
meshGeometry3D.TriangleIndices.Add(indexCurrent);
meshGeometry3D.TriangleIndices.Add(indexCurrent + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 2);
}
}
var geometryModel3D = new GeometryModel3D
{
Geometry = meshGeometry3D,
Material = new DiffuseMaterial
{
Brush = new ImageBrush(imageSource)
}
};
this.SceneViewer.Model = geometryModel3D;
}
private static Point3D GetVertexPosition(int indexSegmentAngle, double depth, double radius, double angleSegment)
{
double angle = (StartAngle + indexSegmentAngle * angleSegment);
return new Point3D(
radius * Math.Cos(angle),
radius * Math.Sin(angle),
-depth);
}
XAML code for the Viewport3D element containing the model:
<Viewport3D x:Name="Viewport3D">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="Camera" Position="0.0,0.0,0.0" LookDirection="0.0,0.0,-1.0" UpDirection="0.0,1.0,0.0" FieldOfView="90"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color="White"/>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D x:Name="ModelVisualModel"/>
</Viewport3D>
When you create your image brush try setting the TileMode to TileMode.Tile and ViewportUnits to BrushMappingMode.Absolute. If that still doesn't work then post a follow-up comment here and I'll try to reproduce it on my end.
I used DrawingBrush instead of Imagebrush. This fixed the seams and I also get an fps boost when setting the cachinghint.
Brush brush = new DrawingBrush(new ImageDrawing(_texture, new Rect(0,0,1,1)));
RenderOptions.SetCachingHint(brush , CachingHint.Cache);
RenderOptions.SetCacheInvalidationThresholdMinimum(brush , 0.5);
RenderOptions.SetCacheInvalidationThresholdMaximum(brush , 2.0);