add ScatterChart example, ScatterChart shape support, fix texture alpha chanel

This commit is contained in:
firedef 2022-03-15 15:12:13 +03:00
Родитель db9466a68d
Коммит 191f5e9967
14 изменённых файлов: 244 добавлений и 27 удалений

Просмотреть файл

@ -1,14 +1,11 @@
using System;
using System.Linq;
using FreeTypeSharp.Native;
using SomeChartsAvaloniaExamples.elements;
using SomeChartsUi.ui.text;
namespace SomeChartsAvaloniaExamples;
internal class Program {
[STAThread]
public static void Main(string[] args) {
ElementsExamples.RunPieChart();
ScatterChartExample.Run();
}
}

Просмотреть файл

@ -44,6 +44,9 @@
<None Update="data\alotoftext.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="data\particle2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.12" />

Двоичные данные
SomeChartsAvaloniaExamples/data/particle2.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.9 KiB

Просмотреть файл

@ -0,0 +1,85 @@
using System;
using MathStuff.vectors;
using SomeChartsUi.data;
using SomeChartsUi.elements.charts.scatter;
using SomeChartsUi.themes.colors;
using SomeChartsUi.themes.themes;
using SomeChartsUi.utils.shaders;
using SomeChartsUiAvalonia.controls.gl;
using SomeChartsUiAvalonia.utils;
namespace SomeChartsAvaloniaExamples.elements;
public static class ScatterChartExample {
// point count
// ScatterChart handle at most 15000 points
private const int _count = 10_000;
private const float _bounds = 10_000;
private const float _pointSizeMul = 80;
private const float _pointSizeAdd = 10;
private static Random _rnd = new();
public static void Run() {
AvaloniaRunUtils.RunAfterStart(AddElements);
AvaloniaRunUtils.RunAvalonia();
}
private static void AddElements() {
AvaloniaGlChartsCanvas canvas = AvaloniaRunUtils.AddGlCanvas();
// generate random data
(float3[] points, indexedColor[] colors, ScatterShape[] shapes) = GenerateRandomPoints();
IChartData<float3> pointsSrc = new ArrayChartData<float3>(points);
IChartData<indexedColor> colorsSrc = new ArrayChartData<indexedColor>(colors);
IChartData<ScatterShape> shapesSrc = new ArrayChartData<ScatterShape>(shapes);
// load shapes shader, so it will render triangles and circles
// by default it uses basic shader, which render only quads
// disable depth test for transparency (required by shape shader)
Material mat = new(GlShaders.shapes);
mat.depthTest = false;
ScatterChart pie = new(canvas.canvas) {
// values using float3
// x,y - point position
// z - point size
values = pointsSrc,
colors = colorsSrc,
// you can skip shapes, because it's using default value (circles)
shapes = shapesSrc,
isDynamic = true,
// set scale, so bound will render properly
// you can disable setting drawBounds to false
scale = _bounds,
material = mat,
};
canvas.AddElement(pie);
}
private static (float3[] points, indexedColor[] colors, ScatterShape[] shapes) GenerateRandomPoints() {
float3[] points = new float3[_count];
indexedColor[] colors = new indexedColor[_count];
ScatterShape[] shapes = new ScatterShape[_count];
for (int i = 0; i < _count; i++) {
float x = RndPosX();
float y = RndPosY();
float s = RndSize();
indexedColor col = x > _bounds * .5f ? theme.good_ind : theme.bad_ind;
ScatterShape shape = y > _bounds * .5f ? ScatterShape.circle : ScatterShape.triangle;
points[i] = new(x, y, s);
colors[i] = col;
shapes[i] = shape;
}
return (points, colors, shapes);
}
private static float RndPosX() => _rnd.NextSingle() * _bounds;
private static float RndPosY() => _rnd.NextSingle() * _bounds;
private static float RndSize() => _rnd.NextSingle() * _pointSizeMul + _pointSizeAdd;
}

Просмотреть файл

@ -5,6 +5,7 @@ using SomeChartsUi.themes.colors;
using SomeChartsUi.themes.themes;
using SomeChartsUi.ui.canvas;
using SomeChartsUi.ui.elements;
using SomeChartsUi.utils.mesh;
namespace SomeChartsUi.elements.charts.scatter;
@ -13,14 +14,18 @@ public class ScatterChart : RenderableBase {
public IChartData<float3> values;
public IChartData<indexedColor> colors;
public IChartData<ScatterShape>? shapes;
public IChartData<ScatterShape> shapes = new ConstChartData<ScatterShape>(ScatterShape.circle);
public indexedColor boundsColor = theme.default2_ind;
public float2 scale = 1000;
public bool drawBounds = true;
public ScatterChart(ChartsCanvas owner) : base(owner) { }
private Mesh noTextureMesh;
public ScatterChart(ChartsCanvas owner) : base(owner) {
noTextureMesh = owner.factory.CreateMesh();
}
protected override unsafe void GenerateMesh() {
mesh!.Clear();
@ -31,12 +36,14 @@ public class ScatterChart : RenderableBase {
int bufferLen = math.min(_bufferSize, len);
float3* bufferValues = stackalloc float3[bufferLen];
indexedColor* bufferColors = stackalloc indexedColor[bufferLen];
ScatterShape* bufferShapes = stackalloc ScatterShape[bufferLen];
int vCount = (bufferLen + 4) * 4;
int iCount = (bufferLen + 4) * 6;
int vCount = bufferLen * 4;
int iCount = bufferLen * 6;
values.GetValues(0, bufferLen, 0, bufferValues);
colors.GetValues(0, bufferLen, 0, bufferColors);
shapes.GetValues(0, bufferLen, 0, bufferShapes);
mesh.vertices.EnsureCapacity(vCount);
mesh.indexes.EnsureCapacity(iCount);
@ -44,6 +51,15 @@ public class ScatterChart : RenderableBase {
for (int i = 0; i < bufferLen; i++) {
float3 element = bufferValues[i];
color col = bufferColors[i].GetColor();
ScatterShape shape = bufferShapes[i];
rect uvs = new(0, 0, 1, 1);
uvs.left = shape switch {
ScatterShape.circle => 0,
ScatterShape.triangle => 1,
ScatterShape.quad => 2,
_ => throw new ArgumentOutOfRangeException()
};
float s = element.z * .5f;
mesh.AddRect(
@ -51,17 +67,31 @@ public class ScatterChart : RenderableBase {
new(element.x - s, element.y + s),
new(element.x + s, element.y + s),
new(element.x + s, element.y - s),
col );
}
if (drawBounds) {
float thickness = 10;
AddLine(mesh, new(0,0), new(0,scale.y), thickness, boundsColor.GetColor());
AddLine(mesh, new(0,scale.y), new(scale.x,scale.y), thickness, boundsColor.GetColor());
AddLine(mesh, new(scale.x,scale.y), new(scale.x,0), thickness, boundsColor.GetColor());
AddLine(mesh, new(scale.x,0), new(0,0), thickness, boundsColor.GetColor());
col,
uvs);
}
mesh.OnModified();
}
protected override void OnFrequentUpdate() {
noTextureMesh.Clear();
noTextureMesh.vertices.EnsureCapacity(4 * 4);
noTextureMesh.indexes.EnsureCapacity(4 * 6);
if (drawBounds) {
float thickness = 2 / canvas.transform.scale.animatedValue.avg;
AddLine(noTextureMesh, new(0,0), new(0,scale.y), thickness, boundsColor.GetColor());
AddLine(noTextureMesh, new(0,scale.y), new(scale.x,scale.y), thickness, boundsColor.GetColor());
AddLine(noTextureMesh, new(scale.x,scale.y), new(scale.x,0), thickness, boundsColor.GetColor());
AddLine(noTextureMesh, new(scale.x,0), new(0,0), thickness, boundsColor.GetColor());
}
noTextureMesh.OnModified();
}
protected override void AfterDraw() {
base.AfterDraw();
DrawMesh(null, noTextureMesh);
}
}

Просмотреть файл

@ -22,6 +22,9 @@ public abstract partial class RenderableBase {
/// <summary>frame skip on dynamic update</summary>
public int updateFrameSkip = 8;
/// <summary>frame skip on dynamic update</summary>
public int updateRareFrameSkip = 64;
protected int framesCount;
/// <summary>material of mesh <br/><br/>if null, renderer will use basic material</summary>
@ -47,6 +50,8 @@ public abstract partial class RenderableBase {
public void Render() {
framesCount++;
beforeRender();
OnFrequentUpdate();
if (framesCount % (updateRareFrameSkip + 1) == 0) OnRareUpdate();
if (CheckMeshForUpdate()) {
GenerateMesh();
isDirty = false;
@ -63,6 +68,12 @@ public abstract partial class RenderableBase {
/// <summary>called every frame after render <br/><br/>usable for rendering multiple meshes, like <see cref="TextMesh"/></summary>
protected virtual void AfterDraw() { }
/// <summary>called every frame after beforeRender()</summary>
protected virtual void OnFrequentUpdate() {}
/// <summary>called after OnFrequentUpdate and before GenerateMesh dynamically, using updateRareFrameSkip</summary>
protected virtual void OnRareUpdate() {}
protected virtual void Destroy() {
mesh?.Dispose();
mesh = null;

Просмотреть файл

@ -5,6 +5,7 @@ using MathStuff.vectors;
using SomeChartsUi.data;
using SomeChartsUi.elements;
using SomeChartsUi.ui.canvas;
using SomeChartsUi.utils.mesh;
using SomeChartsUi.utils.shaders;
namespace SomeChartsUi.ui.elements;
@ -22,9 +23,8 @@ public abstract partial class RenderableBase {
// protected void DrawVertices(float2[] points, float2[]? uvs, color[]? colors, ushort[] indexes) =>
// renderer.backend.DrawMesh(points, uvs, colors, indexes, transform.Get(this));
protected void DrawMesh(Material? material) {
canvas.renderer.backend.DrawMesh(mesh!, material, transform);
}
protected void DrawMesh(Material? mat) => DrawMesh(mat, mesh!);
protected void DrawMesh(Material? mat, Mesh m) => canvas.renderer.backend.DrawMesh(m, mat, transform);
// protected void DrawText(string txt, float2 pos, color col, FontData font, float scale = 12) =>
// renderer.backend.DrawText(txt, col, font, transform + new RenderableTransform(pos, scale, float3.zero));

Просмотреть файл

@ -63,13 +63,21 @@ public class Mesh : IDisposable {
public void AddVertex(Vertex v) => vertices.Add(v);
public void AddIndex(int v) => indexes.Add((ushort) v);
/// <summary>add quadrilateral to mesh (vertices and indices) <br/><br/>use front normal and 0-1 uv coordinates</summary>
/// <summary>add quadrilateral to mesh (vertices and indices) <br/><br/>using front normal and 0-1 uv coordinates</summary>
public void AddRect(float3 p0, float3 p1, float3 p2, float3 p3, color c0) => AddRect(
new(p0, float3.front, new(0,0), c0),
new(p1, float3.front, new(0,1), c0),
new(p2, float3.front, new(1,1), c0),
new(p3, float3.front, new(1,0), c0)
);
/// <summary>add quadrilateral to mesh (vertices and indices) <br/><br/>using front normal</summary>
public void AddRect(float3 p0, float3 p1, float3 p2, float3 p3, color c0, rect uvs) => AddRect(
new(p0, float3.front, uvs.leftBottom, c0),
new(p1, float3.front, uvs.leftTop, c0),
new(p2, float3.front, uvs.rightTop, c0),
new(p3, float3.front, uvs.rightBottom, c0)
);
/// <summary>add quadrilateral to mesh (vertices and indices)</summary>
public void AddRect(Vertex p0, Vertex p1, Vertex p2, Vertex p3) {

Просмотреть файл

@ -54,6 +54,12 @@
<None Update="data\shaders\text.vert">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="data\shaders\shapes.frag">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="data\shaders\shapes.vert">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.12" />

Просмотреть файл

@ -0,0 +1,50 @@
// PROCESS FRAGMENT
// inputs
in vec3 fragPos;
in vec3 fragNormal;
in vec2 fragUv;
in vec4 fragCol;
// outputs
out vec4 outFragColor;
// some code grabbed from https://thebookofshaders.com/07/
#define PI 3.14159265359
#define TWO_PI 6.28318530718
float circle(vec2 coords, float d) {
const float radius = .9;
float sm = radius * 4 * d;
vec2 dist = coords - vec2(.5);
return 1.0 - smoothstep(radius - sm, radius + sm, dot(dist, dist) * 4);
}
float triangle(vec2 coords, float d) {
float sm = 2.5 * d;
coords = coords * 2.0 - 1.0;
const float n = 3;
float angle = atan(coords.x, coords.y) + PI;
float radius = TWO_PI / n;
float dist = cos(floor(.5 + angle / radius) * radius - angle) * length(coords);
return 1.0 - smoothstep(.5 - sm, .5 + sm, dist);
}
float quad(vec2 coords, float d) {
float sm = 2.5 * d;
coords = coords * 2.0 - 1.0;
float dist = max(abs(coords.x), abs(coords.y));
return 1.0 - smoothstep(.8 - sm, .8 + sm, dist);
}
float sample(vec2 coords) {
float d = dFdx(coords.x);
if (coords.x <= 1.0) return circle(coords, d);
if (coords.x <= 2.0) return triangle(coords - vec2(1,0), d);
return quad(coords - vec2(2,0), d);
}
void main() {
gl_FragColor = sample(fragUv) * fragCol;
}

Просмотреть файл

@ -0,0 +1,27 @@
// PROCESS VERTEX
// attributes
in vec3 pos;
in vec3 normal;
in vec2 uv;
in vec4 col;
// uniforms
uniform mat4 mvp;
// output
out vec3 fragPos;
out vec3 fragNormal;
out vec2 fragUv;
out vec4 fragCol;
void main() {
float scale = 1.0;
vec3 scaledPos = pos * scale;
gl_Position = mvp * vec4(scaledPos, 1.0);
fragPos = gl_Position.xyz;
fragUv = uv;
fragCol = col;
fragNormal = normal;
}

Просмотреть файл

@ -75,7 +75,6 @@ public class AvaloniaGlChartsCanvas : CustomGlControlBase {
}
protected override void OnOpenGlRender(GlInterface gl, int framebuffer) {
gl.Enable(GL_DEPTH_TEST);
gl.Enable(GL_MULTISAMPLE);
gl.Enable(GL_BLEND);

Просмотреть файл

@ -3,6 +3,7 @@ namespace SomeChartsUiAvalonia.utils;
public static class GlShaders {
public static readonly GlShader basic = GlShader.LoadFrom("basic", "data/shaders/basic.vert", "data/shaders/basic.frag");
public static readonly GlShader basicTextured = GlShader.LoadFrom("basicTextured", "data/shaders/basicTextured.vert", "data/shaders/basicTextured.frag");
public static readonly GlShader shapes = GlShader.LoadFrom("shapes", "data/shaders/shapes.vert", "data/shaders/shapes.frag");
public static readonly GlShader fxaa = GlShader.LoadFrom("fxaa", "data/shaders/fxaa.vert", "data/shaders/fxaa.frag");
public static readonly GlShader bloom = GlShader.LoadFrom("bloom", "data/shaders/bloom.vert", "data/shaders/bloom.frag");
public static readonly GlShader basicText = GlShader.LoadFrom("text", "data/shaders/text.vert", "data/shaders/text.frag");

Просмотреть файл

@ -52,14 +52,14 @@ public class GlTexture : Texture, IDisposable {
using ILockedFramebuffer lockedFramebuffer = bitmap!.Lock();
IntPtr ptr = lockedFramebuffer.Address;
(int type, int format) = lockedFramebuffer.Format switch {
PixelFormat.Rgb565 => (GlConsts.GL_UNSIGNED_SHORT_5_6_5, GlConsts.GL_RGB),
PixelFormat.Rgba8888 => (GlConsts.GL_UNSIGNED_INT_8_8_8_8, GlConsts.GL_RGBA),
PixelFormat.Bgra8888 => (GlConsts.GL_UNSIGNED_INT_8_8_8_8, GlConsts.GL_BGRA),
(int type, int format, int internalFormat) = lockedFramebuffer.Format switch {
PixelFormat.Rgb565 => (GlConsts.GL_UNSIGNED_SHORT_5_6_5, GlConsts.GL_RGB, GlConsts.GL_RGB),
PixelFormat.Rgba8888 => (GlConsts.GL_UNSIGNED_INT_8_8_8_8, GlConsts.GL_RGBA, GlConsts.GL_RGBA),
PixelFormat.Bgra8888 => (GlConsts.GL_UNSIGNED_INT_8_8_8_8, GlConsts.GL_BGRA, GlConsts.GL_RGBA),
_ => throw new ArgumentOutOfRangeException()
};
GlInfo.gl.TexImage2D(GlConsts.GL_TEXTURE_2D, 0, GlConsts.GL_RGB, (int)bitmap.Size.Width, (int)bitmap.Size.Height, 0, format, type, ptr);
GlInfo.gl.TexImage2D(GlConsts.GL_TEXTURE_2D, 0, internalFormat, (int) size.x, (int) size.y, 0, format, type, ptr);
}
public void Bind() {