add ScatterChart example, ScatterChart shape support, fix texture alpha chanel
This commit is contained in:
Родитель
db9466a68d
Коммит
191f5e9967
|
@ -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" />
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 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() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче