Render Updates
This commit is contained in:
parent
fe606c6e39
commit
2c64caf284
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
using Godot;
|
||||
|
||||
|
||||
namespace Rokojori
|
||||
{
|
||||
[Tool][GlobalClass ]
|
||||
public partial class CopyYawFromDirection : Action
|
||||
{
|
||||
[Export]
|
||||
public Node3D sourceA;
|
||||
|
||||
[Export]
|
||||
public Node3D sourceB;
|
||||
|
||||
[Export]
|
||||
public Node3D target;
|
||||
|
||||
protected override void _OnTrigger()
|
||||
{
|
||||
if ( sourceA == null || sourceB == null || target == null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var direction = sourceB.GlobalPosition - sourceA.GlobalPosition;
|
||||
var yaw = Math3D.GlobalYaw( direction );
|
||||
target.SetGlobalYaw( yaw );
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://barfbywochbs3
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
|
||||
namespace Rokojori
|
||||
{
|
||||
[Tool]
|
||||
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Tween.svg")]
|
||||
public partial class TweenColor:SequenceAction, Animator
|
||||
{
|
||||
[Export]
|
||||
public GodotObject target;
|
||||
|
||||
[Export]
|
||||
public string targetMemberPath;
|
||||
|
||||
[Export]
|
||||
public Color endValue;
|
||||
|
||||
[Export]
|
||||
public Duration duration;
|
||||
|
||||
[Export]
|
||||
public Curve curve;
|
||||
|
||||
public void OnAnimatorStart(){}
|
||||
public void OnAnimatorEnd(){}
|
||||
public void OnAnimatorCancel(){}
|
||||
|
||||
int _actionID = -1;
|
||||
int _timeID = -1;
|
||||
|
||||
|
||||
|
||||
public Color GetTargetValue( GodotObject go, string targetMember, Color alternative )
|
||||
{
|
||||
var path = targetMember.Split( "/" );
|
||||
var prop = path[ path.Length - 1 ];
|
||||
|
||||
var target = ReflectionHelper.GetMemberByPath<object>( go, path, false );
|
||||
|
||||
return target == null ? alternative : ReflectionHelper.GetValue<Color>( target, prop );
|
||||
|
||||
}
|
||||
|
||||
public void SetTargetValue( GodotObject go, string targetMember, Color value )
|
||||
{
|
||||
var path = targetMember.Split( "/" );
|
||||
var prop = path[ path.Length - 1 ];
|
||||
|
||||
var target = ReflectionHelper.GetMemberByPath<object>( go, path, false );
|
||||
|
||||
if ( target == null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReflectionHelper.SetValue( target, prop, value );
|
||||
}
|
||||
|
||||
protected override void _OnTrigger()
|
||||
{
|
||||
this.LogInfo( "Started Float Tween" );
|
||||
|
||||
|
||||
// if ( Engine.IsEditorHint() )
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if ( _actionID != -1 )
|
||||
{
|
||||
CancelAction( _actionID );
|
||||
}
|
||||
|
||||
_actionID = DispatchStart();
|
||||
|
||||
// var startValue = ReflectionHelper.GetValue<float>( target, targetMember );
|
||||
var startValue = GetTargetValue( target, targetMemberPath, Colors.White );
|
||||
|
||||
AnimationManager.StartAnimation( this, target, targetMemberPath );
|
||||
|
||||
this.LogInfo( "Start Value Float Tween", HierarchyName.OfAny( target ), target.GetType().Name, targetMemberPath, ">>", startValue );
|
||||
|
||||
_timeID = TimeLineManager.ScheduleSpanWith( duration,
|
||||
( span, type )=>
|
||||
{
|
||||
|
||||
// this.LogInfo( "Update Float Tween", startValue );
|
||||
if ( span.id != _timeID )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! AnimationManager.IsAnimating( this, target, targetMemberPath ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var phase = span.phase;
|
||||
|
||||
if ( curve != null )
|
||||
{
|
||||
phase = curve.Sample( phase );
|
||||
}
|
||||
|
||||
var value = ColorX.Lerp( startValue, endValue, phase );
|
||||
|
||||
// this.LogInfo( "Updating Float Tween", "phase:", phase, "value:", value, target );
|
||||
|
||||
// ReflectionHelper.SetValue( target, targetMember, value );
|
||||
// target._Set( targetMember, value );
|
||||
SetTargetValue( target, targetMemberPath, value );
|
||||
|
||||
if ( type == TimeLineSpanUpdateType.End )
|
||||
{
|
||||
// this.LogInfo( "End Float Tween", endValue );
|
||||
|
||||
SetTargetValue( target, targetMemberPath, value );
|
||||
AnimationManager.EndAnimation( this, target, targetMemberPath );
|
||||
DispatchEnd( _actionID );
|
||||
_actionID = -1;
|
||||
_timeID = -1;
|
||||
}
|
||||
},
|
||||
|
||||
this
|
||||
).id;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://c5x4cvwov6iog
|
||||
|
|
@ -194,6 +194,11 @@ namespace Rokojori
|
|||
targets.ForEach(
|
||||
( t )=>
|
||||
{
|
||||
if ( t == null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
highlighted[ t ] = this;
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,20 @@ namespace Rokojori
|
|||
[Export]
|
||||
public TimeLine timeLine;
|
||||
|
||||
[ExportToolButton( "Copy Camera Pose With Target")]
|
||||
public Callable copyCameraPoseWithTargetButton => Callable.From(
|
||||
()=>
|
||||
{
|
||||
#if TOOLS
|
||||
|
||||
var camera = EditorInterface.Singleton.GetEditorViewport3D().GetCamera3D();
|
||||
target.GlobalPosition = camera.GlobalPosition + camera.GlobalForward() * 25;
|
||||
Pose.CopyTo( camera, this );
|
||||
|
||||
#endif
|
||||
}
|
||||
);
|
||||
|
||||
public override void _Process( double delta )
|
||||
{
|
||||
if ( Engine.IsEditorHint() )
|
||||
|
|
|
|||
|
|
@ -18,6 +18,19 @@ namespace Rokojori
|
|||
[Export]
|
||||
public bool inputEnabled = true;
|
||||
|
||||
[ExportToolButton( "Copy Camera Pose")]
|
||||
public Callable copyCameraPoseButton => Callable.From(
|
||||
()=>
|
||||
{
|
||||
#if TOOLS
|
||||
|
||||
var camera = EditorInterface.Singleton.GetEditorViewport3D().GetCamera3D();
|
||||
Pose.CopyTo( camera, this );
|
||||
|
||||
#endif
|
||||
}
|
||||
);
|
||||
|
||||
public Vector3 GetCameraPosition()
|
||||
{
|
||||
return GlobalPosition;
|
||||
|
|
|
|||
|
|
@ -112,6 +112,11 @@ namespace Rokojori
|
|||
return splinePoints;
|
||||
}
|
||||
|
||||
public void ResetCurve()
|
||||
{
|
||||
splineCurve = null;
|
||||
}
|
||||
|
||||
public SplineCurve GetCurve()
|
||||
{
|
||||
if ( splineCurve == null )
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ namespace Rokojori
|
|||
[Export]
|
||||
public bool undistortSplineSegments = false;
|
||||
|
||||
[Export]
|
||||
public bool updateSpline = false;
|
||||
|
||||
protected override void _OnTrigger()
|
||||
{
|
||||
CreateMesh();
|
||||
|
|
@ -41,6 +44,11 @@ namespace Rokojori
|
|||
|
||||
public void CreateMesh()
|
||||
{
|
||||
if ( updateSpline )
|
||||
{
|
||||
spline.ResetCurve();
|
||||
}
|
||||
|
||||
var curve = spline.GetCurve();
|
||||
|
||||
var length = curve.ComputeLength( 100 );
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#[compute]
|
||||
#version 450
|
||||
|
||||
|
||||
layout( local_size_x = 8, local_size_y = 8, local_size_z = 1 ) in;
|
||||
|
||||
layout( rgba16f, set = 0, binding = 0 )
|
||||
|
|
|
|||
|
|
@ -11,23 +11,47 @@ namespace Rokojori
|
|||
{
|
||||
public static readonly string shaderPath = Path( "Vignette/VignetteShader.glsl" );
|
||||
|
||||
[ExportGroup("Vignette")]
|
||||
[Export( PropertyHint.Range, "0,1")]
|
||||
public float amount = 1.0f;
|
||||
|
||||
[Export( PropertyHint.Range, "0.01,10")]
|
||||
public float radius = 0.5f;
|
||||
[Export( PropertyHint.Range, "-1,1")]
|
||||
public float fadePosition;
|
||||
|
||||
[Export( PropertyHint.Range, "-2,2")]
|
||||
public float fadeInOffset = -0.1f;
|
||||
|
||||
[Export( PropertyHint.Range, "-2,2")]
|
||||
public float fadeOutOffset= 0.1f;
|
||||
|
||||
[Export( PropertyHint.Range, "-10,10")]
|
||||
public float fadePower= 1f;
|
||||
|
||||
[Export( PropertyHint.Range, "0,1")]
|
||||
public float ellipseToCircle= 0.5f;
|
||||
|
||||
[ExportGroup("Colors")]
|
||||
|
||||
[Export]
|
||||
public float power = 1.0f;
|
||||
public Color colorTop = Colors.Black;
|
||||
|
||||
[Export]
|
||||
public float offset = 1.0f;
|
||||
|
||||
public Color colorBottom = Colors.Black;
|
||||
|
||||
|
||||
[ExportGroup("Blend Mode")]
|
||||
[Export]
|
||||
public Color colorTop;
|
||||
public float replace = 1.0f;
|
||||
|
||||
[Export]
|
||||
public Color colorBottom;
|
||||
public float add = 0.0f;
|
||||
|
||||
[Export]
|
||||
public float multiply = 0.0f;
|
||||
|
||||
[Export]
|
||||
public float colorize = 0.0f;
|
||||
|
||||
|
||||
[Export]
|
||||
public string info = "";
|
||||
|
|
@ -42,11 +66,18 @@ namespace Rokojori
|
|||
protected override void SetConstants()
|
||||
{
|
||||
constants.Set(
|
||||
colorTop.SrgbToLinear(),
|
||||
colorBottom.SrgbToLinear(),
|
||||
(Vector2) context.internalSize,
|
||||
amount,
|
||||
radius,
|
||||
power,
|
||||
offset,
|
||||
(Vector2) context.internalSize
|
||||
fadePosition + fadeInOffset,
|
||||
fadePosition + fadeOutOffset,
|
||||
ellipseToCircle,
|
||||
Mathf.Pow( 10f, fadePower / 10f ),
|
||||
replace,
|
||||
add,
|
||||
multiply,
|
||||
colorize
|
||||
);
|
||||
|
||||
info = "constants: " + constants.info;
|
||||
|
|
|
|||
|
|
@ -1,41 +1,135 @@
|
|||
#[compute]
|
||||
#version 450
|
||||
|
||||
float clamp01( float value )
|
||||
{
|
||||
return clamp( value, 0.0, 1.0 );
|
||||
}
|
||||
|
||||
vec4 clamp01_v4( vec4 value )
|
||||
{
|
||||
value.r = clamp01( value.r );
|
||||
value.g = clamp01( value.g );
|
||||
value.b = clamp01( value.b );
|
||||
value.a = clamp01( value.a );
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
vec2 clamp01( vec2 value )
|
||||
{
|
||||
return clamp( value, 0.0, 1.0 );
|
||||
}
|
||||
|
||||
vec3 clamp01( vec3 value )
|
||||
{
|
||||
return clamp( value, 0.0, 1.0 );
|
||||
}
|
||||
|
||||
vec4 clamp01( vec4 value )
|
||||
{
|
||||
return clamp( value, 0.0, 1.0 );
|
||||
}
|
||||
|
||||
float normalizeToRange( float value, float min, float max )
|
||||
{
|
||||
return ( value - min ) / ( max - min );
|
||||
}
|
||||
|
||||
float normalizeToRange01( float value, float min, float max )
|
||||
{
|
||||
return clamp01( normalizeToRange( value, min, max ) );
|
||||
}
|
||||
|
||||
float map( float value, float inMin, float inMax, float outMin, float outMax )
|
||||
{
|
||||
return mix( outMin, outMax, normalizeToRange( value, inMin, inMax ) );
|
||||
}
|
||||
|
||||
vec2 map_v2( vec2 value, vec2 inMin, vec2 inMax, vec2 outMin, vec2 outMax )
|
||||
{
|
||||
float x = map( value.x, inMin.x, inMax.x, outMin.x, outMax.x );
|
||||
float y = map( value.y, inMin.y, inMax.y, outMin.y, outMax.y );
|
||||
|
||||
return vec2( x, y );
|
||||
}
|
||||
|
||||
float mapClamped( float value, float inMin, float inMax, float outMin, float outMax )
|
||||
{
|
||||
return mix( outMin, outMax, normalizeToRange01( value, inMin, inMax ) );
|
||||
}
|
||||
|
||||
vec3 colorize( vec3 original, vec3 color )
|
||||
{
|
||||
float grey = ( original.r + original.g + original.b ) / 3.0;
|
||||
|
||||
return grey < 0.5 ? mix( vec3( 0.0 ), color, grey * 2.0 ) :
|
||||
mix( color, vec3( 1.0 ), ( grey - 0.5 ) * 2.0 );
|
||||
}
|
||||
|
||||
layout( local_size_x = 16, local_size_y = 16, local_size_z = 1 ) in;
|
||||
|
||||
layout( rgba16f, set = 0, binding = 0)
|
||||
uniform image2D color_image;
|
||||
uniform image2D screenImage;
|
||||
|
||||
layout(push_constant, std430)
|
||||
uniform Params
|
||||
{
|
||||
float amount;
|
||||
float radius;
|
||||
float power;
|
||||
float offset;
|
||||
vec2 raster_size;
|
||||
layout( push_constant, std430 )
|
||||
uniform Parameters
|
||||
{
|
||||
vec4 topColor; // 4
|
||||
vec4 bottomColor; // 8
|
||||
vec2 rasterSize; // 6
|
||||
float amount; // 7
|
||||
float fadeIn; // 8
|
||||
float fadeOut; // 9
|
||||
|
||||
} params;
|
||||
float circelize; // 10
|
||||
float power; // 12
|
||||
|
||||
float replace; // 13
|
||||
float add; // 14
|
||||
float multiply; // 15
|
||||
float colorize; // 16
|
||||
|
||||
|
||||
} parameters;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 uv = ivec2( gl_GlobalInvocationID.xy );
|
||||
ivec2 size = ivec2( params.raster_size );
|
||||
ivec2 coordinate = ivec2( gl_GlobalInvocationID.xy );
|
||||
ivec2 size = ivec2( parameters.rasterSize );
|
||||
|
||||
if ( uv.x >= size.x || uv.y >= size.y )
|
||||
if ( coordinate.x >= size.x || coordinate.y >= size.y )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vec4 color = imageLoad( color_image, uv );
|
||||
vec4 color = imageLoad( screenImage, coordinate );
|
||||
// color = vec4( 0.0, 0.0, 0.0, 1.0 );
|
||||
vec2 uv = ( vec2( coordinate ) + vec2( 0.5 ) ) / vec2( parameters.rasterSize ) ;
|
||||
|
||||
float d = length( uv - size/2 ) / length( size/2 * params.radius );
|
||||
d = pow( d, params.power );
|
||||
float amount = clamp( 1.0 - d + params.offset, 0.0, 1.0 );
|
||||
float ratio = parameters.rasterSize.y / parameters.rasterSize.x;
|
||||
|
||||
uv.y = mix( uv.y, ( uv.y - 0.5 ) * ratio + 0.5, parameters.circelize );
|
||||
|
||||
float centerDistance = length( uv - vec2( 0.5 ) );
|
||||
float distanceAmount = mapClamped( centerDistance, parameters.fadeIn, parameters.fadeOut, 0.0, 1.0 );
|
||||
distanceAmount = pow( distanceAmount, parameters.power );
|
||||
|
||||
vec4 vignetteColor = mix( parameters.topColor, parameters.bottomColor, uv.y );
|
||||
|
||||
vec4 multiplied = vignetteColor * color;
|
||||
vec4 added = vignetteColor + color;
|
||||
vec4 colorized = vec4( colorize( color.rgb, vignetteColor.rgb ), 1.0 );
|
||||
|
||||
|
||||
color.rgb = mix( color.rgb, vec3( 0.01, 0.01, 0.02 ), ( 1.0 - amount ) * params.amount );
|
||||
vec4 mixed = vignetteColor * parameters.replace +
|
||||
added * parameters.add +
|
||||
multiplied * parameters.multiply +
|
||||
colorized * parameters.colorize;
|
||||
|
||||
imageStore( color_image, uv, color );
|
||||
color.rgb = mix( color.rgb, mixed.rgb, vignetteColor.a * distanceAmount * parameters.amount );
|
||||
// color.rgb = vignetteColor.rgb;
|
||||
|
||||
imageStore( screenImage, coordinate, color );
|
||||
}
|
||||
|
|
@ -50,88 +50,52 @@ namespace Rokojori
|
|||
|
||||
for ( int i = 0; i < objects.Length; i++ )
|
||||
{
|
||||
if ( objects[ i ] is int )
|
||||
if ( objects[ i ] is int intValue)
|
||||
{
|
||||
_AddInt( (int) objects[ i ] );
|
||||
_AddInt( intValue );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is float )
|
||||
else if ( objects[ i ] is float floatValue )
|
||||
{
|
||||
_AddFloat( (float) objects[ i ] );
|
||||
_AddFloat( floatValue );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector2 )
|
||||
else if ( objects[ i ] is Vector2 vec2 )
|
||||
{
|
||||
_AddVector2( (Vector2) objects[ i ] );
|
||||
_AddVector2( vec2 );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector2I )
|
||||
else if ( objects[ i ] is Vector2I vec2I )
|
||||
{
|
||||
_AddVector2( (Vector2I) objects[ i ] );
|
||||
_AddVector2( vec2I );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector3 )
|
||||
else if ( objects[ i ] is Vector3 vec3 )
|
||||
{
|
||||
_AddVector3( (Vector3) objects[ i ] );
|
||||
_AddVector3( vec3 );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector3I )
|
||||
else if ( objects[ i ] is Vector3I vec3I )
|
||||
{
|
||||
_AddVector3( (Vector3I) objects[ i ] );
|
||||
_AddVector3( vec3I );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector4 )
|
||||
else if ( objects[ i ] is Vector4 vec4 )
|
||||
{
|
||||
_AddVector4( (Vector4) objects[ i ] );
|
||||
_AddVector4( vec4 );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Color color )
|
||||
{
|
||||
_AddVector4( color.ToVector4() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Set( params object[] objects )
|
||||
{
|
||||
Reset();
|
||||
|
||||
for ( int i = 0; i < objects.Length; i++ )
|
||||
{
|
||||
if ( objects[ i ] is int )
|
||||
{
|
||||
_AddInt( (int) objects[ i ] );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is float )
|
||||
{
|
||||
_AddFloat( (float) objects[ i ] );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector2 )
|
||||
{
|
||||
_AddVector2( (Vector2) objects[ i ] );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector2I )
|
||||
{
|
||||
_AddVector2( (Vector2I) objects[ i ] );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector3 )
|
||||
{
|
||||
_AddVector3( (Vector3) objects[ i ] );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector3I )
|
||||
{
|
||||
_AddVector3( (Vector3I) objects[ i ] );
|
||||
}
|
||||
|
||||
else if ( objects[ i ] is Vector4 )
|
||||
{
|
||||
_AddVector4( (Vector4) objects[ i ] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SetOrAdd( true, objects );
|
||||
}
|
||||
|
||||
protected void _AddFloat( float value )
|
||||
|
|
|
|||
|
|
@ -22,6 +22,43 @@ namespace Rokojori
|
|||
}
|
||||
);
|
||||
|
||||
|
||||
[ExportToolButton( "Update Shaders")]
|
||||
public Callable updateShadersButton => Callable.From(
|
||||
()=>
|
||||
{
|
||||
shaders.ForEach(
|
||||
( s )=>
|
||||
{
|
||||
var code = RenderingServer.Singleton.ShaderGetCode( s.GetRid() );
|
||||
code += "\n ";
|
||||
|
||||
RenderingServer.Singleton.ShaderSetCode( s.GetRid(), code );
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
[Export]
|
||||
public Shader[] shaders;
|
||||
|
||||
[ExportToolButton( "Get Material Code")]
|
||||
public Callable getMaterialCodeButton => Callable.From(
|
||||
()=>
|
||||
{
|
||||
var shaderRID = material._GetShaderRid();
|
||||
var code = RenderingServer.Singleton.ShaderGetCode( shaderRID );
|
||||
|
||||
this.LogInfo( "Shader:", code );
|
||||
}
|
||||
);
|
||||
|
||||
[Export]
|
||||
public Material material;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void EnsureGlobalShaderProperties()
|
||||
{
|
||||
var globalProperties = RenderingServer.GlobalShaderParameterGetList();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// #include "res://addons/rokojori_action_library/Runtime/Shading/Library/Math.gdshaderinc"
|
||||
|
||||
const float DegreesToRadians = PI/180.0;
|
||||
const float DegreesToRadians = 3.14159265358979/180.0;
|
||||
|
||||
float clamp01( float value )
|
||||
{
|
||||
return clamp( value, 0.0, 1.0 );
|
||||
}
|
||||
}
|
||||
|
||||
vec4 clamp01_v4( vec4 value )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -115,6 +115,12 @@ float sdBox( in vec2 p, in vec2 b )
|
|||
return length(max(d,0.0)) + min(max(d.x,d.y),0.0);
|
||||
}
|
||||
|
||||
float sdSegment( in vec2 p, in vec2 a, in vec2 b )
|
||||
{
|
||||
vec2 pa = p-a, ba = b-a;
|
||||
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
|
||||
return length( pa - ba*h );
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue