560 changed files with 42270 additions and 4941 deletions
@ -1,6 +1,6 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="SkiaSharp" Version="1.68.1" /> |
<PackageReference Include="SkiaSharp" Version="1.68.2.1" /> |
||||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1" /> |
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.2.1" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,89 @@ |
|||||
|
#include "common.h" |
||||
|
|
||||
|
extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop) |
||||
|
{ |
||||
|
int effects = 0; |
||||
|
if((nsop & NSDragOperationCopy) != 0) |
||||
|
effects |= (int)AvnDragDropEffects::Copy; |
||||
|
if((nsop & NSDragOperationMove) != 0) |
||||
|
effects |= (int)AvnDragDropEffects::Move; |
||||
|
if((nsop & NSDragOperationLink) != 0) |
||||
|
effects |= (int)AvnDragDropEffects::Link; |
||||
|
return (AvnDragDropEffects)effects; |
||||
|
}; |
||||
|
|
||||
|
extern NSString* GetAvnCustomDataType() |
||||
|
{ |
||||
|
char buffer[256]; |
||||
|
sprintf(buffer, "net.avaloniaui.inproc.uti.n%in", getpid()); |
||||
|
return [NSString stringWithUTF8String:buffer]; |
||||
|
} |
||||
|
|
||||
|
@interface AvnDndSource : NSObject<NSDraggingSource> |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
@implementation AvnDndSource |
||||
|
{ |
||||
|
NSDragOperation _operation; |
||||
|
ComPtr<IAvnDndResultCallback> _cb; |
||||
|
void* _sourceHandle; |
||||
|
}; |
||||
|
|
||||
|
- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context |
||||
|
{ |
||||
|
return NSDragOperationCopy; |
||||
|
} |
||||
|
|
||||
|
- (AvnDndSource*) initWithOperation: (NSDragOperation)operation |
||||
|
andCallback: (IAvnDndResultCallback*) cb |
||||
|
andSourceHandle: (void*) handle |
||||
|
{ |
||||
|
self = [super init]; |
||||
|
_operation = operation; |
||||
|
_cb = cb; |
||||
|
_sourceHandle = handle; |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (void)draggingSession:(NSDraggingSession *)session |
||||
|
endedAtPoint:(NSPoint)screenPoint |
||||
|
operation:(NSDragOperation)operation |
||||
|
{ |
||||
|
if(_cb != nil) |
||||
|
{ |
||||
|
auto cb = _cb; |
||||
|
_cb = nil; |
||||
|
cb->OnDragAndDropComplete(ConvertDragDropEffects(operation)); |
||||
|
} |
||||
|
if(_sourceHandle != nil) |
||||
|
{ |
||||
|
FreeAvnGCHandle(_sourceHandle); |
||||
|
_sourceHandle = nil; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void*) gcHandle |
||||
|
{ |
||||
|
return _sourceHandle; |
||||
|
} |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
extern NSObject<NSDraggingSource>* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle) |
||||
|
{ |
||||
|
return [[AvnDndSource alloc] initWithOperation:op andCallback:cb andSourceHandle:handle]; |
||||
|
}; |
||||
|
|
||||
|
extern void* GetAvnDataObjectHandleFromDraggingInfo(NSObject<NSDraggingInfo>* info) |
||||
|
{ |
||||
|
id obj = [info draggingSource]; |
||||
|
if(obj == nil) |
||||
|
return nil; |
||||
|
if([obj isKindOfClass: [AvnDndSource class]]) |
||||
|
{ |
||||
|
auto src = (AvnDndSource*)obj; |
||||
|
return [src gcHandle]; |
||||
|
} |
||||
|
return nil; |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="ControlCatalog.Pages.OpenGlPage" |
||||
|
xmlns:pages="clr-namespace:ControlCatalog.Pages"> |
||||
|
<Grid> |
||||
|
<pages:OpenGlPageControl x:Name="GL"/> |
||||
|
<StackPanel> |
||||
|
<TextBlock Text="{Binding #GL.Info}"/> |
||||
|
</StackPanel> |
||||
|
<Grid ColumnDefinitions="*,Auto" Margin="20"> |
||||
|
<StackPanel Grid.Column="1" MinWidth="300"> |
||||
|
<TextBlock>Yaw</TextBlock> |
||||
|
<Slider Value="{Binding Yaw, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
||||
|
<TextBlock>Pitch</TextBlock> |
||||
|
<Slider Value="{Binding Pitch, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
||||
|
<TextBlock>Roll</TextBlock> |
||||
|
<Slider Value="{Binding Roll, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
||||
|
<StackPanel Orientation="Horizontal"> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#C000C0">D</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#00C090">I</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#90C000">S</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#C09000">C</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#00C090">O</TextBlock> |
||||
|
</StackPanel> |
||||
|
<Slider Value="{Binding Disco, Mode=TwoWay, ElementName=GL}" Maximum="1"/> |
||||
|
</StackPanel> |
||||
|
</Grid> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,401 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.Platform.Interop; |
||||
|
using Avalonia.Threading; |
||||
|
using static Avalonia.OpenGL.GlConsts; |
||||
|
// ReSharper disable StringLiteralTypo
|
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class OpenGlPage : UserControl |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public class OpenGlPageControl : OpenGlControlBase |
||||
|
{ |
||||
|
private float _yaw; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, float> YawProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Yaw", o => o.Yaw, (o, v) => o.Yaw = v); |
||||
|
|
||||
|
public float Yaw |
||||
|
{ |
||||
|
get => _yaw; |
||||
|
set => SetAndRaise(YawProperty, ref _yaw, value); |
||||
|
} |
||||
|
|
||||
|
private float _pitch; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, float> PitchProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Pitch", o => o.Pitch, (o, v) => o.Pitch = v); |
||||
|
|
||||
|
public float Pitch |
||||
|
{ |
||||
|
get => _pitch; |
||||
|
set => SetAndRaise(PitchProperty, ref _pitch, value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private float _roll; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, float> RollProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Roll", o => o.Roll, (o, v) => o.Roll = v); |
||||
|
|
||||
|
public float Roll |
||||
|
{ |
||||
|
get => _roll; |
||||
|
set => SetAndRaise(RollProperty, ref _roll, value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private float _disco; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, float> DiscoProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Disco", o => o.Disco, (o, v) => o.Disco = v); |
||||
|
|
||||
|
public float Disco |
||||
|
{ |
||||
|
get => _disco; |
||||
|
set => SetAndRaise(DiscoProperty, ref _disco, value); |
||||
|
} |
||||
|
|
||||
|
private string _info; |
||||
|
|
||||
|
public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty = |
||||
|
AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v); |
||||
|
|
||||
|
public string Info |
||||
|
{ |
||||
|
get => _info; |
||||
|
private set => SetAndRaise(InfoProperty, ref _info, value); |
||||
|
} |
||||
|
|
||||
|
static OpenGlPageControl() |
||||
|
{ |
||||
|
AffectsRender<OpenGlPageControl>(YawProperty, PitchProperty, RollProperty, DiscoProperty); |
||||
|
} |
||||
|
|
||||
|
private int _vertexShader; |
||||
|
private int _fragmentShader; |
||||
|
private int _shaderProgram; |
||||
|
private int _vertexBufferObject; |
||||
|
private int _indexBufferObject; |
||||
|
private int _vertexArrayObject; |
||||
|
private GlExtrasInterface _glExt; |
||||
|
|
||||
|
private string GetShader(bool fragment, string shader) |
||||
|
{ |
||||
|
var version = (GlVersion.Type == GlProfileType.OpenGL ? |
||||
|
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 150 : 120 : |
||||
|
100); |
||||
|
var data = "#version " + version + "\n"; |
||||
|
if (GlVersion.Type == GlProfileType.OpenGLES) |
||||
|
data += "precision mediump float;\n"; |
||||
|
if (version >= 150) |
||||
|
{ |
||||
|
shader = shader.Replace("attribute", "in"); |
||||
|
if (fragment) |
||||
|
shader = shader |
||||
|
.Replace("varying", "in") |
||||
|
.Replace("//DECLAREGLFRAG", "out vec4 outFragColor;") |
||||
|
.Replace("gl_FragColor", "outFragColor"); |
||||
|
else |
||||
|
shader = shader.Replace("varying", "out"); |
||||
|
} |
||||
|
|
||||
|
data += shader; |
||||
|
|
||||
|
return data; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private string VertexShaderSource => GetShader(false, @"
|
||||
|
attribute vec3 aPos; |
||||
|
attribute vec3 aNormal; |
||||
|
uniform mat4 uModel; |
||||
|
uniform mat4 uProjection; |
||||
|
uniform mat4 uView; |
||||
|
|
||||
|
varying vec3 FragPos; |
||||
|
varying vec3 VecPos; |
||||
|
varying vec3 Normal; |
||||
|
uniform float uTime; |
||||
|
uniform float uDisco; |
||||
|
void main() |
||||
|
{ |
||||
|
float discoScale = sin(uTime * 10.0) / 10.0; |
||||
|
float distortionX = 1.0 + uDisco * cos(uTime * 20.0) / 10.0; |
||||
|
|
||||
|
float scale = 1.0 + uDisco * discoScale; |
||||
|
|
||||
|
vec3 scaledPos = aPos; |
||||
|
scaledPos.x = scaledPos.x * distortionX; |
||||
|
|
||||
|
scaledPos *= scale; |
||||
|
gl_Position = uProjection * uView * uModel * vec4(scaledPos, 1.0); |
||||
|
FragPos = vec3(uModel * vec4(aPos, 1.0)); |
||||
|
VecPos = aPos; |
||||
|
Normal = normalize(vec3(uModel * vec4(aNormal, 1.0))); |
||||
|
} |
||||
|
");
|
||||
|
|
||||
|
private string FragmentShaderSource => GetShader(true, @"
|
||||
|
varying vec3 FragPos; |
||||
|
varying vec3 VecPos; |
||||
|
varying vec3 Normal; |
||||
|
uniform float uMaxY; |
||||
|
uniform float uMinY; |
||||
|
uniform float uTime; |
||||
|
uniform float uDisco; |
||||
|
//DECLAREGLFRAG
|
||||
|
|
||||
|
void main() |
||||
|
{ |
||||
|
float y = (VecPos.y - uMinY) / (uMaxY - uMinY); |
||||
|
float c = cos(atan(VecPos.x, VecPos.z) * 20.0 + uTime * 40.0 + y * 50.0); |
||||
|
float s = sin(-atan(VecPos.z, VecPos.x) * 20.0 - uTime * 20.0 - y * 30.0); |
||||
|
|
||||
|
vec3 discoColor = vec3( |
||||
|
0.5 + abs(0.5 - y) * cos(uTime * 10.0), |
||||
|
0.25 + (smoothstep(0.3, 0.8, y) * (0.5 - c / 4.0)), |
||||
|
0.25 + abs((smoothstep(0.1, 0.4, y) * (0.5 - s / 4.0)))); |
||||
|
|
||||
|
vec3 objectColor = vec3((1.0 - y), 0.40 + y / 4.0, y * 0.75 + 0.25); |
||||
|
objectColor = objectColor * (1.0 - uDisco) + discoColor * uDisco; |
||||
|
|
||||
|
float ambientStrength = 0.3; |
||||
|
vec3 lightColor = vec3(1.0, 1.0, 1.0); |
||||
|
vec3 lightPos = vec3(uMaxY * 2.0, uMaxY * 2.0, uMaxY * 2.0); |
||||
|
vec3 ambient = ambientStrength * lightColor; |
||||
|
|
||||
|
|
||||
|
vec3 norm = normalize(Normal); |
||||
|
vec3 lightDir = normalize(lightPos - FragPos); |
||||
|
|
||||
|
float diff = max(dot(norm, lightDir), 0.0); |
||||
|
vec3 diffuse = diff * lightColor; |
||||
|
|
||||
|
vec3 result = (ambient + diffuse) * objectColor; |
||||
|
gl_FragColor = vec4(result, 1.0); |
||||
|
|
||||
|
} |
||||
|
");
|
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)] |
||||
|
private struct Vertex |
||||
|
{ |
||||
|
public Vector3 Position; |
||||
|
public Vector3 Normal; |
||||
|
} |
||||
|
|
||||
|
private readonly Vertex[] _points; |
||||
|
private readonly ushort[] _indices; |
||||
|
private readonly float _minY; |
||||
|
private readonly float _maxY; |
||||
|
|
||||
|
|
||||
|
public OpenGlPageControl() |
||||
|
{ |
||||
|
var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin")); |
||||
|
using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name))) |
||||
|
{ |
||||
|
var buf = new byte[sr.ReadInt32()]; |
||||
|
sr.Read(buf, 0, buf.Length); |
||||
|
var points = new float[buf.Length / 4]; |
||||
|
Buffer.BlockCopy(buf, 0, points, 0, buf.Length); |
||||
|
buf = new byte[sr.ReadInt32()]; |
||||
|
sr.Read(buf, 0, buf.Length); |
||||
|
_indices = new ushort[buf.Length / 2]; |
||||
|
Buffer.BlockCopy(buf, 0, _indices, 0, buf.Length); |
||||
|
_points = new Vertex[points.Length / 3]; |
||||
|
for (var primitive = 0; primitive < points.Length / 3; primitive++) |
||||
|
{ |
||||
|
var srci = primitive * 3; |
||||
|
_points[primitive] = new Vertex |
||||
|
{ |
||||
|
Position = new Vector3(points[srci], points[srci + 1], points[srci + 2]) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < _indices.Length; i += 3) |
||||
|
{ |
||||
|
Vector3 a = _points[_indices[i]].Position; |
||||
|
Vector3 b = _points[_indices[i + 1]].Position; |
||||
|
Vector3 c = _points[_indices[i + 2]].Position; |
||||
|
var normal = Vector3.Normalize(Vector3.Cross(c - b, a - b)); |
||||
|
|
||||
|
_points[_indices[i]].Normal += normal; |
||||
|
_points[_indices[i + 1]].Normal += normal; |
||||
|
_points[_indices[i + 2]].Normal += normal; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < _points.Length; i++) |
||||
|
{ |
||||
|
_points[i].Normal = Vector3.Normalize(_points[i].Normal); |
||||
|
_maxY = Math.Max(_maxY, _points[i].Position.Y); |
||||
|
_minY = Math.Min(_minY, _points[i].Position.Y); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void CheckError(GlInterface gl) |
||||
|
{ |
||||
|
int err; |
||||
|
while ((err = gl.GetError()) != GL_NO_ERROR) |
||||
|
Console.WriteLine(err); |
||||
|
} |
||||
|
|
||||
|
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb) |
||||
|
{ |
||||
|
CheckError(GL); |
||||
|
_glExt = new GlExtrasInterface(GL); |
||||
|
|
||||
|
Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}"; |
||||
|
|
||||
|
// Load the source of the vertex shader and compile it.
|
||||
|
_vertexShader = GL.CreateShader(GL_VERTEX_SHADER); |
||||
|
Console.WriteLine(GL.CompileShaderAndGetError(_vertexShader, VertexShaderSource)); |
||||
|
|
||||
|
// Load the source of the fragment shader and compile it.
|
||||
|
_fragmentShader = GL.CreateShader(GL_FRAGMENT_SHADER); |
||||
|
Console.WriteLine(GL.CompileShaderAndGetError(_fragmentShader, FragmentShaderSource)); |
||||
|
|
||||
|
// Create the shader program, attach the vertex and fragment shaders and link the program.
|
||||
|
_shaderProgram = GL.CreateProgram(); |
||||
|
GL.AttachShader(_shaderProgram, _vertexShader); |
||||
|
GL.AttachShader(_shaderProgram, _fragmentShader); |
||||
|
const int positionLocation = 0; |
||||
|
const int normalLocation = 1; |
||||
|
GL.BindAttribLocationString(_shaderProgram, positionLocation, "aPos"); |
||||
|
GL.BindAttribLocationString(_shaderProgram, normalLocation, "aNormal"); |
||||
|
Console.WriteLine(GL.LinkProgramAndGetError(_shaderProgram)); |
||||
|
CheckError(GL); |
||||
|
|
||||
|
// Create the vertex buffer object (VBO) for the vertex data.
|
||||
|
_vertexBufferObject = GL.GenBuffer(); |
||||
|
// Bind the VBO and copy the vertex data into it.
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
||||
|
CheckError(GL); |
||||
|
var vertexSize = Marshal.SizeOf<Vertex>(); |
||||
|
fixed (void* pdata = _points) |
||||
|
GL.BufferData(GL_ARRAY_BUFFER, new IntPtr(_points.Length * vertexSize), |
||||
|
new IntPtr(pdata), GL_STATIC_DRAW); |
||||
|
|
||||
|
_indexBufferObject = GL.GenBuffer(); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
||||
|
CheckError(GL); |
||||
|
fixed (void* pdata = _indices) |
||||
|
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata), |
||||
|
GL_STATIC_DRAW); |
||||
|
CheckError(GL); |
||||
|
_vertexArrayObject = _glExt.GenVertexArray(); |
||||
|
_glExt.BindVertexArray(_vertexArrayObject); |
||||
|
CheckError(GL); |
||||
|
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT, |
||||
|
0, vertexSize, IntPtr.Zero); |
||||
|
GL.VertexAttribPointer(normalLocation, 3, GL_FLOAT, |
||||
|
0, vertexSize, new IntPtr(12)); |
||||
|
GL.EnableVertexAttribArray(positionLocation); |
||||
|
GL.EnableVertexAttribArray(normalLocation); |
||||
|
CheckError(GL); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected override void OnOpenGlDeinit(GlInterface GL, int fb) |
||||
|
{ |
||||
|
// Unbind everything
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, 0); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); |
||||
|
_glExt.BindVertexArray(0); |
||||
|
GL.UseProgram(0); |
||||
|
|
||||
|
// Delete all resources.
|
||||
|
GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject }); |
||||
|
_glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject }); |
||||
|
GL.DeleteProgram(_shaderProgram); |
||||
|
GL.DeleteShader(_fragmentShader); |
||||
|
GL.DeleteShader(_vertexShader); |
||||
|
} |
||||
|
|
||||
|
static Stopwatch St = Stopwatch.StartNew(); |
||||
|
protected override unsafe void OnOpenGlRender(GlInterface gl, int fb) |
||||
|
{ |
||||
|
gl.ClearColor(0, 0, 0, 0); |
||||
|
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
||||
|
gl.Enable(GL_DEPTH_TEST); |
||||
|
gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height); |
||||
|
var GL = gl; |
||||
|
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
||||
|
_glExt.BindVertexArray(_vertexArrayObject); |
||||
|
GL.UseProgram(_shaderProgram); |
||||
|
CheckError(GL); |
||||
|
var projection = |
||||
|
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)(Bounds.Width / Bounds.Height), |
||||
|
0.01f, 1000); |
||||
|
|
||||
|
|
||||
|
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0)); |
||||
|
var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll); |
||||
|
var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel"); |
||||
|
var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView"); |
||||
|
var projectionLoc = GL.GetUniformLocationString(_shaderProgram, "uProjection"); |
||||
|
var maxYLoc = GL.GetUniformLocationString(_shaderProgram, "uMaxY"); |
||||
|
var minYLoc = GL.GetUniformLocationString(_shaderProgram, "uMinY"); |
||||
|
var timeLoc = GL.GetUniformLocationString(_shaderProgram, "uTime"); |
||||
|
var discoLoc = GL.GetUniformLocationString(_shaderProgram, "uDisco"); |
||||
|
GL.UniformMatrix4fv(modelLoc, 1, false, &model); |
||||
|
GL.UniformMatrix4fv(viewLoc, 1, false, &view); |
||||
|
GL.UniformMatrix4fv(projectionLoc, 1, false, &projection); |
||||
|
GL.Uniform1f(maxYLoc, _maxY); |
||||
|
GL.Uniform1f(minYLoc, _minY); |
||||
|
GL.Uniform1f(timeLoc, (float)St.Elapsed.TotalSeconds); |
||||
|
GL.Uniform1f(discoLoc, _disco); |
||||
|
CheckError(GL); |
||||
|
GL.DrawElements(GL_TRIANGLES, _indices.Length, GL_UNSIGNED_SHORT, IntPtr.Zero); |
||||
|
|
||||
|
CheckError(GL); |
||||
|
if (_disco > 0.01) |
||||
|
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); |
||||
|
} |
||||
|
|
||||
|
class GlExtrasInterface : GlInterfaceBase<GlInterface.GlContextInfo> |
||||
|
{ |
||||
|
public GlExtrasInterface(GlInterface gl) : base(gl.GetProcAddress, gl.ContextInfo) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public delegate void GlDeleteVertexArrays(int count, int[] buffers); |
||||
|
[GlMinVersionEntryPoint("glDeleteVertexArrays", 3,0)] |
||||
|
[GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")] |
||||
|
public GlDeleteVertexArrays DeleteVertexArrays { get; } |
||||
|
|
||||
|
public delegate void GlBindVertexArray(int array); |
||||
|
[GlMinVersionEntryPoint("glBindVertexArray", 3,0)] |
||||
|
[GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")] |
||||
|
public GlBindVertexArray BindVertexArray { get; } |
||||
|
public delegate void GlGenVertexArrays(int n, int[] rv); |
||||
|
|
||||
|
[GlMinVersionEntryPoint("glGenVertexArrays",3,0)] |
||||
|
[GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")] |
||||
|
public GlGenVertexArrays GenVertexArrays { get; } |
||||
|
|
||||
|
public int GenVertexArray() |
||||
|
{ |
||||
|
var rv = new int[1]; |
||||
|
GenVertexArrays(1, rv); |
||||
|
return rv[0]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Binary file not shown.
@ -0,0 +1,126 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
using System.Linq; |
||||
|
using System.Reactive; |
||||
|
using Avalonia.Controls; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ControlCatalog.ViewModels |
||||
|
{ |
||||
|
public class TreeViewPageViewModel : ReactiveObject |
||||
|
{ |
||||
|
private readonly Node _root; |
||||
|
private SelectionMode _selectionMode; |
||||
|
|
||||
|
public TreeViewPageViewModel() |
||||
|
{ |
||||
|
_root = new Node(); |
||||
|
|
||||
|
Items = _root.Children; |
||||
|
Selection = new SelectionModel(); |
||||
|
Selection.SelectionChanged += SelectionChanged; |
||||
|
|
||||
|
AddItemCommand = ReactiveCommand.Create(AddItem); |
||||
|
RemoveItemCommand = ReactiveCommand.Create(RemoveItem); |
||||
|
SelectRandomItemCommand = ReactiveCommand.Create(SelectRandomItem); |
||||
|
} |
||||
|
|
||||
|
public ObservableCollection<Node> Items { get; } |
||||
|
public SelectionModel Selection { get; } |
||||
|
public ReactiveCommand<Unit, Unit> AddItemCommand { get; } |
||||
|
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; } |
||||
|
public ReactiveCommand<Unit, Unit> SelectRandomItemCommand { get; } |
||||
|
|
||||
|
public SelectionMode SelectionMode |
||||
|
{ |
||||
|
get => _selectionMode; |
||||
|
set |
||||
|
{ |
||||
|
Selection.ClearSelection(); |
||||
|
this.RaiseAndSetIfChanged(ref _selectionMode, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void AddItem() |
||||
|
{ |
||||
|
var parentItem = Selection.SelectedItems.Count > 0 ? (Node)Selection.SelectedItems[0] : _root; |
||||
|
parentItem.AddItem(); |
||||
|
} |
||||
|
|
||||
|
private void RemoveItem() |
||||
|
{ |
||||
|
while (Selection.SelectedItems.Count > 0) |
||||
|
{ |
||||
|
Node lastItem = (Node)Selection.SelectedItems[0]; |
||||
|
RecursiveRemove(Items, lastItem); |
||||
|
Selection.DeselectAt(Selection.SelectedIndices[0]); |
||||
|
} |
||||
|
|
||||
|
bool RecursiveRemove(ObservableCollection<Node> items, Node selectedItem) |
||||
|
{ |
||||
|
if (items.Remove(selectedItem)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
foreach (Node item in items) |
||||
|
{ |
||||
|
if (item.AreChildrenInitialized && RecursiveRemove(item.Children, selectedItem)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SelectRandomItem() |
||||
|
{ |
||||
|
var random = new Random(); |
||||
|
var depth = random.Next(4); |
||||
|
var indexes = Enumerable.Range(0, 4).Select(x => random.Next(10)); |
||||
|
var path = new IndexPath(indexes); |
||||
|
Selection.SelectedIndex = path; |
||||
|
} |
||||
|
|
||||
|
private void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) |
||||
|
{ |
||||
|
var selected = string.Join(",", e.SelectedIndices); |
||||
|
var deselected = string.Join(",", e.DeselectedIndices); |
||||
|
System.Diagnostics.Debug.WriteLine($"Selected '{selected}', Deselected '{deselected}'"); |
||||
|
} |
||||
|
|
||||
|
public class Node |
||||
|
{ |
||||
|
private ObservableCollection<Node> _children; |
||||
|
private int _childIndex = 10; |
||||
|
|
||||
|
public Node() |
||||
|
{ |
||||
|
Header = "Item"; |
||||
|
} |
||||
|
|
||||
|
public Node(Node parent, int index) |
||||
|
{ |
||||
|
Parent = parent; |
||||
|
Header = parent.Header + ' ' + index; |
||||
|
} |
||||
|
|
||||
|
public Node Parent { get; } |
||||
|
public string Header { get; } |
||||
|
public bool AreChildrenInitialized => _children != null; |
||||
|
public ObservableCollection<Node> Children => _children ??= CreateChildren(); |
||||
|
public void AddItem() => Children.Add(new Node(this, _childIndex++)); |
||||
|
public void RemoveItem(Node child) => Children.Remove(child); |
||||
|
public override string ToString() => Header; |
||||
|
|
||||
|
private ObservableCollection<Node> CreateChildren() |
||||
|
{ |
||||
|
return new ObservableCollection<Node>( |
||||
|
Enumerable.Range(0, 10).Select(i => new Node(this, i))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,7 @@ |
|||||
<Project> |
<Project> |
||||
<PropertyGroup> |
<PropertyGroup> |
||||
<IsPackable>false</IsPackable> |
<IsPackable>false</IsPackable> |
||||
|
<AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\netcoreapp2.0\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath> |
||||
</PropertyGroup> |
</PropertyGroup> |
||||
<Import Project="..\build\SharedVersion.props" /> |
<Import Project="..\build\SharedVersion.props" /> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,53 @@ |
|||||
|
using System; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Rendering.SceneGraph; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace RenderDemo.Controls |
||||
|
{ |
||||
|
public class LineBoundsDemoControl : Control |
||||
|
{ |
||||
|
static LineBoundsDemoControl() |
||||
|
{ |
||||
|
AffectsRender<LineBoundsDemoControl>(AngleProperty); |
||||
|
} |
||||
|
|
||||
|
public LineBoundsDemoControl() |
||||
|
{ |
||||
|
var timer = new DispatcherTimer(); |
||||
|
timer.Interval = TimeSpan.FromSeconds(1 / 60.0); |
||||
|
timer.Tick += (sender, e) => Angle += Math.PI / 360; |
||||
|
timer.Start(); |
||||
|
} |
||||
|
|
||||
|
public static readonly StyledProperty<double> AngleProperty = |
||||
|
AvaloniaProperty.Register<LineBoundsDemoControl, double>(nameof(Angle)); |
||||
|
|
||||
|
public double Angle |
||||
|
{ |
||||
|
get => GetValue(AngleProperty); |
||||
|
set => SetValue(AngleProperty, value); |
||||
|
} |
||||
|
|
||||
|
public override void Render(DrawingContext drawingContext) |
||||
|
{ |
||||
|
var lineLength = Math.Sqrt((100 * 100) + (100 * 100)); |
||||
|
|
||||
|
var diffX = LineBoundsHelper.CalculateAdjSide(Angle, lineLength); |
||||
|
var diffY = LineBoundsHelper.CalculateOppSide(Angle, lineLength); |
||||
|
|
||||
|
|
||||
|
var p1 = new Point(200, 200); |
||||
|
var p2 = new Point(p1.X + diffX, p1.Y + diffY); |
||||
|
|
||||
|
var pen = new Pen(Brushes.Green, 20, lineCap: PenLineCap.Square); |
||||
|
var boundPen = new Pen(Brushes.Black); |
||||
|
|
||||
|
drawingContext.DrawLine(pen, p1, p2); |
||||
|
|
||||
|
drawingContext.DrawRectangle(boundPen, LineBoundsHelper.CalculateBounds(p1, p2, pen)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
|
xmlns:controls="clr-namespace:RenderDemo.Controls" |
||||
|
x:Class="RenderDemo.Pages.LineBoundsPage"> |
||||
|
<controls:LineBoundsDemoControl /> |
||||
|
</UserControl> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace RenderDemo.Pages |
||||
|
{ |
||||
|
public class LineBoundsPage : UserControl |
||||
|
{ |
||||
|
public LineBoundsPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,5 +1,5 @@ |
|||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\ |
||||
|
|||||
@ -1,5 +1,5 @@ |
|||||
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\ |
||||
@ -0,0 +1,349 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using System.Globalization; |
||||
|
using System.Text; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
// Ported from WPF open-source code.
|
||||
|
// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
|
||||
|
|
||||
|
namespace Avalonia.Animation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Determines how an animation is used based on a cubic bezier curve.
|
||||
|
/// X1 and X2 must be between 0.0 and 1.0, inclusive.
|
||||
|
/// See https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.animation.keyspline
|
||||
|
/// </summary>
|
||||
|
[TypeConverter(typeof(KeySplineTypeConverter))] |
||||
|
public class KeySpline : AvaloniaObject |
||||
|
{ |
||||
|
// Control points
|
||||
|
private double _controlPointX1; |
||||
|
private double _controlPointY1; |
||||
|
private double _controlPointX2; |
||||
|
private double _controlPointY2; |
||||
|
private bool _isSpecified; |
||||
|
private bool _isDirty; |
||||
|
|
||||
|
// The parameter that corresponds to the most recent time
|
||||
|
private double _parameter; |
||||
|
|
||||
|
// Cached coefficients
|
||||
|
private double _Bx; // 3*points[0].X
|
||||
|
private double _Cx; // 3*points[1].X
|
||||
|
private double _Cx_Bx; // 2*(Cx - Bx)
|
||||
|
private double _three_Cx; // 3 - Cx
|
||||
|
|
||||
|
private double _By; // 3*points[0].Y
|
||||
|
private double _Cy; // 3*points[1].Y
|
||||
|
|
||||
|
// constants
|
||||
|
private const double _accuracy = .001; // 1/3 the desired accuracy in X
|
||||
|
private const double _fuzz = .000001; // computational zero
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create a <see cref="KeySpline"/> with X1 = Y1 = 0 and X2 = Y2 = 1.
|
||||
|
/// </summary>
|
||||
|
public KeySpline() |
||||
|
{ |
||||
|
_controlPointX1 = 0.0; |
||||
|
_controlPointY1 = 0.0; |
||||
|
_controlPointX2 = 1.0; |
||||
|
_controlPointY2 = 1.0; |
||||
|
_isDirty = true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create a <see cref="KeySpline"/> with the given parameters
|
||||
|
/// </summary>
|
||||
|
/// <param name="x1">X coordinate for the first control point</param>
|
||||
|
/// <param name="y1">Y coordinate for the first control point</param>
|
||||
|
/// <param name="x2">X coordinate for the second control point</param>
|
||||
|
/// <param name="y2">Y coordinate for the second control point</param>
|
||||
|
public KeySpline(double x1, double y1, double x2, double y2) |
||||
|
{ |
||||
|
_controlPointX1 = x1; |
||||
|
_controlPointY1 = y1; |
||||
|
_controlPointX2 = x2; |
||||
|
_controlPointY2 = y2; |
||||
|
_isDirty = true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Parse a <see cref="KeySpline"/> from a string. The string
|
||||
|
/// needs to contain 4 values in it for the 2 control points.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">string with 4 values in it</param>
|
||||
|
/// <param name="culture">culture of the string</param>
|
||||
|
/// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
|
||||
|
/// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns>
|
||||
|
public static KeySpline Parse(string value, CultureInfo culture) |
||||
|
{ |
||||
|
using (var tokenizer = new StringTokenizer((string)value, CultureInfo.InvariantCulture, exceptionMessage: "Invalid KeySpline.")) |
||||
|
{ |
||||
|
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// X coordinate of the first control point
|
||||
|
/// </summary>
|
||||
|
public double ControlPointX1 |
||||
|
{ |
||||
|
get => _controlPointX1; |
||||
|
set |
||||
|
{ |
||||
|
if (IsValidXValue(value)) |
||||
|
{ |
||||
|
_controlPointX1 = value; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new ArgumentException("Invalid KeySpline X1 value. Must be >= 0.0 and <= 1.0."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Y coordinate of the first control point
|
||||
|
/// </summary>
|
||||
|
public double ControlPointY1 |
||||
|
{ |
||||
|
get => _controlPointY1; |
||||
|
set => _controlPointY1 = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// X coordinate of the second control point
|
||||
|
/// </summary>
|
||||
|
public double ControlPointX2 |
||||
|
{ |
||||
|
get => _controlPointX2; |
||||
|
set |
||||
|
{ |
||||
|
if (IsValidXValue(value)) |
||||
|
{ |
||||
|
_controlPointX2 = value; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new ArgumentException("Invalid KeySpline X2 value. Must be >= 0.0 and <= 1.0."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Y coordinate of the second control point
|
||||
|
/// </summary>
|
||||
|
public double ControlPointY2 |
||||
|
{ |
||||
|
get => _controlPointY2; |
||||
|
set => _controlPointY2 = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates spline progress from a linear progress.
|
||||
|
/// </summary>
|
||||
|
/// <param name="linearProgress">the linear progress</param>
|
||||
|
/// <returns>the spline progress</returns>
|
||||
|
public double GetSplineProgress(double linearProgress) |
||||
|
{ |
||||
|
if (_isDirty) |
||||
|
{ |
||||
|
Build(); |
||||
|
} |
||||
|
|
||||
|
if (!_isSpecified) |
||||
|
{ |
||||
|
return linearProgress; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
SetParameterFromX(linearProgress); |
||||
|
|
||||
|
return GetBezierValue(_By, _Cy, _parameter); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Check to see whether the <see cref="KeySpline"/> is valid by looking
|
||||
|
/// at its X values.
|
||||
|
/// </summary>
|
||||
|
/// <returns>true if the X values for this <see cref="KeySpline"/> fall in
|
||||
|
/// acceptable range; false otherwise.</returns>
|
||||
|
public bool IsValid() |
||||
|
{ |
||||
|
return IsValidXValue(_controlPointX1) && IsValidXValue(_controlPointX2); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
///
|
||||
|
/// </summary>
|
||||
|
/// <param name="value"></param>
|
||||
|
/// <returns></returns>
|
||||
|
private bool IsValidXValue(double value) |
||||
|
{ |
||||
|
return value >= 0.0 && value <= 1.0; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compute cached coefficients.
|
||||
|
/// </summary>
|
||||
|
private void Build() |
||||
|
{ |
||||
|
if (_controlPointX1 == 0 && _controlPointY1 == 0 && _controlPointX2 == 1 && _controlPointY2 == 1) |
||||
|
{ |
||||
|
// This KeySpline would have no effect on the progress.
|
||||
|
_isSpecified = false; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_isSpecified = true; |
||||
|
|
||||
|
_parameter = 0; |
||||
|
|
||||
|
// X coefficients
|
||||
|
_Bx = 3 * _controlPointX1; |
||||
|
_Cx = 3 * _controlPointX2; |
||||
|
_Cx_Bx = 2 * (_Cx - _Bx); |
||||
|
_three_Cx = 3 - _Cx; |
||||
|
|
||||
|
// Y coefficients
|
||||
|
_By = 3 * _controlPointY1; |
||||
|
_Cy = 3 * _controlPointY2; |
||||
|
} |
||||
|
|
||||
|
_isDirty = false; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get an X or Y value with the Bezier formula.
|
||||
|
/// </summary>
|
||||
|
/// <param name="b">the second Bezier coefficient</param>
|
||||
|
/// <param name="c">the third Bezier coefficient</param>
|
||||
|
/// <param name="t">the parameter value to evaluate at</param>
|
||||
|
/// <returns>the value of the Bezier function at the given parameter</returns>
|
||||
|
static private double GetBezierValue(double b, double c, double t) |
||||
|
{ |
||||
|
double s = 1.0 - t; |
||||
|
double t2 = t * t; |
||||
|
|
||||
|
return b * t * s * s + c * t2 * s + t2 * t; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get X and dX/dt at a given parameter
|
||||
|
/// </summary>
|
||||
|
/// <param name="t">the parameter value to evaluate at</param>
|
||||
|
/// <param name="x">the value of x there</param>
|
||||
|
/// <param name="dx">the value of dx/dt there</param>
|
||||
|
private void GetXAndDx(double t, out double x, out double dx) |
||||
|
{ |
||||
|
double s = 1.0 - t; |
||||
|
double t2 = t * t; |
||||
|
double s2 = s * s; |
||||
|
|
||||
|
x = _Bx * t * s2 + _Cx * t2 * s + t2 * t; |
||||
|
dx = _Bx * s2 + _Cx_Bx * s * t + _three_Cx * t2; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compute the parameter value that corresponds to a given X value, using a modified
|
||||
|
/// clamped Newton-Raphson algorithm to solve the equation X(t) - time = 0. We make
|
||||
|
/// use of some known properties of this particular function:
|
||||
|
/// * We are only interested in solutions in the interval [0,1]
|
||||
|
/// * X(t) is increasing, so we can assume that if X(t) > time t > solution. We use
|
||||
|
/// that to clamp down the search interval with every probe.
|
||||
|
/// * The derivative of X and Y are between 0 and 3.
|
||||
|
/// </summary>
|
||||
|
/// <param name="time">the time, scaled to fit in [0,1]</param>
|
||||
|
private void SetParameterFromX(double time) |
||||
|
{ |
||||
|
// Dynamic search interval to clamp with
|
||||
|
double bottom = 0; |
||||
|
double top = 1; |
||||
|
|
||||
|
if (time == 0) |
||||
|
{ |
||||
|
_parameter = 0; |
||||
|
} |
||||
|
else if (time == 1) |
||||
|
{ |
||||
|
_parameter = 1; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Loop while improving the guess
|
||||
|
while (top - bottom > _fuzz) |
||||
|
{ |
||||
|
double x, dx, absdx; |
||||
|
|
||||
|
// Get x and dx/dt at the current parameter
|
||||
|
GetXAndDx(_parameter, out x, out dx); |
||||
|
absdx = Math.Abs(dx); |
||||
|
|
||||
|
// Clamp down the search interval, relying on the monotonicity of X(t)
|
||||
|
if (x > time) |
||||
|
{ |
||||
|
top = _parameter; // because parameter > solution
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
bottom = _parameter; // because parameter < solution
|
||||
|
} |
||||
|
|
||||
|
// The desired accuracy is in ultimately in y, not in x, so the
|
||||
|
// accuracy needs to be multiplied by dx/dy = (dx/dt) / (dy/dt).
|
||||
|
// But dy/dt <=3, so we omit that
|
||||
|
if (Math.Abs(x - time) < _accuracy * absdx) |
||||
|
{ |
||||
|
break; // We're there
|
||||
|
} |
||||
|
|
||||
|
if (absdx > _fuzz) |
||||
|
{ |
||||
|
// Nonzero derivative, use Newton-Raphson to obtain the next guess
|
||||
|
double next = _parameter - (x - time) / dx; |
||||
|
|
||||
|
// If next guess is out of the search interval then clamp it in
|
||||
|
if (next >= top) |
||||
|
{ |
||||
|
_parameter = (_parameter + top) / 2; |
||||
|
} |
||||
|
else if (next <= bottom) |
||||
|
{ |
||||
|
_parameter = (_parameter + bottom) / 2; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Next guess is inside the search interval, accept it
|
||||
|
_parameter = next; |
||||
|
} |
||||
|
} |
||||
|
else // Zero derivative, halve the search interval
|
||||
|
{ |
||||
|
_parameter = (bottom + top) / 2; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts string values to <see cref="KeySpline"/> values
|
||||
|
/// </summary>
|
||||
|
public class KeySplineTypeConverter : TypeConverter |
||||
|
{ |
||||
|
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
||||
|
{ |
||||
|
return sourceType == typeof(string); |
||||
|
} |
||||
|
|
||||
|
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
||||
|
{ |
||||
|
return KeySpline.Parse((string)value, culture); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
|
||||
|
namespace Avalonia.Utilities |
||||
|
{ |
||||
|
public class DisposableLock |
||||
|
{ |
||||
|
private readonly object _lock = new object(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to take a lock
|
||||
|
/// </summary>
|
||||
|
/// <returns>IDisposable if succeeded to obtain the lock</returns>
|
||||
|
public IDisposable TryLock() |
||||
|
{ |
||||
|
if (Monitor.TryEnter(_lock)) |
||||
|
return new UnlockDisposable(_lock); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enters a waiting lock
|
||||
|
/// </summary>
|
||||
|
public IDisposable Lock() |
||||
|
{ |
||||
|
Monitor.Enter(_lock); |
||||
|
return new UnlockDisposable(_lock); |
||||
|
} |
||||
|
|
||||
|
private sealed class UnlockDisposable : IDisposable |
||||
|
{ |
||||
|
private object _lock; |
||||
|
|
||||
|
public UnlockDisposable(object @lock) |
||||
|
{ |
||||
|
_lock = @lock; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
object @lock = Interlocked.Exchange(ref _lock, null); |
||||
|
|
||||
|
if (@lock != null) |
||||
|
{ |
||||
|
Monitor.Exit(@lock); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue